Skip to content

Changelog

fast-pager follows SemVer; 0.x versions signal an unstable API, and breaking changes bump the minor version and are called out below. See the Roadmap for what's coming next, and the development plan for the execution detail behind each stage.

0.1.3 — Stage 3 checkpoint: arrays of nested models + maps

The compound-type tables of design doc 02 are now complete. Arrays of nested models (list[NestedModel]) get element matching via the elem path segment: every condition sharing an orders__elem__ prefix in one request compiles into a single $elemMatch — same-element semantics (?orders__elem__amount__gte=100&orders__elem__status=refunded finds one order that is both), the loudly-documented opposite of Mongo's independent dotted-path default. elem parameters are full-tier given that subtlety (generated under default_profile="full" or an explicit per-field/per-route operator opt-in); the array field itself keeps the safe-tier shape operators (len__*/empty, reusing the 3a compilation) plus isnull/exists when Optional; the elem hop counts as one max_depth boundary exactly like an embedding; elem paths are never sortable; text_search never applies inside elements. Maps (dict[str, T]) stay unfilterable by default; a Filterable annotation enables has_key (key presence, with the key — request input inserted into a field path — rejected with a 422 when it contains ., $, or null bytes), and Filterable(keys=["region"]) additionally generates typed, eq-only value-at-key parameters (?metadata__region=emea{"metadata.region": "emea"}). Unsupported map shapes (non-str keys, unsupported value types) with a Filterable raise ConfigurationError at registration, as does keys= on a non-map field. Strictly additive: no behavior changes to anything shipped in 0.1.2. See the new arrays-of-nested-models and maps sections of the Operator Reference and the matching tutorial sections. 100% test coverage, mypy --strict clean.

0.1.2 — Stage 3 checkpoint: nested models

Nested Pydantic models (embedded documents) are now filterable by dotted path: introspection recurses into a field whose type is another model, so ?address__city__contains=ams compiles to {"address.city": {"$regex": "ams"}} — and everything shipped for flat fields carries over to nested leaves unchanged (scalar operators, the array family for a nested list[T], Filterable(...), type_profiles, bare-eq sugar, sorting by public name, FilterConfig keys in the dotted-param spelling "address__city"). Recursion is depth-bounded by the new FilterConfig(max_depth=...) knob (default 2 embedded-model levels below the root; deeper fields are silently skipped), which keeps the parameter surface finite and makes self-referential models safe by construction. The embedding field itself exposes no operators — except isnull/exists on an Optional embedding — and a whole subtree opts out with Filterable(ops=ops.NONE) on the embedding field or FilterConfig(exclude=["address"]). Generated-name collisions (a literal address__city field vs. a nested address.city path) raise a ConfigurationError at registration naming both sources. list[NestedModel] and dict stay unfilterable until v0.1.3. Strictly additive: no behavior changes to anything shipped in 0.1.1. See the new nested-models section in the Operator Reference and the nested-models section of the filtering tutorial. 100% test coverage, mypy --strict clean.

0.1.1 — Stage 3 checkpoint: arrays of scalars

list[T] and set[T] fields (any supported scalar element type, including Optional[list[T]]) are now filterable with their own membership and shape operator family: has, has_any, has_all, len__eq and the empty operator in the safe tier, plus the len__ne/len__gt/len__gte/ len__lt/len__lte comparisons in the full tier (they compile to $expr over $size, which can't use an index). The empty operator pins down the classic empty-vs-missing Mongo trap — a missing field matches neither empty=true nor empty=false — and array fields deliberately get no scalar operators (tags__contains doesn't exist) and are not sortable by default (Filterable(sortable=True) opts in). max_list_length guards has_any/has_all exactly as it does in/nin. Strictly additive: no behavior changes to anything shipped in 0.1.0. See the new array tables in the Operator Reference and the arrays section of the filtering tutorial. 100% test coverage, mypy --strict clean.

0.1.0 — first minor release: Stages 1–2 finalized

Stages 1–2 are complete and consolidated: the core filter → sort → paginate pipeline, the MongoDB query compiler, zero-config FilterQuery[Model] / FilterDepends(Model) for scalar fields, per-field Filterable(...) control, per-type profiles, strict mode, and this documentation site. No functional changes from 0.0.3 — this release is the polish milestone: every page on this site now describes exactly the shipped, installable API, with the "not released yet" caveats retired.

0.0.3 — Stage 2 checkpoint: per-field control

The filter surface is now curatable. Annotated[T, Filterable(...)] metadata on the model controls each field: ops=[...] for an exact operator allow-list (with ops.ALL / ops.NONE markers, the latter making a field explicitly unfilterable), source= to point the compiled Mongo query at a different document key, param= to rename the public URL parameter, and sortable= to override sortability per field (including sort-only fields). FilterConfig grows type_profiles={...} for per-type operator overrides and unknown_params="strict" — a standard 422 on unrecognized field__op parameters instead of silently ignoring them. Precedence is a four-layer ladder (route per-field > field Filterable > per-type profile > global profile), every misconfiguration still fails at route registration, and the error messages now name the field, the operator, and the valid alternatives. See the new Controlling the filter surface tutorial. 100% test coverage, mypy --strict clean.

0.0.2 — Stage 1 checkpoint: the core works

The README example is real: zero-config FilterQuery[Model] / FilterDepends(Model) for scalar fields (str, numerics, bool, datetimes, UUID, Enum/Literal, Optional), typed filter/sort/pagination query parameters in OpenAPI, standard 422s on bad input, and a MongoDB compiler producing plain query dicts (no driver dependency). Safety defaults on: safe operator profile, regex gated off, list/filter/limit caps, sortable allow-list. 100% test coverage, mypy --strict clean.

Still to come for v0.1.0: per-field Filterable(...) control and final polish — see the roadmap below.

0.0.1 — placeholder release

The current state of the project on PyPI: the package name is reserved and the repository has CI + fully automated release tooling (scripts/release.sh, tag-triggered PyPI publish via Trusted Publishing), but no library functionality yet. Nothing in this documentation site is installable against 0.0.1 — see Getting Started.

Coming next

The FilterSet class caps Stage 3 in v0.2.0: an allow-list fields mapping per filter surface, multiple filter surfaces per model, and custom declared filters — plus a non-trivial example app (users + addresses + tags + orders) exercised in CI. Tracked in design doc 05 — Roadmap & Release Plan.