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