Models & Relationships
Models are the data structures that represent your API’s entities and their relationships. They serve as the foundation for interacting with databases, validating data, and exchanging information between your API endpoints and business logic.
Each model must be derived from the Entity base class, which provides core functionalities and integration with the Atlas service and repository layers.
Below are the details on how to create and customize a model.
Initializing the models package
Before creating models, create the src/models directory. This will be the Python package where all the API's models will be stored. Also, create a __init__.py file inside it for convenient importing.
Example:
from .product import Product
from .category import Category
from .price import Price
from .stock import Stock
from .user import User
from .admin import Admin
from .customer import Customer
__all__ = [
"Product",
"Category",
"Price",
"Stock",
"User",
"Admin",
"Customer"
]
Creating a Model
To create a model, follow the steps below:
- Create a file inside
src/modelswith the name of the model in snake_case; - Declare the model's class by extending Entity and naming it with the same name as the file, but in PascalCase;
- Declare the internal variable
__tablename__— this will be the name of the model's table in the database (use snake_case plural names); - Add an entry for your model in
src/models/__init__.py; - Declare your fields using SQLAlchemy's resources and syntax, such as
Mappedandmapped_column;
Done. Your model (or changes in its columns) will be included in the next database migration.
Example:
from typing import Optional
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
Relationships
Your API’s structure will likely include multiple models interacting through different types of data relationships.
Note that for importing other entities inside an entity, you will need to use forward references (using class names as strings instead of actual class references) and TYPE_CHECKING (imports at the top and bottom of your model file — for avoiding problems with circular imports and language servers, e.g. Pylance).
Below is an overview of each relationship type and how to define them properly.
1:N — Bidirectional
A bidirectional 1:N (one-to-many) relationship links a single parent to multiple children, while each child also points back to that parent. In SQLAlchemy, both sides expose a relationship() connected through back_populates, keeping parent and child in sync.
Example: Product 1:N Price
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .price import Price
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# OneToMany
prices: Mapped[Optional[List["Price"]]] = relationship(
"Price",
foreign_keys="Price.product_uuid",
cascade="all, delete-orphan"
back_populates="product"
)
from .price import Price
from typing import TYPE_CHECKING, Optional
from sqlalchemy import String, Float, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .product import Product
class Price(Entity):
__tablename__ = "prices"
value: Mapped[Optional[float]] = mapped_column(Float, default=0)
previous_value: Mapped[Optional[float]] = mapped_column(Float, default=0)
currency: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys=[product_uuid],
back_populates="prices"
)
from .product import Product
1:N — Unidirectional, Omitted Field in Side 1 (Only Children Can Access Parent)
In a unidirectional 1:N relationship with the field omitted on the 1 side, only the child objects maintain a reference to the parent. The parent does not expose a collection of its children. This pattern keeps the model simpler when navigation is only required from child to parent, and the ORM does not synchronize changes in the opposite direction.
Example: Product 1:N Price
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# "prices" omitted
from typing import TYPE_CHECKING, Optional
from sqlalchemy import String, Float, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .product import Product
class Price(Entity):
__tablename__ = "prices"
value: Mapped[Optional[float]] = mapped_column(Float, default=0)
previous_value: Mapped[Optional[float]] = mapped_column(Float, default=0)
currency: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys=[product_uuid]
) # back_populates="prices" omitted
from .product import Product
1:N — Unidirectional, Omitted Field in Side N (Only Parent Can Access Children)
In a unidirectional 1:N relationship with the field omitted on the N side, only the parent exposes a collection of related children. The children store the foreign key but do not define a relationship back to the parent. This pattern is useful when navigation is required solely from parent to children, while still preserving referential integrity at the database level.
Example: Product 1:N Price
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .price import Price
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# OneToMany
prices: Mapped[Optional[List["Price"]]] = relationship(
"Price",
foreign_keys="Price.product_uuid",
cascade="all, delete-orphan"
) # back_populates="product" omitted
from .price import Price
from typing import Optional
from sqlalchemy import String, Float, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Price(Entity):
__tablename__ = "prices"
value: Mapped[Optional[float]] = mapped_column(Float, default=0)
previous_value: Mapped[Optional[float]] = mapped_column(Float, default=0)
currency: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
# "product" omitted; keep only the foreign key (e.g. "product_uuid")
1:1 — Bidirectional, a Constrained Variant of 1:N
A bidirectional 1:1 relationship works like a 1:N with a uniqueness constraint on the child, ensuring only one child per parent. Parent and child roles remain the same, and the same omission rules from the unidirectional 1:N cases apply. Both sides expose a relationship() connected through back_populates.
Example: User 1:1 Admin
from typing import TYPE_CHECKING, Optional
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .admin import Admin
class User(Entity):
__tablename__ = "users"
name: Mapped[Optional[str]] = mapped_column(String)
email: Mapped[Optional[str]] = mapped_column(String)
username: Mapped[Optional[str]] = mapped_column(String)
password: Mapped[Optional[str]] = mapped_column(String)
# OneToOne
admin: Mapped[Optional["Admin"]] = relationship(
"Admin",
foreign_keys="Admin.user_uuid",
uselist=False,
back_populates="user"
)
from .admin import Admin
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
if TYPE_CHECKING:
from .user import User
class Admin(Entity):
__tablename__ = "admins"
# OneToOne
user_uuid: Mapped[str] = mapped_column(ForeignKey("users.uuid"), unique=True)
user: Mapped["User"] = relationship(
"User",
foreign_keys=[user_uuid],
cascade="all, delete-orphan"
uselist=False,
single_parent=True,
back_populates="admin"
)
from .user import User
N:N — Bidirectional
A bidirectional N:N relationship connects two models through an association table, allowing each side to reference multiple records from the other. In SQLAlchemy, both models define a relationship() using the same secondary table and link to each other via back_populates, enabling navigation in both directions. Create the relational tables in src/models/__relationships__.py using the pattern entity1_rel_entity2 ("rel" is for "relationship").
Example: Product N:N Category
from sqlalchemy import Table, Column, ForeignKey
from atlas.core import Entity
products_rel_categories = Table(
"products_rel_categories",
Entity.metadata,
Column("product_uuid", ForeignKey("products.uuid"), primary_key=True),
Column("category_uuid", ForeignKey("categories.uuid"), primary_key=True)
)
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import products_rel_categories
if TYPE_CHECKING:
from .category import Category
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToMany
categories: Mapped[Optional[List["Category"]]] = relationship(
"Category",
secondary=products_rel_categories,
back_populates="products"
)
from .category import Category
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import products_rel_categories
if TYPE_CHECKING:
from .product import Product
class Category(Entity):
__tablename__ = "categories"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToMany
products: Mapped[Optional[List["Product"]]] = relationship(
"Product",
secondary=products_rel_categories,
back_populates="categories"
)
from .product import Product
N:N — Unidirectional
A unidirectional N:N relationship exposes the association only from one model. The parent defines the relationship() using the shared secondary table, while the other side omits its counterpart. This keeps navigation one-way, but the underlying many-to-many link still exists and is fully enforced at the database level. Create the relational tables in src/models/__relationships__.py using the pattern entity1_rel_entity2 ("rel" is for "relationship").
Example: Product N:N Category
from sqlalchemy import Table, Column, ForeignKey
from atlas.core import Entity
products_rel_categories = Table(
"products_rel_categories",
Entity.metadata,
Column("product_uuid", ForeignKey("products.uuid"), primary_key=True),
Column("category_uuid", ForeignKey("categories.uuid"), primary_key=True)
)
from typing import TYPE_CHECKING, Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import products_rel_categories
if TYPE_CHECKING:
from .category import Category
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToMany
categories: Mapped[Optional[List["Category"]]] = relationship(
"Category",
secondary=products_rel_categories
) # back_populates="products" omitted
from .category import Category
from typing import Optional, List
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import products_rel_categories
class Category(Entity):
__tablename__ = "categories"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# "products" omitted
Self Relationships
Your API may include models that relate not only to other models, but also to themselves. These self-referential relationships are commonly used to represent hierarchical or associative structures—such as parent/child trees, bidirectional links, or many-to-many associations within the same table.
Note that for importing other entities inside an entity, you will need to use forward references (using class names as strings instead of actual class references) and TYPE_CHECKING (imports at the top and bottom of your model file — for avoiding problems with circular imports and language servers, e.g. Pylance).
Below is an overview of each type of self relationship and how to define them correctly in SQLAlchemy.
Self 1:N — Bidirectional
A bidirectional self 1:N relationship models a hierarchical structure within the same table, where each record can reference a single parent and also expose a collection of its children. In SQLAlchemy, this pattern requires remote_side to clarify which side represents the parent, and both directions are kept synchronized through back_populates.
Example: Product 1:N Product
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
parent_product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
parent_product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys=[parent_product_uuid],
remote_side="Product.uuid",
back_populates="child_products"
)
# OneToMany
child_products: Mapped[Optional[List["Product"]]] = relationship(
"Product",
foreign_keys=[parent_product_uuid],
back_populates="parent_product"
)
Self 1:N — Unidirectional, Omitted Field in Side 1 (Only Children Can Access Parent)
In a unidirectional self 1:N relationship with the field omitted on the 1 side, each record can access its parent but the parent does not expose a list of children. The model preserves the hierarchy through the foreign key, and remote_side identifies the parent, but navigation is intentionally restricted to the child → parent direction.
Example: Product 1:N Product
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
parent_product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
parent_product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys=[parent_product_uuid],
remote_side="Product.uuid"
) # back_populates="child_products" omitted
# "child_products" omitted
Self 1:N — Unidirectional, Omitted Field in Side N (Only Parent Can Access Children)
In a unidirectional self 1:N relationship with the field omitted on the N side, the parent exposes its children but each child does not define a relationship back to the parent. The hierarchy is still enforced through the foreign key, and SQLAlchemy manages the collection on the parent side without maintaining any reverse navigation on the child.
Example: Product 1:N Product
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToOne
parent_product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"))
# "parent_product" omitted; keep only the foreign key (e.g. "parent_product_uuid")
# OneToMany
child_products: Mapped[Optional[List["Product"]]] = relationship(
"Product",
foreign_keys=[parent_product_uuid]
) # back_populates="parent_product" omitted
Self 1:1 — Bidirectional, a Constrained Variant of 1:N
A bidirectional self 1:1 relationship is a restricted form of a self 1:N, where the foreign key on the child is marked as unique so that each record can have only one parent and one child. As in any self-relationship, remote_side identifies the parent side, and both directions are exposed through back_populates, allowing full navigation between the two linked records.
Example: Product 1:1 Product
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# OneToOne
parent_product_uuid: Mapped[Optional[str]] = mapped_column(ForeignKey("products.uuid"), unique=True)
parent_product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys=[parent_product_uuid],
remote_side="Product.uuid",
uselist=False,
single_parent=True,
back_populates="child_product"
)
# OneToOne
child_product: Mapped[Optional["Product"]] = relationship(
"Product",
foreign_keys="Product.parent_product_uuid",
uselist=False,
back_populates="parent_product"
)
Self N:N — Bidirectional
A bidirectional self N:N relationship links records within the same table through an association table, allowing each entry to reference multiple peers and be referenced by them in return. Both sides define complementary relationship() configurations using the same secondary table, enabling full navigation in both directions.
Example: Product N:N Product
from sqlalchemy import Table, Column, ForeignKey
from atlas.core import Entity
a_products_rel_b_products = Table(
"a_products_rel_b_products",
Entity.metadata,
Column("a_product_uuid", ForeignKey("products.uuid"), primary_key=True),
Column("b_product_uuid", ForeignKey("products.uuid"), primary_key=True)
)
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import a_products_rel_b_products
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToMany
a_products = relationship(
"Product",
secondary=a_products_rel_b_products,
primaryjoin="Product.uuid == a_products_rel_b_products.c.a_product_uuid",
secondaryjoin="Product.uuid == a_products_rel_b_products.c.b_product_uuid",
back_populates="b_products"
)
# ManyToMany
b_products = relationship(
"Product",
secondary=a_products_rel_b_products,
primaryjoin="Product.uuid == a_products_rel_b_products.c.b_product_uuid",
secondaryjoin="Product.uuid == a_products_rel_b_products.c.a_product_uuid",
back_populates="a_products"
)
Self N:N — Unidirectional
A unidirectional self N:N relationship exposes the many-to-many link only from one side of the model. The association table still defines the full connection between records of the same table, but only one relationship is declared in the ORM, restricting navigation to a single direction while preserving all database-level semantics.
Example: Product N:N Product
from sqlalchemy import Table, Column, ForeignKey
from atlas.core import Entity
a_products_rel_b_products = Table(
"a_products_rel_b_products",
Entity.metadata,
Column("a_product_uuid", ForeignKey("products.uuid"), primary_key=True),
Column("b_product_uuid", ForeignKey("products.uuid"), primary_key=True)
)
from typing import Optional, List
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from atlas.core import Entity
from .__relationships__ import a_products_rel_b_products
class Product(Entity):
__tablename__ = "products"
name: Mapped[Optional[str]] = mapped_column(String)
slug: Mapped[Optional[str]] = mapped_column(String)
sku: Mapped[Optional[str]] = mapped_column(String)
barcode: Mapped[Optional[str]] = mapped_column(String)
description: Mapped[Optional[str]] = mapped_column(String)
short_description: Mapped[Optional[str]] = mapped_column(String)
# ManyToMany
a_products = relationship(
"Product",
secondary=a_products_rel_b_products,
primaryjoin="Product.uuid == a_products_rel_b_products.c.a_product_uuid",
secondaryjoin="Product.uuid == a_products_rel_b_products.c.b_product_uuid",
) # back_populates="b_products" omitted
# "b_products" omitted