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
Release files for wasat 1.5.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 | |
|---|---|---|---|
| wasat-1.5.0.tar.gz | 32.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wasat-1.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.5 kB
Release files / wasat-1.5.0.tar.gz
| Download URL | wasat-1.5.0.tar.gz |
|---|---|
| Size | 32.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a540490f354c5e9875b64d1e6e158632df10f64a305549a0e6ef2a940b7c8717
|
|
BLAKE2b-256 checksum How to use checksums |
37fc64475860e4e85db46eb0568274995bb80ab37e851ed1376bdc220464be77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.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.5.0-py3-none-any.whl
| Download URL | wasat-1.5.0-py3-none-any.whl |
|---|---|
| Size | 36.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
822aa48af1aad0638a85767b9ea786e4c59d300a9f6d97de2d80cf321b602083
|
|
BLAKE2b-256 checksum How to use checksums |
f94958f2665b11b70fca5c80ebf0e825bd4eca96a76f500a859cce4b751005ef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.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}
|