Class-Based Handlers for FastAPI¶
Wireup provides class-based handlers for FastAPI allowing grouping of related endpoints and dependency injection with zero runtime overhead.
Benefits¶
- Zero runtime overhead for constructor-injected dependencies
- State persistence between requests within the same class
- Better code organization through class-based endpoint grouping
Basic Usage¶
Create a class with a field router
of type fastapi.Router
then decorate class methods like you normally do.
Register class-based handlers during setup through Wireup. Do not include these routers in fastapi directly.
class UserHandler:
router = fastapi.Router(prefix="/users", route_class=WireupRoute) # (1)!
def __init__(self, user_service: UserProfileService) -> None: # (2)!
self.user_service = user_profile_service
@router.get("/")
async def list_all(self):
return self.user_service.find_all()
@router.get("/me")
async def get_current_user_profile(
self,
auth_service: Injected[AuthenticationService] # (3)!
) -> web.Response:
return self.user_service.get_profile(auth_service.current_user)
- Define a router for this class. Tip: Use
route_class=WireupRoute
if you have dependencies being injected in route handlers (methods decorated with@router
). - Inject dependencies directly in the constructor. This is a one-time operation and has no runtime overhead.
- This is treated just like any other FastAPI route and as such, use of
Injected[T]
is required.AuthenticationService
is a request-scoped dependency.
The router instance is a regular FastAPI router so you can handle any connections in your class-based handlers, including WebSockets.
Integration¶
wireup.integration.fastapi.setup(
container,
app,
class_based_handlers=[UserProfileHandler]
)
How Dependencies Work¶
-
Constructor Dependencies
- Injected once at startup
- No
Injected[T]
syntax needed - Cannot be overridden after startup
- Only parameters and singleton services
-
Route Dependencies
- Use
Injected[T]
syntax - Supports all scopes*
- Injected per-request
- Use
* Injecting parameters and singletons here has no benefit and is not zero-cost. Inject those in __init__
instead.
Testing
FastAPI lifespan events are used for initialization. When testing, create clients as context managers:
@pytest.fixture()
def client(app: FastAPI) -> Iterator[TestClient]:
with TestClient(app) as client:
yield client