Skip to content

Python Classes

Just define your own Python class and inherit from Model class.

Note, you can import Model and other related classes directly from amsdal_models package or use shorter import path amsdal.models.

from typing import Optional
from amsdal.models import Model


class Person(Model):
    first_name: Optional[str] = 'John'
    last_name: Optional[str] = 'Doe'

...and AMSDAL automatically generates connection/database schema.

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT DEFAULT 'John',
    `last_name` TEXT DEFAULT 'Doe',
    PRIMARY KEY (partition_key)
)

Model properties

These properties are used to store actual user's data. Due to the nature of the AMSDAL and the fact it is built on top of Pydantic Models the syntax is similar to Pydantic Models.

Required property

To define a required property, you can use the following syntax:

from amsdal.models import Model

class Person(Model):
    first_name: str
    last_name: str

Both first_name and last_name are required properties.

Optional property

To define an optional property, you can use the following syntax:

from typing import Optional
from amsdal.models import Model

class Person(Model):
    first_name: Optional[str]
    last_name: Optional[str]
from amsdal.models import Model

class Person(Model):
    first_name: str | None
    last_name: str | None

Default value

To define a default value for a property, you can use the following syntax:

from amsdal.models import Model

class Person(Model):
    first_name: str = 'John'
    last_name: str = 'Doe'
from pydantic import Field
from amsdal.models import Model

class Person(Model):
    first_name: str = Field(default='John')
    last_name: str = Field(default='Doe')

Validator - Options

To define options for a property, you can use the following syntax:

from typing import Any
from pydantic.functional_validators import field_validator

from amsdal.models import Model
from amsdal.models import validate_options

class Person(Model):
    first_name: str
    last_name: str
    gender: str

    @field_validator('gender')
    @classmethod
    def validate_value_in_options_type(cls: type, value: Any) -> Any:
        return validate_options(
            value,
            options=[
                'Male',
                'Female',
            ],
        )

It's recommended way to add options for a property. It allows to track and store these options in lakehouse database.

Validator - Non-empty key dictionary

AMSDAL has built-in validator for non-empty key dictionary. To use it, you can use the following syntax:

from typing import Any
from pydantic.functional_validators import field_validator

from amsdal.models import Model
from amsdal.models import validate_non_empty_keys

class Person(Model):
    first_name: str
    last_name: str
    equipment: dict[str, str]

    @field_validator('equipment')
    @classmethod
    def _non_empty_keys_equipment(cls: type, value: Any) -> Any: 
        return validate_non_empty_keys(value)

Many-to-one relationships

AMSDAL supports many-to-one relationships. To define a many-to-one relationship, you can use the following syntax:

from amsdal.models import Model

class Asset(Model):
    asset_name: str

class Person(Model):
    first_name: str
    last_name: str
    asset: Asset

Person model has a many-to-one relationship with the Asset model. By default, it will generate database field asset_partition_key in the Person table. It's possible to customize the field name by using the ReferenceField:

from amsdal.models import Model, ReferenceField

class Person(Model):
    first_name: str
    last_name: str
    asset: Asset = ReferenceField(..., db_field='asset_id')

Note, ReferenceField extends Pydantic's Field class and accepts the same arguments.

It also possible to use composite foreign keys:

from amsdal.models import Model, ReferenceField

class Asset(Model):
    __primary_key__ = ['asset_name', 'asset_type']  # composite primary key

    asset_name: str
    asset_type: str

class Person(Model):
    first_name: str
    last_name: str
    asset: Asset = ReferenceField(..., db_field=['asset_name', 'asset_type'])

It means, the database schema for the Person table will be the following:

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT,
    `last_name` TEXT,
    `asset_name` TEXT,
    `asset_type` TEXT,
    PRIMARY KEY (partition_key),
    FOREIGN KEY (asset_name, asset_type) REFERENCES Asset(asset_name, asset_type)
)

Many-to-many relationships

AMSDAL supports many-to-many relationships. To define a many-to-many relationship, you can use the following syntax:

from amsdal.models import Model

class Asset(Model):
    name: str

class Person(Model):
    first_name:  str  
    last_name: str

    assets: list[Asset]

AMSDAL will generate a separate model and table for the many-to-many relationship. The database schema for these models will be the following:

CREATE TABLE Asset (
    `partition_key` TEXT NOT NULL,
    `name` TEXT,
    PRIMARY KEY (partition_key)
)

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT,
    `last_name` TEXT,
    PRIMARY KEY (partition_key)
)

CREATE TABLE PersonAsset (
    `partition_key` TEXT NOT NULL,
    `person_partition_key` TEXT NOT NULL,
    `asset_partition_key` TEXT NOT NULL,
    PRIMARY KEY (partition_key),
    FOREIGN KEY (person_partition_key) REFERENCES Person(partition_key),
    FOREIGN KEY (asset_partition_key) REFERENCES Asset(partition_key)
)

It's also possible to define this extra model explicitly using ManyReferenceField:

from amsdal.models import Model, ManyReferenceField, ReferenceField

class Asset(Model):
    name: str

class Person(Model):
    first_name: str
    last_name: str

    assets: list[Asset] = ManyReferenceField(through='PersonAsset', through_fields=('person', 'asset'))

class PersonAsset(Model):
    person: Person = ReferenceField(db_field='person_id')
    asset: Asset = ReferenceField(db_field='asset_id')
    is_favorite: bool = False

Note, ManyReferenceField.through argument is required and should be the name of the extra model. The through_fields argument is also required and should be a tuple of two strings that represent the field names in the extra model.

Model class properties

These properties are used to define the database schema and does not store user's data.

[optional] __table_name__ : ClassVar[str]

A property that allows to define the table name in the database. If not defined, the table name will be the same as the class name.

For example:

from typing import Optional, ClassVar
from amsdal.models import Model

class Person(Model):
    __table_name__: ClassVar[str] = 'people'
    first_name: Optional[str] = 'John'
    last_name: Optional[str] = 'Doe'

It will generate the following SQL schema:

CREATE TABLE people (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT DEFAULT 'John',
    `last_name` TEXT DEFAULT 'Doe',
    PRIMARY KEY (partition_key)
)

[optional] __primary_key__: ClassVar[list[str]]

A property that allows to define the primary key in the database. If not defined, AMSDAL will add and use the partition_key field as the primary key.

For example:

from typing import Optional, ClassVar
from amsdal.models import Model

class Person(Model):
    __primary_key__: ClassVar[list[str]] = ['person_id']

    person_id: int
    first_name: Optional[str] = 'John'
    last_name: Optional[str] = 'Doe'

It will generate the following SQL schema:

CREATE TABLE Person (
    `person_id` INTEGER NOT NULL,
    `first_name` TEXT DEFAULT 'John',
    `last_name` TEXT DEFAULT 'Doe',
    PRIMARY KEY (person_id)
)

Example with composite primary key:

from typing import ClassVar
from amsdal.models import Model

class Person(Model):
    __primary_key__: ClassVar[list[str]] = ['first_name', 'last_name']

    first_name: str
    last_name: str

It will generate the following SQL schema:

CREATE TABLE Person (
    `first_name` TEXT,
    `last_name` TEXT,
    PRIMARY KEY (first_name, last_name)
)

[optional] __indexes__: ClassVar[list[IndexInfo]]

A property that allows to define the indexes in the database.

For example:

from typing import Optional, ClassVar
from amsdal.models import Model, IndexInfo

class Person(Model):
    __indexes__: ClassVar[list[IndexInfo]] = [
        IndexInfo(field='first_name', name='idx_first_name'),
    ]

    first_name: Optional[str] = 'John'
    last_name: Optional[str] = 'Doe'

It will generate the following SQL schema:

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT DEFAULT 'John',
    `last_name` TEXT DEFAULT 'Doe',
    PRIMARY KEY (partition_key),
    INDEX idx_first_name (first_name)
)

[optional] __constraints__: ClassVar[list[UniqueConstraint]]

A property that allows to define the unique constraints in the database.

For example:

from typing import Optional, ClassVar
from amsdal.models import Model, UniqueConstraint

class Person(Model):
    __constraints__: ClassVar[list[UniqueConstraint]] = [
        UniqueConstraint(fields=['first_name', 'last_name'], name='unique_full_name'),
    ]

    first_name: Optional[str] = 'John'
    last_name: Optional[str] = 'Doe'

It will generate the following SQL schema:

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT DEFAULT 'John',
    `last_name` TEXT DEFAULT 'Doe',
    PRIMARY KEY (partition_key),
    UNIQUE (first_name, last_name) CONSTRAINT unique_full_name
)

Model inheritance

AMSDAL supports model inheritance. To define a model that inherits from another model, you can use the following syntax:

from amsdal.models import Model

class Person(Model):
    first_name: str
    last_name: str

class Employee(Person):
    company_name: int

It will generate the following SQL schema:

CREATE TABLE Person (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT,
    `last_name` TEXT,
    PRIMARY KEY (partition_key)
)

CREATE TABLE Employee (
    `partition_key` TEXT NOT NULL,
    `first_name` TEXT,
    `last_name` TEXT,
    `company_name` TEXT,
    PRIMARY KEY (partition_key),
    FOREIGN KEY (partition_key) REFERENCES Person(partition_key)
)

Inline JSON Models (TypeModel)

When you define a model that inherits from TypeModel class, AMSDAL will not generate a separate table for it in the database. Instead, it will be stored as a JSON object in the parent model's table. For example:

from typing import Optional
from amsdal.models import TypeModel

class Address(TypeModel):
    street: str
    city: str
    postal_code: Optional[str] = None

This model will not be stored in the database as a separate table. But it can be used as a field type in other model:

from amsdal.models import Model

class Person(Model):
    first_name: str
    last_name: str
    home_address: Address

The home_address field will be stored in the database as a JSON string that contains the structured Address data. The Address model will validate that the JSON structure.

This is particularly useful when you need to store structured data that doesn't warrant its own table but still requires validation and a defined structure.