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.