Wasat: Async Gemini Protocol Client Library
Wasat is a fully type-hinted asynchronous client library for the Gemini
Protocol, relying only on cryptography for
certificate management.
Features
- Async All the Way: Built on top of Python's standard
asyncioloop with streaming/chunking support. - Type Safe: Fully typed API.
- TOFU & Hybrid Verification: Supports CA, TOFU, and Hybrid (CA validation with TOFU fallback) verification modes with built-in file-based trust stores and interactive hooks.
- Auto Redirect Handling: Automatically handles temporary and permanent redirects (with protection against loops and infinite redirect limits), caching permanent redirects locally.
- Client Authentication: Native support for client TLS certificates.
- Minimal Dependencies: Relies only on
cryptographyfor certificate management, otherwise using Python's standard library. - CLI Utility: Includes a
wasatcommand-line interface out of the box.
Installation
wasat is available from pypi and can be
installed with your package installer of choice.
With pip:
pip install wasat
With uv:
uv add wasat
Quick Start
1. Make a Simple Request
Use Client with standard async context managers to query a Gemini capsule
and decode the response:
import asyncio
from wasat import Client, WasatError
async def main():
# 'tofu' mode is ideal for standard Gemini capsules (self-signed certs)
client = Client(verify_mode="tofu")
try:
# Perform the request (automatically resolves host, port, TLS, and redirects)
async with await client.request("gemini://geminiprotocol.net/") as response:
print(f"Status: {response.status.value} ({response.status.name})")
print(f"MIME type: {response.mime_type}")
# Fetch the decoded body text
body = await response.text()
print(body)
except WasatError as error:
print(f"Request failed: {error}")
if __name__ == "__main__":
asyncio.run(main())
2. Streaming Chunk Responses
For large files or media streams, read the response body in chunks to prevent exhausting memory:
async with await client.request("gemini://example.com/large-file.txt") as response:
if response.status.is_success:
async for chunk in response.iter_chunks(chunk_size=1024):
# Process each chunk as it arrives
sys.stdout.buffer.write(chunk)
3. Client Certificate Authentication
If a server requires client auth (status code 60), supply your certificate
files to the client configuration:
client = Client(
verify_mode="tofu",
client_cert="/path/to/client.crt",
client_key="/path/to/client.key" # Optional if key is embedded in cert
)
4. Interactive TOFU Confirmation
Implement a custom asynchronous callback to prompt the user before trusting new self-signed certificates:
import sys
async def confirm_cert(host: str, port: int, fingerprint: str) -> bool:
print(f"New certificate encountered for {host}:{port}")
print(f"Fingerprint: sha256:{fingerprint}")
response = input("Do you trust this certificate? [y/N]: ").strip().lower()
return response == "y"
client = Client(
verify_mode="tofu",
on_new_certificate=confirm_cert
)
Command Line Interface (CLI)
Wasat comes with a command-line interface to fetch Gemini capsules from your shell:
# Basic fetch using the entrypoint script (uses TOFU)
uv run wasat gemini://geminiprotocol.net/
# Alternatively, execute the package directly using python -m
uv run python -m wasat gemini://geminiprotocol.net/
# Fetch a local or custom port capsule
uv run wasat gemini://localhost:1965/index.gmi
License
MIT
Metadata
Release files for wasat 1.11.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| wasat-1.11.0.tar.gz | 44.6 kB | Without provenance | Details for wasat-1.11.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| wasat-1.11.0-py3-none-any.whl | Python 3 | none | any | Details for wasat-1.11.0-py3-none-any.whl |
Total release size: 92.3 kB
Release files / wasat-1.11.0.tar.gz
| Download URL | wasat-1.11.0.tar.gz |
|---|---|
| Size | 44.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b4ad60d8aad65d118bde06425f8d267fec6a83d39f40643e8525d439d7544554
|
|
BLAKE2b-256 checksum How to use checksums |
ee25b7b1d1931b01e40a5f846b204b621dcdf827f05efef845d46bbba9df98b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / wasat-1.11.0-py3-none-any.whl
| Download URL | wasat-1.11.0-py3-none-any.whl |
|---|---|
| Size | 47.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d512a6a531d0fdadfc2ab8c24e743e02b4f05a13dd0aa4404582072db641ba77
|
|
BLAKE2b-256 checksum How to use checksums |
fb658eed23b5381f335415e5fc99706f563da5c1fc3c860e950c147cee1cfb7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for wasat 1.11.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.