# 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)`, 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`. - `GET /{id}` and `DELETE /{id}` return `404` when the object is not found. - Read-only schema fields are ignored on create/update. - `*_id: IDSchema[Model]` inputs are resolved to SQLAlchemy objects and validated against the database. The nested `id` accepts the related primary-key type, such as `int` or `UUID`. ## Query Parameters (List Endpoint) `GET /{prefix}/` supports query modifiers through `fr.apply_query_modifiers(...)`. ### V1 (JSONAPI-style) - Filtering: `?filter[name]=John&filter[age]=>21` - OR-values: `?filter[id]=1,2,3` (comma-separated values are OR'd together) - Sorting: `?sort=name,-created_at` - Pagination: `?limit=10&offset=20` (no limit is applied when omitted) - Contains: `?contains[name]=john` (multiple space-separated words are AND'd: all must be contained) - V1 uses schema field names, not aliases ### V2 (HTTP-style) - Filtering: `?name=John&created_at__gte=2024-01-01` - Suffixes: `__gte`, `__lte`, `__gt`, `__lt`, `__ne`, `__isnull`, `__contains` (string fields only) - OR-values: `?id=1,2,3` (comma-separated values are OR'd together) - Flat aliased fields use the alias name. If `populate_by_name=True` is enabled, flat fields also accept the Python field name. - Contains: `?name__contains=john` (multiple space-separated words are AND'd: all must be contained) - `%`, `_`, and `\\` are escaped before building the SQL `ILIKE` - Sorting: `?order_by=name,-created_at` - Pagination: `?page=2&page_size=10` - **Always applied:** even with no pagination params, V2 applies `LIMIT 100 OFFSET 0` by default. - Defaults: `page=1`, `page_size=100`. Relation-filtering caveat for V2: - The relation segment must still use the schema/model field name - Only nested field segments may use aliases - Example: `?author.authorName=Alice` can work, while `?writer.authorName=Alice` does not ### Query Modifier Version Configuration The active version is controlled via: - `fr.set_query_modifier_version(fr.QueryModifierVersion.V2)` — set globally - `fr.get_query_modifier_version()` — read the current global setting - `fr.use_query_modifier_version(version)` — context manager; preferred for tests and direct low-level helper calls `fr.QueryModifierVersion` is an enum with two members: `QueryModifierVersion.V1` (default) and `QueryModifierVersion.V2`. Views capture the active query-modifier version when they are registered with `@fr.include_view(...)`. Set the global version before registering the view, or set `query_modifier_version = fr.QueryModifierVersion.V2` on the view class directly for explicit per-view behavior. `fr.create_query_param_schema(schema_cls)` is context-sensitive: it creates a V1 or V2 query-parameter schema depending on the currently active version. ## 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": 1, "page_size": 100, "total_pages": 2, "limit": 100, "offset": 0 } ``` `page`, `page_size`, and `total_pages` are populated when: - The view uses the V2 query interface (always), or - A V1 view receives `?page=` or `?page_size=` as query parameters. Otherwise on V1 views, those three fields stay `null` and `limit`/`offset` reflect the V1 `?limit=`/`?offset=` parameters if present. ## 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` | FastAPI default (`200`) | | `@fr.put(path)` | `PUT` | FastAPI default (`200`) | | `@fr.delete(path)` | `DELETE` | `204` | | `@fr.route(path, ...)` | Custom | As configured | `@fr.get()`, `@fr.post()`, and `@fr.delete()` explicitly set the default status code shown. `@fr.patch()` and `@fr.put()` set only the HTTP method; FastAPI applies its own default of `200`. Pass `status_code=` explicitly to either decorator to override. `@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 = ("delete", "patch") ``` Valid route names for exclusion: `"index"`, `"get"`, `"post"`, `"patch"`, `"delete"`. `exclude_routes` is typed `ClassVar[tuple[str, ...]]`; a list literal is also accepted at runtime. ## Response Modeling For generated CRUD endpoints: - Response schema defaults to `schema` (or auto-generated schema when omitted). - Input schema for `POST` defaults to schema without read-only fields (`creation_schema`). - Input schema for `PATCH` defaults to optionalized schema (`update_schema`, via `PatchMixin`). - 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.IDStampsBase` | Extends `IDBase` with `created_at` / `updated_at` timestamps (UTC-aware). | | `fr.TimestampsMixin` | Dataclass mixin adding `created_at` / `updated_at` to any `DataclassBase` subclass. | | `fr.PlainBase` | Alternative declarative base without dataclass semantics. | | `fr.PlainIDBase` | Convenience alias combining `PlainBase` with an auto-incrementing integer `id` primary key. | | `fr.PlainIDStampsBase` | Extends `PlainIDBase` with `created_at` / `updated_at` timestamps. | | `fr.IDMixin` | Dataclass mixin adding integer `id` to a custom `DataclassBase` subclass. | | `fr.PlainIDMixin` | Non-dataclass mixin adding integer `id` to a `PlainBase` subclass. | | `fr.PlainTimestampsMixin` | Non-dataclass mixin adding `created_at` / `updated_at` to a `PlainBase` subclass. | | `fr.get_one_or_create(model, session, **kwargs)` | Return the unique matching row or create it using a sync SQLAlchemy session. | | `fr.async_get_one_or_create(model, session, **kwargs)` | Async variant of `get_one_or_create`. | | `fr.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. | | `fr.CASCADE_ALL_DELETE_ORPHAN_ASYNC` | Like `CASCADE_ALL_ASYNC` but also includes `"delete-orphan"`. | ### Schema Classes and Utilities | Symbol | Description | |---|---| | `fr.BaseSchema` | Base Pydantic model with `from_attributes=True`. All schemas should inherit from this. | | `fr.IDSchema[Model]` | Generic schema that serializes only the `id` of a related model. Used for FK inputs. | | `fr.IDStampsSchema` | Combines `IDSchema` with read-only `created_at` / `updated_at` 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 excluded from responses. | | `fr.OmitReadOnlyMixin` | Mixin that strips `ReadOnly` fields from a schema subclass (used by `creation_schema`). | | `fr.PatchMixin` | Mixin that makes all writable fields optional with `None` default (used by `update_schema`). | | `fr.create_schema_from_model(model)` | Auto-generate a Pydantic schema from a SQLAlchemy model. | | `fr.auto_generate_schema_for_view(view_cls, model_cls)` | Generate a schema for a view from its model, excluding relationship fields. Used internally by `include_view`. | | `fr.resolve_ids_to_sqlalchemy_objects(session, schema_obj)` | Walk a schema instance, load `_id`-suffixed `IDSchema` fields from the database, and replace them with ORM objects. Called automatically during create/update. | ### 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. | | `fr.BaseRestView` | Abstract base shared by `AsyncRestView` and `RestView`. Holds all CRUD class attributes and `process_*` hooks. | | `fr.AsyncRestView` | Async CRUD view. Use with async SQLAlchemy sessions. | | `fr.RestView` | Sync CRUD view. Use with sync SQLAlchemy sessions. | `fr.View` class attributes: | Attribute | Type | Description | |---|---|---| | `prefix` | `ClassVar[str]` | URL prefix for all routes in the view (e.g. `"/users"`). Required. | | `tags` | `ClassVar[list[str] \| None]` | OpenAPI tags. The view class name is always added automatically; set this to add extra tags. | | `dependencies` | `ClassVar[list[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[BaseSchema]]` | The primary Pydantic schema for responses. If omitted, auto-generated from `model`. | | `creation_schema` | `ClassVar[type[BaseSchema]]` | Schema for `POST` input. Auto-derived by removing `ReadOnly` fields. | | `update_schema` | `ClassVar[type[BaseSchema]]` | Schema for `PATCH` input. Auto-derived by making all writable fields optional. | | `model` | `ClassVar[type[DeclarativeBase]]` | The SQLAlchemy model class. | | `id_type` | `ClassVar[type]` | Primary key type used in generated `GET /{id}`, `PATCH /{id}`, and `DELETE /{id}` routes. Defaults to `int`. | | `include_pagination_metadata` | `ClassVar[bool]` | Set `True` to return the paginated metadata envelope. Defaults to `False`. | | `exclude_routes` | `ClassVar[tuple[str, ...]]` | Route names to suppress. | | `query_modifier_version` | `ClassVar[QueryModifierVersion]` | Per-view query style override. Defaults to the global setting at registration time. | ### View Free Functions These module-level functions mirror the instance methods on `AsyncRestView` / `RestView` but take an explicit session argument. Use them when you need the same logic outside a view class. | Symbol | Description | |---|---| | `fr.make_new_object(session, model_cls, schema_obj, schema_cls=None)` | Create a new model instance from a schema, resolve FK `IDSchema` fields, add to session, and return the object. | | `fr.update_object(session, obj, schema_obj, schema_cls=None)` | Apply schema fields to an existing ORM object, resolve FK fields, flush, and return the object. | | `fr.save_object(session, obj)` | Flush the session and refresh `obj` from the database, then return it. | ### Database | Symbol | Description | |---|---| | `fr.AsyncSessionDep` | FastAPI `Depends`-compatible async session dependency. | | `fr.SessionDep` | FastAPI `Depends`-compatible sync session dependency. | | `fr.configure(async_database_url=..., ...)` | Configure the framework. Accepts async/sync URLs, engines, session makers, or custom session generators. | | `fr.get_async_engine()` | Return the configured `AsyncEngine` instance. | | `fr.get_engine()` | Return the configured sync `Engine` instance. | | `fr.activate_savepoint_only_mode(make_session)` | **Intended for tests.** Wraps the session factory in a savepoint so test data never commits to the database. Each test rolls back instantly without touching the real data. Requires the session maker as argument. | | `fr.deactivate_savepoint_only_mode(make_session)` | Restore normal session behavior after testing. | | `fr.use_fr_globals(globals_obj)` | Context manager that swaps the global state for test isolation. | | `fr.get_fr_globals()` | Return the current `FRGlobals` instance (engine, session factory, etc.). | ## Important Limitations and Capabilities - Nested schemas are supported for **responses** and relation filtering, including nested aliases - Nested schemas are **not** supported for create/update payloads; write payloads must still map directly to model fields or use `*_id: IDSchema[Model]` - `fr.PlainBase` / `fr.PlainIDBase` models work with generated CRUD views - UUID and other non-`int` primary keys are supported through `id_type` and `IDSchema[Model]` ## 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 ```