feat: Pydantic v2 + async/internal cleanup (v1.2.0)#3
Merged
Conversation
Pydantic v2 first - CRUD handlers use model_dump(exclude_unset=...) instead of dict(item). - Fixes PATCH semantics: unset fields are no longer overwritten with Pydantic defaults — only the fields the client actually set are written to the ORM. - New helper fastapi_viewsets._compat.model_to_dict transparently supports both Pydantic v1 (.dict) and v2 (.model_dump). - tests/conftest.py migrated to model_config = ConfigDict(from_attributes=True). - pydantic>=2.5,<3 is now a runtime dependency. Async / configuration cleanup - db_conf.py resolves SQLAlchemy globals lazily via module-level __getattr__. Importing fastapi_viewsets no longer creates engines or imports SQLAlchemy when another ORM is selected, and no longer fails when an async driver such as aiosqlite is missing. - Single helper to_async_database_url replaces three duplicated conversions in db_conf, factory and the SQLAlchemy adapter. - Removed the duplicate _default_adapter singleton from db_conf; ORMFactory.get_default_adapter() is now the only source of truth. Added ORMFactory.reset_default_adapter() for tests/hot reloads. Internal refactor - BaseViewset.register and AsyncBaseViewset.register were two near- identical 90-line methods. They now share a single _RegisterMixin.register implementation. - Replaced bare 'except: pass' guards around annotation patching with narrow (AttributeError, TypeError) handlers. - Renamed butle to _noop_dependency; butle is kept as a backward- compatible alias. - Removed unused imports. Packaging - Added PEP 621 pyproject.toml; setup.py is now a one-line shim. - python_requires>=3.9 (was >=3.6 while CI tested 3.9–3.13). - fastapi>=0.110 to align with Pydantic v2 support. - Pre-configured [tool.ruff], [tool.black], [tool.mypy] and a 'lint' optional-dependencies extra. Tests - 237 passed locally (no regressions vs. baseline). Coverage 83% (gate 70%). - The 8 pre-existing Tortoise/Peewee failures are caused by upstream library changes and are not introduced by this refactor.
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.
Summary
This PR modernizes
fastapi_viewsetsfor Pydantic v2 and removesseveral long-standing rough edges around async support, configuration
loading and code duplication. There are no breaking changes for
applications that already pass Pydantic v2 schemas.
Pydantic v2 first
model_dump(exclude_unset=...)instead of the legacydict(item)call.
model_dump(exclude_unset=True)only returns fields the clientactually sent.
BaseViewset.update_elementandAsyncBaseViewset.update_elementuse this forpartial=True, sounset fields are no longer accidentally overwritten with Pydantic
defaults. This was a real correctness bug in 1.1.0.
fastapi_viewsets._compat.model_to_dicttransparently supports both Pydantic v1 (
.dict) and v2(
.model_dump) models.tests/conftest.pymigrated tomodel_config = ConfigDict(from_attributes=True).pydantic>=2.5,<3is now a hard runtime dependency.Async / configuration cleanup
db_conf.pyresolves SQLAlchemy globals lazily via module-level__getattr__. Importingfastapi_viewsetsno longer creates enginesor imports SQLAlchemy when another ORM is selected, and no longer
fails when an async driver such as
aiosqliteis missing.sqlite:///→sqlite+aiosqlite:///, etc.) lives in a single helper,to_async_database_url. Previously it was duplicated acrossdb_conf,factoryand the SQLAlchemy adapter._default_adaptersingleton fromdb_conf;ORMFactory.get_default_adapter()is now the only source of truth.ORMFactory.reset_default_adapter()was added for tests and hotreloads.
Internal refactor
BaseViewset.registerandAsyncBaseViewset.registerwere twonear-identical ~90-line methods. They are now a single
_RegisterMixin.registershared by both classes.except: passblocks around annotation patching with anarrow
(AttributeError, TypeError)guard.butleto_noop_dependency;butleis kept as a backward-compatible alias.os, duplicateAny).Packaging
pyproject.toml(withsetuptoolsbuild backend).setup.pyis now a one-line shim.python_requiresto>=3.9to match the CI test matrixand Pydantic v2 requirements.
[project.optional-dependencies] lint(ruff + black + mypy)and pre-configured
[tool.ruff],[tool.black],[tool.mypy].>=0.110to align with Pydantic v2 support.Tests
vs. baseline. The 8 remaining failures are pre-existing
Tortoise/Peewee issues caused by upstream library changes
(e.g.
tortoise.exceptions.ConfigurationError: AppConfig.models must be a non-empty list of strings) and are not introduced by this PR.Backward compatibility
butledependency name still works.model_to_dictshim.BaseViewset/AsyncBaseViewsetpublic APIs are unchanged.Files
fastapi_viewsets/_compat.py,fastapi_viewsets/_register.py,pyproject.toml,RELEASE_1.2.0.md.fastapi_viewsets/__init__.py,fastapi_viewsets/async_base.py,fastapi_viewsets/db_conf.py,fastapi_viewsets/orm/factory.py,fastapi_viewsets/orm/sqlalchemy_adapter.py,setup.py,tests/conftest.py,README.md,RELEASE_NOTES.md.