Skip to main content

Command Palette

Search for a command to run...

Advanced Python Dependency Injection with Pydantic and FastAPI

Updated
5 min readView as Markdown
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.