Fields¶
All field types for models.
Basic Fields (Field)¶
Field.pk()¶
Auto-increment integer primary key.
id: Mapped[int] = Field.pk()
Field.integer()¶
Field.integer(
primary_key=False,
autoincrement=False,
nullable=False,
default=None,
index=False,
)
# Examples
age: Mapped[int] = Field.integer()
score: Mapped[int] = Field.integer(default=0, index=True)
count: Mapped[int | None] = Field.integer(nullable=True)
Field.string()¶
Field.string(
max_length=255,
nullable=False,
default=None,
unique=False,
index=False,
)
# Examples
name: Mapped[str] = Field.string(max_length=100)
code: Mapped[str] = Field.string(max_length=10, unique=True)
title: Mapped[str | None] = Field.string(nullable=True)
Field.text()¶
Unlimited text.
Field.text(nullable=False, default=None)
# Examples
content: Mapped[str] = Field.text()
bio: Mapped[str | None] = Field.text(nullable=True)
Field.boolean()¶
Field.boolean(nullable=False, default=False, index=False)
# Examples
is_active: Mapped[bool] = Field.boolean(default=True)
is_verified: Mapped[bool] = Field.boolean(default=False, index=True)
Field.datetime()¶
Field.datetime(
nullable=False,
default=None,
auto_now=False, # Update on INSERT and UPDATE
auto_now_add=False, # Set only on INSERT
index=False,
)
# Examples
created_at: Mapped[DateTime] = Field.datetime(auto_now_add=True)
updated_at: Mapped[DateTime] = Field.datetime(auto_now=True)
expires_at: Mapped[DateTime | None] = Field.datetime(nullable=True)
Field.float()¶
Field.float(nullable=False, default=None, index=False)
# Examples
price: Mapped[float] = Field.float()
rating: Mapped[float] = Field.float(default=0.0)
Field.foreign_key()¶
Field.foreign_key(
target="table.column",
nullable=False,
ondelete="CASCADE", # CASCADE, SET NULL, RESTRICT, NO ACTION
index=True,
)
# Examples
user_id: Mapped[int] = Field.foreign_key("users.id")
category_id: Mapped[int | None] = Field.foreign_key(
"categories.id",
nullable=True,
ondelete="SET NULL"
)
Field.choice()¶
Field.choice(
choices_class, # TextChoices or IntegerChoices
default=None,
nullable=False,
index=False,
use_native_enum=False, # PostgreSQL ENUM type
)
# Example
from strider.choices import TextChoices
class Status(TextChoices):
DRAFT = "draft", "Draft"
PUBLISHED = "published", "Published"
status: Mapped[str] = Field.choice(Status, default=Status.DRAFT)
Advanced Fields (AdvancedField)¶
AdvancedField.uuid_pk()¶
UUID primary key (uses UUID7 by default).
from strider.fields import AdvancedField
from uuid import UUID
id: Mapped[UUID] = AdvancedField.uuid_pk()
AdvancedField.uuid()¶
AdvancedField.uuid(
nullable=False,
default=None,
unique=False,
index=False,
use_uuid7=True, # Time-sortable
)
# Examples
public_id: Mapped[UUID] = AdvancedField.uuid(unique=True)
reference: Mapped[UUID | None] = AdvancedField.uuid(nullable=True)
AdvancedField.uuid4()¶
Random UUID4 (no temporal ordering).
token: Mapped[UUID] = AdvancedField.uuid4(unique=True)
AdvancedField.json_field()¶
JSON/JSONB field.
AdvancedField.json_field(nullable=False, default=None)
# Examples
metadata: Mapped[dict] = AdvancedField.json_field(default={})
tags: Mapped[list] = AdvancedField.json_field(default=[])
config: Mapped[dict | None] = AdvancedField.json_field(nullable=True)
PostgreSQL uses JSONB (supports indexing). SQLite/MySQL use JSON.
AdvancedField.long_text()¶
Alias for Field.text().
description: Mapped[str] = AdvancedField.long_text()
AdvancedField.slug()¶
URL-friendly slug.
AdvancedField.slug(
max_length=255,
nullable=False,
unique=True, # Default: True
index=True, # Default: True
)
# Example
slug: Mapped[str] = AdvancedField.slug()
AdvancedField.email()¶
Email field.
AdvancedField.email(
max_length=255,
nullable=False,
unique=True, # Default: True
index=True, # Default: True
)
# Example
email: Mapped[str] = AdvancedField.email()
AdvancedField.bigint_pk()¶
BigInteger primary key.
id: Mapped[int] = AdvancedField.bigint_pk()
AdvancedField.file() — FileField (Django-style)¶
Campo de arquivo com interface rica para upload, download e URLs assinadas.
AdvancedField.file(
db_column="cover_url", # Coluna do banco que armazena o path
upload_to="courses/covers/", # Diretório de upload ou função
url_expiration=3600, # Expiração da signed URL (segundos)
)
Exemplo completo:
from strider import Model, Field
from strider.fields import AdvancedField
class Course(Model):
__tablename__ = "courses"
id: Mapped[int] = Field.pk()
name: Mapped[str] = Field.string(255)
# Coluna do banco (string que armazena o path)
cover_url: Mapped[str | None] = Field.string(500, nullable=True)
# FileField - interface rica para arquivos
cover = AdvancedField.file("cover_url", upload_to="courses/covers/")
# Uso:
course.cover.name # "courses/covers/abc.jpg"
course.cover.url # "https://...?X-Goog-Signature=..." (signed URL)
course.cover.save("foto.jpg", content, "image/jpeg") # Upload
course.cover.delete() # Remove do storage
bool(course.cover) # True se tem arquivo
course.cover.exists() # Verifica se existe
Upload com path dinâmico:
def course_path(instance, filename):
return f"courses/{instance.id}/{filename}"
cover = AdvancedField.file("cover_url", upload_to=course_path)
Ver também: Storage para configuração de GCS e signed URLs.
StructSchema em models¶
Campos JSON estruturados com validação, defaults e acesso tipado. O banco armazena JSON/JSONB; em Python você usa um schema (classe herdando de StructSchema) para definir formato, validação e conversão.
Definir o schema¶
Em strider.schema use os campos disponíveis: StringField, IntegerField, FloatField, BooleanField, ListField, NestedField, DictField, ChoiceField.
from strider.schema import (
StructSchema,
StringField,
BooleanField,
IntegerField,
NestedField,
)
class UserPreferences(StructSchema):
theme = StringField(
default="system",
choices=["light", "dark", "system"],
)
language = StringField(default="pt-BR", aliases=["lang"])
notifications = NestedField({
"email": BooleanField(default=True),
"push": BooleanField(default=True),
})
items_per_page = IntegerField(default=20, min_value=5, max_value=100)
- aliases: nomes alternativos ao ler do banco (ex.:
"lang"→language). Útil para migrar dados antigos sem alterar o schema. - NestedField: objeto aninhado; pode ser um
dictde campos ou outra classeStructSchema. - Campos ausentes no JSON usam o default do campo; campos desconhecidos ficam em
_extra_datae são preservados ao salvar.
Usar na model com tipagem¶
Use Field.struct(schema_class) e anote com Mapped[SeuSchema]:
from strider import Model, Field
from strider.schema import StructSchema, StringField, BooleanField, NestedField
from sqlalchemy.orm import Mapped
class UserPreferences(StructSchema):
theme = StringField(default="system", choices=["light", "dark", "system"])
language = StringField(default="pt-BR", aliases=["lang"])
notifications = NestedField({
"email": BooleanField(default=True),
"push": BooleanField(default=True),
})
class User(Model):
__tablename__ = "users"
id: Mapped[int] = Field.pk()
name: Mapped[str] = Field.string(255)
preferences: Mapped[dict] = Field.struct(
UserPreferences,
default=None, # opcional; se None, usa UserPreferences.default_dict()
nullable=False,
index=False, # True = índice GIN no PostgreSQL
)
Parâmetros de Field.struct():
| Parâmetro | Descrição |
|---|---|
schema_class |
Classe que herda de StructSchema. |
default |
None (usa defaults do schema), dict ou instância do schema. |
nullable |
Se a coluna pode ser NULL. |
index |
Se deve criar índice GIN no PostgreSQL (útil para filtros em JSON). |
O tipo na coluna é AdaptiveJSON: no PostgreSQL vira JSONB, nos demais dialetos JSON. A anotação Mapped[dict] reflete o que o banco guarda; para tipo “schema” use Mapped[dict] e converta com o schema quando precisar de atributos tipados.
Acesso tipado e leitura/gravação¶
O valor carregado do banco é um dict. Para acesso por atributos e validação, converta com o schema:
# Carregar do banco (instance.preferences é um dict)
prefs = UserPreferences.from_dict_safe(user.preferences)
prefs.theme # "system"
prefs.notifications.email # True
# Atribuir: pode passar dict (será normalizado/validado pelo default_factory ao inserir)
user.preferences = {"theme": "dark", "language": "en"}
# Ou construir e serializar
prefs = UserPreferences(theme="dark", language="en")
user.preferences = prefs.to_dict()
- from_dict_safe: tolerante a dados faltando, aliases e campos extras; preenche com defaults e mapeia aliases.
- to_dict: serializa para gravar no banco; usa sempre os nomes oficiais dos campos e inclui
_extra_data.
Validação ao atribuir em código pode ser feita com prefs.validate() ou prefs.is_valid(); ao definir via model, o default é montado com schema_class.default_dict() e, se você passar dict, pode normalizar com schema_class.from_dict_safe(d).to_dict() antes de setar.
Migrações¶
- A coluna gerada é JSON/JSONB (tipo interno
AdaptiveJSON). O sistema de migrações trata esse tipo como equivalente a JSON/JSONB (state.EQUIVALENT_TYPES:JSON,JSONB,ADAPTIVEJSON). - makemigrations gera
CreateTable/AddColumncom tipo mapeado para o dialeto (ex.: PostgreSQL → JSONB). - Não existe migração automática do “formato” do StructSchema: mudar campos ou defaults no Python não altera o banco. Só a coluna (JSON) existe no schema do BD.
- Evolução de dados: ao adicionar um novo campo no StructSchema, registros antigos continuam com o JSON que tinham; ao serem lidos,
from_dict_safepreenche o novo campo com o default definido no schema. Renomear campos pode ser tratado com aliases (ex.: alias"lang"paralanguage) para compatibilidade com dados antigos.
Resumo: migrações cuidam apenas da coluna JSON/JSONB; a “migração” dos dados dentro do JSON é feita em tempo de leitura pelo schema (defaults + aliases).
Filtros em QuerySet (path JSON)¶
Colunas com struct_schema no info são tratadas como JSON para filtros por path:
# Filtro por campo interno do struct
users = await User.objects.filter(preferences__theme="dark").all()
users = await User.objects.filter(preferences__language__exact="pt-BR").all()
O mesmo mecanismo de path JSON usado em AdvancedField.json_field() se aplica a campos definidos com Field.struct().
Admin¶
No admin, colunas que têm info["struct_schema"] usam o widget struct_editor para editar o JSON de acordo com os campos do schema (tipos e opções vêm de _fields do schema).
Resumo¶
| Aspecto | Comportamento |
|---|---|
| Tipo no banco | JSON (SQLite/MySQL) ou JSONB (PostgreSQL). |
| Tipo em Python | dict na coluna; use Schema.from_dict_safe() para acesso tipado. |
| Default | schema_class.default_dict() ou o que você passar em default. |
| Migrações | Apenas coluna JSON/JSONB; evolução de “schema” via defaults e aliases ao ler. |
| Filtros | Suporte a path (preferences__theme=...). |
| Admin | Widget struct_editor para edição do struct. |
Complete Model Example¶
from strider import Model, Field
from strider.fields import AdvancedField
from strider.choices import TextChoices
from strider.datetime import DateTime
from sqlalchemy.orm import Mapped
from uuid import UUID
class Status(TextChoices):
DRAFT = "draft", "Draft"
PUBLISHED = "published", "Published"
ARCHIVED = "archived", "Archived"
class Post(Model):
__tablename__ = "posts"
# Primary key
id: Mapped[int] = Field.pk()
# Basic fields
title: Mapped[str] = Field.string(max_length=200)
slug: Mapped[str] = AdvancedField.slug()
content: Mapped[str] = Field.text()
# Status with choices
status: Mapped[str] = Field.choice(Status, default=Status.DRAFT)
# JSON metadata
metadata: Mapped[dict] = AdvancedField.json_field(default={})
# Foreign key
author_id: Mapped[int] = Field.foreign_key("users.id")
# Timestamps
created_at: Mapped[DateTime] = Field.datetime(auto_now_add=True)
updated_at: Mapped[DateTime] = Field.datetime(auto_now=True)
published_at: Mapped[DateTime | None] = Field.datetime(nullable=True)
# Flags
is_featured: Mapped[bool] = Field.boolean(default=False, index=True)
Database Type Mapping¶
| Field | PostgreSQL | SQLite | MySQL |
|---|---|---|---|
Field.pk() |
SERIAL |
INTEGER |
INT AUTO_INCREMENT |
Field.integer() |
INTEGER |
INTEGER |
INT |
Field.string() |
VARCHAR(n) |
VARCHAR(n) |
VARCHAR(n) |
Field.text() |
TEXT |
TEXT |
TEXT |
Field.boolean() |
BOOLEAN |
INTEGER |
TINYINT(1) |
Field.datetime() |
TIMESTAMP WITH TIME ZONE |
DATETIME |
DATETIME |
Field.float() |
FLOAT |
REAL |
FLOAT |
AdvancedField.uuid_pk() |
UUID |
VARCHAR(36) |
VARCHAR(36) |
AdvancedField.json_field() |
JSONB |
JSON |
JSON |
Field.struct(StructSchema) |
JSONB |
JSON |
JSON |
(O campo struct usa internamente AdaptiveJSON; no PostgreSQL vira JSONB, nos demais JSON.)