Bird
Raised Fist0
FastAPIframework~15 mins

Path operation dependencies in FastAPI - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Path Operation Dependencies
What is it?
Path Operation Dependencies in FastAPI are a way to share common logic or data between different parts of your web application. They let you run code before or alongside your route handlers, like checking user permissions or connecting to a database. This helps keep your code clean and avoids repeating the same steps in many places. Essentially, dependencies are reusable pieces that your routes can ask for to work properly.
Why it matters
Without path operation dependencies, you would have to write the same setup or checks inside every route, making your code messy and error-prone. Dependencies solve this by centralizing shared tasks, so if you need to change something, you do it once and it affects all routes. This saves time, reduces bugs, and makes your app easier to maintain and scale.
Where it fits
Before learning path operation dependencies, you should understand basic FastAPI routes and Python functions. After mastering dependencies, you can explore advanced topics like security, database sessions, and background tasks that often use dependencies to work smoothly.
Mental Model
Core Idea
Path operation dependencies are like helpers that prepare or provide needed things before your route runs, so your route can focus on its main job.
Think of it like...
Imagine you are cooking a meal and need chopped vegetables and preheated oven before you start. Instead of doing these tasks yourself every time, you ask a helper to prepare them for you. Your cooking (route) then just uses what the helper provides without worrying about the prep.
┌───────────────────────────────┐
│        HTTP Request            │
└──────────────┬────────────────┘
               │
       ┌───────▼────────┐
       │ Dependency Code │  <-- runs first to prepare data or checks
       └───────┬────────┘
               │
       ┌───────▼────────┐
       │ Route Function  │  <-- uses what dependency provides
       └─────────────────┘
Build-Up - 7 Steps
1
FoundationBasic Route Functions in FastAPI
🤔
Concept: Learn how to create simple routes that respond to HTTP requests.
In FastAPI, you define a route by creating a function and decorating it with @app.get or @app.post. This function runs when someone visits that URL. For example: from fastapi import FastAPI app = FastAPI() @app.get("/hello") async def say_hello(): return {"message": "Hello World"} This route returns a JSON message when accessed.
Result
Visiting /hello returns {"message": "Hello World"} as JSON.
Understanding how routes work is essential before adding dependencies, as dependencies modify or add to this basic behavior.
2
FoundationIntroducing Dependency Functions
🤔
Concept: Learn how to create simple functions that can be used as dependencies.
A dependency is just a function that does some work and returns a value. You can tell FastAPI to run this function before your route and give its result to the route. For example: from fastapi import Depends def get_number(): return 42 @app.get("/number") async def read_number(number: int = Depends(get_number)): return {"number": number} Here, get_number runs first and its result is passed to read_number.
Result
Visiting /number returns {"number": 42}.
Dependencies let you separate concerns by moving reusable logic out of routes into standalone functions.
3
IntermediateUsing Dependencies for Shared Logic
🤔Before reading on: do you think dependencies can only return data, or can they also perform actions like checks? Commit to your answer.
Concept: Dependencies can do more than return data; they can run code like validation or setup before the route runs.
Dependencies can raise errors or perform checks. For example, a dependency can check if a user is authorized: from fastapi import HTTPException, status def verify_token(token: str): if token != "secret": raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) @app.get("/secure") async def secure_route(token: str = Depends(verify_token)): return {"message": "Access granted"} If the token is wrong, the route never runs.
Result
Accessing /secure with wrong token returns 401 error; with correct token returns access message.
Knowing dependencies can control access or setup helps build secure and robust APIs.
4
IntermediateDependency Injection with Parameters
🤔Before reading on: do you think dependencies can accept parameters themselves? Commit to your answer.
Concept: Dependencies can accept parameters, allowing dynamic behavior based on input or other dependencies.
You can create dependencies that take parameters, including other dependencies. For example: from fastapi import Query def get_query_param(q: str = Query(None)): return q @app.get("/search") async def search(q: str = Depends(get_query_param)): return {"query": q} Here, get_query_param reads a query parameter and passes it to the route.
Result
Visiting /search?q=fastapi returns {"query": "fastapi"}.
Dependencies can be flexible and compose with each other, enabling complex setups with simple building blocks.
5
IntermediateGlobal Dependencies for Multiple Routes
🤔
Concept: You can apply dependencies to many routes at once to avoid repeating code.
FastAPI lets you add dependencies to the whole app or router: from fastapi import APIRouter router = APIRouter(dependencies=[Depends(verify_token)]) @router.get("/items") async def read_items(): return ["item1", "item2"] app.include_router(router) Now, verify_token runs for every route in this router.
Result
All routes in the router require token verification automatically.
Applying dependencies globally keeps your code DRY and consistent across many routes.
6
AdvancedUsing Classes as Dependencies
🤔Before reading on: do you think dependencies must be functions, or can they be classes too? Commit to your answer.
Concept: Dependencies can be classes with __call__ methods, allowing stateful or complex logic.
You can create a class and use it as a dependency: class CommonQueryParams: def __init__(self, q: str = None, limit: int = 10): self.q = q self.limit = limit @app.get("/items") async def read_items(commons: CommonQueryParams = Depends()): return {"q": commons.q, "limit": commons.limit} This groups related parameters and logic.
Result
Visiting /items?q=fastapi&limit=5 returns {"q": "fastapi", "limit": 5}.
Using classes as dependencies organizes related data and behavior, improving code clarity and reuse.
7
ExpertDependency Caching and Lifecycle Control
🤔Before reading on: do you think FastAPI runs a dependency function once per request or multiple times if used in several places? Commit to your answer.
Concept: FastAPI caches dependencies per request by default, so if multiple routes or parameters use the same dependency, it runs only once.
When a dependency is used multiple times in a request, FastAPI calls it once and shares the result. You can control this with the 'use_cache' parameter in Depends. For example: from fastapi import Depends call_count = 0 def dep(): global call_count call_count += 1 return call_count @app.get("/test") async def test(a: int = Depends(dep), b: int = Depends(dep)): return {"a": a, "b": b} Here, dep runs once, so a and b get the same value.
Result
Visiting /test returns {"a": 1, "b": 1} and dep was called once.
Understanding dependency caching prevents unexpected repeated work and improves performance in complex apps.
Under the Hood
FastAPI uses Python's function parameter inspection to detect dependencies declared with Depends. When a request comes in, it builds a dependency graph by resolving each dependency's own dependencies recursively. It then calls these functions in order, caching results per request to avoid duplicate calls. The final resolved values are passed as arguments to the route function. This process happens asynchronously if the dependencies or route are async functions.
Why designed this way?
This design allows maximum flexibility and reusability without forcing a rigid structure. Using Python's type hints and function signatures makes dependencies explicit and easy to read. Caching per request improves efficiency, and recursive resolution supports complex dependency trees. Alternatives like global singletons or manual injection would be less flexible and harder to maintain.
┌───────────────┐
│ HTTP Request  │
└──────┬────────┘
       │
┌──────▼─────────────┐
│ Dependency Resolver │
│ - Inspects params   │
│ - Builds graph      │
│ - Calls dependencies│
│ - Caches results    │
└──────┬─────────────┘
       │
┌──────▼─────────────┐
│ Route Function      │
│ - Receives resolved │
│   dependencies      │
│ - Returns response  │
└────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think dependencies run once per app lifetime or once per request? Commit to your answer.
Common Belief:Dependencies run once when the app starts and then reuse the result forever.
Tap to reveal reality
Reality:Dependencies run once per request by default, so each request gets fresh data or checks.
Why it matters:If you assume dependencies run only once, you might cache stale data or skip important checks, causing bugs or security issues.
Quick: Can dependencies modify the request or response directly? Commit to your answer.
Common Belief:Dependencies can change the HTTP request or response objects directly.
Tap to reveal reality
Reality:Dependencies provide data or raise errors but do not modify the request or response objects themselves.
Why it matters:Trying to modify request/response in dependencies can lead to unexpected behavior; such changes should happen in middleware or route handlers.
Quick: Do you think dependencies must be synchronous functions? Commit to your answer.
Common Belief:Dependencies must be normal (sync) functions and cannot be async.
Tap to reveal reality
Reality:Dependencies can be async functions, allowing them to perform asynchronous operations like database calls.
Why it matters:Not knowing this limits your ability to write efficient, non-blocking code in FastAPI.
Quick: Do you think dependencies always run even if not used in a route? Commit to your answer.
Common Belief:All dependencies declared anywhere run for every request, regardless of usage.
Tap to reveal reality
Reality:Dependencies run only if they are needed by the route or other dependencies in the call chain.
Why it matters:Assuming all dependencies run wastes resources and can cause confusion about app behavior.
Expert Zone
1
Dependencies can be nested deeply, creating complex graphs that FastAPI resolves efficiently, but circular dependencies cause errors.
2
Using 'yield' in dependencies allows setup and teardown logic, like opening and closing database connections per request.
3
The 'use_cache' parameter in Depends controls whether a dependency is cached per request or called multiple times, useful for side-effectful dependencies.
When NOT to use
Avoid using path operation dependencies for global app-wide concerns like logging or error handling; use middleware instead. Also, for very simple apps, dependencies might add unnecessary complexity. For state that must persist beyond requests, use external storage or background tasks.
Production Patterns
In production, dependencies commonly manage database sessions, user authentication, and configuration loading. They enable clean separation of concerns, making routes focused on business logic. Experts also use dependency overrides in testing to replace real services with mocks.
Connections
Dependency Injection (Software Engineering)
Path operation dependencies are a specific implementation of dependency injection in web frameworks.
Understanding general dependency injection principles helps grasp why FastAPI uses dependencies to provide components to routes, improving modularity and testability.
Middleware (Web Development)
Dependencies and middleware both run code around requests but differ in scope and purpose.
Knowing the difference helps decide when to use dependencies (per-route logic) versus middleware (global request/response processing).
Supply Chain Management
Dependencies in FastAPI resemble supply chain steps where each supplier provides parts needed for final assembly.
Seeing dependencies as suppliers clarifies how complex systems build outputs step-by-step, with each dependency delivering what the next needs.
Common Pitfalls
#1Re-running expensive dependencies multiple times per request.
Wrong approach:async def dep(): print("Running dep") return 42 @app.get("/test") async def test(a: int = Depends(dep), b: int = Depends(dep)): return {"a": a, "b": b}
Correct approach:async def dep(): print("Running dep") return 42 @app.get("/test") async def test(a: int = Depends(dep, use_cache=True), b: int = Depends(dep, use_cache=True)): return {"a": a, "b": b}
Root cause:Not understanding that dependencies are cached by default but can be disabled; forgetting to rely on caching causes repeated work.
#2Trying to modify request or response objects inside dependencies.
Wrong approach:from fastapi import Request def dep(request: Request): request.state.user = "admin" @app.get("/") async def root(dep=Depends(dep)): return {"msg": "hello"}
Correct approach:Use middleware to modify request.state or response objects, not dependencies.
Root cause:Misunderstanding the role of dependencies as providers of data, not modifiers of request lifecycle.
#3Using blocking code in async dependencies causing slow responses.
Wrong approach:def dep(): import time time.sleep(5) return "done" @app.get("/") async def root(dep=Depends(dep)): return {"result": dep}
Correct approach:import asyncio async def dep(): await asyncio.sleep(5) return "done" @app.get("/") async def root(dep=Depends(dep)): return {"result": dep}
Root cause:Not using async functions for I/O-bound operations in async frameworks leads to blocking the event loop.
Key Takeaways
Path operation dependencies in FastAPI let you share and reuse code that runs before your routes, keeping your app clean and maintainable.
Dependencies can provide data, perform checks, or manage resources, and they run once per request by default with caching.
You can use functions or classes as dependencies, and they can accept parameters or depend on other dependencies.
Understanding how FastAPI resolves and caches dependencies helps you write efficient and secure web applications.
Knowing when to use dependencies versus middleware or other patterns is key to building scalable and clear APIs.

Practice

(1/5)
1. What is the main purpose of using Depends() in FastAPI path operations?
easy
A. To create a new database connection manually
B. To define the HTTP method for the route
C. To specify the response status code
D. To run shared code before handling requests

Solution

  1. Step 1: Understand the role of Depends()

    Depends() is used to declare dependencies that run shared code before the main path operation function executes.

  2. Step 2: Identify the purpose in FastAPI

    This helps keep code clean by reusing common logic like authentication or database access.

  3. Final Answer:

    To run shared code before handling requests -> Option D
  4. Quick Check:

    Depends() runs shared code before requests [OK]
Hint: Depends() runs shared code before request handling [OK]
Common Mistakes:
  • Thinking Depends() sets HTTP methods
  • Confusing Depends() with response status codes
  • Assuming Depends() manually creates DB connections
2. Which of the following is the correct way to declare a dependency in a FastAPI path operation function?
easy
A. def read_item(item_id: int, user=Depends[get_current_user]):
B. def read_item(item_id: int, user=Depends):
C. def read_item(item_id: int, user=Depends(get_current_user)):
D. def read_item(item_id: int, user=get_current_user()):

Solution

  1. Step 1: Recall the syntax for dependencies

    Dependencies are declared by assigning a parameter to Depends() with the dependency function inside.

  2. Step 2: Check each option

    def read_item(item_id: int, user=Depends(get_current_user)): correctly uses user=Depends(get_current_user). Others have syntax errors or call the function directly.

  3. Final Answer:

    def read_item(item_id: int, user=Depends(get_current_user)): -> Option C
  4. Quick Check:

    Depends() with function inside parentheses [OK]
Hint: Use Depends(function_name) with parentheses [OK]
Common Mistakes:
  • Calling the dependency function directly
  • Using Depends without parentheses
  • Using square brackets instead of parentheses
3. Given the code below, what will be the output when accessing /items/5?
from fastapi import FastAPI, Depends

app = FastAPI()

def get_token():
    return "token123"

@app.get("/items/{item_id}")
def read_item(item_id: int, token: str = Depends(get_token)):
    return {"item_id": item_id, "token": token}
medium
A. {"item_id": 5, "token": "token123"}
B. {"item_id": 5, "token": null}
C. RuntimeError due to missing token parameter
D. SyntaxError in dependency declaration

Solution

  1. Step 1: Understand dependency execution

    The get_token function returns "token123" and is injected into token parameter via Depends().

  2. Step 2: Check the returned dictionary

    The path operation returns a dictionary with item_id and token keys, so the output includes the token string.

  3. Final Answer:

    {"item_id": 5, "token": "token123"} -> Option A
  4. Quick Check:

    Dependency injects token value correctly [OK]
Hint: Depends injects return value as parameter [OK]
Common Mistakes:
  • Expecting token to be null without dependency
  • Thinking dependency causes runtime error
  • Confusing syntax with runtime errors
4. Identify the error in the following FastAPI code using dependencies:
from fastapi import FastAPI, Depends

app = FastAPI()

def get_user():
    return "user1"

@app.get("/profile")
def profile(user: str = Depends(get_user)):
    return {"user": user}

@app.get("/dashboard")
def dashboard(user = Depends(get_user)):
    return {"dashboard_user": user}
medium
A. Missing type annotation for 'user' in dashboard function
B. Depends() used incorrectly without parentheses
C. get_user function missing return statement
D. Path operation decorator missing on dashboard function

Solution

  1. Step 1: Compare both path operation functions

    The profile function declares user: str = Depends(get_user) with a type annotation.

  2. Step 2: Identify the issue in dashboard

    The dashboard function uses user = Depends(get_user) but lacks a type annotation, which FastAPI requires for dependencies.

  3. Final Answer:

    Missing type annotation for 'user' in dashboard function -> Option A
  4. Quick Check:

    Dependency parameters need type annotations [OK]
Hint: Always add type annotations for Depends parameters [OK]
Common Mistakes:
  • Omitting type annotations on dependency parameters
  • Forgetting parentheses on Depends()
  • Assuming missing decorator causes error
5. You want to reuse a dependency that extracts a user from a token and also check if the user is active before allowing access to multiple routes. How should you combine these checks using FastAPI dependencies?
hard
A. Create two separate dependency functions and use Depends() on both in the path operation
B. Call the user extraction function inside the active check function and use Depends() only on the active check
C. Use a single dependency function that returns user without any checks
D. Use Depends() only on the user extraction function and check active status inside each path operation

Solution

  1. Step 1: Understand dependency chaining

    You can call one dependency inside another to reuse logic and combine checks.

  2. Step 2: Apply chaining for user extraction and active check

    By calling the user extraction inside the active check dependency, you only need to use Depends() on the active check in routes.

  3. Final Answer:

    Call the user extraction function inside the active check function and use Depends() only on the active check -> Option B
  4. Quick Check:

    Chain dependencies by calling one inside another [OK]
Hint: Chain dependencies by calling one inside another [OK]
Common Mistakes:
  • Using multiple Depends() separately causing repeated calls
  • Not chaining dependencies and duplicating code
  • Checking user active status outside dependencies