Skip to content

fix: CRUD write paths, per-viewset body schemas, 409 responses, packaging (v1.5.2) - #13

Merged
svalench merged 3 commits into
masterfrom
fix/crud-write-paths-and-validation
Sep 12, 2026
Merged

fix: CRUD write paths, per-viewset body schemas, 409 responses, packaging (v1.5.2)#13
svalench merged 3 commits into
masterfrom
fix/crud-write-paths-and-validation

Conversation

@svalench

Copy link
Copy Markdown
Owner

Summary

Found by running a test project against the PyPI 1.5.1 build across SQLAlchemy sync/async, Peewee and Tortoise.

Critical fixes

  • Body-schema leak between viewsetsregister() mutated __annotations__ of the shared class-level CRUD functions, so with 2+ viewsets every POST/PUT/PATCH endpoint validated against the last registered schema. Handlers are now cloned per instance before annotation patching. Regression test: two viewsets, one process.
  • PATCH was unusable with required schema fields — body validation rejected partial payloads with 422 before exclude_unset could help. PATCH now validates against an auto-generated all-optional <Schema>Patch model.
  • PUT nulled columns absent from the Pydantic schema (silent data loss) — only payload fields are written now; PK never overwritten.
  • PATCH silently ignored explicit null — JSON null now clears nullable columns.
  • Tortoise POST broken with the documented id: Optional[int] = None schema pattern (id is non nullable field, but null was passed) — adapters strip NULL primary keys on create (also fixes latent PostgreSQL issue).
  • Raw SQL leaked to clients — duplicate unique value returned 400 with full SQL + parameters. Now 409 Conflict with a sanitized message across all three adapters.

Minor fixes

  • limit/offset validated (ge=0, limit<=10000); negative values → 422 (previously limit=-5 returned all rows).
  • ListConfig filters and __op variants advertised in OpenAPI (openapi_extra).
  • Base install is now truly ORM-agnostic: SQLAlchemy/uvicorn removed from core deps; package imports without SQLAlchemy. [sqlalchemy] extra installs SQLAlchemy[asyncio] (includes greenlet — async worked out of the box previously only by accident).

Tests & docs

  • 9 new regression tests (tests/test_write_path_regressions.py); existing PUT/PATCH/integrity tests updated to the new semantics.
  • 308 passed, coverage 88.6% (gate 70%).
  • README / docs updated: install section, PATCH/PUT semantics, pagination defaults, 409 behavior; RELEASE_1.5.2.md added; version bumped to 1.5.2.

Behavior changes (documented in RELEASE_1.5.2.md)

  • Integrity violations: 400 → 409.
  • PATCH explicit null writes NULL.
  • PUT no longer clears fields missing from the payload.
  • pip install fastapi-viewsets no longer pulls SQLAlchemy/uvicorn.

Note: ruff check reports many pre-existing issues repo-wide (1700+); this PR follows the existing code style and does not reformat unrelated code.

svalench and others added 2 commits September 12, 2026 10:03
- register(): clone handlers before patching body annotations — viewset
  schemas no longer leak across instances (broke multi-viewset apps)
- PATCH validated against auto-generated all-optional <Schema>Patch
  model; explicit JSON null clears nullable columns
- PUT writes only payload fields, never nulls columns absent from the
  schema and never touches the primary key
- adapters: strip NULL primary key on create (fixes Tortoise POST and
  PostgreSQL inserts with Optional id schemas)
- integrity violations return 409 with sanitized message; raw SQL no
  longer exposed in response bodies
- list(): Query(ge=0, le=10000) validation for limit/offset
- LIST filters from ListConfig advertised in OpenAPI via openapi_extra
- packaging: SQLAlchemy/uvicorn removed from core deps; sqlalchemy
  extra installs SQLAlchemy[asyncio] (includes greenlet)
- tests: 9 new regression tests; updated PUT/PATCH/409 semantics tests
- docs: README/docs/release notes for 1.5.2
The base install became ORM-agnostic (SQLAlchemy moved to the
'sqlalchemy' extra), but tests/conftest.py imports sqlalchemy
unconditionally. The CI matrix installs '.[test]' and the
test-extras job installs '.[test,tortoise,peewee]' — neither
pulls SQLAlchemy, so every test job died with
ModuleNotFoundError: No module named 'sqlalchemy'.

Add SQLAlchemy[asyncio]>=2.0.0 to the test extra (dev-only;
the published wheel stays ORM-agnostic).
@codecov

codecov Bot commented Sep 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

* Add regression tests for the _clone_bound_method fallback branch
  (handlers that are not plain bound methods, incl. unpatchable ones)
  and for the Query-default fallback in sync/async list() when called
  programmatically.
* The test-extras job (tortoise/peewee adapters installed) now runs
  coverage and uploads it to codecov with the 'extras' flag, so the
  adapter lines changed in this PR count towards patch coverage —
  previously only the 3.11 matrix job (no ORM extras) uploaded.
@svalench
svalench merged commit bdc9045 into master Sep 12, 2026
11 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