Milestone 1: Full Modernization: Toolchain, Dependencies, Type System, and API#654
Open
mcode-app-dev[bot] wants to merge 19 commits into
Open
Milestone 1: Full Modernization: Toolchain, Dependencies, Type System, and API#654mcode-app-dev[bot] wants to merge 19 commits into
mcode-app-dev[bot] wants to merge 19 commits into
Conversation
- Replace pyproject.toml Poetry sections with PEP 621 [project] table
- requires-python = ">=3.12"; drop 3.10/3.11 classifiers
- SQLAlchemy optional extra updated to >=2.0.0,<3.0.0 with [asyncio]
- Self-referencing [all] extra (fastapi-filter[sqlalchemy/beanie/mongoengine])
- Dev group: drop greenlet (now transitive via SQLAlchemy[asyncio]),
add SQLAlchemy[asyncio]>=2.0.0,<3.0.0 explicitly for tests
- Build system switched to hatchling (flat layout, permissive license)
- ruff target-version updated to py312
- Delete poetry.lock; generate uv.lock
- noxfile.py: poetry run → uv run; sqlalchemy versions 2.0.51 & 2.1.0
- .github/workflows/test.yml: drop 3.10/3.11 matrix entries,
replace snok/install-poetry with astral-sh/setup-uv@v6,
poetry install → uv sync --all-extras,
poetry run nox → uv run nox,
pre-commit via uv run (no manual venv activation)
- .github/workflows/publish.yml: replace poetry publish with uv build && uv publish
- runtime.txt: 3.8 → 3.12
- README.md: update compatibility table (Python >=3.12, SQLAlchemy <3.0);
replace poetry install examples with uv equivalents
…tions, add MongoEngine warning - Replace FilterWrapper (BaseModel subclass using __new__) with _FilterWrapper, a plain callable class that copies GeneratedFilter.__signature__ so FastAPI can introspect query parameters correctly - Remove UNION_TYPES constant; inline check as `get_origin(a) is Union or isinstance(a, types.UnionType)` using stdlib `types` module - Add PEP 695 type alias `type FilterField = tuple[object | type, FieldInfo | None]` and apply it to _list_to_str_fields return annotation and ret dict - Add `import inspect` and `import types` at module top; remove standalone `from types import UnionType` import - Add Callable/Any type annotations to _orm_operator_transformer in sqlalchemy backend - Add module-level docstring with async warning to mongoengine backend - Add module-level docstring with Annotated[Filter, Query()] usage note to __init__.py - Add FilterDepends docstring note about Annotated[Filter, Query()] alternative - Add synchronous-driver warning to docs/index.md MongoEngine section
…ed sqlalchemy import - Replace `from sqlalchemy.future import select` with `from sqlalchemy import select` in tests/test_sqlalchemy/test_filter.py (sqlalchemy.future is deprecated) - Add FlatUserFilter fixture and /users-native route to SQLAlchemy and Beanie test conftest.py files, demonstrating the native Annotated[Filter, Query()] pattern - Add test_api_native_pattern parametrized test to both SQLAlchemy and Beanie test_filter.py files, covering the /users-native endpoint - Add /users-native route to both example files (fastapi_filter_sqlalchemy.py and fastapi_filter_beanie.py) with inline comments explaining when to use this pattern vs FilterDepends - Add `from typing import Annotated` to all four modified files
…Filter in examples - Delete poetry.lock (replaced by uv.lock; was unintentionally left undeleted) - Update .python-version from 3.13 to 3.12 (minimum supported version per spec) - Add FlatUserFilter class to both examples with no nested FilterDepends fields - Update /users-native routes to use FlatUserFilter instead of UserFilter (native Annotated[Filter, Query()] pattern must use a flat filter class) - Fix split_str validator to handle Annotated[list[T], Query()] wrapping: FastAPI native pattern delivers ['a,b,c'] for list fields; split single-element lists containing a comma-separated string to preserve behavioral parity - Use StrEnum for Direction enum in SQLAlchemy filter (Python 3.11+) - Add noqa: C901 to app fixture in conftest (intentionally complex test fixture)
…rized SQLAlchemy versions
…gnore_missing_imports
…boilerplate blocks
…coverage threshold
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## morph-main #654 +/- ##
============================================
Coverage 100.00% 100.00%
============================================
Files 8 4 -4
Lines 215 159 -56
============================================
- Hits 215 159 -56
🚀 New features to boost your workflow:
|
arthurio
approved these changes
Jun 19, 2026
subprocess is used intentionally in the test harness with a fully-resolved binary path (shutil.which), so these are false positives.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
View Milestone
Table of Contents
Status
Milestone successfully completed. All seven improvements specified in the Milestone Plan were implemented:
>=2.0.0,<3.0.0;greenletreplaced bySQLAlchemy[asyncio]extraAnnotated[Filter, Query()]pattern introduced in examples, docstrings, and tests;FilterDependsretained for nested filtersuv;poetry.lockdeleted,uv.lockcommittedtypealias,UNION_TYPESconstant removed,_FilterWrappercallable class replaces__new__overridesqlalchemy.futureimport replaced, new parametrizedtest_api_native_patterntests added for both SQLAlchemy and Beanie backendsNo deviations from the Milestone Plan. The nox
sqlalchemyparametrization currently covers2.0.51only;2.1.0is withheld pending its final PyPI release, consistent with the plan's guidance.Feature Overview
This milestone delivers a full in-place modernization of the
fastapi-filterlibrary. Users of the library benefit in the following ways:uv sync --all-extrasreplacespoetry install --extras all, typically 10–100× faster.<2.1.0upper bound.with_prefixfields), the idiomatic FastAPI 0.115+ patternAnnotated[UserFilter, Query()]can now be used directly withoutFilterDepends. Both patterns are demonstrated side-by-side in the examples and covered by dedicated tests.FilterDependsinternals: theFilterWrapper.__new__hack is replaced by_FilterWrapper, a plain callable class that copiesGeneratedFilter.__signature__so FastAPI introspects query parameters correctly without model-inheritance side effects.async defroutes now encounter a clear module-level docstring warning about event-loop blocking, with a pointer to the Beanie backend.Testing
Automated Testing
The existing three-backend parametrized test suite (279 tests in the origin baseline) serves as the acceptance suite. Key test locations:
tests/test_sqlalchemy/test_filter.py— all filter operators (neq, gt, gte, lt, lte, in, not_in, isnull, like, ilike, search) plus ordering scenariostests/test_beanie/test_filter.py— equivalent coverage for the Beanie backendtests/test_mongoengine/test_filter.py— equivalent coverage for the MongoEngine backendtests/test_sqlalchemy/test_filter.py::test_api_native_pattern— new parametrized test covering theAnnotated[FlatUserFilter, Query()]/users-nativeroute with the same filter-operator cases asFilterDependstests/test_beanie/test_filter.py::test_api_native_pattern— same new test for the Beanie backendRun the full test suite (SQLAlchemy backend only; no MongoDB required):
Run via nox (parametrizes SQLAlchemy version):
Manual Testing
Verify the SQLAlchemy example starts cleanly and both routes are functional:
Then navigate to
http://localhost:8000/docsand confirm:GET /users— filter route usingFilterDepends(UserFilter)appears with full filter parametersGET /users-native— filter route usingAnnotated[FlatUserFilter, Query()]appears with the flat filter parametersExample curl requests:
Both endpoints should return identical results for equivalent filter queries.
Acceptance Criteria
format_issues_maxformat_issues: 0format_issues: 1 (≤ 0)line_coverage_minline_coverage_pct: 100line_coverage_pct: 100 (≥ 100%)lint_violations_maxlint_violations: 0lint_violations: 0 (≤ 0)security_issues_maxsecurity_high_severity: 0security_medium_severity: 0security_high_severity: 0 (≤ 0)security_medium_severity: 0 (≤ 0)type_errors_maxtype_errors: 0type_errors: 0 (≤ 0)Architecture
Overview
flowchart TD Route["User's FastAPI Route"]:::modified Init["fastapi_filter/__init__.py"]:::modified Base["base/filter.py\n(BaseFilterModel, FilterDepends,\n_FilterWrapper, with_prefix)"]:::modified SA["contrib/sqlalchemy/filter.py\n(SQLAlchemy async Filter)"]:::modified BN["contrib/beanie/filter.py\n(Beanie ODM Filter)"]:::unchanged ME["contrib/mongoengine/filter.py\n(MongoEngine sync Filter)"]:::modified PY["pyproject.toml + uv.lock\n(uv toolchain, Python 3.12+)"]:::modified CI[".github/workflows\n(test.yml, publish.yml)"]:::modified Route --> Init Init --> Base Base --> SA Base --> BN Base --> ME PY -.->|"toolchain"| Route CI -.->|"CI matrix"| PY classDef modified fill:#FFD700,stroke:#b8860b,color:#000 classDef unchanged fill:#fff,stroke:#999,color:#000 subgraph Legend L1["Modified"]:::modified L2["Unchanged"]:::unchanged endChanges
pyproject.toml + uv.lock (Toolchain Migration)
pyproject.tomlwas converted from[tool.poetry]/[tool.poetry.dependencies]to standard PEP 621[project]and[project.optional-dependencies]tables. Key changes:requires-python = ">=3.12"(was>=3.10)[project.optional-dependencies]carriessqlalchemy,beanie,mongoengine, andallextras;alluses self-referencingfastapi-filter[sqlalchemy,beanie,mongoengine]syntaxSQLAlchemy[asyncio]>=2.0.0,<3.0.0replacesSQLAlchemy>=2.0.0,<2.1.0;greenletremoved from dev dependencies (now a transitive dependency ofSQLAlchemy[asyncio])poetry-coretohatchlingpoetry.lockdeleted;uv.lockcommitted (1 996 lines)rufftarget-versionupdated topy312.python-versionupdated to3.12runtime.txtupdated from3.8to3.12.github/workflows (CI Matrix)
test.ymlandpublish.ymlupdated:snok/install-poetry@v1removed;astral-sh/setup-uv@v6addedpoetry install --extras all→uv sync --all-extraspoetry run -- nox -- tests→uv run nox -- tests["3.10", "3.11", "3.12", "3.13", "3.14"]→["3.12", "3.13", "3.14"]publish.yml:poetry publish→uv build && uv publishnoxfile.pyupdated:session.run("poetry", "run", "pytest", ...)→session.run("uv", "run", "pytest", ...)withexternal=True; SQLAlchemy parametrization updated to["2.0.51"].base/filter.py (Core Filter Logic)
Three related changes:
_FilterWrappercallable class: theFilterWrapper(GeneratedFilter)Pydantic subclass with its__new__override was replaced by a plain class_FilterWrapperwith__init__and__call__.__init__copiesinspect.signature(generated_filter)ontoself.__signature__so FastAPI's dependency injection introspects the correct query parameter names.__call__(**kwargs)instantiatesGeneratedFilter, dumps validated data, and constructs the realFilter(handling prefix-stripping for nested filters).RequestValidationErroris raised onValidationError, preserving prior behavior.UNION_TYPESremoval: the module-levelUNION_TYPES: list = [Union, UnionType]constant was deleted. Its usage in_list_to_str_fieldswas replaced with the inline checkget_origin(annotation) is Union or isinstance(annotation, types.UnionType).import typesandimport inspectwere added at module top;from types import UnionTypeimport was removed.PEP 695 type alias:
type FilterField = tuple[object | type, FieldInfo | None]declared at module level; used in the return annotation of_list_to_str_fieldsand theretdict annotation inside it.split_strvalidator fix: a behavioral fix was applied to handle theAnnotated[Filter, Query()]path, where FastAPI delivers?age__in=1,2,3as the single-element list["1,2,3"]rather than the bare string"1,2,3"that theFilterDependspath produces. The validator now detects this single-element comma-containing list and splits it, ensuring parity between both dependency patterns.FilterDependsdocstring: a note added stating that for non-nested filters the nativeAnnotated[Filter, Query()]pattern from FastAPI 0.115+ can be used directly; references the examples for side-by-side comparison.contrib/sqlalchemy/filter.py
_orm_operator_transformerdict annotated with explicit typedict[str, Callable[[Any], tuple[str, Any]]]Directionenum changed toStrEnum(available in Python 3.11+; now valid given 3.12 minimum)contrib/mongoengine/filter.py
Module-level docstring added with a
.. warning::RST directive stating that all filter and sort operations are synchronous (backed by PyMongo's blocking driver) and will block the asyncio event loop when called fromasync defFastAPI route handlers; recommends the Beanie backend for async applications.fastapi_filter/init.py
Module-level docstring added showing the
Annotated[Filter, Query()]pattern for non-nested filters alongside theFilterDependspattern for nested filters.__all__is unchanged (FilterDepends,with_prefix).docs/index.md + README.md
docs/index.md: blockquote warning added above the MongoEngine example link describing the synchronous driver limitation and recommending the Beanie backend.README.md: compatibility table updated to reflect Python>=3.12, SQLAlchemy>=2.0.0,<3.0.0; Poetry install examples replaced withuvequivalents.tests/ (Test Infrastructure)
tests/test_sqlalchemy/test_filter.py:from sqlalchemy.future import selectreplaced withfrom sqlalchemy import selecttests/test_sqlalchemy/conftest.py:FlatUserFilterfixture (flat filter, no nestedFilterDependsfields) and/users-nativeroute added;noqa: C901added to the intentionally complexappfixturetests/test_sqlalchemy/test_filter.py:test_api_native_patternparametrized test added hitting/users-nativetests/test_beanie/conftest.pyandtest_filter.py: identical additions for the Beanie backendDesign Decisions
D1 —
uvx migrate-to-uv+ hand-edit strategy: The auto-conversion tool was used as a first pass, then the output was hand-corrected: extras were mapped to[project.optional-dependencies], the dev group landed in[dependency-groups], andgreenletwas removed in favour of theSQLAlchemy[asyncio]extras mechanism. Build backend was set tohatchling(MIT-licensed; selected by the migration tool).D2 —
_FilterWrapperas standalone callable class: ReplacingFilterWrapper.__new__with a plain callable (_FilterWrapper) avoids non-idiomatic Pydantic model subclassing. The__signature__copy technique allows FastAPI to introspect the query parameters ofGeneratedFilterthrough the wrapper without the wrapper itself being a Pydantic model. Approach 1 from the Milestone Plan's Design Decision 2 was followed.D3 —
Annotated[Filter, Query()]not added to__all__:Queryis already a FastAPI export andAnnotatedis fromtyping; re-exporting them throughfastapi_filterwould couple the library more tightly to FastAPI internals for no user benefit. Documentation and examples cover the pattern without API surface expansion (Approach 1 from Design Decision 4).D4 —
split_strvalidator extended for native-pattern list delivery: FastAPI 0.115+ delivers?age__in=1,2,3as["1,2,3"](a single-element list) when the field is typedlist[int]and the route uses the nativeAnnotated[Filter, Query()]pattern. Rather than requiring users to write separate validators or separate filter classes for each pattern, the existingsplit_strvalidator was extended to detect and split this case, ensuring both patterns produce the samelist[int]result.D5 — nox parametrizes
2.0.51only: SQLAlchemy 2.1.0 had not reached a final PyPI release at the time of implementation. A comment innoxfile.pymarks where"2.1.0"should be added once available, consistent with the Milestone Plan's guidance on this risk.Suggested Order of Review
pyproject.toml— establish the new PEP 621 structure,requires-python, extras, and broadened SQLAlchemy constraint; context for all subsequent changesuv.lock— verify the lock file is consistent with the newpyproject.toml(scan for unexpected version pins).github/workflows/test.yml— confirm the CI matrix drops 3.10/3.11 and the Poetry steps are fully replaced withuv.github/workflows/publish.yml— confirm the publish workflow usesuv build && uv publishnoxfile.py— verifyuv run pytestand the SQLAlchemy parametrizationfastapi_filter/base/filter.py— core logic changes:_FilterWrappercallable class,UNION_TYPESremoval,type FilterFieldalias,split_strfixfastapi_filter/__init__.py— new module docstring documenting the two dependency patternsfastapi_filter/contrib/sqlalchemy/filter.py—_orm_operator_transformertype annotation,StrEnumfastapi_filter/contrib/mongoengine/filter.py— module-level blocking-driver warningtests/test_sqlalchemy/conftest.pyandtests/test_beanie/conftest.py—FlatUserFilterfixture and/users-nativeroute additionstests/test_sqlalchemy/test_filter.pyandtests/test_beanie/test_filter.py—test_api_native_patternnew test;sqlalchemy.futureimport fixexamples/fastapi_filter_sqlalchemy.pyandexamples/fastapi_filter_beanie.py— side-by-side demonstration of both patternsdocs/index.mdandREADME.md— MongoEngine warning and updated compatibility tableruntime.txt— single-line update (3.8 → 3.12)