Skip to content

Working with Models

Once you've defined a model, you can create, read, update, and delete records. All models inherit from Model, which provides the objects manager for querying, instance methods for persistence, and built-in version history.

All operations have both sync and async variants.


Creating Objects

Instantiate a model and call save() to persist it:

person = Person(first_name='John', last_name='Doe')
person.country = 'United States'
person.save()
person = Person(first_name='John', last_name='Doe')
person.country = 'United States'
await person.asave()

Field validation (types, required fields, validators) runs at instantiation. The record is written to the database only when you call save() / asave().

Nested references are saved automatically — if a referenced object hasn't been saved yet, save() persists it first:

event = Event(name='Birthday', person=Person(first_name='John', last_name='Doe'))
event.save()  # saves Person first, then Event

Warning

Unsaved objects cannot be used in queries. Person.objects.filter(profile=unsaved_profile) won't match anything because the object doesn't exist in the database yet.

Bulk Create

For creating multiple records efficiently:

people = [
    Person(first_name='Alice', last_name='Smith'),
    Person(first_name='Bob', last_name='Jones'),
]
Person.objects.bulk_create(people)
await Person.objects.bulk_acreate(people)

Updating Objects

Fetch an object, modify it, and call save():

person = Person.objects.get(first_name='John', last_name='Doe').execute()
person.country = 'United Kingdom'
person.save()
person = await Person.objects.get(first_name='John', last_name='Doe').aexecute()
person.country = 'United Kingdom'
await person.asave()

Each save() creates a new version of the object. With a historical connection, all previous versions are preserved. With a state connection, the record is overwritten but history is still tracked in the Lakehouse.

Bulk Update

for person in people:
    person.country = 'Canada'
Person.objects.bulk_update(people)
await Person.objects.bulk_aupdate(people)

Refetch from Database

Reload the object's current state from the database:

person = person.refetch_from_db()

# Or fetch the latest version specifically:
person = person.refetch_from_db(latest=True)
person = await person.arefetch_from_db()

Deleting Objects

person = Person.objects.get(first_name='John', last_name='Doe').execute()
person.delete()
person = await Person.objects.get(first_name='John', last_name='Doe').aexecute()
await person.adelete()

With a state connection, the record is removed. With a historical connection, a new version is created with a deleted flag.

Bulk Delete

Person.objects.bulk_delete(people)
await Person.objects.bulk_adelete(people)

Version History

AMSDAL tracks every change as a version. You can navigate between versions of any object:

person = Person.objects.get(first_name='John').execute()

# Get the previous version
prev = person.previous_version()

# Get the next version (if navigating from an older version)
nxt = person.next_version()
person = await Person.objects.get(first_name='John').aexecute()
prev = await person.aprevious_version()
nxt = await person.anext_version()

Both return None if there is no previous/next version.

Fetching a Specific Version

specific = Person.objects.get_specific_version(
    object_id='abc123',
    object_version='v2',
)
specific = await Person.objects.aget_specific_version(
    object_id='abc123',
    object_version='v2',
)

References

When a model has a reference (foreign key) to another model, AMSDAL loads the referenced object automatically on access:

class Event(Model):
    name: str
    person: Person

event = Event.objects.get(name='Birthday').execute()
print(event.person.first_name)
#> John
event = await Event.objects.get(name='Birthday').aexecute()
print(event.person.first_name)
#> John

Frozen References

By default, a reference points to the latest version of the referenced object. To pin a reference to a specific version, pass a frozen Reference object instead of the model instance:

person = Person.objects.get(first_name='John', age=18).execute()

# Create a frozen reference to the current version
frozen_ref = person.build_reference(is_frozen=True)

event = Event(name='Birthday 18', person=frozen_ref, date='2023-02-20')
event.save()
person = await Person.objects.get(first_name='John', age=18).aexecute()

frozen_ref = await person.abuild_reference(is_frozen=True)

event = Event(name='Birthday 18', person=frozen_ref, date='2023-02-20')
await event.asave()

If person.age is updated later, this event still references the version where age=18.

ReferenceLoader

You can load objects from a Reference object using ReferenceLoader:

from amsdal_models.classes.helpers.reference_loader import ReferenceLoader

ref = person.build_reference()
loaded_person = ReferenceLoader(ref).load_reference()
from amsdal_models.classes.helpers.reference_loader import ReferenceLoader

ref = await person.abuild_reference()
loaded_person = await ReferenceLoader(ref).aload_reference()

Serialization

Models provide Pydantic's model_dump() and additional AMSDAL-specific methods for serializing with references:

# Standard Pydantic serialization
data = person.model_dump()
json_str = person.model_dump_json()

# With references (foreign keys serialized as reference objects)
data = person.model_dump_refs()
json_str = person.model_dump_json_refs()

ExternalModel

ExternalModel provides read-only access to existing database tables that AMSDAL doesn't manage. No migrations, no versioning — just querying:

from amsdal_models.classes.external_model import ExternalModel

class LegacyUser(ExternalModel):
    __table_name__ = 'users'
    __connection__ = 'legacy_db'

    id: int
    username: str
    email: str

Query it like a regular model:

users = LegacyUser.objects.filter(username='alice').execute()
users = await LegacyUser.objects.filter(username='alice').aexecute()

ExternalModel does not have save(), delete(), lifecycle hooks, or metadata. It's designed for integrating with external databases you don't control.

Feature Model ExternalModel
CRUD operations Full Read-only
Version history Yes No
Migrations Yes No
Lifecycle hooks Yes No
References Yes No
objects manager Full Manager ExternalManager