Skip to content

Models & fields

yara_orm is an async Python ORM with a Rust engine. You describe your schema as plain Python classes: subclass Model, declare typed fields as class attributes, and the ORM maps each one onto a column for your database (PostgreSQL or SQLite). Field declarations read like type hints, so models stay concise and self-documenting.

Defining a model

A model is a subclass of Model whose class attributes are Field instances. A metaclass collects those fields at class-creation time and builds a _meta descriptor holding the resolved table name, field map and primary key.

from yara_orm import Model, fields

class Author(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=120, index=True)
    created_at = fields.DatetimeField(auto_now_add=True)

class Book(Model):
    id = fields.IntField(pk=True)
    title = fields.CharField(max_length=200)
    rating = fields.DecimalField(max_digits=3, decimal_places=1, default=0)
    author = fields.ForeignKeyField("Author", related_name="books")
    tags = fields.ManyToManyField("Tag", related_name="books")

class Tag(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=50, unique=True)

Automatic primary key

If you declare no field with pk=True, the metaclass inserts an auto-increment id = IntField(pk=True) for you. Declaring id explicitly, as above, simply makes that behaviour visible.

The Meta inner class

An optional Meta inner class customises table-level metadata.

class Author(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=120)

    class Meta:
        table = "authors"                       # default: "author" (lowercase model name)
        table_description = "Catalogue authors"  # emitted as a table COMMENT
Meta attribute Purpose
table Custom table name. Defaults to the lowercase model class name.
table_description Human-readable table comment. Aliased as description.
description Alternative spelling of table_description (used if the latter is unset).
abstract Mark the model as an abstract base — no table of its own; see below.
ordering Default ORDER BY for queries that set no explicit order_by; see below.

Default ordering

Set ordering to a list of field names to apply a default ORDER BY to every query on the model that does not call order_by itself. Prefix a name with - for descending order; pk is accepted as an alias for the primary key. Each name is validated against the model's fields at class-creation time.

class Message(Model):
    id = fields.IntField(pk=True)
    created_at = fields.DatetimeField(auto_now_add=True)

    class Meta:
        table = "messages"
        ordering = ["-created_at"]      # newest first by default

await Message.all()                      # ORDER BY created_at DESC
await Message.all().order_by("id")       # explicit order_by overrides the default

An explicit order_by() always takes precedence over Meta.ordering.

Abstract base models

Set abstract = True to define a reusable base whose fields are inherited by subclasses but which has no table of its own. An abstract model is left out of the registry, so schema generation and migrations skip it. abstract is read from each class's own Meta only — it is not inherited, so a subclass is concrete unless it redeclares abstract = True.

import uuid

class TimestampedModel(Model):
    id = fields.UUIDField(pk=True, default=uuid.uuid4)
    created_at = fields.DatetimeField(auto_now_add=True)
    updated_at = fields.DatetimeField(auto_now=True)

    class Meta:
        abstract = True

class Article(TimestampedModel):     # concrete: gets an "article" table with
    title = fields.CharField()       # id, created_at, updated_at and title

Field reference

Every field maps to one column. Numeric and UUID primary keys auto-increment or default automatically; see Primary keys below.

Field Key parameters Stores
SmallIntField small integer
IntField integer
BigIntField 64-bit integer
FloatField floating-point number
DecimalField max_digits=12, decimal_places=2 decimal.Decimal
CharField max_length=255 variable-length string
TextField unbounded text
BinaryField bytes
BooleanField bool
DatetimeField auto_now=False, auto_now_add=False datetime.datetime
DateField datetime.date
TimeField datetime.time
UUIDField pk defaults default to uuid4 uuid.UUID
JSONField JSON-serialisable value
IntEnumField enum_type IntEnum member (as integer)
CharEnumField enum_type, max_length=255 string Enum member
ForeignKeyField reference, related_name, on_delete, source_field foreign key (<name>_id column)
OneToOneField reference (unique FK) one-to-one foreign key
ManyToManyField reference, related_name, through join-table relation (no column)

Auto timestamps

Set auto_now_add=True to stamp the row once at creation, or auto_now=True to refresh the value on every save(). In the canonical Author model, created_at uses auto_now_add=True.

Common keyword arguments

Every concrete field accepts the same column options:

Keyword Default Meaning
pk False Mark the column as the primary key.
null False Allow NULL values.
default None Default value, or a callable producing one.
unique False Add a unique constraint.
index False Create an index on the column.
db_column None Explicit column name (defaults to the attribute name).
description None Column comment (emitted as a SQL COMMENT).
import uuid

class Session(Model):
    token = fields.CharField(max_length=64, unique=True, index=True)
    ref = fields.UUIDField(default=uuid.uuid4)         # callable default
    attempts = fields.IntField(default=0)              # value default
    label = fields.CharField(max_length=80, db_column="display_label")
    note = fields.TextField(null=True)

Callable defaults

A default that is callable is invoked per row at insert time, so default=uuid.uuid4 produces a fresh value for each instance rather than one shared value.

Enum fields

IntEnumField stores an IntEnum as its integer value; CharEnumField stores a string Enum as its .value. Both read back as live enum members, and you can filter by member directly.

from enum import Enum, IntEnum

class Service(IntEnum):
    PYTHON = 1
    RUST = 2

class Currency(str, Enum):
    HUF = "HUF"
    USD = "USD"

class Account(Model):
    service = fields.IntEnumField(Service)
    currency = fields.CharEnumField(Currency, max_length=3, default=Currency.HUF)

acc = await Account.create(service=Service.RUST)
reloaded = await Account.get(id=acc.id)
assert reloaded.service is Service.RUST          # reads back as the enum member

huf = await Account.filter(currency=Currency.HUF)  # filter by member

Column & table comments

Use description= on a field for a column comment and Meta.table_description for a table comment.

class Described(Model):
    name = fields.CharField(max_length=50, description="the display name")
    note = fields.TextField(null=True)

    class Meta:
        table = "d_described"
        table_description = "a fully described table"

PostgreSQL comments

On PostgreSQL these become real SQL COMMENT statements: the table comment is readable via obj_description(...) and the column comment via col_description(...). They document your schema directly in the database.

Primary keys

  • IntField(pk=True) (also SmallIntField and BigIntField) creates an auto-increment primary key; the database assigns the value on insert.
  • UUIDField(pk=True) defaults its default to uuid.uuid4, so a fresh UUID is generated for each row unless you pass an explicit default.
import uuid

class Event(Model):
    id = fields.UUIDField(pk=True)   # default=uuid.uuid4 applied automatically
    name = fields.CharField(max_length=100)

Relation fields

ForeignKeyField, OneToOneField and ManyToManyField declare relationships between models. A foreign key synthesises a concrete <name>_id backing column and installs forward and reverse accessors; a many-to-many field adds no column and instead manages a join table. Their full behaviour — accessors, related_name, on_delete and prefetching — is covered in the Relations guide.

See the Relations guide

For await book.author, reverse managers and await book.tags.add(...), see Relations.

See also