ASGI protocol inspector — validates, traces, and analyzes your ASGI app
Project description
asgion
ASGI protocol inspector and trace engine. Catch subtle protocol violations your tests miss — before they hit production.
Why asgion?
ASGI apps can pass all tests while still violating the protocol:
- Sending response body before
http.response.start - Writing to a closed WebSocket connection
- Exiting without completing a streaming response
- Returning malformed event payloads
Frameworks catch some of this. asgion validates the full ASGI contract — state machines, event schemas, scope fields, and semantic constraints across HTTP, WebSocket, and Lifespan.
Highlights
- Full ASGI contract validation — 163 rules across HTTP, WebSocket, and Lifespan
- Trace engine — record every
receive()/send()with nanosecond timestamps and inline violation markers - CI-ready — deterministic exit codes and JSON output
- CLI, Python API, pytest plugin — fits any workflow
- Zero runtime dependencies — pure Python 3.12+
- O(1) per message — safe for hot paths, no overhead when tracing is off
Works with any ASGI app: FastAPI, Starlette, Litestar, Django (ASGI), or bare ASGI handlers.
Quickstart
Get started in under a minute:
pip install asgion[cli]
Given a buggy app that sends body before http.response.start:
# myapp.py
async def app(scope, receive, send):
if scope["type"] == "http":
await send({"type": "http.response.body", "body": b"oops"}) # wrong order
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"hello"})
Check finds protocol violations:
asgion check myapp:app
CHECK myapp:app
── GET / ─────────────────────────────────────────────────────────
[HF-002] error: http.response.body sent without preceding http.response.start
hint: Send http.response.start before any http.response.body
[SEM-002] info: No Content-Type header on 2xx response
hint: Responses with a body should include a Content-Type header
2 violations (1 error, 1 info) | 709µs
What is HF-002? Look up any rule directly:
asgion rules HF-002
RULE [HF-002] error
http.response.body sent without preceding http.response.start
hint: Send http.response.start before any http.response.body
layer: http.fsm
applies to: http
Trace shows the full event timeline for the same app:
asgion trace myapp:app
TRACE GET / (88µs, TTFB 54µs)
26µs send http.response.body 4 bytes ← HF-002 (error)
54µs send http.response.start 200 (+28µs) ← SEM-002 (info)
75µs send http.response.body 5 bytes (+21µs)
Events: 3 | Violations: 2 (1 error, 1 info)
asgion trace myapp:app --min-severity error # only error-level markers
asgion trace myapp:app --out ./traces/ # save as JSON files
Filter, export, target specific endpoints:
asgion check myapp:app --select "HF-*" --min-severity warning
asgion check myapp:app --path /api/users --path "POST:/api/users" -H "Content-Type: application/json"
asgion check myapp:app --format sarif --out report.sarif # GitHub Code Scanning
asgion check myapp:app --format junit --out report.xml # Jenkins / GitLab CI
Bootstrap a config file:
asgion init # creates .asgion.toml with commented-out defaults
asgion init --pyproject # prints [tool.asgion] block to stdout
Exit codes: 0 = clean, 1 = violations (--strict), 2 = runtime error. See asgion check --help.
App exceptions and timeouts are reported but do not affect exit codes — only protocol violations do.
Note:
asgion check myapp:appimports your module and drives the ASGI app. All application dependencies (FastAPI, SQLAlchemy, etc.) must be installed in the same environment as asgion.
See the full rule list for all 163 rules and their descriptions.
Python API
from asgion import Inspector
inspector = Inspector(app)
# ... drive the app via httpx, TestClient, etc. ...
assert inspector.violations == [] # fails if any protocol violation occurred
With tracing:
inspector = Inspector(app, trace=True)
async with httpx.AsyncClient(transport=ASGITransport(inspector)) as client:
await client.get("/api/users")
record = inspector.traces[0]
record.scope.method # "GET"
record.scope.path # "/api/users"
record.summary.ttfb_ns # time to first byte (ns)
As middleware:
from asgion import inspect
uvicorn.run(inspect(app))
Pytest Plugin
Integrate protocol validation directly into your test suite:
pip install asgion[pytest]
async def test_my_app(asgi_inspect):
inspector = asgi_inspect(my_app)
async with httpx.AsyncClient(transport=ASGITransport(inspector)) as client:
await client.get("/users")
assert inspector.violations == []
pytest --asgi-strict # auto-check all tests
CI Integration
GitHub Action
# .github/workflows/asgion.yml
name: ASGI Check
on: [push, pull_request]
jobs:
asgion:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ack1d/asgion@v0
with:
app: myapp:app
strict: true
format: sarif
pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/ack1d/asgion
rev: v0.6.0 # update with: pre-commit autoupdate
hooks:
- id: asgion
args: [myapp:app, --strict] # args is required — specify your app target
pre-commit runs in an isolated virtualenv — asgion can only validate raw ASGI apps that have no external imports. For apps with dependencies, use the CLI or GitHub Action instead.
Configuration
Via pyproject.toml, .asgion.toml, or Python API.
See configuration docs and full rule list.
[tool.asgion]
profile = "recommended"
exclude_rules = ["SEM-006"]
Contributing
git clone https://github.com/ack1d/asgion.git
cd asgion
uv sync --group dev
uv run pytest
If you have Task installed: task check runs lint, typecheck, and tests.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file asgion-0.6.1.tar.gz.
File metadata
- Download URL: asgion-0.6.1.tar.gz
- Upload date:
- Size: 49.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58f9538a3d610b1da22f1bd2dc3e36ddb2da8e25d98777d0c676cf408a53fdd1
|
|
| MD5 |
7aa68a1eca3c41ff0da8b2670bda2260
|
|
| BLAKE2b-256 |
4ffaecc954e18f03208331e0711334ef2d4f218c72439759380544cb367b721a
|
Provenance
The following attestation bundles were made for asgion-0.6.1.tar.gz:
Publisher:
publish.yml on ack1d/asgion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asgion-0.6.1.tar.gz -
Subject digest:
58f9538a3d610b1da22f1bd2dc3e36ddb2da8e25d98777d0c676cf408a53fdd1 - Sigstore transparency entry: 1109319167
- Sigstore integration time:
-
Permalink:
ack1d/asgion@753d8214839429954aa09a3f128d7c8b1b832dd7 -
Branch / Tag:
refs/tags/v0.6.1 - Owner: https://github.com/ack1d
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@753d8214839429954aa09a3f128d7c8b1b832dd7 -
Trigger Event:
release
-
Statement type:
File details
Details for the file asgion-0.6.1-py3-none-any.whl.
File metadata
- Download URL: asgion-0.6.1-py3-none-any.whl
- Upload date:
- Size: 67.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b697477cdf3599a7c02753177834df0da3f8dd1a0fcd2c502ca9299665f07750
|
|
| MD5 |
ab59bd7f6fc099a2b75db21c98988f8b
|
|
| BLAKE2b-256 |
c93c70123d20806d559c6ca117a5a1459284aaa994e62942caba9b868e1909b9
|
Provenance
The following attestation bundles were made for asgion-0.6.1-py3-none-any.whl:
Publisher:
publish.yml on ack1d/asgion
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asgion-0.6.1-py3-none-any.whl -
Subject digest:
b697477cdf3599a7c02753177834df0da3f8dd1a0fcd2c502ca9299665f07750 - Sigstore transparency entry: 1109319168
- Sigstore integration time:
-
Permalink:
ack1d/asgion@753d8214839429954aa09a3f128d7c8b1b832dd7 -
Branch / Tag:
refs/tags/v0.6.1 - Owner: https://github.com/ack1d
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@753d8214839429954aa09a3f128d7c8b1b832dd7 -
Trigger Event:
release
-
Statement type: