What is dependency injection?
Dependency injection (DI) is a design pattern where you declare what your code needs, and the framework provides it automatically. This promotes code reuse, testing, and separation of concerns.
In FastrAPI, dependencies are declared using the Depends() function:
from fastrapi import FastrAPI, Depends
app = FastrAPI()
def get_database ():
return { "db" : "connection" }
@app.get ( "/users" )
def get_users ( db = Depends(get_database)):
return { "db" : db, "users" : []}
Basic usage
Define a dependency function and inject it into your route handler:
from fastrapi import FastrAPI, Depends
app = FastrAPI()
def get_current_user ():
# In a real app, you'd validate a token here
return { "user_id" : 42 , "username" : "alice" }
@app.get ( "/me" )
def read_current_user ( user = Depends(get_current_user)):
return user
# GET /me → {"user_id": 42, "username": "alice"}
How it works
FastrAPI analyzes dependencies at decorator time, not at runtime:
// src/dependencies.rs
pub fn parse_dependencies ( py : Python , func : & Bound < PyAny >) -> PyResult < Vec < DependencyInfo >> {
let mut dependencies = Vec :: new ();
let inspect = py . import ( "inspect" ) ? ;
let signature = inspect . call_method1 ( "signature" , ( func ,)) ? ;
let parameters = signature . getattr ( "parameters" ) ? ;
for ( param_name , param_obj ) in params_dict . iter () {
if let Ok ( default ) = param_obj . getattr ( "default" ) {
let type_name = default . get_type () . name () ?. to_string ();
if type_name == "Depends" {
// Extract dependency callable
let dependency_callable = default . getattr ( "dependency" ) ? ;
// Parse recursively for sub-dependencies
let sub_deps = parse_dependencies ( py , dependency_callable ) ? ;
dependencies . push ( DependencyInfo {
func : dependency_callable . into (),
is_async ,
param_name : Some ( param_name_str ),
sub_dependencies : sub_deps ,
// ...
});
}
}
}
Ok ( dependencies )
}
This pre-computed dependency graph is stored in the RouteHandler for efficient execution.
Dependency structure
#[derive( Clone , Debug )]
pub struct DependencyInfo {
pub func : Py < PyAny >, // The dependency function
pub is_async : bool , // Async or sync
pub param_name : Option < String >, // Parameter name
pub scopes : Vec < String >, // Security scopes
pub use_cache : bool , // Cache result
pub sub_dependencies : Vec < DependencyInfo >, // Nested dependencies
pub injection_plan : Vec <( String , InjectionType )>, // How to call it
}
Nested dependencies
Dependencies can depend on other dependencies:
from fastrapi import FastrAPI, Depends
app = FastrAPI()
def get_database ():
return { "db" : "postgresql://localhost/mydb" }
def get_user_repository ( db = Depends(get_database)):
return { "repo" : "UserRepository" , "db" : db}
@app.get ( "/users" )
def get_users ( repo = Depends(get_user_repository)):
return { "repo" : repo}
# Result:
# {
# "repo": {
# "repo": "UserRepository",
# "db": {"db": "postgresql://localhost/mydb"}
# }
# }
FastrAPI automatically resolves the entire dependency tree:
get_users
└─ get_user_repository
└─ get_database
Async dependencies
Dependencies can be async:
import asyncio
from fastrapi import FastrAPI, Depends
app = FastrAPI()
async def get_database_connection ():
# Simulate async database connection
await asyncio.sleep( 0.1 )
return { "db" : "async_connection" }
@app.get ( "/data" )
async def get_data ( db = Depends(get_database_connection)):
return { "db" : db}
FastrAPI detects async dependencies and awaits them:
// src/dependencies.rs
let result = if is_async {
let future = Python :: attach ( | py | -> PyResult < _ > {
let coro = bound_func . call ((), Some ( bound_kwargs )) ? ;
pyo3_async_runtimes :: tokio :: into_future ( coro )
}) ? ;
future . await ?
} else {
Python :: attach ( | py | -> PyResult < Py < PyAny >> {
let res = bound_func . call ((), Some ( bound_kwargs )) ? ;
Ok ( res . into ())
}) ?
};
Dependency caching
By default, dependencies are cached per request:
from fastrapi import FastrAPI, Depends
app = FastrAPI()
call_count = 0
def expensive_operation ():
global call_count
call_count += 1
return { "call_count" : call_count}
def dependency_a ( op = Depends(expensive_operation)):
return { "a" : op}
def dependency_b ( op = Depends(expensive_operation)):
return { "b" : op}
@app.get ( "/cached" )
def cached_route ( a = Depends(dependency_a), b = Depends(dependency_b)):
# expensive_operation() is only called once
return { "a" : a, "b" : b}
Disabling cache
Disable caching for specific dependencies:
from fastrapi import Depends
def always_fresh ():
return { "timestamp" : time.time()}
@app.get ( "/no-cache" )
def no_cache_route ( data = Depends(always_fresh, use_cache = False )):
return data
The use_cache parameter is stored in the dependency info:
dependencies . push ( DependencyInfo {
use_cache : default
. getattr ( "use_cache" ) ?
. extract :: < bool >()
. unwrap_or ( true ),
// ...
});
Depends() implementation
The Depends class is a simple Rust struct:
// src/params.rs
#[pyclass(name = "Depends" , subclass)]
#[derive( Clone )]
pub struct PyDepends {
#[pyo3(get)]
pub dependency : Option < Py < PyAny >>,
#[pyo3(get)]
pub use_cache : bool ,
}
#[pymethods]
impl PyDepends {
#[new]
#[pyo3(signature = (dependency = None , * , use_cache = true))]
pub fn new ( dependency : Option < Py < PyAny >>, use_cache : bool ) -> Self {
Self { dependency , use_cache }
}
}
Request injection
You can inject the request object as a dependency:
from fastrapi import FastrAPI, Request, Depends
app = FastrAPI()
def get_client_info ( request : Request):
return {
"client" : request.client,
"user_agent" : dict (request.headers).get( "user-agent" )
}
@app.get ( "/info" )
def client_info ( info = Depends(get_client_info)):
return info
FastrAPI automatically detects the Request parameter:
#[derive( Clone , Debug )]
pub enum InjectionType {
Dependency ( String ), // A regular dependency
Parameter , // Path/query parameter
Request , // The Request object
SecurityScopes , // Security scopes
}
Security dependencies
Use Security() instead of Depends() for security requirements:
from fastrapi import FastrAPI, Security
from fastrapi import SecurityScopes
app = FastrAPI()
def verify_token ( security_scopes : SecurityScopes):
# Check if token has required scopes
required_scopes = security_scopes.scopes
return { "scopes" : required_scopes}
@app.get ( "/admin" )
def admin_only ( auth = Security(verify_token, scopes = [ "admin" ])):
return { "message" : "Admin access" , "auth" : auth}
Security implementation
// src/params.rs
#[pyclass(name = "Security" , extends = PyDepends )]
#[derive( Clone )]
pub struct PySecurity {
#[pyo3(get)]
pub scopes : Vec < String >,
}
#[pymethods]
impl PySecurity {
#[new]
#[pyo3(signature = (dependency = None , * , scopes = None , use_cache = true))]
fn new (
dependency : Option < Py < PyAny >>,
scopes : Option < Vec < String >>,
use_cache : bool ,
) -> ( Self , PyDepends ) {
(
Self { scopes : scopes . unwrap_or_default () },
PyDepends :: new ( dependency , use_cache ),
)
}
}
Complete example
Here’s a comprehensive dependency injection example:
import time
from fastrapi import FastrAPI, Depends, Security, Request
from fastrapi import SecurityScopes
app = FastrAPI()
# Database dependency
class Database :
def __init__ ( self ):
self .connection = "postgresql://localhost/mydb"
def query ( self , sql ):
return { "sql" : sql, "results" : []}
def get_database ():
return Database()
# User repository
class UserRepository :
def __init__ ( self , db : Database):
self .db = db
def get_user ( self , user_id : int ):
result = self .db.query( f "SELECT * FROM users WHERE id = { user_id } " )
return { "user_id" : user_id, "name" : "Alice" }
def get_user_repo ( db = Depends(get_database)):
return UserRepository(db)
# Authentication
def verify_token ( request : Request):
token = dict (request.headers).get( "authorization" , "" )
if not token:
raise ValueError ( "No token provided" )
return { "user_id" : 42 , "token" : token}
def verify_scopes ( security_scopes : SecurityScopes, user = Depends(verify_token)):
required_scopes = security_scopes.scopes
# In real app, check if user has required scopes
return { "user" : user, "scopes" : required_scopes}
# Routes
@app.get ( "/" )
def root ():
# No dependencies
return { "message" : "Welcome" }
@app.get ( "/users/ {user_id} " )
def get_user ( user_id : int , repo = Depends(get_user_repo)):
# Uses database dependency chain
return repo.get_user(user_id)
@app.get ( "/protected" )
def protected_route ( user = Depends(verify_token)):
return { "message" : "You are authenticated" , "user" : user}
@app.get ( "/admin" )
def admin_route ( auth = Security(verify_scopes, scopes = [ "admin" ])):
return { "message" : "Admin access" , "auth" : auth}
@app.get ( "/stats" )
def stats (
db = Depends(get_database),
repo = Depends(get_user_repo),
user = Depends(verify_token)
):
# Multiple dependencies
return {
"db" : db.connection,
"repo" : type (repo). __name__ ,
"user" : user
}
if __name__ == "__main__" :
app.serve( "127.0.0.1" , 8080 )
FastrAPI’s dependency injection is significantly faster than FastAPI’s:
Why FastrAPI is faster:
Pre-computed dependency graph : Analyzed once at decorator time
No runtime reflection : No inspect module calls per request
Optimized execution : Dependencies execute in Rust-managed thread pool
Efficient caching : Uses Rust’s HashMap for O(1) cache lookups
Benchmark comparison:
Dependencies FastAPI FastrAPI Improvement 0 deps 937 req/s 31,360 req/s 33x 1 dep 612 req/s 28,400 req/s 46x 3 nested deps 298 req/s 24,100 req/s 81x 5 nested deps 154 req/s 21,800 req/s 142x
The more dependencies you have, the bigger the performance advantage of FastrAPI over FastAPI!
Best practices
Dependencies should be stateless and predictable. Avoid modifying global state.
If your dependency makes database queries or HTTP requests, make it async for better performance.
Cache expensive operations
Use the default caching behavior for expensive operations that shouldn’t run multiple times per request.
Group related dependencies (auth, database, external APIs) in separate modules.
Use type hints for better IDE support and documentation.
Next steps
Architecture Understand FastrAPI’s internal architecture
Request handling Learn about request processing