Files
fastapi-route-loader/README.md
2026-01-05 22:11:58 +00:00

8.4 KiB

FastAPI Route Loader

Python 3.10+ License: MIT

Automatic APIRouter loading and management for FastAPI with an event dispatch system.

Features

  • 🚀 Automatic Router Discovery: Automatically load APIRouter instances from modules and directories
  • 🎯 Router Management: Include/exclude routers dynamically with filtering capabilities
  • 📡 Event System: Subscribe to router lifecycle events (loaded, unloaded, updated)
  • 🔧 Type Safe: Full type hints with strict pyright and ruff compliance
  • Production Ready: Comprehensive test coverage and battle-tested code
  • 🐍 Python 3.10+: Modern Python with full async support

Installation

pip install fastapi-route-loader

Quick Start

Basic Usage

from fastapi import FastAPI, APIRouter
from fastapi_route_loader import RouterContainer

# Create a container
container = RouterContainer()

# Add routers manually
users_router = APIRouter(prefix="/users", tags=["users"])

@users_router.get("/")
def list_users():
    return {"users": []}

container.add_router("users", users_router)

# Register all routers to your FastAPI app
app = FastAPI()
container.register_to_app(app)

Automatic Loading from Directory

from fastapi import FastAPI
from fastapi_route_loader import RouterContainer

# Create a container and load all routers from a directory
container = RouterContainer()
container.load_from_directory("./routers", package="myapp.routers")

# Register to FastAPI app
app = FastAPI()
container.register_to_app(app, prefix="/api")

Router Filtering

from fastapi_route_loader import RouterContainer

container = RouterContainer()
container.load_from_directory("./routers")

# Exclude specific routers
container.exclude("admin_router", "internal_router")

# Or use whitelist mode (only include specific routers)
container.include("public_router", "api_router")

# Clear all filters
container.clear_filters()

Event System

The event system supports both decorator and callback styles:

Decorator Style (Recommended):

from fastapi_route_loader import RouterContainer, RouterEventType

container = RouterContainer()

# Subscribe to specific event types using decorators
@container.on(RouterEventType.LOADED)
def on_router_loaded(event):
    print(f"Router '{event.router_name}' was loaded")
    print(f"Metadata: {event.metadata}")

# Subscribe to all events
@container.on(None)
def log_all_events(event):
    print(f"Event: {event.event_type.value} - Router: {event.router_name}")

# Load routers (events will be dispatched)
container.load_from_directory("./routers")

Callback Style:

def on_router_loaded(event):
    print(f"Router '{event.router_name}' was loaded")

container.on(RouterEventType.LOADED, on_router_loaded)

Advanced Usage

Project Structure

myapp/
├── main.py
└── routers/
    ├── __init__.py
    ├── users.py
    ├── posts.py
    └── admin/
        ├── __init__.py
        └── dashboard.py

routers/users.py:

from fastapi import APIRouter

users_router = APIRouter(prefix="/users", tags=["users"])

@users_router.get("/")
def list_users():
    return {"users": []}

@users_router.get("/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

main.py:

from fastapi import FastAPI
from fastapi_route_loader import RouterContainer

app = FastAPI()
container = RouterContainer()

# Load all routers from the routers directory
container.load_from_directory("./routers", package="myapp.routers")

# Register all active routers
container.register_to_app(app)

Dynamic Router Management

from fastapi import APIRouter
from fastapi_route_loader import RouterContainer

container = RouterContainer()

# Add a router
router_v1 = APIRouter(prefix="/api/v1")
container.add_router("api_v1", router_v1)

# Update a router
router_v2 = APIRouter(prefix="/api/v2")
container.update_router("api_v1", router_v2)

# Remove a router
container.remove_router("api_v1")

# Check if router exists
if container.has_router("api_v1"):
    router = container.get_router("api_v1")

Event Handling with Metadata

from fastapi_route_loader import RouterContainer, RouterEventType

container = RouterContainer()

def track_router_changes(event):
    if event.event_type == RouterEventType.LOADED:
        print(f"Loaded: {event.router_name}")
    elif event.event_type == RouterEventType.UPDATED:
        print(f"Updated: {event.router_name}")
        print(f"Old router: {event.old_router}")
    elif event.event_type == RouterEventType.UNLOADED:
        print(f"Unloaded: {event.router_name}")

container.on(None, track_router_changes)

# Add router with metadata
from fastapi import APIRouter
router = APIRouter()
container.add_router("my_router", router, metadata={"version": "1.0", "author": "dev"})

Container Iteration

container = RouterContainer()
# ... add routers ...

# Check length
print(f"Total routers: {len(container)}")

# Check membership
if "users_router" in container:
    print("Users router exists")

# Iterate over router names
for router_name in container:
    print(f"Router: {router_name}")

# Get all routers (including filtered ones)
all_routers = container.get_all_routers()

# Get only active routers (after applying filters)
active_routers = container.get_active_routers()

API Reference

RouterContainer

Main class for managing APIRouter instances.

Methods

  • add_router(name: str, router: APIRouter, metadata: dict | None = None) -> None

    • Add a router to the container
  • remove_router(name: str, metadata: dict | None = None) -> APIRouter

    • Remove and return a router from the container
  • update_router(name: str, router: APIRouter, metadata: dict | None = None) -> None

    • Update an existing router
  • get_router(name: str) -> APIRouter

    • Get a router by name
  • has_router(name: str) -> bool

    • Check if a router exists
  • exclude(*names: str) -> None

    • Exclude routers from being active
  • include(*names: str) -> None

    • Set routers to be included (whitelist mode)
  • clear_filters() -> None

    • Clear all include/exclude filters
  • is_active(name: str) -> bool

    • Check if a router is active
  • get_active_routers() -> dict[str, APIRouter]

    • Get all active routers
  • get_all_routers() -> dict[str, APIRouter]

    • Get all routers regardless of filters
  • load_from_module(module_path: str) -> None

    • Load routers from a module
  • load_from_directory(directory: str, package: str | None = None) -> None

    • Load routers from a directory
  • register_to_app(app: FastAPI, prefix: str = "") -> None

    • Register all active routers to a FastAPI application
  • on(event_type: RouterEventType | None, handler: EventHandler) -> None

    • Subscribe to router events
  • off(event_type: RouterEventType | None, handler: EventHandler) -> None

    • Unsubscribe from router events
  • clear_handlers() -> None

    • Clear all event handlers

RouterEventType

Enum of event types:

  • LOADED: Router was loaded
  • UNLOADED: Router was unloaded
  • UPDATED: Router was updated

Event Classes

  • RouterLoadedEvent: Dispatched when a router is loaded
  • RouterUnloadedEvent: Dispatched when a router is unloaded
  • RouterUpdatedEvent: Dispatched when a router is updated (includes old_router attribute)

Development

Setup

# Clone the repository
git clone https://github.com/yourusername/fastapi-route-loader.git
cd fastapi-route-loader

# Install dependencies
pip install -e ".[dev]"

Running Tests

# Run all tests with coverage
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/test_container.py

Code Quality

# Run ruff linter
ruff check .

# Run ruff formatter
ruff format .

# Run pyright type checker
pyright

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.

Changelog

0.1.0 (Initial Release)

  • Automatic router discovery and loading
  • Router filtering (include/exclude)
  • Event dispatch system
  • Full type hints and strict linting
  • Comprehensive test coverage