Back to Blog
Python

Python FastAPI Dependency Injection with Depends

python fastapi dependency injection with depends: Learn how FastAPI's Depends enables clean dependency injection, from basic usage to overrides, scopes, and testing.

FastAPIDependency InjectionDependsPythonWeb APITesting
Illustration of FastAPI dependency injection with Depends showing a function feeding a request handler

python fastapi dependency injection with depends requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

FastAPI's Depends is the core mechanism for dependency injection in Python FastAPI applications. It lets you declare reusable logic—like authentication, database sessions, or request validation—as functions that are automatically called and injected into your path operations. This article covers the syntax, behavior, and practical patterns for using Depends effectively, including how to override dependencies in tests and what happens under the hood at runtime.

What Depends Solves in FastAPI

Without dependency injection, you'd repeat the same setup code in every endpoint. For example, to get a database session, you might write:

@app.get("/items") def read_items(): db = SessionLocal() try: return db.query(Item).all() finally: db.close()

Every endpoint that needs a session repeats that boilerplate. Depends moves the session creation and cleanup into a separate function, and FastAPI calls it automatically when the endpoint runs. The same applies to authentication, permission checks, pagination parameters, or any logic that multiple endpoints share.

The key benefit is not just less code—it's that the dependency function is declared once, tested independently, and can be overridden when you need different behavior (for example, in tests). FastAPI also handles the call order and caching for you, which we'll examine shortly.

The Basic Depends Syntax

A dependency is simply a function that returns a value. You declare it as a parameter in a path operation function, using Depends to tell FastAPI to call it and inject the result.

from fastapi import Depends, FastAPI app = FastAPI() def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): return {"q": q, "skip": skip, "limit": limit} @app.get("/items") def read_items(params: dict = Depends(common_parameters)): return params

Here, common_parameters is a dependency. FastAPI sees Depends(common_parameters) and knows to call common_parameters before read_items. The returned dictionary becomes the value of params. The dependency can itself take parameters—FastAPI parses them from the request, just like it would for a path operation. This means query parameters like q, skip, and limit are automatically documented and validated.

The dependency function can be async as well. If you define async def common_parameters(...), FastAPI will await it. This is useful for dependencies that need to perform I/O, like checking a token against a database.

Dependencies with Parameters and Sub-dependencies

Dependencies can call other dependencies. This allows you to build a hierarchy where a higher-level dependency depends on a lower-level one. FastAPI resolves the entire chain and caches the results within the same request by default.

Consider an authentication flow: you need to get the current user from a token, and you also need a database session to look up that user.

from fastapi import Depends, FastAPI, HTTPException, Header from sqlalchemy.orm import Session app = FastAPI() def get_db(): db = SessionLocal() try: yield db finally: db.close() def get_current_user(db: Session = Depends(get_db), authorization: str = Header(...)): # decode token, fetch user from db user = db.query(User).filter(User.token == authorization).first() if user is None: raise HTTPException(status_code=401, detail="Invalid token") return user @app.get("/profile") def read_profile(current_user: User = Depends(get_current_user)): return current_user

Here, get_current_user depends on get_db. When read_profile is called, FastAPI first calls get_db to get a session, then passes it to get_current_user. The authorization header is also parsed. If the user isn't found, an HTTPException is raised, and the endpoint never runs.

Notice that get_db is a generator function (it uses yield). FastAPI supports this pattern for dependencies that need to set up and tear down resources. The code before yield runs before the endpoint, and the code after yield runs after the endpoint finishes, even if an exception occurs. This is the idiomatic way to manage database sessions.

Dependency Scopes and Lifecycle

By default, FastAPI caches the result of a dependency within a single request. If the same dependency is used in multiple places in the same request, it's called only once, and the same instance is reused. This is efficient and avoids redundant work, but it also means that if you need a fresh instance per call, you must adjust the behavior.

For example, consider a dependency that returns a random number:

from fastapi import Depends, FastAPI import random app = FastAPI() def get_random(): return random.random() @app.get("/random") def read_random(a: float = Depends(get_random), b: float = Depends(get_random)): return {"a": a, "b": b}

Because of caching, a and b will have the same value. If you need different values, you can disable caching with use_cache=False:

@app.get("/random") def read_random( a: float = Depends(get_random, use_cache=False), b: float = Depends(get_random, use_cache=False), ): return {"a": a, "b": b}

Now each call gets a fresh result. This is a contrived example, but the same principle applies when a dependency returns a mutable object that you intend to modify—caching could cause unintended shared state within a request.

Dependencies are scoped to the request by default. They are not shared across requests. If you need a dependency that lives for the application's lifetime (like a connection pool), you can create it at startup and inject it, but that's not a typical use of Depends. For application-scoped resources, you'd typically use FastAPI's lifespan or a global object.

Overriding Dependencies for Testing

One of the most powerful features of Depends is the ability to override dependencies in tests. This lets you replace real database connections, external APIs, or authentication logic with mocks or stubs, without changing the endpoint code.

FastAPI provides app.dependency_overrides, a dictionary that maps the original dependency function to a replacement.

from fastapi.testclient import TestClient app = FastAPI() def get_current_user(): return {"username": "real_user"} @app.get("/me") def read_me(user: dict = Depends(get_current_user)): return user def fake_get_current_user(): return {"username": "test_user"} app.dependency_overrides[get_current_user] = fake_get_current_user client = TestClient(app) response = client.get("/me") assert response.json() == {"username": "test_user"}

You can also clear the overrides after tests using app.dependency_overrides.clear(). This pattern is essential for writing isolated unit tests for your endpoints without touching real infrastructure.

When you have a chain of dependencies, you can override any level. For example, you might override get_db to return an in-memory SQLite session, while keeping get_current_user as the real implementation. This gives you fine-grained control over what each test exercises.

Common Pitfalls and How to Avoid Them

A few mistakes are common when working with Depends. Understanding them will save you debugging time.

Forgetting to use Depends in the parameter default. If you write def read_items(params: dict = common_parameters), FastAPI treats common_parameters as a default value, not as a dependency. The function won't be called, and you'll get a type error at runtime. Always wrap the function with Depends().

Using mutable default arguments in dependencies. Just like any Python function, avoid using mutable defaults like def dep(items: list = []). FastAPI allows it, but the list will be shared across requests, leading to subtle bugs. Use None and create a new list inside the function.

Raising exceptions inside dependencies. If a dependency raises an exception, FastAPI will propagate it to the global exception handler. For HTTP errors, raise HTTPException directly. For other exceptions, you can add custom exception handlers. This is fine, but be aware that the exception will prevent the endpoint from running.

Overusing dependencies for logic that doesn't need request context. If a function doesn't need any request data (like headers, query parameters, or path parameters), it can be a regular function called directly. Using Depends adds a small overhead and makes the dependency appear in the OpenAPI docs, which may be unnecessary. Use Depends when the dependency needs to access request data or when you need to override it in tests.

Performance and Operational Considerations

Depends adds minimal overhead per request—essentially a function call and dictionary lookup. The real performance impact comes from what your dependency does. For example, if a dependency performs a database query, that query runs on every request that uses it. Caching within a request helps, but it doesn't reduce work across requests.

When you have a dependency that is expensive and doesn't change frequently, consider caching at the application level. For instance, if you fetch configuration from a remote service, you might cache it for a few seconds or minutes. But be careful: dependencies are meant to be request-scoped. Application-level caching should be implemented separately, not inside a Depends function.

Another operational concern is error handling. If a dependency fails, FastAPI returns a 500 error by default unless you handle the exception. For expected failures (like invalid authentication), raise HTTPException with an appropriate status code. For unexpected failures, you can add a middleware or exception handler to log and return a generic response.

Finally, remember that dependencies are resolved at import time? No—they are resolved per request. The Depends object is created at import time, but the actual function call happens when the request arrives. This means you can safely define dependencies that rely on request data without worrying about global state.

Understanding how Depends works—its caching, scoping, and override capabilities—lets you build clean, testable FastAPI applications. The patterns shown here cover the most common uses, from simple shared logic to complex authentication chains. When you encounter a new requirement, ask whether it can be modeled as a dependency. If it can, you'll gain automatic injection, testability, and consistent behavior across your API.

python fastapi dependency injection with depends: Practical | RYUSLOG DEV