FastCRUD 0.19.0 Release Notes
Warning
The fastcrud.paginated module is deprecated and will be removed in the next major version. Import pagination utilities directly from fastcrud instead:
# OLD (deprecated, but still works)
from fastcrud.paginated import PaginatedListResponse
# NEW (recommended)
from fastcrud import PaginatedListResponseFastCRUD 0.19.0 represents the largest architectural overhaul in the project's history, moving from a helper-based architecture to a protocol-based six-layer system. Most changes are internal - the public API remains largely the same with full backward compatibility, but users will benefit from enhanced type safety, improved performance, and more reliable operations.
Summary
What's New for Users:
- Enhanced Type Safety: Method overloads eliminate manual
isinstance()checks - your IDE now knows exact return types - New
return_as_modelParameter: 7 methods gained this parameter for consistent dict/Pydantic model control - New Configuration Classes:
CreateConfig,UpdateConfig,DeleteConfigfor server-side field injection and security - Performance Improvements: Query caching, memory optimization, faster SQLAlchemy introspection
- Bug Fixes: Cartesian product issue in complex joins resolved
- Better Imports: All utilities now available from main
fastcrudimport
What's New Internally:
- Complete Architectural Rewrite: From 2 helper files to 6-layer protocol-based architecture
- Protocol-Based Design: 8 new protocols eliminate circular dependencies, enable clean testing
- Core Module System: 15+ new specialized modules replace monolithic helper files
- 99.31% Test Coverage: Strategic testing with comprehensive edge case coverage
- Framework Independence: Core logic separated from FastAPI - ready for other frameworks
Migration Required: None - full backward compatibility maintained. Only deprecation: import pagination utilities from fastcrud instead of fastcrud.paginated.
🔧 Public API Changes
Enhanced Type Safety
Method Overloads for Better Developer Experience
- Enhanced Methods: All major CRUD methods now have
@overloadsignatures for type-safe return types - New Parameter: Added
return_as_model: bool = Falseto:create(),get(),update(),upsert(),upsert_multi(),get_multi_by_cursor(),get_multi_joined() - Type Inference: IDE automatically infers return types - no more manual
isinstance()checks needed
# Type automatically inferred as UserRead
user = await crud.get(db, id=1, return_as_model=True)
name = user.name # Type-safe access
# Type automatically inferred as dict[str, Any]
user_dict = await crud.get(db, id=1, return_as_model=False)
name = user_dict["name"] # Dictionary accessNew Configuration Classes
Server-Side Field Injection
- New Exports:
CreateConfig,UpdateConfig,DeleteConfigavailable from main import - Auto Fields: Automatic field injection with
auto_fieldsparameter - Security: Exclude sensitive fields from API schemas with
exclude_from_schema
from fastcrud import CreateConfig
config = CreateConfig(
auto_fields={"created_at": lambda: datetime.utcnow()},
exclude_from_schema={"created_at"}
)Enhanced Import Structure
Centralized Imports
- New Exports:
CountConfig,JoinConfig,FilterConfignow available from main import - Pagination:
PaginatedListResponse,PaginatedRequestQuerycentralized
# All available from main import
from fastcrud import (
FastCRUD, CreateConfig, UpdateConfig, DeleteConfig,
CountConfig, JoinConfig, PaginatedListResponse
)Performance Improvements
Noticeable Performance Gains
- Query Caching: Complex filter parsing memoized - same queries execute faster
- Memory Optimization: Reduced object allocation through strategic reuse
- Instance Caching: SQLAlchemy introspection cached per model instance
Bug Fixes
Cartesian Product Fix
- Fixed:
get_joinedwith multiple one-to-many joins andnest_joins=Trueno longer produces duplicate results - Impact: More reliable data in complex join scenarios
Deprecation
Module Deprecation
- Deprecated:
fastcrud.paginatedmodule (imports still work but show warning) - Migration: Import pagination utilities directly from
fastcrudinstead
🏗️ Internal Architecture Changes
Complete Architectural Overhaul
Six-Layer Protocol-Based Architecture
FastCRUD now follows a strict six-level dependency hierarchy that solves real problems in maintaining and scaling CRUD applications:
Level 6: Framework Layer (fastapi_dependencies.py, endpoint/)
Level 5: CRUD Layer (crud/fast_crud.py, validation.py, execution.py)
Level 4: Integration Layer (core/data/formatting.py, join_processing.py)
Level 3: Orchestration Layer (core/query/, field_management.py, data/nesting.py)
Level 2: Core Logic Layer (core/config/, filtering/, data/transforms.py, pagination.py)
Level 1: Foundation Layer (core/protocols.py, introspection.py, types.py)
Benefits of Layer Separation:
- Debugging: When join operations break, check Integration layer. Filter parsing issues? Start in Orchestration
- Testing: Each layer can be tested in isolation with focused unit tests
- Evolution: Change query building logic? Only affects Level 3 and above
- Framework Independence: Everything below Level 6 could work with Flask, Django, or other frameworks
Protocol-Based Design
Eliminates Circular Dependencies
- 8 New Protocols:
CRUDInstance,ModelIntrospector,DataProcessor,FilterProcessor,QueryBuilder,ResponseFormatter,DatabaseAdapter,ValidationProcessor - Duck Typing Made Explicit: Protocol interfaces define what methods a class should have without implementation
- Clean Dependency Flow: Lower levels define interfaces, higher levels implement them
# OLD: Circular import hell
from ..crud.fast_crud import FastCRUD
def some_function(crud: FastCRUD): # Creates circular imports
pass
# NEW: Protocol-based design
from ..core.protocols import CRUDInstance
def some_function(crud: CRUDInstance): # Clean, no circular imports
passProtocol Implementation Pattern
- Foundation Layer: Define the interface (
CRUDInstanceprotocol) - Higher Layers: Use the protocol (execution layer imports protocol, not concrete class)
- Implementation Layer: Automatically satisfies protocol (FastCRUD implements the methods)
This creates clean dependency flow with no runtime dependency resolution or magic - just type-safe interfaces.
New Core Module Structure
Complete File Reorganization
fastcrud/core/
├── config/ # Configuration management
│ ├── crud_configs.py # CreateConfig, UpdateConfig, DeleteConfig
│ └── join_configs.py # JoinConfig, CountConfig
├── data/ # Data processing
│ ├── formatting.py # Response formatting with join processing
│ ├── nesting.py # Data nesting with introspection
│ └── transforms.py # Pure data transformation functions
├── filtering/ # Filter processing
│ ├── operators.py # SUPPORTED_FILTERS, get_sqlalchemy_filter
│ ├── processor.py # FilterProcessor class
│ └── validators.py # Filter validation functions
├── query/ # SQL query building
│ ├── builder.py # SQLQueryBuilder class
│ ├── joins.py # JoinBuilder class
│ └── sorting.py # SortProcessor class
├── field_management.py # Schema and field operations
├── introspection.py # Model introspection with caching
├── join_processing.py # Join processing logic
├── pagination.py # Pagination utilities
└── protocols.py # Protocol interfaces
Removed Legacy Files:
fastcrud/crud/helper.py- Functionality moved to specialized core modulesfastcrud/endpoint/helper.py- Consolidated intofastcrud/fastapi_dependencies.py
Enhanced Type System
Strategic Type Improvements
- New Types:
FilterValueType,FilterValue,FilterValueSequencefor precise database operations - Response Types:
GetMultiResponseDict,GetMultiResponseModel,UpsertMultiResponseDict,UpsertMultiResponseModel - Type Guards: Proper type guards in filtering modules with
isinstance()checks
# Enhanced filter typing
FilterValue = Union[str, int, float, bool, datetime, Decimal, None]
FilterValueSequence = Union[list[FilterValue], tuple[FilterValue, ...], set[FilterValue]]
FilterValueType = Union[FilterValue, FilterValueSequence, dict[str, FilterValue]]Performance Optimizations
Instance-Level Caching Strategy
class ModelIntrospector:
def __init__(self, model: type[ModelType]):
self._inspector: Optional[Inspector] = None
self._primary_keys: Optional[list[str]] = None # Cached results
@property
def inspector(self) -> Inspector:
if self._inspector is None:
self._inspector = sa_inspect(self.model)
return self._inspectorMulti-Level Caching Implementation
- SQLAlchemy Introspection: Cached per model instance to avoid redundant reflection
- Schema Modifications: Function-level caching for schema field extraction
- Query Building: Complex filter parsing memoized - same queries reuse parsed conditions
- Memory Management: Strategic object reuse through cached inspectors and processors
Pure Function Architecture Pattern
def handle_one_to_many(data: List[Dict], nested_key: str) -> List[Dict]:
"""Pure function - same input always produces same output."""
# No side effects, no database calls, no global state changes
# Easy to test: assert handle_one_to_many(input_data) == expected_outputData Transformation Optimization
- Common Case Fast Path: Simple operations (no joins, basic pagination) take optimized route
- Complex Case Comprehensive Path: Multiple joins and nested data use full processing pipeline
- Pattern: Optimize for the common case, make the complex case possible but not necessarily fast
Quality Improvements
Enhanced Testing and Coverage
- 99.31% Coverage: Achieved through strategic testing and appropriate pragma usage for defensive programming
- Layer-Isolated Testing: Foundation layer = pure unit tests, Core Logic = complex fixtures, CRUD layer = database fixtures
- Protocol Testing: Each protocol can be tested independently without concrete implementations
Layer-Specific Error Handling Strategy
# Foundation layer - low-level issues
class ModelIntrospectionError(Exception): pass
# Core Logic layer - business rule violations
class InvalidFilterError(Exception): pass
# CRUD layer - operation-specific problems
class JoinConfigurationError(Exception): passFail-Fast Validation Pattern
# Validate early, before expensive operations
async def complex_operation(params):
validate_params(params) # Fast validation upfront
result = await expensive_database_operation(params) # Only if validation passes
return resultContext-Preserving Error Handling
try:
result = await complex_operation()
except SQLAlchemyError as e:
raise CRUDOperationError(f"Failed to execute joined query: {e}") from e
# Preserves both high-level context AND low-level detailsWhen something fails, the exception type tells you which layer to investigate. Better to catch problems in validation than in a failed database transaction.
Technical Implementation Patterns
Delegation Pattern Implementation
# FastCRUD (Level 5) delegates to execution layer
async def get_multi_joined(self, db, **kwargs):
return await handle_joined_filters_delegation(self, db, **kwargs)
# Execution (Level 5) delegates to join processing
async def handle_joined_filters_delegation(crud_instance, db, **kwargs):
validated_params = validate_joined_query_params(**kwargs)
return await execute_joined_query(db, crud_instance.model, validated_params)Framework Separation Strategy
- All FastAPI-specific code lives in Level 6:
fastapi_dependencies.pyandendpoint/ - Everything below Level 6 is framework-agnostic: Could work with Flask, Django, GraphQL
- Clean separation enables: CLI tools, different web frameworks, batch processing - all reusing the same core logic
Pure Function Composition Pattern
def format_response(data):
return apply_field_selection(
apply_sorting(
apply_pagination(data)
)
)
# Build complex operations from simple, testable functionsProtocol Dispatch Performance
- Zero Runtime Overhead: Python protocols compile to regular method calls
- Type Safety: Full compile-time checking with no performance cost
- No Magic: No runtime dependency resolution or dynamic dispatch
Breaking Changes
⚠️ None - This release maintains full backward compatibility with 0.18.x
Deprecation Only:
fastcrud.paginatedmodule deprecated (still works with warning)
What's Changed
- Major refactor by @igorbenav in #275
- Get joined fix by @igorbenav in #276
- FastCRUD refactor by @igorbenav in #277
- Architecture refactor by @igorbenav in #278
- Better typing by @igorbenav in #279
- Fix codecoverage by @igorbenav in #280
- Final preparations for 0.19.0 by @igorbenav in #281
Full Changelog: v0.18.1...v0.19.0