# API Reference This page documents: - Generated HTTP endpoints and query behavior. - Key public symbols with brief descriptions. - Full Python API reference generated via Sphinx autodoc. ## Generated CRUD Endpoints When you register a view with `fr.include_view(app, ViewClass)` (or the small-app decorator shortcut `@fr.include_view(app)`), both `fr.AsyncRestView` and `fr.RestView` expose the same default CRUD surface: | Method | Path | Purpose | Default Status | |---|---|---|---| | `GET` | `/{prefix}/` | List resources | `200` | | `POST` | `/{prefix}/` | Create resource | `201` | | `GET` | `/{prefix}/{id}` | Get resource by ID | `200` | | `PATCH` | `/{prefix}/{id}` | Partial update | `200` | | `DELETE` | `/{prefix}/{id}` | Delete resource | `204` | Notes: - Update semantics are `PATCH` (partial update), not `PUT`. (`AsyncReactAdminView` / `ReactAdminView` additionally expose `PUT /{id}` to match `ra-data-simple-rest`; see [How-To: React Admin Integration](howto_react_admin.md).) - `GET /{id}` and `DELETE /{id}` return `404` when the object is not found. - Read-only schema fields are ignored on create/update. - `*_id: IDRef[Model]` inputs are resolved to SQLAlchemy objects and validated against the database. The scalar id accepts the related primary-key type, such as `int` or `UUID`. ## Query Parameters (List Endpoint) `GET /{prefix}/` exposes a stable URL parameter dialect — the **list parameters** — derived from the response schema. This contract is part of the public API: parameter keys follow the response schema's public field names (aliases when set, Python names otherwise), end-to-end, including dotted relation paths. - Filtering: `?name=John&created_at__gte=2024-01-01` - Suffixes: `__in`, `__gte`, `__lte`, `__gt`, `__lt`, `__ne`, `__isnull`, `__contains`, `__icontains` (contains operators are string fields only) - OR-values (IN): `?id=1,2,3` (comma-separated values are OR-combined for `eq`) - Explicit IN: `?status__in=active,pending` - NOT-IN: `?status__ne=archived,deleted` (comma-separated values are AND-combined for `__ne`) - Aliased fields use only the alias as the URL key; the Python field name is not accepted (``populate_by_name`` only affects body parsing, not the URL surface). - Contains: `?name__contains=John` (case-sensitive where the SQL backend supports that distinction) - IContains: `?name__icontains=john` (case-insensitive) - Repeat the parameter to AND multiple terms — this is the precise form: `?name__contains=john&name__contains=doe`. - As a convenience, whitespace inside one value is also AND-split: `?name__contains=john%20doe` is equivalent. - `%`, `_`, and `\\` are escaped before building the SQL `LIKE` / `ILIKE`. - Sorting: `?sort=name,-created_at` - Pagination: `?page=2&page_size=10` - **Opt-in.** Omitting `page_size` returns every matching row (no implicit cap). - For public/production endpoints, set `default_page_size` and `max_page_size` explicitly on the view class. **Unknown query keys are rejected.** Generated list endpoints validate the request's query string against the schema's declared parameters. Any key that isn't part of the generated schema — a typoed filter, a Python field name on an aliased field, an operator suffix that wasn't emitted for the field's type (e.g. ``__gte`` on a boolean) — produces a 422 response with a FastAPI-style validation envelope. This prevents typos from silently widening the result set. To allow extra query keys that a view consumes outside the schema (e.g. an ``?include_deleted=true`` escape hatch on a soft-delete mixin), declare them on the view class: ```python class UserView(fr.AsyncRestView): extra_query_params = ("include_deleted",) ``` Relation filtering uses dot notation, and aliases apply to every segment of the path. If `ArticleRead.author` has `Field(alias="writer")` and `AuthorRead.name` has `Field(alias="authorName")`, the URL key is `writer.authorName`. Canonical Python names are not exposed. ### Low-level helpers `fr.create_list_params_schema(schema_cls, *, default_page_size=None, max_page_size=1000)` and `fr.apply_list_params(params, query, model, schema_cls)` are the primitives behind the generated endpoints. The happy path is to define a `RestView` / `AsyncRestView` and let the framework wire them up — the generated FastAPI endpoint validates incoming requests against the params schema before the SQL clauses are applied. Reach for these helpers directly only when you need to apply list parameters to a custom (non-`RestView`) endpoint, and pass a validated `create_list_params_schema(...)` instance rather than a raw `QueryParams` so pagination/filter bounds are enforced. ## Optional Pagination Metadata List endpoints return a JSON array by default. Set `include_pagination_metadata = True` on a view to return metadata together with the list items: ```json { "items": [], "total": 123, "page": 2, "page_size": 50, "total_pages": 3 } ``` `page`, `page_size`, and `total_pages` are populated when the request was actually paginated — that is, when the client sent `?page=` / `?page_size=`, or when the view sets a non-`None` `default_page_size`. When pagination is not engaged the fields stay `null` and only `total` reflects the full result count. ## Endpoint Decorators Use these decorators on methods in a view class: | Decorator | HTTP Method | Default Status | |---|---|---| | `@fr.get(path)` | `GET` | `200` | | `@fr.post(path)` | `POST` | `201` | | `@fr.patch(path)` | `PATCH` | `200` | | `@fr.put(path)` | `PUT` | `200` | | `@fr.delete(path)` | `DELETE` | `204` | | `@fr.route(path, ...)` | Custom | As configured | The shorthand decorators explicitly set the default status code shown. Pass `status_code=` to override it. All other keyword arguments are passed through to FastAPI's route registration, so class-based routes use the same configuration surface as regular FastAPI routes: `response_model=`, `dependencies=`, `responses=`, `tags=`, and other `APIRouter.add_api_route()` options. `@fr.put(...)` is available for custom endpoints, but default generated update endpoints use `PATCH`. ## Route Exclusion To disable generated endpoints on a view, use: ```python @fr.include_view(app) class UserView(fr.AsyncRestView): prefix = "/users" model = User exclude_routes = [fr.ViewRoute.DELETE, fr.ViewRoute.UPDATE] ``` Valid route values for exclusion: `fr.ViewRoute.LIST`, `fr.ViewRoute.GET`, `fr.ViewRoute.CREATE`, `fr.ViewRoute.UPDATE`, `fr.ViewRoute.DELETE`. `exclude_routes` accepts any iterable of `ViewRoute` values. Current route-name strings such as `"delete"` are also accepted. ## Response Modeling For generated CRUD endpoints: - Response schema defaults to `schema` (or an auto-generated `*Read` schema when omitted). - Input schema for `POST` defaults to schema without read-only fields (`creation_schema`, generated as `*Create`). - Input schema for `PATCH` defaults to optionalized schema (`update_schema`, generated as `*Update`). - Alias-aware serialization is applied so response payload keys follow schema aliases. ## Key Public Symbols ### Model Base Classes | Symbol | Description | |---|---| | `fr.DataclassBase` | SQLAlchemy declarative base with dataclass semantics and auto snake_case table names. | | `fr.IDBase` | Convenience alias combining `DataclassBase` with an auto-incrementing integer `id` primary key. | | `fr.TimestampsMixin` | Dataclass mixin adding `created_at` / `updated_at` to any `DataclassBase` subclass. | | `fr.IDMixin` | Dataclass mixin adding integer `id` to a custom `DataclassBase` subclass. | | `fastapi_restly.models.CASCADE_ALL_ASYNC` | Cascade string for use with `relationship(cascade=...)` in async SQLAlchemy models. Equivalent to `"save-update, merge, delete, expunge"`. SQLAlchemy's default `"all"` includes `"refresh-expire"` which is incompatible with async sessions. Import from `fastapi_restly.models` (not exposed at the top level). | | `fastapi_restly.models.CASCADE_ALL_DELETE_ORPHAN_ASYNC` | Like `CASCADE_ALL_ASYNC` but also includes `"delete-orphan"`. | FastAPI-Restly also works with ordinary SQLAlchemy declarative models that inherit from your own `sqlalchemy.orm.DeclarativeBase`. Use `fr.IDBase` when you want Restly's dataclass-oriented convenience base; bring your own SQLAlchemy base when you prefer standard declarative constructor semantics or are adding Restly to an existing model layer. `RestView` and `AsyncRestView` assume a single resource identifier: one primary key column addressable as `/{id}`. That column does not have to be named `id` when you provide explicit schemas and `id_type`, but the default CRUD routes, `IDSchema[Model]`, `IDRef[Model]`, React Admin integration, and OpenAPI identity shape are all scalar-id contracts. Composite primary keys are therefore not supported by the generated CRUD views. For composite-key tables, use `fr.View` and declare explicit routes such as `@fr.get("/{tenant_id}/{slug}")`, then write the SQLAlchemy query that matches that identity. ### Schema Classes and Utilities | Symbol | Description | |---|---| | `fr.BaseSchema` | Thin Pydantic base equivalent to `class BaseSchema(pydantic.BaseModel): model_config = pydantic.ConfigDict(from_attributes=True)`. Plain Pydantic models are also accepted for explicit create/update schemas. | | `fr.IDSchema` | Response-schema base class that adds the resource's own read-only `id` field. | | `fr.IDRef[Model]` | Scalar FK reference type. Wire format is the raw id (`5`) on request and response; dict input (`{"id": 5}`) is also accepted. Use this for typical REST FK fields and React Admin scalar id arrays. | | `fr.IDSchema[Model]` | Nested relationship-object field type. Wire format is `{"id": 5}` on request and response. Use this when a client expects relationship objects instead of scalar FK fields. | | `fr.TimestampsSchemaMixin` | Pydantic mixin adding read-only `created_at` / `updated_at` fields to a schema. | | `fr.ReadOnly[T]` | Type annotation marker. Fields annotated `ReadOnly[T]` are excluded from create/update inputs. | | `fr.WriteOnly[T]` | Type annotation marker. Fields annotated `WriteOnly[T]` are stripped by `self.to_response_schema(obj)`, which the generated CRUD and ReactAdmin routes use. Direct FastAPI/Pydantic serialization treats it as schema metadata only. | | `fastapi_restly.schemas.create_schema_from_model(model)` | Auto-generate a Pydantic schema from a SQLAlchemy model. Useful for scaffolding, prototypes, and internal tools; prefer explicit schemas for stable public API contracts. Import from `fastapi_restly.schemas`; it is intentionally not exported at the top level. | ### View Classes | Symbol | Description | |---|---| | `fr.View` | Base class for all class-based views. Subclass this directly when you do not need CRUD — add endpoints with `@fr.get`, `@fr.post`, etc. | | `fastapi_restly.views.BaseRestView` | Supported advanced base class for custom CRUD foundations shared by sync and async views. Import from `fastapi_restly.views`; it is intentionally not exported at the top level. | | `fr.AsyncRestView` | Async CRUD view. Use with async SQLAlchemy sessions. | | `fr.RestView` | Sync CRUD view. Use with sync SQLAlchemy sessions. | | `fr.ListingResult` | Value object returned by `perform_listing`, with `.objects` and `.total_count`, before `to_listing_response` formats the HTTP response. | | `fr.AsyncReactAdminView` | Async CRUD view that speaks the `ra-data-simple-rest` wire contract used by [react-admin](https://marmelab.com/react-admin/). See [How-To: React Admin Integration](howto_react_admin.md). | | `fr.ReactAdminView` | Sync variant of `AsyncReactAdminView`. | ### View Method Surface Methods on `RestView` / `AsyncRestView` fall into three categories: - **Route methods** define the HTTP contract and are decorated as FastAPI endpoints. Override these only when you need to change status codes, response shape, headers, or request parameters. - **Handler hooks** contain the default CRUD business logic and are the normal customization point. - **Public helpers** are utility methods intended for use inside handlers or custom routes. | Category | Method | Signature | Return | Purpose | |---|---|---|---|---| | Route | `listing` | `(query_params)` | response schema list or pagination envelope | `GET /`; validates query parameters and serializes listing results. | | Route | `get` | `(id)` | response schema | `GET /{id}`; serializes one retrieved object. | | Route | `create` | `(schema_obj)` | response schema | `POST /`; serializes the created object. | | Route | `update` | `(id, schema_obj)` | response schema | `PATCH /{id}`; serializes the updated object. | | Route | `delete` | `(id)` | `fastapi.Response` | `DELETE /{id}`; returns `204` by default. | | Handler hook | `perform_listing` | `(query_params)` | `ListingResult[Model]` | Fetch list rows and total count before pagination; override for list business logic after query construction. Use `build_query()` for SQL-level base query changes. | | Handler hook | `perform_get` | `(id)` | `Model` | Fetch one row through `build_query()` or raise `404`. Read-side filters layered into `build_query` apply here too — so `update`/`delete` (which call `perform_get`) inherit the visibility check for free. | | Handler hook | `perform_create` | `(schema_obj)` | `Model` | Build and save a new row; override for create-time business rules. | | Handler hook | `perform_update` | `(id, schema_obj)` | `Model` | Fetch, mutate, and save an existing row; override for update rules. | | Handler hook | `perform_delete` | `(id)` | `fastapi.Response` | Fetch and delete a row; override for delete rules while keeping the HTTP contract. | | Public helper | `build_query` | `()` | `sqlalchemy.Select` | Base query shared by listing and single-row retrieve. | | Public helper | `count_listing` | `(query)` | `int` | Count an already-built list query after removing ordering and pagination. | | Public helper | `to_listing_response` | `(query_params, listing_result)` | response schema list or pagination envelope | Serialize a `ListingResult` into the configured list HTTP response shape. | | Public helper | `to_paginated_listing_response` | `(query_params, listing_result)` | pagination envelope | Serialize a `ListingResult` into the paginated list response shape. | | Public helper | `to_response_schema` | `(obj)` | response schema | Validate and serialize an ORM object with Restly's alias/reference/write-only handling. | | Public helper | `build_from_schema` | `(schema_obj)` | `Model` | Build and stage a new object without flushing. | | Public helper | `apply_schema` | `(obj, schema_obj)` | `Model` | Apply writable fields without flushing. | | Public helper | `save_object` | `(obj)` | `Model` | Flush and refresh a staged object. Shared by the default create and update flows; deletes use `delete_object`. | | Public helper | `delete_object` | `(obj)` | `None` | Delete and flush an existing object. Override alongside `save_object` when you need side effects for every write event. | Internal methods prefixed with `_`, including `_reject_unknown_query_params`, are implementation details even though they are visible on instances. See [Class-Based Views](class_based_views.md#the-view-hierarchy) for the class hierarchy and [How-To: Override Endpoints](howto_override_endpoints.md) for examples of choosing between handler hooks and route replacement. `fr.View` class attributes: | Attribute | Type | Description | |---|---|---| | `prefix` | `ClassVar[str]` | URL prefix for all routes in the view (e.g. `"/users"`). Required. | | `tags` | `ClassVar[Iterable[str] \| None]` | OpenAPI tags. The view class name is always added automatically; set this to add extra tags. | | `dependencies` | `ClassVar[Iterable[Any] \| None]` | FastAPI dependencies applied to every route in the view. | | `responses` | `ClassVar[dict[int, Any]]` | OpenAPI response overrides. Defaults to `{404: {"description": "Not found"}}`. | ### View Class Attributes | Attribute | Type | Description | |---|---|---| | `schema` | `ClassVar[type[pydantic.BaseModel]]` | The read/response schema. If omitted, auto-generated from `model` as `ModelRead`. | | `creation_schema` | `ClassVar[type[pydantic.BaseModel]]` | Schema for `POST` input. Auto-derived by removing `ReadOnly` fields and named `ModelCreate`. | | `update_schema` | `ClassVar[type[pydantic.BaseModel]]` | Schema for `PATCH` input. Auto-derived by making all writable fields optional and named `ModelUpdate`. | | `model` | `ClassVar[type[DeclarativeBase]]` | The SQLAlchemy model class. | | `id_type` | `ClassVar[type]` | Scalar primary-key type used in generated `GET /{id}`, `PATCH /{id}`, and `DELETE /{id}` routes. Defaults to `int`. Composite primary keys are not supported by the generated CRUD route contract; use `fr.View` for custom multi-part identities. | | `include_pagination_metadata` | `ClassVar[bool]` | Set `True` to return the paginated metadata envelope. Defaults to `False`. | | `exclude_routes` | `ClassVar[Iterable[str \| ViewRoute]]` | Route names to suppress. | | `extra_query_params` | `ClassVar[Iterable[str]]` | Query keys to allow on the listing endpoint in addition to those derived from the response schema. Use for view-specific parameters consumed outside `apply_list_params` (e.g. an `?include_deleted=true` escape hatch). | | `default_page_size` | `ClassVar[int \| None]` | Default `?page_size=` for list endpoints. `None` (the default) means "no implicit cap" — every matching row is returned. | | `max_page_size` | `ClassVar[int]` | Upper bound for `?page_size=` on list endpoints. Values above are rejected with 422. Defaults to `1000`. | ### Advanced Object Helpers These advanced helpers live in `fastapi_restly.objects`. They are the primitive surface for building, updating, deleting, and explicitly saving ORM objects from schemas. Use them when you need the framework's schema-to-object mapping outside the view instance methods, for example in custom routes, services, or test setup. Each variant exists in both sync and async form, matching the session type you have on hand. | Symbol | Description | |---|---| | `objects.build_from_schema(session, model_cls, schema_obj, schema_cls=None)` | Build a new `model_cls` instance from `schema_obj`, resolve any `IDRef[...]` / `IDSchema[...]` reference fields against the database, and add the object to `session`. **Does not flush.** Call `objects.save_object(session, obj)` afterwards to persist. | | `objects.apply_schema(session, obj, schema_obj, schema_cls=None)` | Apply the schema's writable fields onto an existing ORM `obj` and resolve FK fields. **Does not flush.** Call `objects.save_object(session, obj)` afterwards to persist. | | `objects.save_object(session, obj)` | Flush the session and refresh `obj` so server-side defaults and generated columns (PKs, timestamps) are populated. Returns `obj`. This is where create/update writes hit the database. | | `objects.delete_object(session, obj)` | Delete `obj` and flush the session. | | `objects.async_build_from_schema(session, model_cls, schema_obj, schema_cls=None)` | Async equivalent of `objects.build_from_schema`. Pass an `AsyncSession`. | | `objects.async_apply_schema(session, obj, schema_obj, schema_cls=None)` | Async equivalent of `objects.apply_schema`. | | `objects.async_save_object(session, obj)` | Async equivalent of `objects.save_object`. | | `objects.async_delete_object(session, obj)` | Async equivalent of `objects.delete_object`. | ### View Instance Methods Every `AsyncRestView` / `RestView` instance exposes ergonomic wrappers around the object helpers above. The wrappers bind `self.session`, `self.model`, and `self.schema` so the dominant case (`self.build_from_schema(schema_obj)`) doesn't have to thread them explicitly. The async/sync split is implicit: `AsyncRestView.build_from_schema` calls `async_build_from_schema` under the hood, `RestView.build_from_schema` calls the sync version. Use these inside `perform_*` handlers or custom route methods. When you need to work with a model that isn't `self.model` (e.g. creating a sibling row in a custom endpoint) reach for the `fastapi_restly.objects` helpers instead. | Method | Description | |---|---| | `self.to_response_schema(obj)` | Serialise an ORM object to the configured response schema, applying alias rules, stripping `WriteOnly` fields, and running Pydantic response validation. Override for custom projections or an intentional `model_construct()` fast path. | | `self.build_from_schema(schema_obj, schema_cls=None)` | Wraps `objects.build_from_schema` / `objects.async_build_from_schema` against `self.session`, `self.model`, `self.schema`. **Does not flush** — call `self.save_object(obj)` afterwards. | | `self.apply_schema(obj, schema_obj, schema_cls=None)` | Wraps `objects.apply_schema` / `objects.async_apply_schema`. **Does not flush** — call `self.save_object(obj)` afterwards. | | `self.save_object(obj)` | Wraps `objects.save_object` / `objects.async_save_object` against `self.session`. Flush + refresh; this is where create/update writes actually hit the database. It does not run for deletes. | | `self.delete_object(obj)` | Wraps `objects.delete_object` / `objects.async_delete_object` against `self.session`. Override alongside `save_object` when a side effect must run for create, update, and delete. | | `self.build_query()` | Return the base SQLAlchemy `Select` used by every read on this view's model — `perform_listing` and `perform_get`. Defaults to `sqlalchemy.select(self.model)`. Override to add `WHERE` clauses that should apply to all reads — tenant scoping, soft-delete filtering, row-level permission visibility. Because retrieve also routes through this query, a row hidden from listing returns 404 from `GET /{id}` too. Call `super().build_query()` and chain `.where(...)` to compose with base-class or mixin filters. See [Composing views with mixins](howto_compose_views_with_mixins.md). | | `self.count_listing(query)` | Return the total row count for an already-built list query. The default `perform_listing` applies list params once, passes that same query to `count_listing`, and `count_listing` removes `ORDER BY`, `LIMIT`, and `OFFSET` before counting. | | `self.to_listing_response(query_params, listing_result)` | Convert a `fr.ListingResult` from `perform_listing` into either the default JSON array or the pagination metadata envelope, depending on `include_pagination_metadata`. Override this when only the list response shape needs to change. | | `self.to_paginated_listing_response(query_params, listing_result)` | Convert a `fr.ListingResult` into the paginated response envelope with `items`, `total`, `page`, `page_size`, and `total_pages`. Called by `to_listing_response` when `include_pagination_metadata = True`; override this when only the paginated envelope should change. | ### Database | Symbol | Description | |---|---| | `fr.AsyncSessionDep` | FastAPI `Depends`-compatible async session dependency. | | `fr.SessionDep` | FastAPI `Depends`-compatible sync session dependency. | | `fr.open_async_session()` | Open an async SQLAlchemy session context manager for use outside request handling, for example in background jobs or scripts. | | `fr.open_session()` | Open a sync SQLAlchemy session context manager for use outside request handling, for example in background jobs or scripts. | | `fr.configure(async_database_url=..., ...)` | Configure the framework. Accepts async/sync URLs, engines, session makers, custom session generators, and `commit_session_on_response`. | | `fr.get_async_engine()` | Return the configured `AsyncEngine` instance. | | `fr.get_engine()` | Return the configured sync `Engine` instance. | Restly has one public process-wide configuration. Configure it once during application startup: ```python fr.configure(async_database_url="sqlite+aiosqlite:///app.db") ``` `fr.configure(...)` must receive at least one meaningful setup option, such as an app for default exception-handler registration, a database URL, an engine, a session maker, a custom session generator, or an explicit `commit_session_on_response` policy. A bare `fr.configure()` call raises `TypeError`. Applications that need more than one database can still use FastAPI and SQLAlchemy directly: provide a custom dependency on a view, or pass a custom session generator to `fr.configure(...)`. Restly does not currently provide a public multi-context or multi-engine API. See [Use a custom session dependency on one view](howto_existing_project.md#use-a-custom-session-dependency-on-one-view) for per-view session wiring. By default, Restly commits sessions created by `AsyncSessionDep` / `SessionDep` when an endpoint successfully produces a response. On FastAPI versions that support dependency scopes, Restly requests function scope so this commit runs before the response is sent. On older FastAPI versions, commit timing follows FastAPI's default `yield` dependency cleanup timing and may run after the response has been sent. Set `commit_session_on_response=False` if your handlers should call `commit()` / `rollback()` explicitly. If you pass `session_generator` or `sync_session_generator`, Restly does not add commit/rollback behavior; that custom generator owns the transaction lifecycle. ### Exceptions | Symbol | Description | |---|---| | `fr.RestlyError` | Base class for FastAPI-Restly framework errors. | | `fr.RestlyConfigurationError` | Raised when a public Restly helper needs configuration that has not been set up yet, such as calling `fr.open_session()` before `fr.configure(...)`. | ### Testing | Symbol | Description | |---|---| | `fastapi_restly.testing.RestlyTestClient` | Sync test client wrapper around FastAPI's `TestClient` with default status-code assertions. It can test async FastAPI routes and `AsyncRestView` endpoints. | | `fastapi_restly.testing.activate_savepoint_only_mode(make_session)` | **Intended for tests.** Wraps a session factory in savepoint-only mode so test data never commits to the database. Requires the session maker as argument. | | `fastapi_restly.testing.deactivate_savepoint_only_mode(make_session)` | Restore normal session behavior after testing. | ### Default Exception Handling FastAPI-Restly installs a default handler for SQLAlchemy `IntegrityError` on FastAPI apps. The handler translates database integrity conflicts — unique constraint, foreign-key, not-null, and check-constraint violations — into HTTP `409 Conflict` responses using FastAPI's normal error body shape: ```json { "detail": "Unique constraint violated on user.email" } ``` The exact `detail` text is best-effort and depends on the database driver. The handler recognizes common PostgreSQL SQLSTATE integrity codes and SQLite constraint messages; unknown dialects fall back to a generic conflict message. Registration is automatic in either of these cases: - `fr.configure(app=app, ...)` is called with the default `install_default_exception_handlers=True`. - A view is registered directly on a `FastAPI` app with `fr.include_view(app)`. This fallback covers apps that configure database sessions separately. Restly only skips this default when the app already has a handler registered specifically for `sqlalchemy.exc.IntegrityError`. Other handlers, such as a generic `Exception` handler, do not prevent Restly from registering its `IntegrityError` handler. To opt out: ```python fr.configure( app=app, async_database_url="sqlite+aiosqlite:///app.db", install_default_exception_handlers=False, ) ``` To use your own response format, register your `IntegrityError` handler before Restly installs its defaults: ```python from fastapi.responses import JSONResponse from sqlalchemy.exc import IntegrityError @app.exception_handler(IntegrityError) async def integrity_error_handler(request, exc): return JSONResponse( status_code=409, content={"error": {"code": "constraint_conflict"}}, ) fr.configure(app=app, async_database_url="sqlite+aiosqlite:///app.db") ``` ## Important Limitations and Capabilities - Nested schemas are supported for **responses** and relation filtering, including nested aliases - Full nested schemas are **not** supported for create/update payloads by the default CRUD flow; write payloads must map directly to model fields, or use model-aware reference fields such as `*_id: IDRef[Model]` and relationship fields typed as `IDSchema[Model]` - Ordinary SQLAlchemy `DeclarativeBase` models work with generated CRUD views - UUID and other non-`int` scalar primary keys are supported through `id_type`, `IDRef[Model]`, and `IDSchema[Model]` - Composite primary keys are not supported by generated `RestView` / `AsyncRestView` CRUD routes; use `fr.View` for custom route shapes ## Minimal Example ```python import asyncio import fastapi_restly as fr from fastapi import FastAPI from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.orm import Mapped engine = create_async_engine("sqlite+aiosqlite:///app.db") fr.configure(async_engine=engine) app = FastAPI() class User(fr.IDBase): name: Mapped[str] @fr.include_view(app) class UserView(fr.AsyncRestView): prefix = "/users" model = User async def init_models() -> None: async with engine.begin() as conn: await conn.run_sync(fr.DataclassBase.metadata.create_all) asyncio.run(init_models()) ``` Generated endpoints: - `GET /users/` - `POST /users/` - `GET /users/{id}` - `PATCH /users/{id}` - `DELETE /users/{id}` ## Full Python API (Autodoc) ```{toctree} :maxdepth: 2 api/index ```