Dependency Injection
How an API route gets its database session, its caller and its permission
checks. They are FastAPI dependencies, in backend/app/api/deps.py.
The dependencies
| Dependency | What the route gets |
|---|---|
get_db |
a database session, closed when the request ends |
get_current_active_user |
the signed-in caller; an inactive account is refused with 400 |
get_current_superuser |
the caller, who must be a superuser; anyone else is refused with 403 |
get_api_key |
the user an X-API-Key header belongs to; a missing or invalid key is refused with 401 |
@router.get("/")
async def read_items(
current_user: User = Depends(deps.get_current_active_user),
):
return {"message": f"Hello, {current_user.username}"}
Permission checks
- Experiments. The route loads the experiment, then calls
deps.get_experiment_read_access(experiment, current_user)to read it, ordeps.get_experiment_change_access(experiment, current_user)to change it. Both are synchronous and refuse with403, and neither looks at who created the experiment. - Feature flags. Access is by role, not by owner. A route that changes a flag
calls
can_act_on_feature_flag(user, owner_id, action)frombackend/app/core/permissions.py, and creating one goes through the asyncdeps.can_create_feature_flag. Build a new route on those two, not on the other flag helpers indeps.py. - Reports. The report checks in
deps.py,get_report_access,can_create_report,can_update_reportandcan_delete_report, are async: await them.
@router.get("/{experiment_id}/results")
async def get_experiment_results(
experiment_id: UUID,
db: Session = Depends(deps.get_db),
current_user: User = Depends(deps.get_current_active_user),
):
experiment = db.query(Experiment).filter(Experiment.id == experiment_id).first()
if experiment is None:
raise HTTPException(status_code=404, detail="Experiment not found")
deps.get_experiment_read_access(experiment, current_user)
...