Skip to main content

Spring-like Dependency Injection and Autowiring for FastAPI

Project description

FastAPI Autowire

Spring-like Dependency Injection and Autowiring for FastAPI

Python Version FastAPI License

Overview

FastAPI Autowire brings Spring-style dependency injection to FastAPI applications. It provides a clean, declarative way to manage dependencies with automatic resolution, lifecycle management, and type-safe autowiring.

Features

  • 🔧 Automatic Dependency Resolution - Dependencies are automatically resolved and injected
  • 🏷️ Semantic Decorators - Use @service, @repository, @component, @configuration, and @provider
  • 🔄 Lifecycle Management - Built-in support for post_construct and shutdown hooks
  • 🎯 Type-Safe Autowiring - Leverage Python's type hints with the Autowired[T] generic
  • 🌳 Circular Dependency Detection - Prevents circular dependencies at startup
  • 📦 Interface-Based Registration - Register implementations against abstract base classes
  • FastAPI Integration - Seamless integration with FastAPI's dependency injection system

Installation

pip install fastapi-autowire

Or using uv:

uv add fastapi-autowire

Quick Start

1. Define Your Components

from fastapi_autowire import service, repository, Autowired

@repository
class UserRepository:
    async def get_user(self, user_id: int):
        # Database logic here
        return {"id": user_id, "name": "John Doe"}

@service
class UserService:
    def __init__(self, user_repo: Autowired[UserRepository]):
        self.user_repo = user_repo
    
    async def get_user(self, user_id: int):
        return await self.user_repo.get_user(user_id)

2. Setup Your FastAPI Application

from fastapi import FastAPI
from fastapi_autowire import lifespan, Autowired

app = FastAPI(lifespan=lifespan)

@app.get("/users/{user_id}")
async def get_user(user_id: int, service: Autowired[UserService]):
    return await service.get_user(user_id)

That's it! The library handles:

  • ✅ Component registration
  • ✅ Dependency resolution
  • ✅ Lifecycle management
  • ✅ Injection into route handlers

Core Concepts

Component Registration

Use semantic decorators to register components:

from fastapi_autowire import service, repository, component, configuration, provider

@service
class EmailService:
    pass

@repository
class UserRepository:
    pass

@component
class CacheManager:
    pass

@configuration
class AppConfig:
    pass

@provider
class DatabaseProvider:
    pass

All decorators are aliases of @component and serve as semantic markers for better code organization.

Dependency Injection

Constructor Injection

from fastapi_autowire import service, Autowired

@service
class OrderService:
    def __init__(
        self,
        user_service: Autowired[UserService],
        payment_service: Autowired[PaymentService]
    ):
        self.user_service = user_service
        self.payment_service = payment_service

Route Handler Injection

from fastapi import FastAPI
from fastapi_autowire import Autowired

@app.get("/orders")
async def get_orders(order_service: Autowired[OrderService]):
    return await order_service.get_all_orders()

Interface-Based Registration

Register concrete implementations against abstract interfaces:

from abc import ABC, abstractmethod
from fastapi_autowire import service

class IEmailService(ABC):
    @abstractmethod
    async def send_email(self, to: str, subject: str, body: str):
        pass

@service(as_type=IEmailService)
class SmtpEmailService(IEmailService):
    async def send_email(self, to: str, subject: str, body: str):
        # SMTP implementation
        pass

# Inject using the interface
@service
class NotificationService:
    def __init__(self, email_service: Autowired[IEmailService]):
        self.email_service = email_service

Lifecycle Hooks

Components can define lifecycle methods:

from fastapi_autowire import service

@service
class DatabaseService:
    async def post_construct(self):
        """Called after all dependencies are injected"""
        print("Initializing database connection...")
        # Setup connection pool, etc.
    
    async def shutdown(self):
        """Called during application shutdown"""
        print("Closing database connection...")
        # Cleanup resources

Lifecycle execution order:

  1. Startup: All components are instantiated → post_construct() called on each
  2. Shutdown: shutdown() or close() called in reverse order

Advanced Usage

Accessing the Application Context

from fastapi import Request
from fastapi_autowire.core import AppContext

@app.get("/debug/services")
async def list_services(request: Request):
    context = request.app.state.app_context
    # Access registered services
    user_service = context.get(UserService)
    return {"service": type(user_service).__name__}

Manual Component Retrieval

from fastapi_autowire.core import AppContext

# After app startup
context = AppContext.current()
user_service = context.get(UserService)

Logging & Debugging

fastapi-autowire comes with a built-in logging system to help you monitor the dependency resolution process and troubleshoot any issues during startup.

The library uses a dedicated logger named fastapi_autowire.

Enabling Logs

By default, the library is silent (using logging.NullHandler). You can enable logs by configuring the level in your application:

import logging
import sys

# Standard logging configuration
logging.basicConfig(level=logging.INFO, stream=sys.stdout)

# Enable detailed debug logs for the DI container
logging.getLogger("fastapi_autowire").setLevel(logging.DEBUG)

How It Works

  1. Registration Phase: Decorators like @service register components in a global registry
  2. Resolution Phase: During app startup, the dependency resolver:
    • Analyzes constructor signatures
    • Builds a dependency graph
    • Detects circular dependencies
    • Instantiates components in the correct order
  3. Injection Phase: Components are injected via:
    • Constructor parameters (for other components)
    • FastAPI's Depends() mechanism (for route handlers)
  4. Lifecycle Phase: post_construct() hooks are called after instantiation

Visualizing the Process

sequenceDiagram
autonumber
participant Code as 📄 Python Files
participant Registry as 🗂️ Global Registry
participant FastAPI as ⚡ FastAPI (Lifespan)
participant Resolver as 🧠 Dependency Resolver
participant Container as 📦 AppContext (Container)
participant Endpoint as 🛣️ Route Handler

    note over Code, Registry: Phase 1: Registration (Import Time)
    Code->>Registry: Decorators execute (@component, @service...)
    Registry-->>Registry: Store class references and types

    note over FastAPI, Container: Phase 2: The "Magic" Startup (Resolution & Instantiation)
    FastAPI->>Resolver: Application starts, lifespan triggers resolution
    Resolver->>Registry: Fetch all registered classes
    Resolver-->>Resolver: Analyze __init__ signatures & build dependency graph (Topological Sort)
    loop Instantiate in Order
        Resolver-->>Resolver: Create instances, injecting dependencies recursively
    end
    Resolver->>Container: Store fully constructed singletons
    Resolver->>Container: Trigger async post_construct() hooks

    note over FastAPI, Endpoint: Phase 3: Runtime Injection (Request Time)
    [Client]->>FastAPI: Incoming HTTP Request
    FastAPI->>Endpoint: Match route handler
    Endpoint->>Container: Autowired[T] requests instance of T
    Container-->>Endpoint: Returns pre-instantiated singleton (O(1) lookup)
    Endpoint-->>[Client]: Send response

The diagram above illustrates the three stages of fastapi-autowire:

  1. Registration Phase (Import Time): As Python loads your code, the decorators (@service, @repository) execute and register your classes in a central registry. No objects are created yet.

  2. Startup Phase (The "Magic"): When FastAPI starts, the lifespan kicks in. The Resolver analyzes the dependency graph of all registered classes, determines the correct order, and instantiates them one by one, injecting necessary dependencies. The final singletons are stored in the AppContext container.

  3. Runtime Phase (Request Time): When a request hits an endpoint using Autowired[T], FastAPI fetches the already-instantiated singleton directly from the AppContext in O(1) time. Zero overhead during requests.

API Reference

Decorators

  • @component(as_type=None) - Register a component
  • @service(as_type=None) - Register a service (alias of @component)
  • @repository(as_type=None) - Register a repository (alias of @component)
  • @configuration(as_type=None) - Register a configuration (alias of @component)
  • @provider(as_type=None) - Register a provider (alias of @component)

Parameters:

  • as_type: Optional type to register the component as (for interface-based registration)

Types

  • Autowired[T] - Generic type for dependency injection
    service: Autowired[UserService]
    

Functions

  • lifespan(app: FastAPI) - Async context manager for FastAPI lifespan events

Classes

  • AppContext - Application context holding all registered components
    • current() - Get the current context instance
    • get(component_type) - Retrieve a component by type

Requirements

  • Python 3.9+
  • FastAPI 0.100.0+
  • typing-extensions 4.0.0+ (for Python < 3.10)

Development

# Clone the repository
git clone https://github.com/leoroop/fastapi-autowire.git
cd fastapi-autowire

# Install dependencies with uv
uv sync

# Run tests
uv run pytest

# Run linter
uv run ruff check .

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Inspired by Spring Framework's dependency injection system, adapted for Python and FastAPI.

Support

If you encounter any issues or have questions, please open an issue on GitHub.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_autowire-0.1.1.tar.gz (35.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_autowire-0.1.1-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_autowire-0.1.1.tar.gz.

File metadata

  • Download URL: fastapi_autowire-0.1.1.tar.gz
  • Upload date:
  • Size: 35.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fastapi_autowire-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a5444d94e29c3a72a79ff6424c3ebd554ee24802b899e0001f4d75ff41755ec4
MD5 0ee7907dc34fcf8f40899f94ff295032
BLAKE2b-256 da13e9c4135c1fb3d5cabd1bec0cb7ffc2bef4510c82ee41f7a178c9017a58f1

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_autowire-0.1.1.tar.gz:

Publisher: release.yml on leoroop/fastapi-autowire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_autowire-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_autowire-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 62370a3b855b6db4e344513031bd3f600267e7bef787a6dcaf2b96377454215c
MD5 bf4aadffa176a2c402807395c84e0d82
BLAKE2b-256 131b745a4a1800155b94b168948f1df5b4cf73e2e8d0611427f5ce99c5b73c0f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_autowire-0.1.1-py3-none-any.whl:

Publisher: release.yml on leoroop/fastapi-autowire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page