360 lines
8.4 KiB
Markdown
360 lines
8.4 KiB
Markdown
# FastAPI Route Loader
|
|
|
|
[](https://www.python.org/downloads/)
|
|
[](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
|