You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OutboxRouter does not forward routers to the inner broker #158
OutboxRouter.__init__ (faststream_outbox/fastapi/router.py) mirrors most of OutboxBroker.__init__'s kwargs and forwards them through StreamRouter's **connection_kwars, but routers is not among them. OutboxBroker(..., routers=[...]) works; OutboxRouter(..., routers=[...]) is a TypeError.
dlq_table and metrics_recorder were the other two gaps here and were forwarded in #88, which
left routers as the only unforwarded OutboxBroker argument.
Why it is still open
Unlike dlq_table / metrics_recorder, this is not mechanical. OutboxRouter is itself a StreamRouter, so app.include_router(router) already contributes the router's own subscribers via
the FastAPI lifespan. A separate routers sequence would introduce a second, differently-timed
source of subscribers into the same broker, and its behaviour is unsettled on two axes:
Start / lifespan — when do the nested routers' subscribers start relative to the FastAPI
lifespan, and what happens if the app never includes the outer router?
AsyncAPI — how the nested routers' channels and operations compose into the document the
router serves at schema_url (default /asyncapi).
Forwarding the kwarg without settling those would ship whichever behaviour falls out of StreamRouter's ordering, which is not obviously the one a user would expect.
Workaround
Register subscribers directly on the OutboxRouter — @router.subscriber(\"queue\") — rather than
composing sub-routers under it. Nothing is unreachable today.
Revisit trigger
A concrete "include a sub-router under the FastAPI outbox router" demand from someone hitting it,
rather than a hypothesised one. That report is also what would pin down the expected lifespan and
AsyncAPI semantics, which is the part currently missing.
Context
Carried over from planning/deferred.md, retired when the repo adopted the PR-body-as-spec
convention. Originally surfaced as F8-01 in the 2026-06-14 pass-3 audit, which confirmed the dlq_table / metrics_recorder half was mechanically forwardable and flagged routers as the part
warranting a design call.
Relabeling needs-triage → needs-info. Staying enhancement: nothing is broken, a capability is absent, and the issue documents a workaround that leaves nothing unreachable.
Reproduced, exactly as described:
OutboxRouter has routers : False
OutboxRouter has **kwargs: False
OutboxBroker has routers : True
TypeError: OutboxRouter.__init__() got an unexpected keyword argument 'routers'. Did you mean 'routes'?
OutboxBroker.__init__ takes routers: Sequence[Registrator[OutboxInnerMessage]] = () (broker.py:133) and forwards it (broker.py:190). OutboxRouter.__init__ (fastapi/router.py:68) enumerates every keyword explicitly with no **kwargs catch-all, and routers is not among them — so this is a hard TypeError, not a silently-ignored kwarg. dlq_table (:73) and metrics_recorder (:84) are both present and forwarded at :122-123, confirming routers as the last unforwarded OutboxBroker argument after #88.
Worth noting from the reproduction: CPython suggests routes, which is a valid OutboxRouter parameter — the FastAPI APIRouter one, meaning something entirely different. A user following that suggestion gets no error and no subscribers. That raises the cost of the current gap slightly above "a TypeError tells you immediately".
Why it stays open rather than being actioned: the design question in the body is real and unanswered. Forwarding the kwarg is one line; deciding when nested routers' subscribers start relative to the FastAPI lifespan, and how their channels compose into the /asyncapi document, is not. Shipping whatever falls out of StreamRouter's ordering would be a guess.
What would move it:
A concrete "include a sub-router under the FastAPI outbox router" case from someone hitting it — the composition they wanted, and what they expected to happen at startup and in the AsyncAPI document.
Absent that, the two axes have no forcing function and any answer is speculative.
Closing as not planned: routers= should not be forwarded, and grouping subscribers under the FastAPI router already works.
The supported path is router.include_router(...).StreamRouter.include_router accepts a broker-level router (faststream_outbox.OutboxRouter), wraps each of its subscribers with the FastAPI compatibility decorator, then includes it on the broker:
That also answers the two open axes. The nested subscribers belong to the same broker, so they start with it in the FastAPI lifespan, and their channels appear in the /asyncapi document.
Forwarding routers= would be harmful. It calls include_routers in the broker constructor, which skips the FastAPI wrap, and the FastAPI router runs with apply_types=False. A handler with d: str = Depends(dep) then receives the raw OutboxMessage as its body and the Depends(...) object as d, without raising. None of FastStream's own FastAPI routers (Kafka, Rabbit, Redis, NATS, Confluent) accepts routers either.
#203 documents the pattern in docs/usage/fastapi.md and adds an invariant test for it.
Activity
chore: adopt the modern-di convention; drop planning/ and architectur…
lesnik512 commentedon Sep 6, 2026
Triage outcome:
needs-infoRelabeling
needs-triage→needs-info. Stayingenhancement: nothing is broken, a capability is absent, and the issue documents a workaround that leaves nothing unreachable.Reproduced, exactly as described:
OutboxBroker.__init__takesrouters: Sequence[Registrator[OutboxInnerMessage]] = ()(broker.py:133) and forwards it (broker.py:190).OutboxRouter.__init__(fastapi/router.py:68) enumerates every keyword explicitly with no**kwargscatch-all, androutersis not among them — so this is a hardTypeError, not a silently-ignored kwarg.dlq_table(:73) andmetrics_recorder(:84) are both present and forwarded at:122-123, confirmingroutersas the last unforwardedOutboxBrokerargument after #88.Worth noting from the reproduction: CPython suggests
routes, which is a validOutboxRouterparameter — the FastAPIAPIRouterone, meaning something entirely different. A user following that suggestion gets no error and no subscribers. That raises the cost of the current gap slightly above "a TypeError tells you immediately".Why it stays open rather than being actioned: the design question in the body is real and unanswered. Forwarding the kwarg is one line; deciding when nested routers' subscribers start relative to the FastAPI lifespan, and how their channels compose into the
/asyncapidocument, is not. Shipping whatever falls out ofStreamRouter's ordering would be a guess.What would move it:
lesnik512 commentedon Oct 3, 2026
Closing as not planned:
routers=should not be forwarded, and grouping subscribers under the FastAPI router already works.The supported path is
router.include_router(...).StreamRouter.include_routeraccepts a broker-level router (faststream_outbox.OutboxRouter), wraps each of its subscribers with the FastAPI compatibility decorator, then includes it on the broker:That also answers the two open axes. The nested subscribers belong to the same broker, so they start with it in the FastAPI lifespan, and their channels appear in the
/asyncapidocument.Forwarding
routers=would be harmful. It callsinclude_routersin the broker constructor, which skips the FastAPI wrap, and the FastAPI router runs withapply_types=False. A handler withd: str = Depends(dep)then receives the rawOutboxMessageas its body and theDepends(...)object asd, without raising. None of FastStream's own FastAPI routers (Kafka, Rabbit, Redis, NATS, Confluent) acceptsrouterseither.#203 documents the pattern in
docs/usage/fastapi.mdand adds an invariant test for it.