How-To: Use Schemas and Field Types#
Note
FastAPI-Restly uses schema for Pydantic request/response models and
model for SQLAlchemy ORM models. A User model is the database object; a
UserRead schema is the public API shape.
Use explicit schemas when you need a stable public contract: aliases, hidden
fields, computed read-only fields, or relationship IDs. If you omit schema on
a view, Restly can auto-generate one from the SQLAlchemy model instead.
BaseSchema#
fr.BaseSchema is Restly’s Pydantic base class. It enables Pydantic’s
from_attributes=True, which lets response schemas validate SQLAlchemy ORM
objects directly.
In code, it is intentionally this small:
class BaseSchema(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
Generated Restly routes still serialize ORM objects through
self.to_response_schema(obj). That is where Restly applies response-specific
behavior such as WriteOnly filtering and relationship-id normalization.
Use BaseSchema when you want to declare every field yourself, including id:
class UserRead(fr.BaseSchema):
id: int
name: str
email: str
This keeps the id field explicit and visible in the schema definition. You are
then responsible for marking it read-only if you do not want it accepted in
create/update payloads.
IDSchema#
Most response schemas inherit from fr.IDSchema. It is essentially
BaseSchema with a read-only id field added:
class IDSchema(fr.BaseSchema):
id: fr.ReadOnly[Any]
That is why examples usually look like this:
class UserRead(fr.IDSchema):
name: str
email: str
The id appears in responses but is excluded from generated POST and PATCH
input schemas. You do not need to redeclare it unless you want a different type
or different field metadata.
Timestamps#
Use fr.TimestampsSchemaMixin when a schema should include read-only
created_at and updated_at fields:
class UserRead(fr.TimestampsSchemaMixin, fr.IDSchema):
name: str
ReadOnly and WriteOnly#
fr.ReadOnly[T] marks a field as response-only. It is removed from create and
update inputs:
class UserRead(fr.IDSchema):
name: str
created_by_id: fr.ReadOnly[int]
fr.WriteOnly[T] marks a field as request-only. It is accepted in create/update
payloads. Restly strips it only when an object is serialized through
self.to_response_schema(obj), which the generated CRUD and ReactAdmin routes
use:
class UserRead(fr.IDSchema):
email: str
password: fr.WriteOnly[str]
Restly applies ReadOnly when it generates creation_schema and
update_schema, and when its object helpers construct or update ORM objects.
WriteOnly is removed from responses by to_response_schema(). If you return
a schema object directly to FastAPI or call Pydantic serialization yourself,
WriteOnly is schema metadata only and is not removed automatically.
Aliases#
Use normal Pydantic aliases when the API field name differs from the Python or database attribute:
from pydantic import Field
class UserRead(fr.IDSchema):
first_name: str = Field(alias="firstName")
email: str
Incoming payloads can use firstName, and Restly responses use the alias on
Restly routes.
IDRef#
Use fr.IDRef[Model] for foreign-key and identifier-reference fields:
class ArticleRead(fr.IDSchema):
title: str
author_id: fr.IDRef[Author]
The wire format is a scalar id:
{
"title": "Intro",
"author_id": 1
}
Restly validates that the referenced Author exists and resolves the id before
creating or updating the ORM object. See Foreign Keys with IDRef
for the full model and view setup.
Nested relationship objects#
If a client expects a nested relationship object, use fr.IDSchema[Model] as a
field type:
class ArticleRead(fr.IDSchema):
title: str
author: fr.IDSchema[Author]
The wire format is:
{
"title": "Intro",
"author": {"id": 1}
}
This is useful for clients or integrations that model relationships as objects.
For ordinary foreign-key fields, use IDRef.
Auto-Generated vs Explicit Schemas#
Auto-generated schemas are useful when your database model is already close to your API contract:
@fr.include_view(app)
class UserView(fr.AsyncRestView):
prefix = "/users"
model = User
Use explicit schemas when you need aliases, read/write field control, relationship references, or a public API shape that intentionally differs from the SQLAlchemy model:
@fr.include_view(app)
class UserView(fr.AsyncRestView):
prefix = "/users"
model = User
schema = UserRead