ASGIMachine: HTTP Decision Graph Framework
ASGIMachine : A webmachine-style HTTP decision-graph framework for Python
Web development frameworks often require developers to manually implement HTTP semantics—checking for authorization, verifying resource existence, handling conditional requests, and returning appropriate status codes. This manual approach leads to subtle bugs, inconsistent behavior, and incomplete HTTP implementations. ASGIMachine takes a radically different approach: correctness by construction.
The Webmachine Heritage
ASGIMachine draws inspiration from Webmachine, a framework originally developed by Basho that introduced the concept of an HTTP decision graph. The core insight behind Webmachine is that HTTP is essentially a finite state machine—given a request and a resource's state, the correct response can be determined by walking a deterministic decision graph.
The webmachine v3 decision graph codifies the full HTTP contract: content negotiation, conditional requests (ETag, If-None-Match, If-Modified-Since), caching headers, and the POST/PUT/PATCH/DELETE write path. By implementing this graph, ASGIMachine ensures that correct HTTP behavior is the default, not something developers must remember to add.
Architecture and Design Philosophy
Layer 1: Own, Layer 2: Rent
ASGIMachine employs a distinctive architectural strategy: it owns the decision graph and resource conventions, but rents everything else from the Starlette ecosystem. This means routing, server infrastructure, CORS, and middleware are delegated to Starlette, while the core HTTP logic remains framework-agnostic. In fact, the core is provably Starlette-free—the substrate lives behind a single adapter module, so the decision graph could run on another ASGI substrate unchanged.
Two Lanes, No Cosplay
Not every endpoint fits the resource-oriented RESTful model. ASGIMachine acknowledges this by providing two distinct paths:
- Resource-shaped endpoints: Walk the full decision graph for RESTful operations
- Command-shaped endpoints: Use a plain Command handler for genuinely procedural operations like token exchanges or webhook receivers, avoiding the overhead of forcing them through the graph
Parse, Don't Validate
The framework adopts a "parse, don't validate" philosophy for request handling. A request body is parsed into a typed model at the boundary—a bad body returns a 400 error before any business logic executes. Write handlers receive values they can trust, eliminating a class of defensive programming.
Core Components
The Resource Class
Developers write a resource class with small async callbacks that override only what they care about. Every callback ships with a correct HTTP default :
python:
@dataclass(slots=True)
class GreetCtx(Ctx):
name: str = "world"
class Greeting(Resource):
context_class = GreetCtx
ALLOWED_METHODS = frozenset({"GET", "HEAD"})
async def resource_exists(self, ctx: GreetCtx) -> bool:
ctx.name = ctx.request.path_params.get("name", "world")
return True
async def generate_etag(self, ctx: GreetCtx) -> str:
return f'"{ctx.name}"'
async def represent(self, ctx: GreetCtx) -> dict:
return {"hello": ctx.name}
The Decision Graph
ASGIMachine walks the complete webmachine v3 decision graph over the resource's callbacks . The graph handles:
- Content Negotiation: Automatically selects appropriate response formats based on Accept headers
- Conditional Requests: ETag and If-None-Match handling for 304 Not Modified responses
- Method Support: 405 Method Not Allowed with automatic Allow header generation
- Write Operations: POST, PUT, PATCH, and DELETE pathways
- Caching Headers: Correct Cache-Control and validation headers
Typed Per-Request State
Each resource defines a context class (subclassing Ctx) that holds typed per-request state. This context flows through all callbacks, providing type safety and clear data flow. The framework uses PEP 695 generics and PEP 696 type-parameter defaults, requiring Python 3.14+.
Practical Example
The following example demonstrates the compactness and power of ASGIMachine :
python
from dataclasses import dataclass
from asgimachine.resource import Ctx, Resource
from asgimachine.substrate.starlette import build_app, resource_route
@dataclass(slots=True)
class GreetCtx(Ctx):
name: str = "world"
class Greeting(Resource):
context_class = GreetCtx
ALLOWED_METHODS = frozenset({"GET", "HEAD"})
async def resource_exists(self, ctx: GreetCtx) -> bool:
ctx.name = ctx.request.path_params.get("name", "world")
return True
async def generate_etag(self, ctx: GreetCtx) -> str:
return f'"{ctx.name}"'
async def represent(self, ctx: GreetCtx) -> dict:
return {"hello": ctx.name}
app = build_app([resource_route("/hello/{name}", Greeting())])
Running this service yields correct HTTP behavior by default :
bash:
$ curl -isS localhost:8000/hello/charles
HTTP/1.1 200 OK
etag: "charles"
content-type: application/json
{"hello":"charles"}
$ curl -isS localhost:8000/hello/charles -H 'If-None-Match: "charles"'
HTTP/1.1 304 Not Modified
$ curl -isS -X POST localhost:8000/hello/charles
HTTP/1.1 405 Method Not Allowed
allow: GET, HEAD, OPTIONS
User Benefits
Correctness by ConstructionCorrectness by Construction
The most compelling benefit is correctness by construction. The right HTTP behavior is default, not something you remember to add . A resource that overrides only represent already answers HEAD, OPTIONS, 405, 406, and 501 correctly. This eliminates entire classes of bugs related to caching, concurrency, and error handling.
Minimal Code
You override only the callbacks your resource actually cares about; every one ships a correct HTTP default . This minimalism means less code to write, less code to test, and fewer opportunities for bugs.
Production-Grade APIs
The framework implements the full HTTP contract, making it ideal for building robust, production-grade APIs that behave correctly with proxies, caches, and clients.
Gradual Adoption
ASGIMachine rents Starlette for infrastructure, enabling gradual adoption of the graph in existing Starlette or FastAPI applications.
Current Status
ASGIMachine is currently experimental. It requires Python 3.14+ due to its use of PEP 695 generics and PEP 696 type-parameter defaults . The decision graph implements the v0–v3 subset of webmachine.
Installation is available via GitHub:
bash:
uv add "git+https://github.com/declaresub/asgimachine"
# or
pip install "git+https://github.com/declaresub/asgimachine"
The package is also available on PyPI and piwheels.
Conclusion
ASGIMachine represents a principled approach to building HTTP APIs. By leveraging the webmachine decision graph, it ensures that correct HTTP behavior is the default, not an afterthought. The framework's architecture—owning the graph while renting infrastructure—keeps it focused and flexible.
For developers building production-grade APIs, ASGIMachine offers a path to correctness with minimal code. The trade-off is the experimental status, Python version requirement, and the need to understand the webmachine decision graph model. However, for projects where HTTP correctness is paramount, these are worthwhile considerations.
