Frequently Asked Questions¶
General¶
What's the difference between pico-fastapi and FastAPI's Depends()?¶
| Aspect | FastAPI Depends() | pico-fastapi |
|---|---|---|
| Injection style | Function parameters | Constructor |
| Scope management | Manual | Automatic |
| Testing | Override Depends() | Container overrides |
| Architecture | Framework-driven | Domain-driven |
FastAPI Default:
from fastapi import Depends
def get_db():
return Database()
@app.get("/users")
def get_users(db: Database = Depends(get_db)):
return db.get_users()
pico-fastapi:
from pico_fastapi import controller, get
@controller(prefix="/users")
class UserController:
def __init__(self, db: Database): # Injected automatically
self.db = db
@get("/")
async def get_users(self):
return self.db.get_users()
Can I use FastAPI's Depends() with pico-fastapi?¶
Yes! They work together. Use Depends() for request-specific data (like getting current user from request), and pico-fastapi for service dependencies.
from fastapi import Depends, Request
from pico_fastapi import controller, get
def get_current_user(request: Request):
return request.state.user
@controller(prefix="/profile")
class ProfileController:
def __init__(self, user_service: UserService):
self.user_service = user_service
@get("/")
async def get_profile(self, user = Depends(get_current_user)):
return self.user_service.get_profile(user.id)
Do I need pico-boot to use pico-fastapi?¶
No, but it's recommended. Without pico-boot, you must explicitly include pico_fastapi modules:
from pico_ioc import init # Note: pico_ioc, not pico_boot
container = init(modules=[
"myapp.controllers",
"myapp.services",
"pico_fastapi.config", # Required
"pico_fastapi.factory", # Required
])
With pico-boot, pico-fastapi is auto-discovered.
Controllers¶
What's the default scope for controllers?¶
Controllers use request scope by default. Each HTTP request gets a fresh controller instance.
For WebSocket controllers, use websocket scope:
Can I use the same controller for both HTTP and WebSocket?¶
No. Controllers are scoped to either HTTP or WebSocket, not both. This is intentional for lifecycle management.
# HTTP Controller
@controller(prefix="/api")
class ApiController:
@get("/status")
async def status(self):
return {"status": "ok"}
# WebSocket Controller (separate)
@controller(scope="websocket")
class WsController:
@websocket("/ws")
async def ws_endpoint(self, websocket):
pass
How do I add tags to my controller routes?¶
Use the tags parameter in the @controller decorator:
Can I return tuples for status codes?¶
Yes! Return (content, status_code) or (content, status_code, headers):
@get("/item/{id}")
async def get_item(self, id: int):
if id not in self.items:
return {"error": "Not found"}, 404
return self.items[id], 200
Configurers¶
What is a Configurer?¶
A Configurer is a class that implements FastApiConfigurer protocol and adds middleware or configures the FastAPI app.
from pico_fastapi import FastApiConfigurer
from pico_ioc import component
@component
class MyConfigurer(FastApiConfigurer):
priority = 0
def configure(self, app: FastAPI) -> None:
app.add_middleware(MyMiddleware)
What does the priority number mean?¶
Priority determines when your middleware runs relative to PicoScopeMiddleware:
- Negative priority (< 0): Outer middleware, runs BEFORE scopes are set up
- Non-negative priority (>= 0): Inner middleware, runs AFTER scopes are set up
When should I use negative vs positive priority?¶
| Middleware Type | Priority | Reason |
|---|---|---|
| CORS | -100 | Must handle preflight before anything |
| Session | -50 | Session data needed before scope setup |
| Auth (token parsing) | 10 | Can use request-scoped services |
| Logging | depends | Before auth = raw requests, after = with user info |
Why isn't my configurer being applied?¶
Check these common issues:
-
Missing
@componentdecorator: -
Module not in init list:
-
Not implementing the protocol:
Scopes¶
What scopes are available?¶
| Scope | Description | Use Case |
|---|---|---|
singleton | One instance for entire app | Stateless services |
request | New instance per HTTP request | Request-specific state |
session | Tied to HTTP session | Shopping cart, user preferences |
websocket | One instance per WS connection | Chat connections |
How does session scope work?¶
Session scope requires SessionMiddleware from Starlette:
from starlette.middleware.sessions import SessionMiddleware
from pico_fastapi import FastApiConfigurer
from pico_ioc import component
@component
class SessionConfigurer(FastApiConfigurer):
priority = -50 # Must be outer (before PicoScopeMiddleware)
def configure(self, app: FastAPI) -> None:
app.add_middleware(
SessionMiddleware,
secret_key="your-secret-key",
)
Then use session-scoped components:
How do I access the current scope ID?¶
You can inject PicoContainer and use its context methods:
from pico_ioc import component, PicoContainer
@component(scope="request")
class RequestContext:
def __init__(self, container: PicoContainer):
# Access request ID from scope context
self.request_id = container.get_scope_id("request")
Testing¶
How do I test controllers?¶
Use TestClient with container overrides:
from fastapi.testclient import TestClient
from pico_boot import init
def test_controller():
container = init(
modules=["myapp"],
overrides={RealService: MockService()},
)
app = container.get(FastAPI)
with TestClient(app) as client:
response = client.get("/api/endpoint")
assert response.status_code == 200
How do I mock a service?¶
Use container overrides:
class MockUserService:
def get_user(self, user_id: int):
return {"id": user_id, "name": "Test User"}
container = init(
modules=["myapp"],
overrides={UserService: MockUserService()},
)
How do I disable plugin auto-discovery in tests?¶
Set the environment variable:
# conftest.py
import os
def pytest_configure(config):
os.environ["PICO_BOOT_AUTO_PLUGINS"] = "false"
Errors¶
NoControllersFoundError¶
Cause: No @controller decorated classes were found.
Solutions:
- Ensure controllers have the
@controllerdecorator - Include controller modules in
init(): - Check for import errors in controller modules
InvalidConfigurerError¶
Cause: A class registered as configurer doesn't implement FastApiConfigurer.configure().
Solution: Implement the configure method:
@component
class MyConfigurer(FastApiConfigurer):
def configure(self, app: FastAPI) -> None: # Required!
pass
"Component not found" for controller dependency¶
Cause: A dependency of your controller isn't registered.
Solutions:
- Add
@componentto the dependency class - Include the dependency's module in
init() - Check for typos in type hints
Advanced¶
Can I have multiple FastAPI apps?¶
Yes, each init() creates a separate container and app:
admin_container = init(modules=["admin"])
admin_app = admin_container.get(FastAPI)
public_container = init(modules=["public"])
public_app = public_container.get(FastAPI)
How do I customize FastAPI settings?¶
Create a config file with fastapi prefix:
Settings are automatically applied via FastApiSettings.
Can I use pico-fastapi with other frameworks?¶
Pico-fastapi is specific to FastAPI. For other frameworks, see:
pico-celeryfor Celerypico-sqlalchemyfor SQLAlchemy
How do I add static files?¶
Use a configurer:
from fastapi.staticfiles import StaticFiles
from pico_fastapi import FastApiConfigurer
from pico_ioc import component
@component
class StaticConfigurer(FastApiConfigurer):
priority = -100
def configure(self, app: FastAPI) -> None:
app.mount("/static", StaticFiles(directory="static"), name="static")
Is pico-fastapi thread-safe?¶
Yes, as thread-safe as FastAPI and pico-ioc. Request scopes are isolated per request. However, singleton components shared across requests must be designed for thread safety.