Validation & Serialization¶
AMSDAL models inherit from Pydantic, giving you built-in data validation, type coercion, and serialization.
Type Validation¶
Fields are validated automatically on assignment:
from amsdal.models import Model
class Person(Model):
name: str
age: int
Person(name='John', age=25) # OK
Person(name='John', age='not_a_number') # ValidationError
Field Validators¶
Use @field_validator for custom validation logic:
from pydantic import field_validator
from amsdal.models import Model
class Person(Model):
name: str
age: int
@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
if v < 0 or v > 150:
raise ValueError('Age must be between 0 and 150')
return v
Model Validators¶
Use @model_validator to validate across multiple fields:
from pydantic import model_validator
from amsdal.models import Model
class DateRange(Model):
start_date: str
end_date: str
@model_validator(mode='after')
def validate_dates(self) -> 'DateRange':
if self.start_date > self.end_date:
raise ValueError('start_date must be before end_date')
return self
Default Values¶
from amsdal.models import Model
class Settings(Model):
theme: str = 'light'
notifications: bool = True
max_items: int = 50
Optional Fields¶
from amsdal.models import Model
class Person(Model):
name: str
nickname: str | None = None
bio: str | None = None
Serialization¶
Convert model instances to dictionaries or JSON:
person = Person(name='John', age=25)
# To dict
person.model_dump()
# {'name': 'John', 'age': 25}
# To JSON string
person.model_dump_json()
# '{"name": "John", "age": 25}'
Note
In async mode (async_mode=True), model_dump() and model_dump_json() automatically keep references as ref dicts (same as model_dump_refs). This is because resolving a reference requires a synchronous ReferenceLoader.load_reference() call, which is not allowed in async mode. To include inlined data in async mode, load references explicitly first (await event.person) and call model_dump() after the cache is populated.
Note
Forward-FK fields are stored under the private name _<fk>_ref with alias='<fk>' for Pydantic. Model.model_dump() and model_dump_refs() default to by_alias=True, so the public name <fk> appears in output. If you call these methods with by_alias=False or invoke the underlying Pydantic method directly, the internal _<fk>_ref key will appear instead.
Models with References¶
For models with FK or M2M fields, serialization methods differ in how they handle related objects:
| Method | Sync mode | Async mode |
|---|---|---|
model_dump() |
Resolves — loads related objects inline | Keeps as Reference — sync FK load is forbidden in async mode, so it falls back to ref dicts |
model_dump_json() |
Resolves — same as model_dump, output as JSON string |
Keeps as Reference — same fallback, output as JSON string |
model_dump_refs() |
Always keeps as Reference — returns {'ref': {...}} dicts without loading |
Same |
model_dump_json_refs() |
Always keeps as Reference — JSON string with ref dicts | Same |
The *_refs variants are useful when you want to roundtrip a model (save/load it) without forcing reference resolution — for example, exporting to a queue or audit log.
class Car(Model):
name: str
owner: User # FK field
car = Car(name='Ferrari', owner=user)
# Resolves references — includes full owner data
car.model_dump()
# {'name': 'Ferrari', 'owner': {'name': 'John', 'age': 25}}
# Always keeps references as dicts (regardless of mode)
car.model_dump_refs()
# {'name': 'Ferrari', 'owner': {'ref': {'class_name': 'User', 'object_id': '...', ...}}}
# Same payload, serialized to JSON
car.model_dump_json_refs()
# '{"name":"Ferrari","owner":{"ref":{"class_name":"User","object_id":"...","object_version":"..."}}}'
Further Reading¶
For the full set of Pydantic features — custom types, complex validators, computed fields, JSON Schema generation, and more — see the Pydantic documentation.