@signicode/verser2-guest-python
v0.4.5
Published
Python ASGI Guest package for Verser2.
Maintainers
Readme
@signicode/verser2-guest-python
Python package for verser2 providing Guest and Broker implementations.
This package connects outbound to an existing verser2 Host over TLS HTTP/2.
It is recognized by the repository's npm workspace tooling through
package.json and by Python packaging tooling through pyproject.toml.
Public API
VERSER2_GUEST_PYTHON_PACKAGE_NAMEVerserGuest/create_verser_guest— Python ASGI GuestVerserBroker/create_verser_broker— Python BrokerVerserBrokerResponse— Broker response type
Commands
npm run build --workspace=@signicode/verser2-guest-python
npm run test --workspace=@signicode/verser2-guest-python
npm run lint --workspace=@signicode/verser2-guest-pythonThe package commands use uv run --project . so Python dependencies such as
h2 are resolved in an isolated project environment.
Python Guest usage
The Guest serves an ASGI 3 app without opening an inbound listening port.
import asyncio
from verser2_guest_python import create_verser_guest
async def app(scope, receive, send):
assert scope["type"] == "http"
body = b""
while True:
event = await receive()
body += event.get("body", b"")
if not event.get("more_body", False):
break
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": body})
async def main():
guest = create_verser_guest(
host_url="https://localhost:8443",
guest_id="python-guest-a",
app=app,
routed_domains=["python-guest-a.local.test"],
tls_ca_file="/etc/verser/ca.crt",
# For mTLS Hosts, present a client identity as PEM:
# tls_cert_file="/etc/verser/client.crt",
# tls_key_file="/etc/verser/client.key",
# Or as PFX/PKCS12:
# tls_pfx_file="/etc/verser/client.p12",
# tls_pfx_password="...",
)
await guest.connect()
await asyncio.Event().wait()
asyncio.run(main())Domain note: Unlike Node and Bun Guests, the Python Guest does not
default the route domain to the Guest ID. You must provide routed_domains
explicitly.
FastAPI-compatible apps
FastAPI and Starlette applications work because the Guest calls the standard ASGI 3 interface. FastAPI is not a core runtime dependency.
from fastapi import FastAPI
from verser2_guest_python import create_verser_guest
app = FastAPI()
@app.get("/health")
async def health():
return {"ok": True}
guest = create_verser_guest(
host_url="https://localhost:8443",
guest_id="fastapi-guest",
app=app,
routed_domains=["fastapi-guest.local.test"],
tls_ca_file="/etc/verser/ca.crt",
)Python Broker usage
The Python Broker connects outbound, registers as broker, and sends requests
to advertised Guest routes.
import asyncio
from verser2_guest_python import create_verser_broker
async def main():
broker = create_verser_broker(
host_url="https://localhost:8443",
broker_id="broker-a",
tls_ca_file="/etc/verser/ca.crt",
)
await broker.connect()
await broker.wait_for_route("python-guest-a.local.test")
response = await broker.get("http://python-guest-a.local.test/health")
print(await response.text())
asyncio.run(main())The Broker supports request, get, post, put, patch, and delete
helpers. VerserBrokerResponse exposes status, headers, request_id,
read(), text(), json(), and aiter_bytes(chunk_size=8192). Response
bodies are one-shot.
TLS for Python Broker
broker = create_verser_broker(
host_url="https://localhost:8443",
broker_id="broker-a",
tls_ca_file="/etc/verser/ca.crt",
tls_cert_file="/etc/verser/client.crt",
tls_key_file="/etc/verser/client.key",
# PFX/PKCS12 also supported:
# tls_pfx_file="/etc/verser/client.p12",
# tls_pfx_password="...",
)Python Guests support the same tls_ca_file, PEM client identity, and
PFX/PKCS12 client identity options. PFX/PKCS12 support uses the package's
cryptography dependency.
Streaming behavior
- Guest: routed request body chunks from the Host/Broker lease stream are
delivered as ASGI
http.requestevents withmore_bodycontinuation flags. - Guest: ASGI
http.response.startis converted to the Verser response envelope before response bytes are written. - Guest: ASGI
http.response.bodyevents are written back to the Host lease stream;more_body: falseends the response side of the lease. - Direct
dispatch_routed_request(...)calls are batch-only — they buffer the ASGI response and enforcemax_response_bytesbefore joining chunks. Use leased Host/Broker routing for streaming. - Guest app exceptions before response start are returned as Verser
local-handler-failureerror envelopes with Guest, request, and path context. - Broker response bodies are one-shot;
read(),text(), andjson()consume the body.
Avoid non-terminating async streams
Verser Python transports use async read loops and async body iteration. Any custom async stream, test double, or request-body async iterable must eventually signal completion:
asynciostream readers should returnb""for EOF.- Async request-body iterables should stop iteration when the body is complete.
- Test mocks should not leave
reader.read()as a bareAsyncMock, because each awaited call can produce another truthy mock object forever.
Use an explicit EOF:
reader = AsyncMock()
reader.read = AsyncMock(return_value=b"")or a finite sequence:
reader.read = AsyncMock(side_effect=[b"first-frame", b""])Known limits
- The first implementation focuses on Python Guest and Broker behavior.
- Python Host, Python-side fetch helper APIs, and Python-side Agent/Dispatcher are not implemented.
- HTTP/3, complete application authentication, public gateway policy, per-request Broker target authorization, WebSockets, upgrades, trailers, and advanced ASGI lifespan behavior are not implemented.
- The transport is intentionally minimal: one outbound TLS HTTP/2 session with a replenished pool of one-use Guest lease streams.
