Skip to content

Starlette

Everything here is pure ASGI, so it works on a plain Starlette app and on anything built from one. A FastAPI app reaches the same names through FastAPI, which adds the OpenAPI schema and the health router. GrelmicroMiddleware lives in App and IdempotencyMiddleware in HTTP, since neither is tied to a framework.

Prefer the polymorphic micro.install(app), which detects the framework and calls install for you.

grelmicro.integrations.starlette

Starlette integration: the lifespan, the binding, and the error responses.

Everything here is pure ASGI, so it works on a plain Starlette app and on anything built from one. grelmicro.integrations.fastapi builds on it and adds what only FastAPI has, an OpenAPI schema and a health router.

install

install(
    app: Starlette,
    micro: Grelmicro,
    *,
    ambient: bool = True,
) -> None

Wire micro into a Starlette app.

Chains async with micro: around the app's existing lifespan, so any lifespan already passed to the framework keeps running and the components are open before the first request. When ambient is True, adds GrelmicroMiddleware so patterns resolve through Grelmicro.current() inside request handlers, and keeps it outside every other middleware however they were added, so one that resolves a backend ambiently, such as IdempotencyMiddleware, always runs inside the request scope. The placement is read back on startup and raises AmbientBindingError if it did not hold.

Prefer the polymorphic micro.install(app), which detects the framework and calls this for you.

PARAMETER DESCRIPTION
app

The Starlette application to wire.

TYPE: Starlette

micro

The Grelmicro app to open in the lifespan and bind per request.

TYPE: Grelmicro

ambient

Add GrelmicroMiddleware so patterns resolve ambiently inside request handlers. Default True. Pass False to skip it.

TYPE: bool DEFAULT: True

install_error_responses

install_error_responses(
    app: Starlette, errors: ErrorResponses
) -> None

Render grelmicro rejections in a standard format on a Starlette app.

Registers one exception handler per rejection grelmicro raises to turn a caller away, and reshapes the framework's own errors into the same format, so the whole API answers in one shape.

Starlette looks a handler up through the raised exception's class hierarchy, so registering AdmissionError covers every rejection under it, including one a later release adds.

micro.install(app) calls this when ErrorResponses() is registered. Call it directly only on an app that never goes through install.

Read more in the Error Responses docs.

PARAMETER DESCRIPTION
app

The Starlette application to wire.

TYPE: Starlette

errors

The registered component that renders each rejection.

TYPE: ErrorResponses

install_middleware

install_middleware(
    app: Starlette, components: Sequence[Any]
) -> None

Add the ASGI middleware each registered component asks for.

A component that carries asgi_middleware() returns the middleware class and the arguments to build it with, and this adds it to the app. Registration order is wrapping order among them, so the first one registered answers first.

A middleware that may answer a request goes innermost, behind whatever the app added itself, so authentication, CORS and the rest run before one of ours can answer on its own. One that only watches, an access log, goes the other way, outside the app's own, so a request an outer layer refuses is still seen. A component says which it is with asgi_observes.

Both run inside GrelmicroMiddleware, which stays outermost, so one that resolves a backend ambiently finds the app bound.

micro.install(app) calls this with the components it found, so a direct call is only for an app that never goes through install.

PARAMETER DESCRIPTION
app

The Starlette application to wire.

TYPE: Starlette

components

The registered components that carry an ASGI middleware.

TYPE: Sequence[Any]

is_bound

is_bound(app: Starlette) -> bool

Return whether install added the per-request binding middleware.

Called by Grelmicro.check_ambient_binding and Grelmicro.describe to catch an app that never had micro.install(app) called on it, including a mounted sub-application, which otherwise resolves against the host's components with nothing reporting it.

PARAMETER DESCRIPTION
app

The Starlette or FastAPI application to inspect.

TYPE: Starlette

error_response

error_response(
    request: Request,
    *,
    status: int,
    detail: str | None = None,
    extensions: dict[str, Any] | None = None,
) -> Response

Answer from your own exception handler in the app's error format.

Writing a handler of your own is how one error opts out of the shape grelmicro installs. This is for the other case: you want your own handler and the same shape as everything else.

from grelmicro.integrations.fastapi import error_response


@app.exception_handler(InsufficientFunds)
async def handle(request: Request, exc: InsufficientFunds) -> Response:
    return error_response(
        request,
        status=409,
        detail="The account does not hold enough to cover this charge.",
        extensions={"balance": exc.balance},
    )

The format is read from the app, so a service that registered ErrorResponses.tmf() answers in TMF from here too, with no second place to keep in step. An app that registered nothing gets RFC 9457.

PARAMETER DESCRIPTION
request

The request being answered, which knows its app.

TYPE: Request

status

HTTP status code of the response.

TYPE: int

detail

Explanation of this occurrence, safe to show a client.

TYPE: str | None DEFAULT: None

extensions

Extra members to carry, where the format has room for them.

TYPE: dict[str, Any] | None DEFAULT: None