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.9.1
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.9.1.tar.gz | 40.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wasat-1.9.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 84.3 kB
Release files / wasat-1.9.1.tar.gz
| Download URL | wasat-1.9.1.tar.gz |
|---|---|
| Size | 40.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
26d7d643e93398f865fa25e064ed486d31f90ced838b2e3855a18b44ca10aa37
|
|
BLAKE2b-256 checksum How to use checksums |
e0ae54991304bc0fc0578e1cbceed4d35c1e9174e25b79ecdb3dfd3d314ef9ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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.9.1-py3-none-any.whl
| Download URL | wasat-1.9.1-py3-none-any.whl |
|---|---|
| Size | 44.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b76b37cdbf16412dd0d2d8b85a8d98ceb70f38f0a5e6b7d98c269ac79e50a55
|
|
BLAKE2b-256 checksum How to use checksums |
37c802759404a0fe86db2b158810d654d10a560b4eaccc67e512c84b653b2842
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}
|