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:
create
get_all
get_by_id
update_by
delete_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_endpoints
andexclude_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_prefix
parameter (custom prefix for all endpoint paths)- Individual endpoint
path
configuration
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