(Feat): Initial Commit.
This commit is contained in:
359
README.md
Normal file
359
README.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user