Schemas API#

fastapi_restly.schemas implements the Pydantic schema layer: schema base classes, the ReadOnly and WriteOnly field markers, relationship reference types, and schema generation from SQLAlchemy models.

See also

Work with Foreign Keys and Relationships covers MustExist checked foreign keys and the IDRef / IDSchema relationship wire contract. Custom Schemas and Field Types describes schema bases, field markers, and aliases in prose.

class fastapi_restly.schemas.BaseSchema#

Bases: BaseModel

Thin Pydantic base for ORM-facing Restly schemas.

Equivalent to:

class BaseSchema(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(from_attributes=True)

from_attributes=True lets Pydantic/FastAPI validate objects by attribute when the schema is used directly. Generated Restly routes still serialize through to_response_schema() so Restly-specific behavior such as WriteOnly filtering and relationship-id normalization is applied.

model_config: ClassVar[ConfigDict] = {'from_attributes': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class fastapi_restly.schemas.IDRef(value: ~typing.Any = <object object>, *, id: ~typing.Annotated[~typing.Any, fr.ReadOnly])#

Bases: IDSchema, Generic[SQLAlchemyModel]

Flat-id reference to a related row, for a RELATIONSHIP-named field.

Use IDRef[T] when the field is named after a relationship (author: IDRef[User], products: list[IDRef[Product]]), not after a *_id column. The wire format is the raw id (5); input also accepts {"id": N}. Restly resolves the id to the related ORM object, so data.author is an IDRef wrapper (read .id), not a plain scalar.

For a scalar foreign-key COLUMN (author_id, task_id) use fr.MustExist[int, T] instead – it keeps the field a plain checked id (data.author_id == 1), rather than making a *_id field an object wrapper. For nested {"id": N} wire on a relationship, use IDSchema[T].

author: IDRef[User] # to-one relationship, flat id products: list[IDRef[Product]] # serializes as [1, 2, 3]

Resolution is an UNSCOPED existence check: the row is fetched by primary key only, with no view build_query scoping (tenant, soft-delete, row-level visibility), so a reference to a row the caller cannot otherwise see still resolves. Gate visibility in authorize (data.<field>.id is the requested id, before resolution) or before_commit (the resolved row is on the built object). See the Foreign Keys and Relationships how-to, “Visibility and Multi-Tenancy”.

model_config: ClassVar[ConfigDict] = {'from_attributes': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class fastapi_restly.schemas.IDSchema(*, id: Annotated[Any, fr.ReadOnly])#

Bases: BaseSchema, Generic[SQLAlchemyModel]

Response-schema base that adds a read-only id; parametrized as a field type, a nested-object relationship reference.

  • As a BASE CLASS (class UserRead(IDSchema): ...) it adds the resource’s own read-only id – the common use.

  • As a FIELD TYPE on a RELATIONSHIP-named field (author: IDSchema[User]) the wire format is {"id": N} (JSON-API / React-Admin); Restly resolves the id to the related ORM object, so data.author is a wrapper (read .id). Use IDRef[User] for flat-id wire (5) instead.

For a scalar foreign-key COLUMN (author_id), use fr.MustExist[int, T] rather than IDSchema/IDRef: it keeps the field a plain checked id instead of turning a *_id field into an object wrapper.

get_sql_model_annotation() type[SQLAlchemyModel] | None#

Return the annotation on IDSchema when used as:

foo: IDSchema[Foo]

This property will return “Foo”.

id: ReadOnly, FieldInfo(annotation=NoneType, required=True, json_schema_extra={'readOnly': True})]#
model_config: ClassVar[ConfigDict] = {'from_attributes': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class fastapi_restly.schemas.MustExist#

Bases: object

Existence-checked scalar foreign key.

MustExist[int] is a checked int foreign key; the target model is inferred from the column’s ForeignKey. Spell the model out with a second argument when you want it explicit (MustExist[int, Post]), and use the first for a non-int primary key (MustExist[UUID, Account]).

Unlike IDRef/IDSchema this is not a wrapper: the field stays the pk scalar everywhere (wire, column, data.<field>), plus a batched existence check on write (404 on a miss). It is exactly the marker form Annotated[<pk>, RefExists(Model)] – use that directly if you prefer.

class fastapi_restly.schemas.TimestampsSchemaMixin(*, created_at: Annotated[datetime, fr.ReadOnly], updated_at: Annotated[datetime, fr.ReadOnly])#

Bases: BaseModel

created_at: ReadOnly, FieldInfo(annotation=NoneType, required=True, json_schema_extra={'readOnly': True})]#
model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

updated_at: ReadOnly, FieldInfo(annotation=NoneType, required=True, json_schema_extra={'readOnly': True})]#
fastapi_restly.schemas.create_schema_from_model(model_cls: type[DeclarativeBase], *, schema_name: str | None = None, include_relationships: bool = True, include_readonly_fields: bool = True) type[BaseSchema]#

Auto-generate a Pydantic schema from a SQLAlchemy model.

Args:

model_cls: The SQLAlchemy model class schema_name: Optional name for the generated schema class include_relationships: Whether to include relationship fields include_readonly_fields: Whether to include read-only fields like id, created_at, etc.

Returns:

A Pydantic schema class