Frameworks
grelmicro is not a web framework. It plugs into the one you picked, and
micro.install(app) detects which one from the app object.
Every pattern and every component works on every framework grelmicro
supports. A Lock, a RateLimiter, @cached, the outbox, the scheduler,
health checks, logging, tracing, and metrics behave the same whichever one you
picked. What differs is the wiring install does for you, two routers that
only FastAPI has, and the route declarations FastAPI reads as dependencies.
All of it is named below, and a test holds the patterns.
What install wires
| Framework | Extra | What install wires |
Not portable |
|---|---|---|---|
| FastAPI | grelmicro[fastapi] |
The lifespan, the per-request binding, the error responses, the middleware you registered, and the OpenAPI schema. | Nothing. |
| Starlette | grelmicro[starlette] |
The lifespan, the per-request binding, the error responses, and the middleware you registered. | The health router and the metrics router. The per-route declarations CachedResponse() (name the path in include=), RateLimited and ConditionalRequired (use require_precondition=). Anonymous() (use exclude=). Starlette builds no OpenAPI schema. |
| Litestar | grelmicro[litestar] |
The startup and shutdown hooks, the per-request binding, the error responses, the middleware you registered, and the OpenAPI schema. | The same two routers and the same three per-route declarations, and request auto-instrumentation, which Litestar does with its own plugin. |
| FastStream | grelmicro[faststream] |
The startup and shutdown hooks, the per-message binding, and the broker telemetry middleware. | Everything HTTP, because FastStream serves none. |
The rest of what FastAPI injects has a form on every framework. A Starlette
or Litestar handler reads the caller from request.user and the token with
current_token(request), where FastAPI injects CurrentPrincipal,
OptionalPrincipal, Claims and CurrentToken. Conditional is the
injected form of check_freshness and check_precondition, which work on
every framework.
install also wires what you registered. ErrorResponses() has grelmicro's
rejections answered as RFC 9457 responses, IdempotentRequests() has repeated
requests replayed, and Trace() has requests auto-instrumented. Each happens
only because the component is in uses=[...].
Whatever the framework, install does the same two things: it opens
async with micro: alongside the framework's own lifecycle, so components are
ready before the first request, and it binds the app around each handler, so
Lock("cart") and RateLimiter.sliding_window(...) resolve their backend with
no backend= argument. Read Wiring an App for what that means in
practice.
Nothing else about how your framework answers a request changes. The per-request binding adds one middleware that sets a context variable, and changes nothing a client can see.
The extra installs the framework and sets the oldest version grelmicro runs on. Install it even when your app already depends on the framework. Installation lists each floor.
The OpenAPI schema
FastAPI and Litestar build an OpenAPI schema, and install describes every
registered component in it the same way on both: the bearer token, the 429
of a rate limit, the Idempotency-Key header, and the precondition headers.
Starlette builds no schema, so there is nothing to describe. A route marks
If-Match required with ConditionalRequired on FastAPI. On every framework,
ConditionalRequests(require_precondition=...) requires it per method.
The routers
health_router() and metrics_router() build a
FastAPI APIRouter, so they are FastAPI's door onto the endpoints rather than
the only one. Every framework has the same endpoints through
health_asgi() and metrics_asgi(),
which are pure ASGI. OpsServer serves them on a port of
their own, next to any framework or with none at all, and it is the
recommended way. All three doors render through
one set of functions, and tests/test_endpoint_parity.py holds them to one
status, one set of headers, and one body.
FastStream is a different axis. It serves no HTTP, so every HTTP-shaped
behaviour is out of scope for it by definition. It carries no HTTP-facing
hook, so micro.install(app) skips those without any code anywhere reading
a framework's name. Everything else, the locks, the cache, the outbox, the
scheduler, and the resilience patterns, behaves inside a subscriber exactly as
it does inside a request handler.
How the claim is held
tests/test_framework_parity.py walks the package for every pattern that
resolves through the active app, refuses to pass on an empty scan, and runs
each one inside a request handler on FastAPI, Starlette, and Litestar and
inside a subscriber on FastStream. The values have to match, not merely the
absence of an exception.
For what reaches the wire, the bar is higher: the status line, every header, and the body byte for byte, so a client cannot tell which framework answered. A rejection and an idempotent replay are both held to it.
A pattern added tomorrow is covered without anyone remembering to add it, and a piece that cannot be portable has to say so in the table above.
FastAPI and Starlette
from fastapi import FastAPI
from grelmicro import Grelmicro
micro = Grelmicro(uses=[...])
app = FastAPI()
micro.install(app)
FastAPI adds two optional pieces: a health router you include yourself, and the metrics router.
Litestar
Litestar builds its middleware stack when you construct the app, so call
install after it:
from litestar import Litestar
from grelmicro import Grelmicro
micro = Grelmicro(uses=[...])
app = Litestar(route_handlers=[...])
micro.install(app)
Hooks and lifespan managers already passed to Litestar(...) keep running.
The binding wraps the app's ASGI handler, so it sits outside every middleware
Litestar built and any of them that resolves a backend still runs inside the
request scope.
Litestar carries its own OpenTelemetry plugin, configured at construction, so
install does not instrument requests for you. Trace still exports the spans
your own code emits.
FastStream
from faststream import FastStream
from faststream.redis import RedisBroker
from grelmicro import Grelmicro
micro = Grelmicro(uses=[...])
app = FastStream(RedisBroker("redis://localhost:6379/0"))
micro.install(app)
The binding runs per consumed message, so patterns resolve inside subscribers.
Any other ASGI framework
GrelmicroMiddleware is pure ASGI and runs in front of any ASGI app. Open
micro in whatever lifecycle the framework gives you, then wrap the app
yourself:
from grelmicro import GrelmicroMiddleware
app = GrelmicroMiddleware(app, micro=micro)
micro.install(app) raises TypeError for a framework it does not know,
naming the ones it does. A package can register its own integration under the
grelmicro.integrations entry point group, and install then finds it like
any first-party one. See Plugins.
Not tied to a framework
Some pieces are pure ASGI and work anywhere, whether or not install knows
your framework:
GrelmicroMiddlewarebinds the app per request.IdempotencyMiddlewarereplays a stored response when a request repeats its key.ClientAddressMiddlewareresolves the real caller behind a reverse proxy.health_asgi()andmetrics_asgi()serve the probes and the scrape target, mounted in any ASGI app.OpsServerserves those same endpoints on a port of its own, next to any framework or with none at all.
Everything else, the locks, the cache, the outbox, the scheduler, and the resilience patterns, is plain async Python and needs no framework at all.