Changelog#
FastAPI-Restly follows Semantic Versioning. While the
version is below 1.0.0, breaking changes may land in minor releases
(0.x → 0.y) and are always listed below under the release that ships them;
patch releases are fixes only. To opt into fixes without surprises, pin to a
minor version — for example fastapi-restly~={{ release }}.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased#
0.7.0 - 2026-06-11#
Added#
The generated route shells (
get_many_endpoint,create_endpoint, …) now carry one-line override-redirect docstrings, sohelp(RestView), source readers, and coding agents see which tier to override (<verb>for domain logic,handle_<verb>for orchestration,to_responsefor shape). The docstrings are stripped from generated routes at registration so framework guidance never appears as OpenAPI operation descriptions in your API; endpoints you define or override yourself keep FastAPI’s normal docstring behavior.Scalar
fr.IDRef[T]foreign-key fields are now filterable on list endpoints by their own public name. Previouslypost_id: fr.IDRef[Post]— the FK form the tutorial teaches — generated no filter parameter at all, soGET /comments/?post_id=1returned a 422 that looked like client error; the only filterable form was a plainpost_id: int. AnIDRefid is treated as opaque, so it gets equality,__in,__ne, and__isnull(uniform across int/UUID/string primary keys) but not the range or substring operator families.Python 3.14 is now officially supported and tested. It was previously in the CI matrix as an experimental (allowed-to-fail) target while
orjsonlacked a 3.14 wheel; that wheel now ships, the full test suite passes on 3.14, and the job gates CI like every other supported version.fr.db.create_all(Base)/fr.db.async_create_all(Base)— dev/demo helpers that create every table for a declarative base (or aMetaData) on the engine configured viafr.configure(), replacing theengine = fr.db.get_async_engine(); async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)boilerplate in quickstarts and test setup. Use Alembic migrations in production.Opt-in registration-time misuse warnings:
fr.configure(warn_on_misuse=True)makesinclude_viewlint each registered view class and emitfr.exc.RestlyMisuseWarningfor the three dominant misuse patterns — overriding a route shell (<verb>_endpoint) where a business-verb override was meant, callingsession.commit()directly in a view method, and hand-rolling a CRUD route set on a bareViewinstead of subclassingRestView/AsyncRestView. Each message names the idiomatic fix. Off by default; intended for development, project templates, and CI.
Changed#
The
RestlyUncommittedChangesWarningmessage now leads with the fix (bracket the mutation withwrite_action(...)or reuse ahandle_<verb>) and offers only the per-route suppression (session.info["_fr_suppress_uncommitted"] = True) for intentional dry runs. It no longer advertises the globalwarn_on_uncommitted=Falseopt-out, which readers took as a fix for the warning instead of committing their changes.Breaking — top-level
fr.*namespace curated. Errors, HTTP exceptions, and the uncommitted-changes warning moved tofr.exc(fr.exc.NotFound,fr.exc.RestlyError, …) — theexceptionsmodule is renamedexc, mirroringsqlalchemy.exc. Advanced helpers moved to their layer submodules: schema↔ORM helpers tofr.objects.*(make_new_object/save_object/snapshot/ … and theasync_*variants), list query helpers tofr.query.*(create_list_params_schema,apply_list_params), engine accessors tofr.db.*(get_engine,get_async_engine), andIDMixintofr.models.*. The route decorators (@fr.get/@fr.post/ … /@fr.route), views, registration, schemas, model bases,configure, the session helpers/dependencies, and the view support types (Action/ViewRoute/ResponseShape/ListingResult) stay top-level. Migration: e.g.fr.NotFound→fr.exc.NotFound,fr.make_new_object→fr.objects.make_new_object,fr.get_async_engine→fr.db.get_async_engine.
Fixed#
fr.open_session()/fr.open_async_session()now resolve the same session source asSessionDep/AsyncSessionDep: a custom session generator passed tofr.configure(session_generator=...)/sync_session_generator=...takes precedence over the built-in factory. Previously the context managers always used the built-in factory, so a generator-only configuration worked inside request handlers but raisedRestlyConfigurationErroroff-HTTP (in scripts, background jobs, or a custom dependency wrappingopen_*session()). The two session entry points are now consistent.
0.6.1 - 2026-06-02#
Changed#
Reference resolution (
IDRef/IDSchema) no longer mutates the validated request model in place. The resolver now returns a{field: resolved}mapping that the write path consumes, so the request model keeps its wire shape (its reference fields stayIDRef[T]values rather than being overwritten with ORM rows). Behavior of create/update is unchanged; the internal helpersbuild_create_plan/apply_update_to_object/validate_resolved_reference_consistencygained aresolvedargument.The
standardextra is now runtime-only and mirrorsfastapi[standard]. It no longer pulls the test toolchain (pytest,pytest-asyncio,pytest-cov,httpx) or bundles theaiosqlitedriver, so installingfastapi-restly[standard]for production no longer drags pytest into the image. Test tooling stays in thetestingextra; the database driver is now an explicit choice (Restly remains driver-agnostic). Migration: if you relied on[standard]for test dependencies, switch to[testing]; if you ran on SQLite, addaiosqliteto your dependencies directly.The
testingextra now includes only the third-party packages Restly’s shipped test helpers import —pytest,pytest-asyncio, andhttpx(forRestlyTestClientand therestly_*fixtures).pytest-covis no longer pulled in; add it yourself if you want coverage reports.Removed the
docsextra from the published extras. It only installed the toolchain for building Restly’s own documentation site (a maintainer concern); those dependencies remain in thedevdependency group. The published extras are nowstandardandtesting.
Fixed#
RestlyUncommittedChangesWarningno longer false-positives on every write run under the savepoint test fixtures: the patchedcommitclears the pending-changes flag (mimicking the realafter_commit), while a genuinely forgotten commit still warns.An
IDReflist field that references the same id more than once no longer raises a confusingId not found: set()404 when the referenced rows all exist; a genuinely missing id is now named in the error.An
IDReflist field now resolves in the client-sent order instead of silently reordering to the database’s primary-key order (duplicate ids are collapsed, first occurrence wins).A
ReadOnlyorWriteOnlymarker nested inside a field’s type instead of wrapping it (such asOptional[WriteOnly[str]],WriteOnly[str] | None, orlist[WriteOnly[str]]) is now rejected with aRestlyConfigurationErrorinstead of silently no-op’ing. Nested there the marker has no effect — aWriteOnlyfield would leak into responses and aReadOnlyfield would stay writable — so the framework now raises when the schema is defined (and again at view registration for schemas that do not derive fromBaseSchema), pointing to the safeMarker[Optional[T]]form.A list view no longer advertises filter query parameters for fields that are not filterable columns — a to-many relationship (
books: list[BookRef]) or a reference field that does not resolve to a column. These appeared in OpenAPI but always returned 400. Filter-param generation now validates each field against the model with the same column-resolution predicate the request path uses, so non-column fields no longer get filter params; to-one dotted traversal is unchanged. (create_list_params_schemanow takes the queriedmodelas a required argument.)
0.6.0 - 2026-06-01#
Reworks the class-based view API around a three-tier “handle” design. This is a breaking change; views written for 0.5.x need updating.
Changed (breaking)#
Each CRUD verb now has three tiers: route shell (
*_endpoint), request handler (handle_*), and domain verb (get_many,get_one,create,update,delete). This replaces thelisting/get/create/update/deleteendpoints and the singleperform_*tier.The framework owns commits.
handle_<verb>andwrite_actionrunbefore_commit→ commit →after_commit; request-session dependencies no longer commit on response. Custom write routes should reusehandle_<verb>or bracket mutations withself.write_action(...). Manualsession.commit()is only for shapes the bracket does not model, such as batch commits.commit_session_on_responseis removed; custom session generators construct and clean up sessions but do not own commits.Renamed:
creation_schema/update_schema→schema_create/schema_update;build_from_schema/apply_schema→make_new_object/update_object;count_listing→count. Response shaping goes through a singleto_response(obj_or_list, shape=ResponseShape.SINGLE)method.ViewRoute.LIST/GET→ViewRoute.GET_MANY/GET_ONE.
Added#
authorize(action, obj, data)override — an empty override by default; raisefr.Forbidden/fr.NotFoundto gate a verb (row visibility goes inbuild_query).before_commit/after_committransaction hooks and asnapshot()helper for old-vs-new comparison.Typed request-time exceptions
NotFound,Forbidden,Conflict, andBadQueryParam, subclassingfastapi.HTTPException(so a singleapp.add_exception_handler(fr.NotFound, ...)can reshape them).Top-level
make_new_object/update_object/save_object/delete_object/snapshothelpers (and theirasync_*variants where applicable) for use outside a view.write_action(action, *, obj, data)— a context manager for custom write actions. It shares the CRUD authorize + commit bracket. Create-shaped actions that omitobj=must assign the yielded handle’s.objbefore exit.RestlyUncommittedChangesWarning(default on;warn_on_uncommitted=Falseto disable) when a request finishes with uncommitted changes — the tell of a write route that forgot to commit.
Fixed#
A
@routemethod named like a bare verb (create/update/delete/get_one/get_many) is now rejected at registration: it shadowed the verb and collided with its*_endpointroute shell.Registering a View subclass alongside its parent on the same app no longer duplicates the child’s routes.
React Admin list/count/update paths now use the same
build_query,count, authorization, and commit lifecycle as standard REST views; filter/sort resolution is limited to public schema fields.A paginated list sorted on a non-unique column now appends the primary key as a final
ORDER BYtiebreaker, so rows are no longer skipped or repeated across pages. Applies to both the standard and React Admin sort paths.A
build_querythat joins a to-many relationship no longer fans out:get_manyde-duplicates entities and the list total counts distinct rows, so the page andtotal_countagree. A no-op for queries without such a join.A
WriteOnlyfield no longer leaks into a response, including from a nested response schema.WriteOnlyfields are now excluded from serialization at the field level (exclude=Trueon the marker), so they are stripped recursively on the wire and dropped from the OpenAPI response schema, while staying required, documented request inputs. PreferWriteOnly[Optional[T]]overOptional[WriteOnly[T]]— aWriteOnlymarker buried only inside a union is not excluded.
0.5.1 - 2026-05-11#
Fixed#
Fixed
fr.include_view(...)registration onfastapi.APIRouterparents.
0.5.0 - 2026-05-06#
First public beta release.
Added#
Class-based CRUD views for async and sync SQLAlchemy sessions with generated list, get, create, update, and delete routes.
React Admin compatible
AsyncReactAdminViewandReactAdminViewvariants for thera-data-simple-restwire contract.Generated schema support for read, create, and update payloads, including
ReadOnly,WriteOnly,IDSchema,IDRef, and timestamp schema helpers.Standard list query support for filtering, sorting, pagination, relation aliases, and pagination metadata.
Public
RestlyErrorandRestlyConfigurationErrorexception hierarchy.Testing utilities through
RestlyTestClient, savepoint-only mode helpers, and thefastapi_restly.pytest_fixturespytest plugin.
Changed#
Consolidated framework setup on
fr.configure(...), including async/sync engine configuration and response-session commit policy.Renamed built-in route methods to resource-oriented names:
list,get,create,update, anddelete.Renamed business-logic hooks to
perform_list,perform_get,perform_create,perform_update, andperform_delete.Standardized schema component names on
ModelRead,ModelCreate, andModelUpdate.Made
sortthe standard list ordering parameter.Split
__containsand__icontainsso case-sensitive and case-insensitive matching have distinct public operators.Exposed savepoint-only testing helpers through
fastapi_restly.testinginstead of the top-level package namespace.Renamed
build_listing_querytobuild_queryand broadened its role:perform_getnow also routes through this hook, so a single override filters listing, the pagination total, and single-row fetches. A row hidden from listing returns 404 fromGET /{id}too, andperform_update/perform_deleteinherit the visibility check viaperform_get.perform_getnow issuesSELECT ... WHERE pk = ?instead ofsession.get(...). Single-column primary-key behavior is unchanged. Composite-primary-key subclasses must overrideperform_get.Advanced schema-to-object helpers now live in
fastapi_restly.objects:build_from_schema,apply_schema,save_object, anddelete_object, with async equivalents. The view methods use the same names for the mapping hooks and keep persistence at thesave_object/delete_objectboundary.
Removed#
Removed the pre-stable
query=argument fromperform_listing. Overridebuild_query()for SQL-level base query changes so listing, pagination totals, and single-row fetches stay aligned.Removed pre-release route and hook names such as
index,get,post,patch,delete,handle_list, andhandle_getfrom the public view API.Removed unsupported
get_one_or_createhelpers before the first stable release.Removed internal model helpers such as
TableNameMixin,underscore, andutc_nowfrom the public API surface.Removed duplicate pytest fixture exports from
fastapi_restly.testing; the pytest plugin path isfastapi_restly.pytest_fixtures.