Skip to content

feat: Pydantic v2 + async/internal cleanup (v1.2.0)#3

Merged
svalench merged 1 commit into
masterfrom
feat/pydantic-v2-async-cleanup
Apr 25, 2026
Merged

feat: Pydantic v2 + async/internal cleanup (v1.2.0)#3
svalench merged 1 commit into
masterfrom
feat/pydantic-v2-async-cleanup

Conversation

@svalench
Copy link
Copy Markdown
Owner

Summary

This PR modernizes fastapi_viewsets for Pydantic v2 and removes
several 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

  • CRUD handlers now serialize request bodies via
    model_dump(exclude_unset=...) instead of the legacy dict(item)
    call.
  • PATCH semantics fixed. With Pydantic v2,
    model_dump(exclude_unset=True) only returns fields the client
    actually sent. BaseViewset.update_element and
    AsyncBaseViewset.update_element use this for partial=True, so
    unset fields are no longer accidentally overwritten with Pydantic
    defaults. This was a real correctness bug in 1.1.0.
  • New compatibility helper fastapi_viewsets._compat.model_to_dict
    transparently supports both Pydantic v1 (.dict) and v2
    (.model_dump) models.
  • tests/conftest.py migrated to
    model_config = ConfigDict(from_attributes=True).
  • pydantic>=2.5,<3 is now a hard 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.
  • The sync→async URL conversion (sqlite:///
    sqlite+aiosqlite:///, etc.) lives in a single helper,
    to_async_database_url. Previously it was duplicated across
    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.
    ORMFactory.reset_default_adapter() was added for tests and hot
    reloads.

Internal refactor

  • BaseViewset.register and AsyncBaseViewset.register were two
    near-identical ~90-line methods. They are now a single
    _RegisterMixin.register shared by both classes.
  • Replaced bare except: pass blocks around annotation patching with a
    narrow (AttributeError, TypeError) guard.
  • Renamed the no-op auth dependency from butle to
    _noop_dependency; butle is kept as a backward-compatible alias.
  • Trimmed unused imports (os, duplicate Any).

Packaging

  • Added a PEP 621 pyproject.toml (with setuptools build backend).
    setup.py is now a one-line shim.
  • Bumped python_requires to >=3.9 to match the CI test matrix
    and Pydantic v2 requirements.
  • Added [project.optional-dependencies] lint (ruff + black + mypy)
    and pre-configured [tool.ruff], [tool.black], [tool.mypy].
  • FastAPI minimum bumped to >=0.110 to align with Pydantic v2 support.

Tests

  • 237 / 245 tests pass locally on CPython 3.12 — no regressions
    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.
  • Coverage: 83% (above the 70% gate).

Backward compatibility

  • The butle dependency name still works.
  • Pydantic v1 schemas keep working via the model_to_dict shim.
  • BaseViewset / AsyncBaseViewset public APIs are unchanged.

Files

  • New: fastapi_viewsets/_compat.py,
    fastapi_viewsets/_register.py, pyproject.toml,
    RELEASE_1.2.0.md.
  • Modified: 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.

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.
@svalench svalench merged commit 55653a0 into master Apr 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant