0.22.0
FastCRUD 0.22.0 Release Notes
Important
Security Update: This release bumps several dev dependencies to patch known CVEs and adds least-privilege permissions: blocks to every CI workflow. See Security Updates below.
Note
Filter Param Types in OpenAPI: Filter query parameters generated by crud_router now expose proper column types in the OpenAPI schema (integer, string, array<int>, …) instead of falling back to any. Auto-generated docs and codegen clients become noticeably more useful.
FastCRUD 0.22.0 focuses on type fidelity and edge-case robustness. Filter parameters in your generated OpenAPI now report their real types; cursor pagination rejects malformed values up front; polymorphic inheritance no longer trips the join builder; and a handful of long-standing TypeError/typing rough edges are smoothed.
Documentation:
- Filtering — filter operators and types
- Cursor Pagination — cursor-based pagination with
CursorPaginatedRequestQuery
Summary
What's New for Users:
- OpenAPI Filter Types:
crud_routerquery params now render asinteger/number/string/boolean/array<X>in the generated schema — no moreanyplaceholders - Cursor Validation: Invalid cursor values (int32/int64 overflow, malformed UUIDs, unparseable datetimes) now return a clean HTTP 400 with a clear error message
- Polymorphic Joined Filtering: Joined filters across SQLAlchemy joined-table inheritance no longer crash with
UndefinedTableError - Sharper Error Messages:
update(return_columns=True)now raises a clearValueError;?filter=falseis correctly parsed asFalse - Better Type Inference:
schema_to_select=Subclassnow propagates the subclass through to the return type ofget,get_multi,get_joined,upsert, and friends
No breaking changes for typical usage.
New Features
OpenAPI Filter Param Types
crud_router now exposes the real column type for every filter query parameter:
from fastcrud import crud_router, FilterConfig
router = crud_router(
session=get_session,
model=Product,
create_schema=ProductCreate,
update_schema=ProductUpdate,
filter_config=FilterConfig(
name=None, # → string
price__gte=None, # → number
active=None, # → boolean
tag__in=None, # → array<string>
"category.name__eq": None, # → string (from related model)
),
)The OpenAPI schema now has accurate type and items.type entries on each parameter. FastAPI's /docs UI shows proper input controls, and OpenAPI codegen produces correctly-typed clients.
Internally, this is powered by a new Filter metadata model and a FilterProcessor.interpret_filters() method — both available under fastcrud.core.filtering if you need to introspect filters from your own code.
Cursor Value Validation
Cursor values from query parameters are now validated against the target column type before the query runs:
# id is an INTEGER column (int32 on SQLite/most engines)
GET /items/get_multi_by_cursor?cursor=99999999999999999999&sort_column=id
# → HTTP 400: cursor value exceeds valid INTEGER range
# created_at is a DateTime column
GET /items/get_multi_by_cursor?cursor=not-a-date&sort_column=created_at
# → HTTP 400: invalid cursor value for datetime column
# Recognises SQLModel's GUID type for UUID cursor validationPreviously these would either silently fail or surface as opaque database errors.
Bug Fixes
Polymorphic Joined Filtering — closes #305
Joined filters across SQLAlchemy joined-table inheritance no longer crash:
# These models share `entities` as a base table
class ContractPoly(EntityPoly): ... # contracts + entities
class ProjectPoly(EntityPoly): ... # projects + entities (FK: contract_id)
# Before: UndefinedTableError on the auto-aliased FROM clause
# After: works correctly
GET /projects_poly/get_multi?contract.operator_id__eq=10When the primary and joined models share underlying tables, the join target is now auto-aliased with flat=True and the alias is threaded into the ON clause so SQLAlchemy's auto-aliasing stays consistent.
Clearer Error on update(return_columns=<bad>) — closes #316
# Before
await crud.update(db, {"name": "X"}, return_columns=True, id=1)
# → TypeError: 'bool' object is not iterable (from deep in SQLAlchemy)
# After
# → ValueError: return_columns must be a list of column name strings or None.Validation now happens at the top of update() so the function fails fast.
schema_to_select Return-Type Propagation — closes #320
When you pass a more specific schema to schema_to_select, type checkers now infer the right return type:
class LocationRead(BaseModel):
id: int
name: str
class LocationReadDetailed(LocationRead):
extra_info: str
# crud is typed with LocationRead as the class-level SelectSchemaType
result = await crud.get(
db,
schema_to_select=LocationReadDetailed,
return_as_model=True,
id=1,
)
# Type checker now correctly infers: LocationReadDetailed | None
# Before: LocationRead | None (wrong)Done via a method-level TypeVar that resolves fresh at every call site, applied across get, get_multi, get_joined, get_multi_joined, get_multi_by_cursor, create, upsert, and upsert_multi.
Bool-String Filter Coercion
Boolean filter values from query strings are now correctly coerced:
# Before
GET /items/get_multi?active=false # → matched only active=True records 🐛
# After
GET /items/get_multi?active=false # → matches active=False records
GET /items/get_multi?active=0 # → matches active=False records
GET /items/get_multi?active=FALSE # → case-insensitiveChanged
create_dynamic_filters Signature
The internal helper fastcrud.core.create_dynamic_filters now takes a model instead of a column-types dict:
# Before
filters_func = create_dynamic_filters(filter_config, column_types={"name": str, "age": int})
# After
filters_func = create_dynamic_filters(filter_config, MyModel)This is required so the function can walk joined relationships to derive types for dotted filter keys. The function is internal-flavoured but is exported from fastcrud.core — if you were calling it directly, pass the model instead of the dict.
Improved
- Test Infrastructure: A top-level
tests/conftest.pydisables the testcontainers Ryuk reaper to avoid Docker container name collisions on macOS local runs. CI on Linux is unaffected. - Booking Model Fixtures: New booking-related models in the test conftest and docstring snippet markers for mkdocs
--8<--references. - mypy config: removed a stale
[mypy-src.app.*]section that no longer matched any path (silences a startup warning).
Security Updates
All findings are in dev dependencies only — they do not affect FastCRUD's runtime surface — but they're patched as a matter of hygiene.
Dependency Bumps
| Package | From | To | Issue |
|---|---|---|---|
pytest |
7.4.4 | 9.0.3 | Vulnerable tmpdir handling |
pytest-asyncio |
0.23.8 | 1.3.0 | Required for pytest 9 compatibility |
cryptography |
44.0.3 | 46.0.7 | SECT curve subgroup attack + incomplete DNS name constraint enforcement |
urllib3 |
2.6.3 | 2.7.0 | Decompression-bomb safeguard bypass + sensitive headers forwarded across origins on proxied redirects |
idna |
3.11 | 3.16 | CVE-2024-3651 bypass via crafted inputs to idna.encode() |
python-dotenv |
1.2.1 | 1.2.2 | Symlink following in set_key allows arbitrary file overwrite |
requests |
2.32.5 | 2.34.2 | Insecure temp file reuse in extract_zipped_paths() |
The pyproject.toml constraint for pytest is now >=9.0.3,<10 (was >=7.4.4,<8). If you depend on FastCRUD's dev group, this is a major bump — verify any custom pytest fixtures you have follow the pytest 9 rules (e.g. don't apply marks directly to fixtures).
GitHub Actions Hardening
All four workflows (tests.yml, linting.yml, type-check.yml, python-versions.yml) now declare:
permissions:
contents: readThis locks GITHUB_TOKEN to least-privilege scope, addressing CodeQL's actions/missing-workflow-permissions rule. Each workflow only checks out code and runs tests/lint/type-check — no write access needed.
What's Changed
- fix: properly convert string 'false' to bool in filters by @andrej-suty in #311
- feat: Add cursor value validation for cursor-based pagination (#296) by @shreyansh-singh74 in #309
- Models and Schemas, Batch 6 (FINAL): Booking and Associated Models by @slaarti in #308
- Pr 321 by @igorbenav in #322
- Schema to select type recognition fix by @LucasQR in #321
- Pr 315 by @igorbenav in #324
- fix: raise ValueError when update() receives non-list return_columns by @SAY-5 in #323
- Expose types of filter parameters by @rsmeral in #291
- prepare v0.22.0 release by @igorbenav in #325
New Contributors
- @andrej-suty made their first contribution in #311
- @shreyansh-singh74 made their first contribution in #309
- @SAY-5 made their first contribution in #323
- @rsmeral made their first contribution in #291
Full Changelog: v0.21.0...v0.22.0