Skip to content

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:

Everything else, the locks, the cache, the outbox, the scheduler, and the resilience patterns, is plain async Python and needs no framework at all.