PathManager
Overview
The PathManager is a critical component of the CatFeeder routing system that implements a deferred route registration pattern. It allows routes to be defined and registered BEFORE the FastAPI application instance exists, storing them in memory until they can be injected into the FastAPI app at initialization time.
Core Concept: Deferred Registration
The Problem
In traditional FastAPI applications, routes must be added directly to an existing FastAPI app instance using decorators or app.add_api_route(). This creates a chicken-and-egg problem when:
- Routes need to be organized across multiple modules
- The FastAPI app instance doesn't exist yet during configuration
- A centralized route management system is needed
The Solution
PathManager solves this by implementing a two-phase approach:
- Registration Phase: Routes are validated and stored in an internal list
- Injection Phase: All stored routes are bulk-injected into FastAPI when the app is ready
Architecture
@startuml path_manager_architecture.puml
!theme plain
package "PathManager Module" {
class PathManager {
- routes: List[Dict[str, Any]]
- runtime_manager_initialised: RuntimeManager
- endpoints_initialised: EndpointManager
- runtime_control_initialised: RuntimeControl
+ __init__(success, error, debug)
+ add_path(path, endpoint, method)
+ has_endpoint(path, endpoint, method)
+ load_default_paths_initialised()
+ inject_routes()
- endpoint_valid(path, endpoint, method)
- _build_endpoint(path, endpoint, method)
- _find_route_index(path, endpoint)
- _merge_methods(existing, new)
}
class PathConstants {
+ PATH_KEY: str = "path"
+ ENDPOINT_KEY: str = "endpoint"
+ METHOD_KEY: str = "method"
+ ALLOWED_METHODS: List[str]
}
}
package "Core" {
class RuntimeManager {
+ get(class_type)
+ get_if_exists(class_type, default)
+ exists(class_type)
}
class RuntimeControl {
+ app: FastAPI
}
class FinalClass {
<<metaclass>>
}
}
package "EndpointManager" {
class EndpointManager {
+ inject_routes()
}
}
PathManager --> PathConstants : uses
PathManager --> RuntimeManager : retrieves instances
PathManager --> RuntimeControl : accesses FastAPI app
PathManager --> EndpointManager : loads routes from
PathManager ..|> FinalClass : metaclass
PathManager --> "FastAPI" : injects routes into
note right of PathManager::routes
Internal storage for deferred routes
Structure: [
{
"path": "/api/v1/users",
"endpoint": function_ref,
"method": ["GET", "POST"]
}
]
end note
note bottom of PathManager::inject_routes
Final step: injects all stored
routes into FastAPI app instance
end note
@enduml
Key Components
Route Storage Structure
Each route is stored as a dictionary with three keys:
{
"path": str,
"endpoint": callable,
"method": List[str]
}
Allowed HTTP Methods
The system validates all HTTP methods against the ALLOWED_METHODS constant:
ALLOWED_METHODS = [
"GET", "POST",
"PUT", "PATCH",
"DELETE", "HEAD",
"OPTIONS"
]
Core Methods
__init__(success: int = 0, error: int = 84, debug: bool = False)
Initializes the PathManager with:
- Debug logging configuration
- Success/error return codes
- Empty routes list
- References to RuntimeManager, RuntimeControl, and EndpointManager
add_path(path: str, endpoint: object, method: Union[str, List[str]]) -> int
Purpose: Register a new route or merge methods into an existing route
Behavior:
- Validates the path, endpoint, and method(s)
- If the path+endpoint combination already exists, merges the new method(s) with existing ones
- Otherwise, creates a new route entry
- Returns
success (0) or error (84)
Example:
path_manager.add_path("/api/v1/users", get_users, "GET")
path_manager.add_path("/api/v1/users", get_users, "POST")
endpoint_valid(path: str, endpoint: object, method: Union[str, List[str]]) -> bool
Purpose: Validate endpoint configuration before registration
Validates:
- Path is a string
- Endpoint is callable (a function)
- Method(s) are valid HTTP methods from
ALLOWED_METHODS
Returns: True if valid, False otherwise
load_default_paths_initialised() -> None
Purpose: Load all default application routes from EndpointManager
Process:
- Retrieves EndpointManager instance from RuntimeManager
- Calls
EndpointManager.inject_routes() which adds all application routes
- Raises
RuntimeError if EndpointManager is not found
inject_routes() -> None
Purpose: The final injection step - adds all stored routes to FastAPI
Process:
- Retrieves FastAPI app instance from
RuntimeControl.app
- Validates the app has
add_api_route method
- Iterates through all stored routes
- Calls
app.add_api_route(path, endpoint, methods=methods) for each route
Example:
path_manager.inject_routes()
Registration Flow
@startuml path_manager_registration.puml
!theme plain
participant "Application" as App
participant "EndpointManager" as EM
participant "PathManager" as PM
participant "RuntimeControl" as RC
participant "FastAPI" as FA
App -> PM : __init__()
activate PM
PM -> PM : routes = []
PM -> RC : get RuntimeControl
PM -> EM : get EndpointManager (if exists)
return PathManager instance
App -> PM : load_default_paths_initialised()
activate PM
PM -> EM : inject_routes()
activate EM
EM -> PM : add_path("/api/v1/login", login_handler, "POST")
activate PM
PM -> PM : endpoint_valid()
PM -> PM : _build_endpoint()
PM -> PM : routes.append({...})
return success
EM -> PM : add_path("/api/v1/users", get_users, "GET")
PM -> PM : routes.append({...})
return success
EM -> PM : add_path("/api/v1/users", create_user, "POST")
PM -> PM : _find_route_index()
PM -> PM : _merge_methods(["GET"], ["POST"])
PM -> PM : routes[idx]["method"] = ["GET", "POST"]
return success
return all routes registered
App -> PM : inject_routes()
activate PM
PM -> RC : get app instance
RC --> PM : FastAPI app
PM -> FA : add_api_route("/api/v1/login", login_handler, methods=["POST"])
PM -> FA : add_api_route("/api/v1/users", get_users, methods=["GET", "POST"])
PM -> FA : ... (all other routes)
return routes injected
note over PM
Routes are now live and
FastAPI can handle requests
end note
@enduml
Advanced Features
Method Merging
When the same path+endpoint combination is registered multiple times with different methods, PathManager automatically merges them:
path_manager.add_path("/api/v1/resource", handler, "GET")
path_manager.add_path("/api/v1/resource", handler, ["POST", "PUT"])
path_manager.add_path("/api/v1/resource", handler, "DELETE")
Implementation:
_find_route_index(): Locates existing route by path and endpoint
_merge_methods(): Combines methods using a set to eliminate duplicates
- Returns sorted list of unique methods
Route Validation
The _build_endpoint() method creates validated route dictionaries:
def _build_endpoint(self, path: str, endpoint: object, method: Union[str, List[str]]):
if not self.endpoint_valid(path, endpoint, method):
return None
methods = [method] if isinstance(method, str) else method
return {
PATH_KEY: path,
ENDPOINT_KEY: endpoint,
METHOD_KEY: methods
}
Integration with RuntimeManager
PathManager uses the RuntimeManager singleton (RI) to access shared instances:
self.runtime_manager_initialised: RuntimeManager = RI
self.endpoints_initialised = self.runtime_manager_initialised.get_if_exists(
"EndpointManager",
None
)
self.runtime_control_initialised = self.runtime_manager_initialised.get(
RuntimeControl
)
Usage Example
Basic Registration
path_manager = PathManager(debug=True)
async def get_users():
return {"users": []}
async def create_user():
return {"status": "created"}
path_manager.add_path("/api/v1/users", get_users, "GET")
path_manager.add_path("/api/v1/users", create_user, "POST")
path_manager.inject_routes()
With EndpointManager Integration
path_manager = PathManager(debug=True)
path_manager.load_default_paths_initialised()
path_manager.inject_routes()
Error Handling
Validation Errors
Routes that fail validation are logged but don't crash the application:
path_manager.add_path(123, handler, "GET")
path_manager.add_path("/api/test", handler, "INVALID")
Runtime Errors
Critical errors raise exceptions:
path_manager.load_default_paths_initialised()
path_manager.inject_routes()
Design Patterns
Final Class Pattern
PathManager uses the FinalClass metaclass to prevent inheritance:
class PathManager(metaclass=FinalClass):
...
This ensures the route management logic remains centralized and unmodified.
Singleton Access Pattern
PathManager doesn't enforce singleton behavior itself, but relies on RuntimeManager for instance management, allowing controlled shared access across the application.
Benefits
- Decoupling: Routes can be defined independently of FastAPI app creation
- Centralization: Single source of truth for all application routes
- Validation: All routes are validated before registration
- Flexibility: Routes can be registered from multiple modules
- Method Merging: Automatically handles multiple HTTP methods for same endpoint
- Type Safety: Strong validation of paths, endpoints, and HTTP methods
See Also
- EndpointManager - Consumes PathManager for route registration
- Core - RuntimeManager and RuntimeControl integration
- Server - FastAPI application lifecycle
Constants Reference
PATH_KEY
- Value:
"path"
- Usage: Dictionary key for route path
ENDPOINT_KEY
- Value:
"endpoint"
- Usage: Dictionary key for endpoint function
METHOD_KEY
- Value:
"method"
- Usage: Dictionary key for HTTP methods
ALLOWED_METHODS
- Value:
["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]
- Usage: Validation list for HTTP methods