FastAPI
Dependency injection for FastAPI is available in the wireup.integration.fastapi
module.
Features:
- Inject dependencies in FastAPI routes.
- Expose
fastapi.Request
as ascoped
Wireup dependency. - Close the Wireup container upon application termination for proper resource cleanup.
Initialize the integration¶
To initialize the integration, call wireup.integration.fastapi.setup
after adding all routers.
container = wireup.create_async_container(
# Add service modules.
service_modules=[
# Top level module containing service registrations.
services,
# Include the integration if you require `fastapi.Request` in Wireup services.
wireup.integration.fastapi
],
# Expose parameters to Wireup as necessary.
parameters={
"debug": settings.DEBUG
}
)
wireup.integration.fastapi.setup(container, app)
Inject in HTTP and WebSocket routes¶
To inject dependencies, add the type to the route's signature and annotate them as necessary. See Annotations for more details.
@app.get("/random")
async def target(
random_service: Injected[RandomService],
is_debug: Annotated[bool, Inject(param="debug")],
# This is a regular FastAPI dependency.
lucky_number: Annotated[int, Depends(get_lucky_number)]
): ...
@app.websocket("/ws")
async def ws(websocket: WebSocket, greeter: Injected[GreeterService]): ...
Optional Performance Optimization
Optimize dependency injection performance by using a custom APIRoute class. This reduces overhead in endpoints that use Wireup injection by avoiding redundant processing.
from fastapi import APIRouter
from wireup.integration.fastapi import WireupRoute
router = APIRouter(route_class=WireupRoute)
If you already have a custom route class, you can inherit from WireupRoute instead.
Under the hood: FastAPI processes all route parameters, including ones meant for Wireup. The WireupRoute class optimizes this by making Wireup-specific parameters only visible to Wireup, removing unnecessary processing by FastAPI's dependency injection system.
Inject FastAPI request¶
A key feature of the integration is to expose fastapi.Request
in Wireup.
To allow injecting it in your services you must add wireup.integration.fastapi
module to your service modules
when creating a container.
Services depending on it should be transient or scoped, so that these are not shared across requests.
@service(lifetime="scoped")
class HttpAuthenticationService:
def __init__(self, request: fastapi.Request) -> None: ...
@service(lifetime="scoped")
def example_factory(request: fastapi.Request) -> ExampleService: ...
Accessing the Container¶
If you ever need to access the Wireup container directly, use the provided functions:
from wireup.integration.fastapi import get_app_container, get_request_container
# Get application-wide container.
app_container: AsyncContainer = get_app_container(app)
# Get request-scoped container.
# This is what is currently injecting services on the active request.
request_container: ScopedAsyncContainer = get_request_container()
Get dependencies in middleware¶
Wireup integration performs injection only in FastAPI routes. If the container is not stored globally, you can get a reference to it using get_app_container
and get_request_container
from the wireup.integration.fastapi
module.
from wireup.integration.fastapi import get_request_container
async def example_middleware(request: Request, call_next) -> Response:
container = get_request_container()
...
return await call_next(request)
Get dependencies in Depends
.¶
Similarly, you can get a reference to the container in a FastAPI dependency.
from wireup.integration.fastapi import get_request_container
async def example_dependency(request: Request, other_dependency: Depends(...)):
container = get_request_container()
...
Warning
Use fastapi.Depends
only for specific cases.
When using Wireup, let it manage all dependencies instead of mixing it with fastapi.Depends
.
Note that while this approach works, the reverse does not.
You cannot require fastapi.Depends
objects in Wireup services.
Testing¶
For general testing tips with Wireup refer to the test docs. With the FastAPI integration, you can override dependencies in the container as follows.
from wireup.integration.fastapi import get_app_container
def test_override(client):
class DummyGreeter(GreeterService):
def greet(self, name: str) -> str:
return f"Hi, {name}"
with get_app_container(app).override.service(GreeterService, new=DummyGreeter()):
res = client.get("/greet?name=Test")
See FastAPI integration tests for more examples.
Warning
The Wireup integration relies on FastAPI's lifespan events to close the container upon termination. To ensure these events are triggered during testing, instantiate the test client as a context manager. This is a requirement due to FastAPI's design, not a limitation of Wireup.
@pytest.fixture()
def client(app: FastAPI) -> Iterator[TestClient]:
with TestClient(app) as client:
yield client