Models¶
A model is a class in <app>/models.py. One class gives you the table, three Pydantic schemas, API routes and an admin page.
import enum
from datetime import datetime
from uuid import UUID, uuid4
from fyro import Field, Model
class Status(enum.Enum):
DRAFT = "draft"
LIVE = "live"
class Post(Model):
title: str = Field(max_length=200)
slug: str = Field(max_length=50, unique=True)
body: str
secret: str = Field(write_only=True)
views: int = Field(default=0, ge=0, read_only=True)
status: Status = Status.DRAFT
subtitle: str | None = None
token: UUID = Field(default_factory=uuid4)
created: datetime = Field(default_factory=datetime.now)
Table¶
- The table name is
<app>_<class in snake_case>:Postinblogisblog_post,BlogPostisblog_blog_post. - An
id(bigint, auto-increment, read-only) is added unless a field setsprimary_key=True. T | Nonemakes a nullable column; plainTisNOT NULL.
Supported types¶
| Python | Postgres |
|---|---|
str with max_length |
VARCHAR(n) |
str |
TEXT |
int |
INTEGER (values outside its range are rejected with a 422) |
bool, float, Decimal, date, UUID |
BOOLEAN, FLOAT, NUMERIC, DATE, UUID |
datetime |
TIMESTAMPTZ |
an Enum |
VARCHAR(255) holding the member's value (no Postgres enum type) |
Any of these may be T | None. Other types raise TypeError when the class is defined, naming the model and field. Text can never contain a NUL character (Postgres cannot store it); such input is a 422.
Removing an enum member
Adding members needs no migration. Before removing one, update the rows that use it: reading a row whose stored value is no longer in the enum raises LookupError.
Field(...)¶
| Argument | Meaning |
|---|---|
default, default_factory |
Python-side default (the database holds no default). |
primary_key, unique, index |
Column options. |
max_length |
Column length and validation. |
read_only |
Never accepted as input (not in Create/Update). |
write_only |
Never returned (not in Read), e.g. a password hash or secret. |
ge, le, min_length, pattern, description |
Passed to the Pydantic schemas. |
A field cannot be both read_only and write_only.
Schemas¶
Every model gets three Pydantic classes:
Post.Create: all fields exceptread_only. Required unless they have a default.Post.Read: all fields exceptwrite_only, readable from model instances (from_attributes).Post.Update: theCreatefields, all optional. An explicitnullon a required field is rejected; usemodel_dump(exclude_unset=True).
Meta¶
class Post(Model):
...
class Meta:
table_name = "posts" # instead of blog_post
api = False # no automatic API routes
permissions = {"write": "superuser"} # see Auth and permissions
admin = False # hide from the admin
list_display = ("id", "title") # admin columns
search_fields = ("title",) # ?search= and admin search (text fields)
read_exclude = ("body",) # drop a field from Read
create_exclude = (...)
update_exclude = (...)
create_schema = MyCreate # replace a schema entirely
read_schema = MyRead
update_schema = MyUpdate
Unknown Meta keys and field names that do not exist are errors at class definition.