Fields¶
The Field() function configures both Pydantic validation and database schema.
Basic Usage¶
from oxyde import OxydeModel, Field
class User(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str = Field(max_length=100)
email: str = Field(db_unique=True, db_index=True)
Field() Parameters¶
Database Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
db_pk |
bool |
False |
Primary key |
db_index |
bool |
False |
Create index |
db_index_name |
str |
Auto | Custom index name |
db_index_method |
str |
"btree" |
Index method: btree, hash, gin, gist |
db_unique |
bool |
False |
UNIQUE constraint |
db_column |
str |
Field name | Database column name |
db_type |
str |
Auto | SQL type override |
db_default |
str |
None | SQL DEFAULT expression |
db_comment |
str |
None | Column comment |
Foreign Key Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
db_fk |
str |
PK of related model | Target field for FK |
db_on_delete |
str |
"RESTRICT" |
ON DELETE action |
db_on_update |
str |
"CASCADE" |
ON UPDATE action |
Relation Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
db_reverse_fk |
str |
None | Reverse FK field name |
db_m2m |
bool |
False |
Many-to-many relation |
db_through |
str |
None | M2M junction table |
Pydantic Parameters¶
All standard Pydantic Field() parameters work:
| Parameter | Type | Description |
|---|---|---|
default |
Any | Default value |
default_factory |
Callable | Factory for default value |
alias |
str |
JSON key name |
description |
str |
Field description |
ge, gt, le, lt |
Number | Numeric bounds |
min_length, max_length |
int |
String length bounds |
pattern |
str |
Regex pattern |
Primary Key¶
# Auto-increment integer
id: int | None = Field(default=None, db_pk=True)
# UUID
from uuid import UUID, uuid4
id: UUID = Field(default_factory=uuid4, db_pk=True)
# Custom type
id: int = Field(db_pk=True, db_type="BIGSERIAL")
Indexes¶
# Simple index
email: str = Field(db_index=True)
# Unique index
username: str = Field(db_unique=True)
# Custom index name
email: str = Field(db_index=True, db_index_name="ix_users_email")
# Index method (PostgreSQL)
data: dict = Field(db_type="JSONB", db_index=True, db_index_method="gin")
SQL Defaults¶
# Timestamp
created_at: datetime = Field(db_default="CURRENT_TIMESTAMP")
# PostgreSQL functions
uuid: str = Field(db_default="gen_random_uuid()")
# String literal (note the quotes)
status: str = Field(db_default="'pending'")
# Numeric
count: int = Field(db_default="0")
Python vs SQL Default
default is used when creating Python objects.
db_default is used by the database when inserting rows.
Column Mapping¶
# Different Python name and DB column
created_at: datetime = Field(db_column="created_timestamp")
# With JSON alias too
created_at: datetime = Field(
alias="createdAt", # JSON API uses camelCase
db_column="created_timestamp" # DB uses snake_case
)
Custom SQL Types¶
# Override inferred type
name: str = Field(db_type="VARCHAR(255)")
# PostgreSQL-specific
data: dict = Field(db_type="JSONB")
tags: list[str] = Field(db_type="TEXT[]")
# MySQL-specific
content: str = Field(db_type="LONGTEXT")
Foreign Keys¶
Foreign keys are defined by type annotation:
class Post(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
title: str
# FK to Author (creates author_id column)
author: "Author" | None = Field(default=None, db_on_delete="CASCADE")
FK to Non-PK Field¶
# FK to Author.uuid instead of Author.id
author: "Author" | None = Field(
default=None,
db_fk="uuid", # Target the uuid field
db_on_delete="CASCADE"
)
# Creates author_uuid column
ON DELETE Actions¶
| Action | Description |
|---|---|
CASCADE |
Delete related rows |
SET NULL |
Set FK to NULL (requires nullable field) |
RESTRICT |
Prevent deletion if references exist |
NO ACTION |
Same as RESTRICT (deferred) |
# CASCADE - delete posts when author is deleted
author: "Author" | None = Field(default=None, db_on_delete="CASCADE")
# SET NULL - set author_id to NULL when author is deleted
author: "Author" | None = Field(default=None, db_on_delete="SET NULL")
# RESTRICT - prevent author deletion if posts exist
author: "Author" | None = Field(default=None, db_on_delete="RESTRICT")
Relations¶
Reverse Foreign Key¶
Define on the "one" side of a one-to-many relationship:
class Author(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str
# Virtual field - not stored in DB
posts: list["Post"] = Field(db_reverse_fk="author")
Use with prefetch():
authors = await Author.objects.prefetch("posts").all()
for author in authors:
print(f"{author.name} has {len(author.posts)} posts")
Many-to-Many¶
class Post(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
title: str
# M2M through junction table
tags: list["Tag"] = Field(db_m2m=True, db_through="PostTag")
class Tag(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str = Field(db_unique=True)
class PostTag(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
post: "Post" | None = Field(default=None, db_on_delete="CASCADE")
tag: "Tag" | None = Field(default=None, db_on_delete="CASCADE")
Pydantic Validation¶
Numeric Bounds¶
age: int = Field(ge=0, le=150) # 0 <= age <= 150
price: float = Field(gt=0) # price > 0
quantity: int = Field(ge=1, le=100) # 1 <= quantity <= 100
String Validation¶
name: str = Field(min_length=1, max_length=100)
email: str = Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
Required vs Optional¶
# Required - no default
name: str
# Optional with None default
bio: str | None = Field(default=None)
# Optional with value default
status: str = Field(default="active")
# Required but can be None
data: str | None # Must be passed, but can be None
Comments¶
Add SQL comments to columns:
Complete Example¶
from datetime import datetime
from decimal import Decimal
from uuid import UUID, uuid4
from oxyde import OxydeModel, Field
class Product(OxydeModel):
class Meta:
is_table = True
table_name = "products"
# Primary key
id: UUID = Field(default_factory=uuid4, db_pk=True)
# Required fields
name: str = Field(min_length=1, max_length=200)
price: Decimal = Field(ge=0, db_type="NUMERIC(10, 2)")
# Optional fields
description: str | None = Field(default=None)
sku: str | None = Field(default=None, db_unique=True, db_index=True)
# With defaults
active: bool = Field(default=True)
stock: int = Field(default=0, ge=0)
# SQL defaults
created_at: datetime = Field(db_default="CURRENT_TIMESTAMP")
updated_at: datetime | None = Field(default=None)
# Foreign key
category: "Category" | None = Field(default=None, db_on_delete="SET NULL")