Views API#

class fastapi_restly.views.AsyncReactAdminView#

Bases: _ReactAdminMixin, AsyncRestView

AsyncRestView that speaks the ra-data-simple-rest wire contract.

Use this instead of AsyncRestView when your frontend is react-admin with ra-data-simple-rest.

async listing() Any#
async put(id: Any, schema_obj: Any) Any#
class fastapi_restly.views.AsyncRestView#

Bases: BaseRestView[ModelT, SchemaT, CreateSchemaT, UpdateSchemaT, IdT]

AsyncRestView creates an async CRUD/REST interface for database objects. Basic usage:

class FooView(AsyncRestView):
    prefix = "/foo"
    schema = FooRead
    model = Foo

Where Foo is a SQLAlchemy model and FooRead a Pydantic model.

build_query() Select#

Return the base SQLAlchemy Select used by every read on this view’s model — listing, count, and retrieve. Override to add WHERE clauses that should apply to all of them — e.g. tenant scoping, soft-delete filtering, row-level permission visibility. Call super().build_query() and chain .where(...) to compose with any base-class or mixin filters.

Because retrieve also routes through this query, a row hidden from listing cannot be fetched directly via GET /{id} — visibility stays consistent across endpoints by construction.

async count_listing(query: Select) int#
async create(schema_obj: Any) Any#
async delete(id: Any) Response#
async get(id: Any) Any#
async listing(query_params: Any) Any#
async perform_create(schema_obj: CreateSchemaT) ModelT#

Handle a POST request on “/”. This should create a new object. Feel free to override this method.

async perform_delete(id: IdT) Response#
async perform_get(id: IdT) ModelT#

Handle a GET request on “/{id}”. This should return a single object. Return a 404 if not found.

Routes through build_query(), so any read-side filters layered there (tenant scoping, soft-delete, row-level permissions) apply to retrieve as well — a row hidden from listing returns 404 here too, without a separate post-fetch guard.

async perform_listing(query_params: Any) ListingResult[ModelT]#

Handle a GET request on “/”. This should return listed objects and the total count before pagination. Feel free to override this method, e.g.:

async def perform_listing(self, query_params):

result = await super().perform_listing(query_params) return ListingResult(add_my_info(result.objects), result.total_count)

query_params is the validated query-parameter Pydantic model injected by FastAPI; pagination bounds (page / page_size) have already been validated by the schema returned from fastapi_restly.query.create_list_params_schema().

For WHERE-clause-only filtering that should also apply to the pagination total and to retrieve, override build_query() instead.

async perform_update(id: IdT, schema_obj: UpdateSchemaT) ModelT#

Handle a PATCH request on “/{id}”. This should partially update an existing object. Feel free to override this method.

session: Annotated[AsyncSession, Depends(_async_generate_session)]#
async update(id: Any, schema_obj: Any) Any#
class fastapi_restly.views.BaseRestView#

Bases: View, Generic[ModelT, SchemaT, CreateSchemaT, UpdateSchemaT, IdT]

Base class for RestView implementations.

This class contains the common functionality shared between AsyncRestView and RestView, including schema definitions, model configuration, and common CRUD operation logic.

classmethod before_include_view()#

Apply type annotations needed for FastAPI, before creating an APIRouter from this view and registering it.

This function can be overridden to further tweak the endpoints before they are added to FastAPI.

creation_schema: ClassVar[type[BaseModel]]#
default_page_size: ClassVar[int | None] = None#

Default page_size for list endpoints. None means “no implicit cap” (the framework default). Override per-view.

exclude_routes: ClassVar[Iterable[str | ViewRoute]] = ()#
extra_query_params: ClassVar[Iterable[str]] = ()#

Extra query-parameter keys to allow on the listing endpoint in addition to those derived from the response schema. Use this when a view intentionally consumes a custom query parameter (e.g. an ?include_deleted=true escape hatch on a soft-delete mixin) that isn’t a filter on a schema field. Without this, the strict unknown-key guard would reject the request with 422.

get_relationship_loader_options() list[Any]#
id_type#

alias of int

include_pagination_metadata: ClassVar[bool] = False#
listing_param_schema: ClassVar[type[BaseModel]]#
max_page_size: ClassVar[int] = 1000#

Maximum page_size accepted on list endpoints. Above this returns 422.

model: ClassVar[type[DeclarativeBase]]#
pagination_response_schema: ClassVar[type[BaseModel]]#
request: Request#
responses: ClassVar[dict[int | str, dict[str, Any]]] = {404: {'description': 'Not found'}}#
schema: ClassVar[type[BaseModel]]#
to_listing_response(query_params: Any, listing_result: ListingResult[ModelT]) Any#
to_paginated_listing_response(query_params: Any, listing_result: ListingResult[Any]) dict[str, Any]#
to_response_schema(obj: ModelT | SchemaT) SchemaT#

Serialize an ORM object to the configured response schema.

update_schema: ClassVar[type[BaseModel]]#
class fastapi_restly.views.ListingResult(objects: Sequence[ModelT], total_count: int)#

Bases: Generic[ModelT]

Result returned by perform_listing before HTTP response formatting.

objects: Sequence[ModelT]#
total_count: int#
class fastapi_restly.views.ReactAdminView#

Bases: _ReactAdminMixin, RestView

RestView that speaks the ra-data-simple-rest wire contract.

Use this instead of RestView when your frontend is react-admin with ra-data-simple-rest.

listing() Any#
put(id: Any, schema_obj: Any) Any#
class fastapi_restly.views.RestView#

Bases: BaseRestView[ModelT, SchemaT, CreateSchemaT, UpdateSchemaT, IdT]

RestView creates a synchronous CRUD/REST interface for database objects. Basic usage:

class FooView(RestView):
    prefix = "/foo"
    schema = FooRead
    model = Foo

Where Foo is a SQLAlchemy model and FooRead a Pydantic model.

build_query() Select#

Return the base SQLAlchemy Select used by every read on this view’s model — listing, count, and retrieve. Override to add WHERE clauses that should apply to all of them — e.g. tenant scoping, soft-delete filtering, row-level permission visibility. Call super().build_query() and chain .where(...) to compose with any base-class or mixin filters.

Because retrieve also routes through this query, a row hidden from listing cannot be fetched directly via GET /{id} — visibility stays consistent across endpoints by construction.

count_listing(query: Select) int#
create(schema_obj: Any) Any#
delete(id: Any) Response#
get(id: Any) Any#
listing(query_params: Any) Any#
perform_create(schema_obj: CreateSchemaT) ModelT#

Handle a POST request on “/”. This should create a new object. Feel free to override this method.

perform_delete(id: IdT) Response#
perform_get(id: IdT) ModelT#

Handle a GET request on “/{id}”. This should return a single object. Return a 404 if not found.

Routes through build_query(), so any read-side filters layered there (tenant scoping, soft-delete, row-level permissions) apply to retrieve as well — a row hidden from listing returns 404 here too, without a separate post-fetch guard.

perform_listing(query_params: Any) ListingResult[ModelT]#

Handle a GET request on “/”. This should return listed objects and the total count before pagination. Feel free to override this method, e.g.:

def perform_listing(self, query_params):

result = super().perform_listing(query_params) return ListingResult(add_my_info(result.objects), result.total_count)

query_params is the validated query-parameter Pydantic model injected by FastAPI; pagination bounds (page / page_size) have already been validated by the schema returned from fastapi_restly.query.create_list_params_schema().

For WHERE-clause-only filtering that should also apply to the pagination total and to retrieve, override build_query() instead.

perform_update(id: IdT, schema_obj: UpdateSchemaT) ModelT#

Handle a PATCH request on “/{id}”. This should partially update an existing object. Feel free to override this method.

session: Annotated[Session, Depends(_generate_session)]#
update(id: Any, schema_obj: Any) Any#
class fastapi_restly.views.View#

Bases: object

Class-based view primitive for FastAPI.

Group related endpoints on a class, share dependencies and metadata via class attributes, and let subclasses override individual handlers. Routes are bound at include_view() time, not at class-definition time, so subclassing works the way Python developers expect: override a method on a subclass and the override is what runs.

Most users will subclass RestView or AsyncRestView, which extend View with CRUD scaffolding. Use View directly for grouped non-CRUD endpoints (auth flows, custom RPC routes, etc.).

classmethod before_include_view()#
dependencies: ClassVar[Any] = None#
prefix: ClassVar[str]#
responses: ClassVar[dict[int | str, dict[str, Any]]] = {}#
tags: ClassVar[Any] = None#
class fastapi_restly.views.ViewRoute(*values)#

Bases: str, Enum

Generated CRUD routes that can be referenced by view options.

CREATE = 'create'#
DELETE = 'delete'#
GET = 'get'#
LIST = 'listing'#
UPDATE = 'update'#
fastapi_restly.views.delete(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as a DELETE endpoint.

Equivalent to:

@route(path, methods=["DELETE"], status_code=204, ... )
fastapi_restly.views.get(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as a GET endpoint.

Equivalent to:

@route(path, methods=["GET"], status_code=200, ... )
fastapi_restly.views.include_view(parent_router: APIRouter | FastAPI, view_cls: V | None = None) V | Callable[[V], V]#

Add a View class’s routes to a FastAPI app or APIRouter.

Prefer the direct call form from your app/router composition layer:

include_view(app, MyView)

For small apps, it can also be used as a decorator:

@include_view(app)
class MyView(AsyncRestView):
    ...
fastapi_restly.views.patch(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as a PATCH endpoint.

Equivalent to:

@route(path, methods=["PATCH"], status_code=200, ... )
fastapi_restly.views.post(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as a POST endpoint.

Equivalent to:

@route(path, methods=["POST"], status_code=201, ... )
fastapi_restly.views.put(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as a PUT endpoint.

Equivalent to:

@route(path, methods=["PUT"], status_code=200, ... )
fastapi_restly.views.route(path: str, **api_route_kwargs: Any) Callable[[...], Any]#

Decorator to mark a View method as an endpoint. The path and api_route_kwargs are passed into APIRouter.add_api_route(), see for example: https://fastapi.tiangolo.com/reference/apirouter/#fastapi.APIRouter.get

Endpoints methods are later added as routes to the FastAPI app using include_view()