8.4 KiB
FastAPI Route Loader
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 loadedUNLOADED: Router was unloadedUPDATED: Router was updated
Event Classes
RouterLoadedEvent: Dispatched when a router is loadedRouterUnloadedEvent: Dispatched when a router is unloadedRouterUpdatedEvent: Dispatched when a router is updated (includesold_routerattribute)
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