Contributor Guide: Design of BaseCRUDRouter
This document explains the internal design and key decisions behind
the BaseCRUDRouter implementation.
Goals
- Auto-generate standard CRUD endpoints with flexible configuration
- Minimal boilerplate for users
- Safe and type-correct endpoint signatures
- Selective endpoint inclusion/exclusion
- Customizable endpoint behavior without losing type safety
Key Design Points
Multi-method API via BaseCRUD
This router relies on a crud object based on BaseCRUD abstraction,
which provides methods like:
createget_allget_by_idupdate_bydelete_by
This separation enforces SRP (Single Responsibility Principle) and allows easy swapping or customizing of storage logic.
Flexible Endpoint Configuration System
Endpoint Selection
The router supports flexible endpoint selection through:
include_endpoints: list[DefaultEndpoint] | Literal["all"] = "all"
exclude_endpoints: list[DefaultEndpoint] | None = None
This allows users to:
- Include all endpoints by default (
"all") - Selectively include only specific endpoints
- Exclude unwanted endpoints from the default set
The logic combines these parameters using set operations:
if include_endpoints == "all":
endpoints_to_include = set(DefaultEndpoint)
else:
endpoints_to_include = set(include_endpoints)
if exclude_endpoints:
endpoints_to_include -= set(exclude_endpoints)
Endpoint Configuration
Each endpoint can be customized via the EndpointConfig model:
class EndpointConfig(BaseModel):
path: str
methods: list[str]
response_model: Any = None
include_in_schema: bool = True
tags: list[str] | None = None
summary: str | None = None
description: str | None = None
deprecated: bool = False
This provides fine-grained control over each endpoint's FastAPI route configuration.
Default Configuration System
The _get_default_endpoint_config method provides sensible defaults
for each endpoint type:
configs = {
DefaultEndpoint.CREATE: EndpointConfig(
path=f"{self.path_prefix}/",
methods=["POST"],
response_model=self.schema,
summary="Create new item",
# ... other defaults
),
# ... other endpoint configurations
}
Custom configurations merge with or override these defaults, allowing users to customize only what they need.
Dynamic Signature Replacement
Pydantic generic models (like Schema, SchemaCreate) are passed to the router.
To ensure FastAPI/OpenAPI generates correct docs and validations,
endpoint signatures are dynamically replaced using:
The _update_handler_signature method inspects method signatures using inspect.signature
and replaces type annotations for parameters where placeholders (Schema, SchemaCreate)
were used:
for name, param in params.items():
if hasattr(param.annotation, "__name__"):
match param.annotation.__name__:
case "Schema":
params[name] = param.replace(annotation=self.schema)
is_replaced = True
case "SchemaCreate":
params[name] = param.replace(annotation=self.schema_create)
is_replaced = True
This guarantees correct typing in route definitions without forcing inheritance or metaclass hacks.
Endpoint Registration Flow
The endpoint building process follows this flow:
- Selection: Determine which endpoints to include
based on
include_endpointsandexclude_endpoints - Configuration: For each selected endpoint, get its configuration (custom or default)
- Registration: Register each endpoint with FastAPI using the resolved configuration
def _build_endpoints(self, endpoints_to_include, endpoint_configs):
endpoint_handlers = {
DefaultEndpoint.CREATE: self.create,
DefaultEndpoint.GET_ALL: self.get_all,
# ... other mappings
}
for endpoint_type in endpoints_to_include:
handler = endpoint_handlers[endpoint_type]
config = endpoint_configs.get(endpoint_type) or self._get_default_endpoint_config(endpoint_type)
updated_handler = self._update_handler_signature(handler)
self.add_api_route(path=config.path, endpoint=updated_handler, **config.to_route_kwargs())
Path Management
The router supports flexible path management through:
- Router
prefix(FastAPI standard) path_prefixparameter (custom prefix for all endpoint paths)- Individual endpoint
pathconfiguration
Paths are constructed by combining these elements, with path_prefix being
stripped of trailing slashes to ensure clean URL construction.
Generic Response Models
List responses use a generic wrapper model:
This provides standard pagination responses while retaining schema typing and allowing proper OpenAPI documentation generation.
Type Safety Considerations
The router maintains type safety through several mechanisms:
- Generic type parameters:
BaseCRUDRouter[Schema, SchemaCreate]ensures compile-time type checking - Dynamic signature replacement: Runtime signature updates ensure FastAPI sees correct types
- Pydantic model validation: All configurations use Pydantic models for runtime validation
Configuration Extensibility
The EndpointConfig.to_route_kwargs() method cleanly separates path from other
route configuration, making it easy to extend supported FastAPI route parameters
without breaking existing code.
Future Extensibility
The design supports future enhancements:
- Custom endpoint handlers: The infrastructure exists to add non-CRUD endpoints
- Middleware integration: Endpoint-specific middleware could be added to
EndpointConfig - Response transformation: Custom response models could be configured per endpoint
- Authentication/authorization: Permission-based endpoint filtering could be added
Testing Considerations
The modular design enables focused testing:
- Endpoint selection logic can be tested independently
- Configuration merging can be verified in isolation
- Signature replacement can be validated with mock schemas
- Each endpoint handler can be tested separately