# FastAPI Route Loader [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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 ```bash pip install fastapi-route-loader ``` ## Quick Start ### Basic Usage ```python 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 ```python 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 ```python 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):** ```python 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:** ```python 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:** ```python 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:** ```python 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 ```python 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 ```python 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 ```python 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 ```bash # 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 ```bash # Run all tests with coverage pytest # Run with verbose output pytest -v # Run specific test file pytest tests/test_container.py ``` ### Code Quality ```bash # 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