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 web applications and other interfaces, such as a CLI.
Getting started¶
First, create an async container
import wireup
from myapp import services
container = wireup.create_async_container(
service_modules=[services],
parameters={"debug": settings.DEBUG},
)
Then initialize the integration by calling wireup.integration.fastapi.setup
after adding all routers:
from fastapi import FastAPI
app = FastAPI()
# Add routers here
# app.include_router(...)
wireup.integration.fastapi.setup(
container,
app,
class_based_handlers=[...] # Include Wireup Class-Based Handlers
)
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.
from typing import Annotated
from fastapi import Depends
from wireup import Injected, Inject
@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)]
): ...
from fastapi import WebSocket
from wireup import Injected
@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 services, include wireup.integration.fastapi
module in the service modules when creating the container.
import wireup
container = wireup.create_async_container(
service_modules=[services, wireup.integration.fastapi],
parameters={"debug": settings.DEBUG},
)
Use Request and WebSocket¶
import fastapi
from wireup import service
@service(lifetime="scoped")
class HttpAuthenticationService:
def __init__(self, request: fastapi.Request) -> None: ...
import fastapi
from wireup import service
@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 using class-based handlers or generator factories in the application.
@pytest.fixture()
def client(app: FastAPI) -> Iterator[TestClient]:
with TestClient(app) as client:
yield client