Models & fields¶
A model subclasses Model (which itself subclasses
pydantic.BaseModel) and declares fields with Field:
class Post(Model):
id: int | None = Field(default=None, primary_key=True)
title: str
slug: str = Field(unique=True)
views: int = Field(default=0, index=True)
author_id: int = Field(foreign_key="users.id", on_delete="CASCADE")
class Meta:
table_name = "posts" # optional; defaults to a pluralized snake_case name
Field wraps pydantic.Field and attaches TinyStore metadata:
| Keyword | Default | Description |
|---|---|---|
primary_key |
False |
Marks the primary key. One per model. Enables auto-increment by default. |
autoincrement |
=primary_key |
Explicitly enable/disable auto-increment for the PK. |
unique |
False |
Enforces a uniqueness constraint across the table. |
index |
False |
Builds an in-memory hash index for O(1) eq/in lookups. |
nullable |
inferred | Allows NULL; fields with a default are nullable. |
foreign_key |
None |
"table.column" reference, validated on write. |
on_delete |
"RESTRICT" |
"RESTRICT", "CASCADE", or "SET_NULL". |
Any other keyword arguments (ge=, min_length=, description=, ...) are
forwarded straight to Pydantic, so all of Pydantic's validation works.
class Product(Model):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(min_length=1, max_length=100)
price: float = Field(ge=0, description="Price in USD")
sku: str = Field(unique=True, pattern=r"^[A-Z]{3}-\d+$")
Table naming¶
By default the table name is derived from the class: User -> users,
Address -> addresses, Person -> people. Override it with a nested Meta:
Field access for queries¶
Class-level field access — e.g. User.age — resolves to a FieldProxy usable
both by static type checkers and at runtime for query building:
This is how the typed query DSL works. See the Querying guide.
Schema evolution¶
When you reopen a database, TinyStore compares the registered model's schema against the stored fingerprint.
- Additive changes (new nullable/defaulted field) are accepted automatically.
- Breaking changes (removed field, type change) raise
SchemaError.
Use db.reset_schema() to discard stored metadata (destructive — it does not
delete data files, but future reads may fail if the data no longer matches).
Relationships¶
Relationships are declared separately with Relationship(...). See the
Relationships guide.