Skip to main content

Command Palette

Search for a command to run...

Advanced Python Dependency Injection with Pydantic and FastAPI

Updated
โ€ข5 min read
Advanced Python Dependency Injection with Pydantic and FastAPI
N

I am a Tech Enthusiast having 13+ years of experience in ๐ˆ๐“ as a ๐‚๐จ๐ง๐ฌ๐ฎ๐ฅ๐ญ๐š๐ง๐ญ, ๐‚๐จ๐ซ๐ฉ๐จ๐ซ๐š๐ญ๐ž ๐“๐ซ๐š๐ข๐ง๐ž๐ซ, ๐Œ๐ž๐ง๐ญ๐จ๐ซ, with 12+ years in training and mentoring in ๐’๐จ๐Ÿ๐ญ๐ฐ๐š๐ซ๐ž ๐„๐ง๐ ๐ข๐ง๐ž๐ž๐ซ๐ข๐ง๐ , ๐ƒ๐š๐ญ๐š ๐„๐ง๐ ๐ข๐ง๐ž๐ž๐ซ๐ข๐ง๐ , ๐“๐ž๐ฌ๐ญ ๐€๐ฎ๐ญ๐จ๐ฆ๐š๐ญ๐ข๐จ๐ง ๐š๐ง๐ ๐ƒ๐š๐ญ๐š ๐’๐œ๐ข๐ž๐ง๐œ๐ž. I have ๐’•๐’“๐’‚๐’Š๐’๐’†๐’… ๐’Ž๐’๐’“๐’† ๐’•๐’‰๐’‚๐’ 10,000+ ๐‘ฐ๐‘ป ๐‘ท๐’“๐’๐’‡๐’†๐’”๐’”๐’Š๐’๐’๐’‚๐’๐’” and ๐’„๐’๐’๐’…๐’–๐’„๐’•๐’†๐’… ๐’Ž๐’๐’“๐’† ๐’•๐’‰๐’‚๐’ 500+ ๐’•๐’“๐’‚๐’Š๐’๐’Š๐’๐’ˆ ๐’”๐’†๐’”๐’”๐’Š๐’๐’๐’” in the areas of ๐’๐จ๐Ÿ๐ญ๐ฐ๐š๐ซ๐ž ๐ƒ๐ž๐ฏ๐ž๐ฅ๐จ๐ฉ๐ฆ๐ž๐ง๐ญ, ๐ƒ๐š๐ญ๐š ๐„๐ง๐ ๐ข๐ง๐ž๐ž๐ซ๐ข๐ง๐ , ๐‚๐ฅ๐จ๐ฎ๐, ๐ƒ๐š๐ญ๐š ๐€๐ง๐š๐ฅ๐ฒ๐ฌ๐ข๐ฌ, ๐ƒ๐š๐ญ๐š ๐•๐ข๐ฌ๐ฎ๐š๐ฅ๐ข๐ณ๐š๐ญ๐ข๐จ๐ง๐ฌ, ๐€๐ซ๐ญ๐ข๐Ÿ๐ข๐œ๐ข๐š๐ฅ ๐ˆ๐ง๐ญ๐ž๐ฅ๐ฅ๐ข๐ ๐ž๐ง๐œ๐ž ๐š๐ง๐ ๐Œ๐š๐œ๐ก๐ข๐ง๐ž ๐‹๐ž๐š๐ซ๐ง๐ข๐ง๐ . I am interested in ๐ฐ๐ซ๐ข๐ญ๐ข๐ง๐  ๐›๐ฅ๐จ๐ ๐ฌ, ๐ฌ๐ก๐š๐ซ๐ข๐ง๐  ๐ญ๐ž๐œ๐ก๐ง๐ข๐œ๐š๐ฅ ๐ค๐ง๐จ๐ฐ๐ฅ๐ž๐๐ ๐ž, ๐ฌ๐จ๐ฅ๐ฏ๐ข๐ง๐  ๐ญ๐ž๐œ๐ก๐ง๐ข๐œ๐š๐ฅ ๐ข๐ฌ๐ฌ๐ฎ๐ž๐ฌ, ๐ซ๐ž๐š๐๐ข๐ง๐  ๐š๐ง๐ ๐ฅ๐ž๐š๐ซ๐ง๐ข๐ง๐  new subjects.

Introduction

Modern backend architectures demand modular, maintainable, and testable code. One of the cornerstones of achieving this is Dependency Injection (DI) โ€” a software design pattern that helps decouple object creation from business logic, making applications easier to scale, test, and extend.

While traditional frameworks like Django use settings modules and class-based views for configuration and service management, FastAPI takes this a step further with its native, lightweight, and elegant dependency injection system built on function parameters and Pythonโ€™s async capabilities.

When combined with Pydantic, FastAPIโ€™s preferred data validation and parsing library, it enables powerful, type-safe, and scalable dependency management โ€” ideal for modern, asynchronous web services.

This guide will take you through:

  • What Dependency Injection is

  • How FastAPI implements DI via Depends

  • Using Pydantic models as dependencies

  • Injecting services, config settings, database sessions

  • Advanced DI techniques like sub-dependencies and class-based dependencies

  • Best practices for clean, testable architectures

Letโ€™s get into it boss.

What Is Dependency Injection?

Dependency Injection (DI) is a design pattern where the required dependencies of a component (like a function, class, or service) are provided externally rather than being created inside the component itself.

Benefits:

  • Decouples modules and services

  • Simplifies unit testing via mock dependencies

  • Enhances code reusability and readability

  • Allows flexible configuration swapping

Dependency Injection in FastAPI

FastAPI uses a lightweight, intuitive DI system based on its Depends class.

Basic example:

from fastapi import FastAPI, Depends

app = FastAPI()

def get_token():
    return "secure-token-123"

@app.get("/items/")
def read_items(token: str = Depends(get_token)):
    return {"token": token}

How it works:

  • Depends(get_token) tells FastAPI to execute get_token() and inject the return value as the token parameter.

  • Dependencies can be synchronous or asynchronous.

  • Multiple dependencies can be stacked per route.

Pydantic-Powered Dependency Models

Pydantic is FastAPIโ€™s native way to define structured, validated data. You can use Pydantic models as dependencies to enforce input schemas for injected configurations or objects.

Example:

from pydantic import BaseModel

class Config(BaseModel):
    api_key: str
    timeout: int

def get_config():
    return Config(api_key="xyz-123", timeout=10)

@app.get("/settings/")
def get_settings(config: Config = Depends(get_config)):
    return config

Benefits:

  • Enforces type safety on injected objects

  • Auto-validates dependencies

  • Provides clear API contracts for developers

Dependency Injection for Services

In scalable apps, you often need to inject services like database clients, authentication modules, or external API clients.

Example:

class Database:
    def __init__(self, url):
        self.url = url

    def connect(self):
        return f"Connected to {self.url}"

def get_db():
    return Database(url="sqlite:///mydb.db")

@app.get("/db/")
def connect_to_db(db: Database = Depends(get_db)):
    return {"message": db.connect()}

Now, Database instances are provided to each request dynamically.

Dependency Overriding (Test-Friendly Design)

FastAPI allows dependency overriding at runtime โ€” making it perfect for injecting test mocks.

Example:

def override_config():
    return Config(api_key="mock-api", timeout=1)

app.dependency_overrides[get_config] = override_config

Now, all routes depending on get_config() will use the override โ€” essential for isolated testing.

Asynchronous Dependencies

FastAPI seamlessly supports async def dependencies.

Example:

import asyncio

async def get_data():
    await asyncio.sleep(1)
    return "async data"

@app.get("/data/")
async def read_data(data: str = Depends(get_data)):
    return {"data": data}

Sub-Dependencies (Dependencies of Dependencies)

One dependency can depend on another, creating a dependency graph.

Example:

def get_username():
    return "vinaykumar"

def get_message(username: str = Depends(get_username)):
    return f"Hello {username}"

@app.get("/welcome/")
def welcome(message: str = Depends(get_message)):
    return {"message": message}

FastAPI handles sub-dependencies automatically, resolving them in order.

Class-Based Dependencies

For complex services with state or methods, FastAPI supports classes as dependencies.

Example:

class Auth:
    def __init__(self, api_key: str):
        self.api_key = api_key

    def verify(self):
        return self.api_key == "secure-key"

def get_auth():
    return Auth(api_key="secure-key")

@app.get("/secure/")
def secure(auth: Auth = Depends(get_auth)):
    if not auth.verify():
        return {"error": "Unauthorized"}
    return {"message": "Welcome Vinay"}

Benefits:

  • Encapsulates logic cleanly

  • Easy to override or mock

  • Supports per-request state

Global Configuration Injection

Large apps often require global config like database URLs, secrets, or environment-specific values. FastAPI encourages using Pydanticโ€™s BaseSettings.

Example:

from pydantic import BaseSettings

class Settings(BaseSettings):
    database_url: str = "sqlite:///prod.db"
    secret_key: str = "secret-123"

settings = Settings()

def get_settings():
    return settings

@app.get("/config/")
def config_endpoint(conf: Settings = Depends(get_settings)):
    return conf.dict()

Advantages:

  • Supports .env files and environment variables

  • Centralized config management

  • Type-safe and validated at startup

Scoped vs Shared Dependencies

By default:

  • Functions and classes are per-request

  • You can force a singleton dependency (one instance reused across requests) by caching it in memory (not advised for mutable/shared state unless carefully managed)

Dependency Caching (Using Depends(cache=True))

FastAPI allows caching dependencies for a single request lifecycle.

Example:

from fastapi import Request

def get_unique_id(request: Request):
    return id(request)

@app.get("/id/")
def id_view(uid: int = Depends(get_unique_id)):
    return {"id": uid}

To cache:

@app.get("/cached/")
def cached_view(uid: int = Depends(get_unique_id, use_cache=True)):
    return {"cached_id": uid}

This prevents redundant calls to the same dependency within a single request.

Dependency Injection for Background Tasks

BackgroundTask instances can also be injected via DI.

Example:

from fastapi import BackgroundTasks

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(f"{message}\n")

@app.post("/notify/")
def notify_user(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "User Notified")
    return {"status": "Task Scheduled"}

Clean and non-blocking.

Dependency Injection and Middleware

Dependencies can interact with middleware too โ€” enabling powerful observability or authentication hooks.

Example:

  • Dependency returns user from token

  • Middleware validates or logs users based on DI resolution

This creates an elegant separation of concerns for cross-cutting concerns.

Conclusion

FastAPIโ€™s native Dependency Injection system is one of the most elegant and powerful features in the modern Python backend ecosystem.
Combined with Pydantic, it creates:

  • Highly modular APIs

  • Clean, testable services

  • Decoupled configuration management

  • Scalable microservices architecture

Whether youโ€™re injecting a simple API key, a full-fledged ORM session, or a chain of dependent services, FastAPIโ€™s Depends decorator makes it seamless.

By adopting these advanced DI patterns:

  • You reduce coupling in your codebase

  • Simplify unit and integration testing

  • Build APIs that are clean, flexible, and future-proof

Mastering Dependency Injection with FastAPI and Pydantic will unlock scalable, enterprise-grade application design in your Python projects.