FastAPI Integration¶
-
Zero Runtime Overhead
Inject dependencies with zero runtime overhead using Class-Based Handlers.
-
Automatic Dependency Management
Inject dependencies in routes and automatically manage container lifecycle.
-
Global Access
Retrieve dependencies in middleware and route handler decorators where they are normally unavailable in FastAPI.
-
Shared business logic
Wireup is framework-agnostic. Use it to share the service layer between your web application and other interfaces, such as a CLI.
Getting started¶
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 need `fastapi.Request`
# or `fastapi.WebSocket` in Wireup services.
wireup.integration.fastapi
],
# Expose parameters to Wireup as necessary.
parameters={
"debug": settings.DEBUG
},
# Include here any Wireup Class-Based Handlers.
class_based_handlers=[...],
)
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]): ...
Tip
Improve 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 or websocket¶
To inject the current request/websocket in your services you must add wireup.integration.fastapi
module to your service modules when creating a container.
@service(lifetime="scoped")
class HttpAuthenticationService:
def __init__(self, request: fastapi.Request) -> None: ...
@service(lifetime="scoped")
class ChatService:
def __init__(self, websocket: fastapi.WebSocket) -> None:
await self.websocket.accept()
async def send(self, data: str):
await self.websocket.send_text(data)
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
FastAPI's lifespan events are required to close the Wireup container properly. Use a context manager when instantiating the test client if you're using class-based handlers or generator factories in your application.
@pytest.fixture()
def client(app: FastAPI) -> Iterator[TestClient]:
with TestClient(app) as client:
yield client