FastAPI Integration¶
InjectQ provides seamless dependency injection for FastAPI using modern, high-performance patterns with ContextVars for per-request container propagation.
Installation¶
Quick Start¶
from typing import Annotated
from fastapi import FastAPI
from injectq import InjectQ, singleton
from injectq.integrations.fastapi import InjectFastAPI, setup_fastapi
# Define your services
@singleton
class UserService:
def get_user(self, user_id: int) -> dict:
return {"id": user_id, "name": "John Doe"}
# Setup
app = FastAPI()
container = InjectQ.get_instance()
setup_fastapi(container, app)
# Define routes with dependency injection
@app.get("/users/{user_id}")
def get_user(
user_id: int,
service: Annotated[UserService, InjectFastAPI(UserService)]
):
return service.get_user(user_id)
How It Works¶
The FastAPI integration uses:
- ContextVar-based middleware (
InjectQRequestMiddleware) that sets the active container per request - Zero overhead context propagation - O(1) ContextVar operations
- InjectFastAPI as a FastAPI Depends marker for type-safe, async-safe dependency resolution
Core API¶
setup_fastapi(container, app)¶
Registers the InjectQ integration with your FastAPI application. This adds middleware to propagate the container via ContextVars.
from injectq import InjectQ
from injectq.integrations.fastapi import setup_fastapi
from fastapi import FastAPI
app = FastAPI()
container = InjectQ.get_instance()
# Register integration - must be called before defining routes
setup_fastapi(container, app)
InjectFastAPI[ServiceType]¶
Type-safe dependency marker for FastAPI routes. Use with Annotated for clean type hints.
from typing import Annotated
from injectq.integrations.fastapi import InjectFastAPI
@app.get("/users/{user_id}")
def get_user(
user_id: int,
service: Annotated[UserService, InjectFastAPI(UserService)]
):
return service.get_user(user_id)
InjectQRequestMiddleware¶
The lightweight middleware that handles context propagation. Automatically added by setup_fastapi().
from injectq.integrations.fastapi import InjectQRequestMiddleware
# Already added by setup_fastapi(), but you can manually add it:
app.add_middleware(InjectQRequestMiddleware, container=container)
Basic Example¶
Complete working example matching examples/api.py:
from typing import Annotated
from fastapi import FastAPI, HTTPException
from injectq import InjectQ, inject, singleton
from injectq.integrations.fastapi import InjectFastAPI, setup_fastapi
# Define services
@singleton
class UserRepo:
def __init__(self) -> None:
self.users = {}
def add_user(self, user_id: str, user_data: dict) -> None:
self.users[user_id] = user_data
def get_user(self, user_id: str) -> dict | None:
return self.users.get(user_id)
def delete_user(self, user_id: str) -> None:
if user_id in self.users:
del self.users[user_id]
@singleton
class UserService:
@inject
def __init__(self, user_repo: UserRepo) -> None:
self.user_repo = user_repo
def create_user(self, user_id: str, user_data: dict) -> None:
self.user_repo.add_user(user_id, user_data)
def retrieve_user(self, user_id: str) -> dict | None:
return self.user_repo.get_user(user_id)
def remove_user(self, user_id: str) -> None:
self.user_repo.delete_user(user_id)
# Setup FastAPI and InjectQ
app = FastAPI()
container = InjectQ.get_instance()
setup_fastapi(container, app)
# Routes with dependency injection
@app.post("/users/{user_id}")
def create_user(
user_id: str,
user_service: Annotated[UserService, InjectFastAPI(UserService)],
) -> dict:
user_service.create_user(user_id, {"name": "John Doe"})
return {"message": "User created successfully"}
@app.get("/users/{user_id}")
def get_user(
user_id: str,
user_service: Annotated[UserService, InjectFastAPI(UserService)],
) -> dict:
user = user_service.retrieve_user(user_id)
if user:
return user
raise HTTPException(status_code=404, detail="User not found")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
Using with Modules¶
Organize FastAPI services with modules:
from typing import Annotated
from injectq import Module, InjectQ
from injectq.integrations.fastapi import InjectFastAPI, setup_fastapi
from fastapi import FastAPI
# Define modules
class DatabaseModule(Module):
def configure(self, binder):
binder.bind(Database, Database())
class ServiceModule(Module):
def configure(self, binder):
binder.bind(UserService, UserService())
# Setup
app = FastAPI()
container = InjectQ(modules=[
DatabaseModule(),
ServiceModule()
])
setup_fastapi(container, app)
# Routes use services from modules
@app.get("/users")
def get_users(service: Annotated[UserService, InjectFastAPI(UserService)]) -> list:
return service.get_users()
@app.post("/users")
def create_user(
name: str,
service: Annotated[UserService, InjectFastAPI(UserService)]
) -> dict:
return service.create_user(name)
Common Patterns¶
Request-Scoped Services¶
from typing import Annotated
from uuid import uuid4
from injectq import scoped
@scoped
class RequestContext:
def __init__(self):
self.request_id = str(uuid4())
self.user_id = None
@app.get("/items/{item_id}")
def get_item(
item_id: int,
context: Annotated[RequestContext, InjectFastAPI(RequestContext)]
):
print(f"[{context.request_id}] Fetching item {item_id}")
return {"id": item_id, "request_id": context.request_id}
Dependency Chains¶
from typing import Annotated
from injectq import singleton, inject
@singleton
class EmailService:
def send_email(self, to: str, subject: str) -> bool:
print(f"Email sent to {to}: {subject}")
return True
@singleton
class UserNotificationService:
@inject
def __init__(self, email: EmailService) -> None:
self.email = email
def notify_user(self, user_id: int, message: str) -> bool:
return self.email.send_email(f"user{user_id}@example.com", message)
@app.post("/notify/{user_id}")
def notify(
user_id: int,
message: str,
notifier: Annotated[UserNotificationService, InjectFastAPI(UserNotificationService)]
) -> dict:
success = notifier.notify_user(user_id, message)
return {"sent": success}
Multiple Dependency Injection¶
from typing import Annotated
from injectq import singleton
@singleton
class AuthService:
def verify_token(self, token: str) -> bool:
return token == "valid_token"
@singleton
class DataService:
def get_data(self) -> dict:
return {"data": "value"}
@app.get("/secure-data")
def get_secure_data(
auth: Annotated[AuthService, InjectFastAPI(AuthService)],
data: Annotated[DataService, InjectFastAPI(DataService)]
) -> dict:
if not auth.verify_token("valid_token"):
raise HTTPException(status_code=401, detail="Unauthorized")
return data.get_data()
Testing¶
Test FastAPI routes with mocked dependencies:
from typing import Annotated
import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI
from injectq import InjectQ
from injectq.integrations.fastapi import InjectFastAPI, setup_fastapi
class MockUserService:
def get_users(self):
return [{"id": 1, "name": "Test User"}]
def create_user(self, name: str):
return {"name": name}
@pytest.fixture
def test_app():
"""Create test app with mocked services"""
app = FastAPI()
container = InjectQ()
container[UserService] = MockUserService()
setup_fastapi(container, app)
# Define routes
@app.get("/users")
def get_users(service: Annotated[UserService, InjectFastAPI(UserService)]):
return service.get_users()
@app.post("/users")
def create_user(
name: str,
service: Annotated[UserService, InjectFastAPI(UserService)]
):
return service.create_user(name)
return app
def test_get_users(test_app):
client = TestClient(test_app)
response = client.get("/users")
assert response.status_code == 200
assert len(response.json()) == 1
assert response.json()[0]["name"] == "Test User"
def test_create_user(test_app):
client = TestClient(test_app)
response = client.post("/users?name=Alice")
assert response.status_code == 200
assert response.json()["name"] == "Alice"
Best Practices¶
✅ Good Patterns¶
1. Use singleton for shared resources
@singleton
class DatabasePool:
def __init__(self):
self.pool = create_connection_pool()
@singleton
class CacheService:
def __init__(self):
self.cache = {}
2. Use scoped for request-specific data
@scoped
class RequestContext:
def __init__(self):
from uuid import uuid4
self.request_id = str(uuid4())
3. Always use Annotated with InjectFastAPI
# ✅ Good - clear, type-safe
@app.get("/items")
def get_items(
service: Annotated[ItemService, InjectFastAPI(ItemService)]
):
return service.get_items()
❌ Bad Patterns¶
1. Don't access container directly in routes
# ❌ Bad - manual resolution
@app.get("/items/{item_id}")
def get_item(item_id: int):
service = container.get(ItemService)
return service.get_item(item_id)
# ✅ Good - let FastAPI inject
@app.get("/items/{item_id}")
def get_item(
item_id: int,
service: Annotated[ItemService, InjectFastAPI(ItemService)]
):
return service.get_item(item_id)
2. Don't use singleton for request-specific data
# ❌ Bad - shared across requests!
@singleton
class UserContext:
def __init__(self):
self.user_id = None
# ✅ Good - isolated per request
@scoped
class UserContext:
def __init__(self):
self.user_id = None
3. Don't forget to call setup_fastapi() first
# ❌ Bad - forgot middleware
app = FastAPI()
container = InjectQ.get_instance()
@app.get("/users")
def get_users(service: Annotated[UserService, InjectFastAPI(UserService)]):
pass # This will fail - no middleware!
# ✅ Good - setup first
app = FastAPI()
container = InjectQ.get_instance()
setup_fastapi(container, app) # Must be called first
@app.get("/users")
def get_users(service: Annotated[UserService, InjectFastAPI(UserService)]):
pass # Now it works!
Performance Notes¶
The FastAPI integration uses ContextVars for per-request container propagation:
- O(1) overhead - ContextVar set/reset is constant time
- No async complications - ContextVars handle async context propagation automatically
- No per-request initialization - Middleware just sets the container reference
- Suitable for high-throughput APIs - Tested with thousands of requests per second
Summary¶
FastAPI integration provides:
- Simple setup - Just
setup_fastapi(container, app)before defining routes - Type-safe injection - Use
Annotated[ServiceType, InjectFastAPI(ServiceType)]in route parameters - Request isolation - Each request gets its own scoped container context via ContextVars
- Zero global state - No singleton container pollution
- High performance - O(1) ContextVar operations, async-safe
- Easy testing - Mock dependencies by rebinding in test container
Key components:
- setup_fastapi(container, app) - Register integration (call first!)
- InjectFastAPI[ServiceType] - Inject dependencies with Annotated
- InjectQRequestMiddleware - Automatic middleware for context propagation
Best practices:
- Use Annotated[ServiceType, InjectFastAPI(ServiceType)] for type safety
- Call setup_fastapi() before defining any routes
- Use singleton for shared resources
- Use scoped for request-specific data
- Test with mocked dependencies
- Organize services with modules
Ready to explore Taskiq integration?