0
0
FastAPIframework~15 mins

Shared dependencies in FastAPI - Deep Dive

Choose your learning style9 modes available
Overview - Shared dependencies
What is it?
Shared dependencies in FastAPI are functions or classes that provide common resources or logic to multiple parts of your web application. They help you avoid repeating code by letting you define something once and use it in many places. For example, you can create a shared database connection or authentication check that many routes use. This makes your code cleaner and easier to maintain.
Why it matters
Without shared dependencies, you would have to write the same setup or checks in every route, which leads to mistakes and harder updates. Shared dependencies solve this by centralizing common logic, saving time and reducing bugs. This means your app can grow without becoming messy or slow to change, making it more reliable and easier to work on with others.
Where it fits
Before learning shared dependencies, you should understand basic FastAPI routes and how to write simple dependency functions. After mastering shared dependencies, you can explore advanced dependency features like dependency overrides for testing, security dependencies, and background tasks. This topic fits in the middle of your FastAPI learning path, connecting simple routes to more complex, reusable app design.
Mental Model
Core Idea
Shared dependencies let you write common setup or logic once and reuse it automatically in many parts of your FastAPI app.
Think of it like...
It's like having a shared toolbox in a workshop that everyone uses instead of each person buying their own tools. This saves space, money, and effort because the tools are ready and organized for everyone.
┌─────────────────────────────┐
│       Shared Dependency      │
│  (e.g., DB connection setup) │
└─────────────┬───────────────┘
              │
  ┌───────────┴───────────┐
  │                       │
┌─▼─┐                   ┌─▼─┐
│R1 │                   │R2 │
│   │                   │   │
└───┘                   └───┘
R1, R2 = Routes using the shared dependency
Build-Up - 7 Steps
1
FoundationUnderstanding FastAPI dependencies
🤔
Concept: Learn what dependencies are in FastAPI and how they help inject logic into routes.
In FastAPI, dependencies are functions that you can add to your route parameters. FastAPI calls these functions and passes their results to your route. For example, a dependency can return a database session or check user authentication. You declare them using the Depends() function in your route parameters.
Result
You can write routes that automatically get needed resources without repeating code inside each route.
Understanding dependencies is key because they are the building blocks for shared logic and cleaner code in FastAPI.
2
FoundationCreating a simple dependency function
🤔
Concept: How to write a basic dependency function and use it in one route.
Define a function that returns a value or resource. Use Depends() in a route parameter to call it. For example: def get_token(): return "secret-token" @app.get("/items/") async def read_items(token: str = Depends(get_token)): return {"token": token} This passes the token from get_token() into the route.
Result
The route returns the token without writing token logic inside the route itself.
Knowing how to write and use a simple dependency shows how FastAPI separates concerns and keeps routes clean.
3
IntermediateSharing dependencies across multiple routes
🤔Before reading on: do you think you need to call the dependency function separately in each route, or can FastAPI reuse it automatically? Commit to your answer.
Concept: Use the same dependency function in many routes to share logic or resources.
You can add the same dependency function to multiple routes by including Depends() in each route's parameters. For example: def get_db(): db = create_db_session() try: yield db finally: db.close() @app.get("/users/") async def read_users(db = Depends(get_db)): return db.query("SELECT * FROM users") @app.get("/items/") async def read_items(db = Depends(get_db)): return db.query("SELECT * FROM items") Both routes share the same database session setup logic.
Result
Multiple routes use the same database connection logic without repeating code.
Sharing dependencies prevents duplication and ensures consistent resource management across routes.
4
IntermediateUsing classes as shared dependencies
🤔Before reading on: do you think dependencies must be functions, or can classes also be used? Commit to your answer.
Concept: FastAPI supports classes with __call__ as dependencies, allowing stateful or complex setups.
You can create a class with a __call__ method and use it as a dependency. For example: class CommonQueryParams: def __init__(self, q: str = None, limit: int = 10): self.q = q self.limit = limit def __call__(self): return {"q": self.q, "limit": self.limit} @app.get("/search/") async def search(params: dict = Depends(CommonQueryParams())): return params This lets you share query parameter parsing logic.
Result
Routes can share complex logic encapsulated in classes, improving organization.
Using classes as dependencies enables more flexible and reusable shared logic with state or configuration.
5
AdvancedDependency scopes and caching behavior
🤔Before reading on: do you think FastAPI calls a shared dependency function once per app, once per request, or once per route call? Commit to your answer.
Concept: FastAPI calls dependencies once per request by default and caches the result for that request, so multiple routes or dependencies can share the same instance.
When a dependency is used multiple times in the same request, FastAPI runs it only once and reuses the result. This caching happens per request, not globally. For example, if two routes or two dependencies depend on the same database session, FastAPI creates it once per request and shares it. This improves performance and consistency.
Result
Shared dependencies are efficient and consistent within a single request lifecycle.
Understanding dependency caching prevents redundant resource creation and helps design efficient apps.
6
AdvancedOverriding shared dependencies for testing
🤔Before reading on: do you think you can replace shared dependencies with mocks during tests without changing route code? Commit to your answer.
Concept: FastAPI allows overriding dependencies globally or per test to replace shared dependencies with test doubles.
You can override dependencies using app.dependency_overrides. For example: app.dependency_overrides[get_db] = override_get_db This lets tests use a fake database session instead of the real one. It keeps tests isolated and fast without changing production code.
Result
Tests can run with controlled dependencies, improving reliability and speed.
Knowing how to override shared dependencies is crucial for writing maintainable and testable FastAPI apps.
7
ExpertComplex dependency graphs and circular references
🤔Before reading on: do you think FastAPI can handle dependencies that depend on each other, or will it fail? Commit to your answer.
Concept: FastAPI builds a dependency graph and detects circular dependencies, raising errors to prevent infinite loops.
When dependencies depend on other dependencies, FastAPI resolves them in order. If a circular reference occurs (A depends on B, B depends on A), FastAPI raises an error. You must design dependencies carefully to avoid cycles. Using shared dependencies properly helps keep the graph clean and manageable.
Result
FastAPI prevents infinite loops and helps you design clear dependency structures.
Understanding dependency graphs and circular detection helps avoid subtle bugs and design scalable apps.
Under the Hood
FastAPI uses Python's function signature inspection to find dependencies declared with Depends(). When a request comes in, FastAPI builds a graph of all dependencies needed for the route. It then calls each dependency function or class in order, caching results per request. This caching means if multiple routes or dependencies need the same resource, FastAPI reuses it within that request. Dependency injection happens automatically, passing results as parameters to routes.
Why designed this way?
This design follows the dependency injection pattern to separate concerns and improve testability. Using Python's type hints and function signatures allows FastAPI to automate wiring dependencies without extra configuration. Caching per request balances efficiency and safety, avoiding global state while preventing redundant work. Circular dependency detection protects against infinite loops, a common problem in dependency injection systems.
Request → Route Handler
       │
       ▼
┌─────────────────────┐
│ Dependency Graph     │
│ (functions/classes)  │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ Dependency Resolver  │
│ - Calls dependencies│
│ - Caches results    │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ Route Function       │
│ - Receives resolved  │
│   dependencies       │
└─────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think FastAPI calls a shared dependency function once for the whole app lifetime? Commit to yes or no.
Common Belief:Shared dependencies run only once when the app starts and then reuse the same instance forever.
Tap to reveal reality
Reality:FastAPI calls dependencies once per request and caches the result only during that request, not globally.
Why it matters:Assuming global lifetime can cause bugs with resources like database sessions that must be fresh per request, leading to stale or broken connections.
Quick: Do you think dependencies must always be functions? Commit to yes or no.
Common Belief:Dependencies can only be simple functions, not classes or other callable objects.
Tap to reveal reality
Reality:FastAPI supports classes with __call__ methods as dependencies, allowing more complex and stateful logic.
Why it matters:Limiting dependencies to functions restricts design flexibility and code organization, making complex apps harder to build.
Quick: Do you think you must manually call shared dependencies inside routes? Commit to yes or no.
Common Belief:You have to call the shared dependency function yourself inside every route to get its value.
Tap to reveal reality
Reality:FastAPI automatically calls dependencies declared with Depends() and injects their results into route parameters.
Why it matters:Not trusting FastAPI's injection leads to duplicated code and defeats the purpose of dependencies.
Quick: Do you think FastAPI allows circular dependencies without error? Commit to yes or no.
Common Belief:FastAPI can handle dependencies that depend on each other without problems.
Tap to reveal reality
Reality:FastAPI detects circular dependencies and raises errors to prevent infinite loops.
Why it matters:Ignoring this can cause your app to crash or hang unexpectedly, making debugging very hard.
Expert Zone
1
Shared dependencies can be combined with FastAPI's security utilities to build layered authentication and authorization systems.
2
Dependency overrides can be scoped per test or globally, allowing fine control over test environments without changing production code.
3
FastAPI's dependency injection supports async dependencies, enabling efficient resource management like async database sessions or HTTP clients.
When NOT to use
Avoid using shared dependencies for very simple or one-off logic that doesn't benefit from reuse, as it can add unnecessary complexity. For global singletons or app-wide state, consider using startup events or external state managers instead of dependencies. Also, avoid circular dependencies by redesigning your dependency graph or merging related logic.
Production Patterns
In production, shared dependencies often manage database sessions, authentication checks, and configuration loading. They are combined with middleware and event handlers to create robust, maintainable apps. Dependency overrides enable seamless testing and staging environments. Experts also use dependency injection to inject logging, caching, or feature flags dynamically.
Connections
Dependency Injection (general software design)
Shared dependencies in FastAPI are a specific implementation of the broader dependency injection pattern.
Understanding FastAPI dependencies deepens knowledge of how dependency injection improves modularity and testability in software.
Inversion of Control Containers (IoC Containers)
FastAPI's dependency system acts like a lightweight IoC container, managing object creation and wiring.
Knowing IoC concepts helps grasp how FastAPI automatically resolves and injects dependencies without manual wiring.
Supply Chain Management
Both manage dependencies and resources efficiently to avoid duplication and bottlenecks.
Seeing shared dependencies like supply chains highlights the importance of resource reuse and flow control in complex systems.
Common Pitfalls
#1Creating a new database session for every dependency call instead of sharing per request.
Wrong approach:def get_db(): db = create_db_session() return db @app.get("/users/") async def read_users(db = Depends(get_db)): users = db.query("SELECT * FROM users") db.close() return users @app.get("/items/") async def read_items(db = Depends(get_db)): items = db.query("SELECT * FROM items") db.close() return items
Correct approach:def get_db(): db = create_db_session() try: yield db finally: db.close() @app.get("/users/") async def read_users(db = Depends(get_db)): return db.query("SELECT * FROM users") @app.get("/items/") async def read_items(db = Depends(get_db)): return db.query("SELECT * FROM items")
Root cause:Not using yield in dependency prevents FastAPI from managing the resource lifecycle and caching the instance per request.
#2Trying to share global state by returning mutable objects from dependencies without proper scope.
Wrong approach:shared_list = [] def get_shared_list(): return shared_list @app.get("/add/") async def add_item(item: str, lst = Depends(get_shared_list)): lst.append(item) return lst
Correct approach:def get_shared_list(): return [] # new list per request @app.get("/add/") async def add_item(item: str, lst = Depends(get_shared_list)): lst.append(item) return lst
Root cause:Misunderstanding dependency scope causes shared mutable state across requests, leading to data leaks and bugs.
#3Overriding dependencies incorrectly in tests causing production code to break.
Wrong approach:app.dependency_overrides[get_db] = fake_db # But fake_db has different signature or side effects @app.get("/users/") async def read_users(db = Depends(get_db)): return db.query("SELECT * FROM users")
Correct approach:def fake_get_db(): class FakeDB: def query(self, q): return ["test_user"] yield FakeDB() app.dependency_overrides[get_db] = fake_get_db
Root cause:Not matching the original dependency's interface or lifecycle causes runtime errors or inconsistent behavior.
Key Takeaways
Shared dependencies in FastAPI let you write common logic once and reuse it automatically across routes, improving code clarity and maintainability.
FastAPI calls dependencies once per request and caches the result, ensuring efficient resource use and consistent behavior within each request.
You can use both functions and classes as dependencies, allowing flexible and stateful shared logic.
Overriding dependencies is essential for testing, letting you swap real resources with mocks without changing production code.
FastAPI detects circular dependencies and prevents infinite loops, so designing a clean dependency graph is crucial for scalable apps.