Releases: rjprins/fastapi-restly
Release list
v0.7.0
Highlights
IDRefforeign keys now filter on list endpoints —GET /comments/?post_id=1works with the FK pattern the tutorial teaches (eq,__in,__ne,__isnull).- Breaking: the top-level
fr.*namespace is curated — errors moved tofr.exc, object helpers tofr.objects, query helpers tofr.query, engine accessors tofr.db. Migration notes below. fr.db.create_all()/async_create_all()replace the engine boilerplate in quickstarts and tests.- Opt-in misuse warnings (
fr.configure(warn_on_misuse=True)) flag the three common framework-misuse patterns with the idiomatic fix named. - Python 3.14 officially supported.
- Docs overhauled for launch: runnable landing-page teaser, a new Patterns page, merged testing guide with a copy-paste conftest, error-shaping and OpenAPI how-tos, a changelog page, and the canonical domain www.fastapi-restly.org. Every example in the docs is executed against the package before publishing.
Full changelog
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, so
GET /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 whileorjsonlacked 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 emit
fr.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 subclassing
RestView/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 global
warn_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, mirroring
sqlalchemy.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 to
fr.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.
v0.6.1
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 helpers
build_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 installing
fastapi-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(for
RestlyTestClientand 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, or
list[WriteOnly[str]]) is now rejected with aRestlyConfigurationError
instead of silently no-op'ing. Nested there the marker has no effect — a
WriteOnlyfield 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.)
v0.6.0
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 is now three tiers: a route shell (
get_many_endpoint,
get_one_endpoint,create_endpoint,update_endpoint,delete_endpoint)
that owns the wire shape; a request handler (handle_get_many,
handle_get_one,handle_create,handle_update,handle_delete) that runs
authorizeand the commit bracket; and a bare domain verb (get_many,
get_one,create,update,delete) that is auth-free and commit-free —
the usual override point. This replaces thelisting/get/create/update/
deleteendpoints and the singleperform_*tier. - The commit now has a single owner: the framework.
handle_<verb>and
write_actionrunbefore_commit→ commit →after_commitaround your
domain logic, and the request-session dependency (AsyncSessionDep/
SessionDep) no longer commits on response.after_committherefore
always runs after the write is durable. A custom (non-CRUD) write route should
reusehandle_<verb>or bracket its mutation withwith/async with self.write_action(action, ...); only a route doing something the bracket
doesn't model (e.g. a batch commit) reaches forawait self.session.commit()
itself. The
commit_session_on_responseoption is removed: custom session generators
(session_generator/sync_session_generator) construct sessions your way
but never own the commit — there is no opt-out. - 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 single
to_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; raise
fr.Forbidden/fr.NotFoundto gate a verb (row visibility goes in
build_query).before_commit/after_committransaction hooks and asnapshot()helper
for old-vs-new comparison.- Typed request-time exceptions
NotFound,Forbidden,Conflict, and
BadQueryParam, subclassingfastapi.HTTPException(so a single
app.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 (async with self.write_action("publish", obj=...): ...) that runs
the same authorize + commit bracket the CRUD handlers do. Plus the self-free
run_write_action/async_run_write_action(infastapi_restly.views)
underneath, usable off the HTTP path. Create-shaped actions that omitobj=
must assign the yielded handle's.objbefore the block exits, otherwise a
clean exit raises instead of committing with hooks unable to see the new
object.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 finalORDER 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_many
de-duplicates entities and the list total counts distinct rows, so the page and
total_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]]over
Optional[WriteOnly[T]]— aWriteOnlymarker buried only inside a union is
not excluded.
v0.5.1
v0.5.0 — first public beta release
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 canonical 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. perform_getnow issues aSELECT ... WHERE pk = ?instead ofsession.get(...). Behaviour is unchanged for single-column primary keys; subclasses with composite primary keys must overrideperform_getthemselves.- Advanced schema-to-object helpers now live in
fastapi_restly.objects:build_from_schema,apply_schema,save_object, anddelete_object, with async equivalents.
Removed
- Removed the pre-stable
query=argument fromperform_listing. Overridebuild_query()for SQL-level base query changes. - 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.