Skip to content

Releases: rjprins/fastapi-restly

v0.7.0

Choose a tag to compare

@rjprins rjprins released this 11 Jun 19:56

Highlights

  • IDRef foreign keys now filter on list endpointsGET /comments/?post_id=1 works with the FK pattern the tutorial teaches (eq, __in, __ne, __isnull).
  • Breaking: the top-level fr.* namespace is curated — errors moved to fr.exc, object helpers to fr.objects, query helpers to fr.query, engine accessors to fr.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, so help(RestView), source
    readers, and coding agents see which tier to override (<verb> for domain
    logic, handle_<verb> for orchestration, to_response for 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. Previously post_id: fr.IDRef[Post] — the FK form
    the tutorial teaches — generated no filter parameter at all, so
    GET /comments/?post_id=1 returned a 422 that looked like client error; the
    only filterable form was a plain post_id: int. An IDRef id 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 orjson lacked 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 a MetaData) on the engine
    configured via fr.configure(), replacing the engine = 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)
    makes include_view lint each registered view class and emit
    fr.exc.RestlyMisuseWarning for the three dominant misuse patterns —
    overriding a route shell (<verb>_endpoint) where a business-verb override
    was meant, calling session.commit() directly in a view method, and
    hand-rolling a CRUD route set on a bare View instead of subclassing
    RestView / AsyncRestView. Each message names the idiomatic fix. Off by
    default; intended for development, project templates, and CI.

Changed

  • The RestlyUncommittedChangesWarning message now leads with the fix
    (bracket the mutation with write_action(...) or reuse a handle_<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=False opt-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 to fr.exc (fr.exc.NotFound,
    fr.exc.RestlyError, …) — the exceptions module is renamed exc, mirroring
    sqlalchemy.exc. Advanced helpers moved to their layer submodules: schema↔ORM
    helpers to fr.objects.* (make_new_object / save_object / snapshot / …
    and the async_* variants), list query helpers to fr.query.*
    (create_list_params_schema, apply_list_params), engine accessors to
    fr.db.* (get_engine, get_async_engine), and IDMixin to fr.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.NotFoundfr.exc.NotFound,
    fr.make_new_objectfr.objects.make_new_object,
    fr.get_async_enginefr.db.get_async_engine.

Fixed

  • fr.open_session() / fr.open_async_session() now resolve the same session
    source as SessionDep / AsyncSessionDep: a custom session generator passed
    to fr.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 raised RestlyConfigurationError off-HTTP (in scripts,
    background jobs, or a custom dependency wrapping open_*session()). The two
    session entry points are now consistent.

v0.6.1

Choose a tag to compare

@rjprins rjprins released this 02 Jun 11:59

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 stay IDRef[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_consistency gained a resolved argument.
  • The standard extra is now runtime-only and mirrors fastapi[standard]. It no
    longer pulls the test toolchain (pytest, pytest-asyncio, pytest-cov,
    httpx) or bundles the aiosqlite driver, so installing
    fastapi-restly[standard] for production no longer drags pytest into the image.
    Test tooling stays in the testing extra; 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, add aiosqlite to your dependencies directly.
  • The testing extra now includes only the third-party packages Restly's shipped
    test helpers import — pytest, pytest-asyncio, and httpx (for
    RestlyTestClient and the restly_* fixtures). pytest-cov is no longer
    pulled in; add it yourself if you want coverage reports.
  • Removed the docs extra from the published extras. It only installed the
    toolchain for building Restly's own documentation site (a maintainer concern);
    those dependencies remain in the dev dependency group. The published extras
    are now standard and testing.

Fixed

  • RestlyUncommittedChangesWarning no longer false-positives on every write run
    under the savepoint test fixtures: the patched commit clears the
    pending-changes flag (mimicking the real after_commit), while a genuinely
    forgotten commit still warns.
  • An IDRef list field that references the same id more than once no longer
    raises a confusing Id not found: set() 404 when the referenced rows all
    exist; a genuinely missing id is now named in the error.
  • An IDRef list 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 ReadOnly or WriteOnly marker nested inside a field's type instead of
    wrapping it (such as Optional[WriteOnly[str]], WriteOnly[str] | None, or
    list[WriteOnly[str]]) is now rejected with a RestlyConfigurationError
    instead of silently no-op'ing. Nested there the marker has no effect — a
    WriteOnly field would leak into responses and a ReadOnly field would stay
    writable — so the framework now raises when the schema is defined (and again at
    view registration for schemas that do not derive from BaseSchema), pointing
    to the safe Marker[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_schema now takes the queried model as a
    required argument.)

v0.6.0

Choose a tag to compare

@rjprins rjprins released this 01 Jun 13:27

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
    authorize and 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 the listing/get/create/update/
    delete endpoints and the single perform_* tier.
  • The commit now has a single owner: the framework. handle_<verb> and
    write_action run before_commit → commit → after_commit around your
    domain logic, and the request-session dependency (AsyncSessionDep /
    SessionDep) no longer commits on response. after_commit therefore
    always runs after the write is durable. A custom (non-CRUD) write route should
    reuse handle_<verb> or bracket its mutation with with / async with self.write_action(action, ...); only a route doing something the bracket
    doesn't model (e.g. a batch commit) reaches for await self.session.commit()
    itself. The
    commit_session_on_response option 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_schemaschema_create/schema_update;
    build_from_schema/apply_schemamake_new_object/update_object;
    count_listingcount. Response shaping goes through a single
    to_response(obj_or_list, shape=ResponseShape.SINGLE) method.
    ViewRoute.LIST/GETViewRoute.GET_MANY/GET_ONE.

Added

  • authorize(action, obj, data) override — an empty override by default; raise
    fr.Forbidden / fr.NotFound to gate a verb (row visibility goes in
    build_query).
  • before_commit / after_commit transaction hooks and a snapshot() helper
    for old-vs-new comparison.
  • Typed request-time exceptions NotFound, Forbidden, Conflict, and
    BadQueryParam, subclassing fastapi.HTTPException (so a single
    app.add_exception_handler(fr.NotFound, ...) can reshape them).
  • Top-level make_new_object / update_object / save_object /
    delete_object / snapshot helpers (and their async_* 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 (in fastapi_restly.views)
    underneath, usable off the HTTP path. Create-shaped actions that omit obj=
    must assign the yielded handle's .obj before 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=False to
    disable) when a request finishes with uncommitted changes — the tell of a
    write route that forgot to commit.

Fixed

  • A @route method 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 *_endpoint route 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 BY tiebreaker, so rows are no longer skipped or repeated across
    pages. Applies to both the standard and React Admin sort paths.
  • A build_query that 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_count agree. A no-op for queries without such a join.
  • A WriteOnly field no longer leaks into a response, including from a nested
    response schema. WriteOnly fields are now excluded from serialization at the
    field level (exclude=True on the marker), so they are stripped recursively on
    the wire and dropped from the OpenAPI response schema, while staying required,
    documented request inputs. Prefer WriteOnly[Optional[T]] over
    Optional[WriteOnly[T]] — a WriteOnly marker buried only inside a union is
    not excluded.

v0.5.1

Choose a tag to compare

@rjprins rjprins released this 11 May 08:12

Fixed

  • Fixed fr.include_view(...) registration on fastapi.APIRouter parents.

v0.5.0 — first public beta release

Choose a tag to compare

@rjprins rjprins released this 06 May 18:57

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 AsyncReactAdminView and ReactAdminView variants for the ra-data-simple-rest wire 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 RestlyError and RestlyConfigurationError exception hierarchy.
  • Testing utilities through RestlyTestClient, savepoint-only mode helpers, and the fastapi_restly.pytest_fixtures pytest 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, and delete.
  • Renamed business-logic hooks to perform_list, perform_get, perform_create, perform_update, and perform_delete.
  • Standardized schema component names on ModelRead, ModelCreate, and ModelUpdate.
  • Made sort the canonical list ordering parameter.
  • Split __contains and __icontains so case-sensitive and case-insensitive matching have distinct public operators.
  • Exposed savepoint-only testing helpers through fastapi_restly.testing instead of the top-level package namespace.
  • Renamed build_listing_query to build_query and broadened its role: perform_get now also routes through this hook, so a single override filters listing, the pagination total, and single-row fetches.
  • perform_get now issues a SELECT ... WHERE pk = ? instead of session.get(...). Behaviour is unchanged for single-column primary keys; subclasses with composite primary keys must override perform_get themselves.
  • Advanced schema-to-object helpers now live in fastapi_restly.objects: build_from_schema, apply_schema, save_object, and delete_object, with async equivalents.

Removed

  • Removed the pre-stable query= argument from perform_listing. Override build_query() for SQL-level base query changes.
  • Removed pre-release route and hook names such as index, get, post, patch, delete, handle_list, and handle_get from the public view API.
  • Removed unsupported get_one_or_create helpers before the first stable release.
  • Removed internal model helpers such as TableNameMixin, underscore, and utc_now from the public API surface.
  • Removed duplicate pytest fixture exports from fastapi_restly.testing; the pytest plugin path is fastapi_restly.pytest_fixtures.