Skip to main content

Async Python ORM for PostgreSQL, MySQL and ClickHouse with dot-notation access

Project description

Python 3.12+ License MIT Coverage 87% Version 2.0.0

๐Ÿš€ DotORM

High-performance async ORM for Python with PostgreSQL, MySQL and ClickHouse support

Simple, Fast, Type-safe


๐Ÿ“‹ Table of Contents


โœจ Features

Feature Description
๐Ÿ”„ Async-first Fully async/await based on asyncpg, aiomysql, asynch
๐ŸŽฏ Type Safety Full Python 3.12+ type support with generics
๐Ÿ”— Relations Many2One, One2Many, Many2Many, One2One
๐Ÿ›ก๏ธ Security Parameterized queries, SQL injection protection
๐Ÿ“ฆ Batch Operations Optimized bulk create/update/delete
๐Ÿ’พ Support Transaction Support async transaction
๐Ÿšซ N+1 Solution Built-in relation loading optimization
๐Ÿ”Œ Multi-DB PostgreSQL, MySQL, ClickHouse
๐Ÿญ DDL Automatic table creation and migration

๐Ÿ“ฆ Installation

# Basic installation
pip install dotorm

# With PostgreSQL support
pip install dotorm[postgres]

# With MySQL support
pip install dotorm[mysql]

# With ClickHouse support
pip install dotorm[clickhouse]

# All drivers
pip install dotorm[all]

Dependencies

# requirements.txt
asyncpg>=0.29.0      # PostgreSQL
aiomysql>=0.2.0      # MySQL
asynch>=0.2.3        # ClickHouse
pydantic>=2.0.0      # Validation

๐Ÿš€ Quick Start

1. Define Models

from dotorm import DotModel, Integer, Char, Boolean, Many2one, One2many
from dotorm.components import POSTGRES

class Role(DotModel):
    __table__ = "roles"
    _dialect = POSTGRES

    id: int = Integer(primary_key=True)
    name: str = Char(max_length=100, required=True)
    description: str = Char(max_length=255)

class User(DotModel):
    __table__ = "users"
    _dialect = POSTGRES

    id: int = Integer(primary_key=True)
    name: str = Char(max_length=100, required=True)
    email: str = Char(max_length=255, unique=True)
    active: bool = Boolean(default=True)
    role_id: Role = Many2one(lambda: Role)

class Role(DotModel):
    # ... fields above ...
    users: list[User] = One2many(lambda: User, "role_id")

2. Connect to Database

from dotorm.databases.postgres import ContainerPostgres
from dotorm.databases.abstract import PostgresPoolSettings, ContainerSettings

# Connection settings
pool_settings = PostgresPoolSettings(
    host="localhost",
    port=5432,
    user="postgres",
    password="password",
    database="myapp"
)

container_settings = ContainerSettings(
    driver="asyncpg",
    reconnect_timeout=10
)

# Create connection pool
container = ContainerPostgres(pool_settings, container_settings)
pool = await container.create_pool()

# Bind pool to models
User._pool = pool
User._no_transaction = container.get_no_transaction_session()
Role._pool = pool
Role._no_transaction = container.get_no_transaction_session()

3. Create Tables

# Automatic table creation with FK
await container.create_and_update_tables([Role, User])

๐Ÿ“– Usage Examples

CRUD Operations

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# CREATE - Creating records
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Single create
user = User(name="John", email="john@example.com", role_id=1)
user_id = await User.create(user)
print(f"Created user with ID: {user_id}")

# Bulk create
users = [
    User(name="Alice", email="alice@example.com"),
    User(name="Bob", email="bob@example.com"),
    User(name="Charlie", email="charlie@example.com"),
]
created_ids = await User.create_bulk(users)
print(f"Created {len(created_ids)} users")

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# READ - Reading records
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Get by ID
user = await User.get(1)
print(f"User: {user.name}")

# Get with field selection
user = await User.get(1, fields=["id", "name", "email"])

# Search with filtering
active_users = await User.search(
    fields=["id", "name", "email"],
    filter=[("active", "=", True)],
    order="ASC",
    sort="name",
    limit=10
)

# Complex filters
users = await User.search(
    fields=["id", "name"],
    filter=[
        ("active", "=", True),
        "and",
        [
            ("name", "ilike", "john"),
            "or",
            ("email", "like", "@gmail.com")
        ]
    ]
)

# Pagination
page_1 = await User.search(fields=["id", "name"], start=0, end=20)
page_2 = await User.search(fields=["id", "name"], start=20, end=40)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# UPDATE - Updating records
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Update single record
user = await User.get(1)
user.name = "New Name"
await user.update()

# Update with payload
user = await User.get(1)
payload = User(name="Updated Name", active=False)
await user.update(payload, fields=["name", "active"])

# Bulk update
await User.update_bulk(
    ids=[1, 2, 3],
    payload=User(active=False)
)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# DELETE - Deleting records
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Delete single record
user = await User.get(1)
await user.delete()

# Bulk delete
await User.delete_bulk([4, 5, 6])

Working with Relations

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Many2One - Many to One
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Get user with role
user = await User.get_with_relations(
    id=1,
    fields=["id", "name", "role_id"]
)
print(f"User: {user.name}, Role: {user.role_id.name}")

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# One2Many - One to Many
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Get role with all users
role = await Role.get_with_relations(
    id=1,
    fields=["id", "name", "users"],
    fields_info={"users": ["id", "name", "email"]}
)
print(f"Role: {role.name}")
for user in role.users["data"]:
    print(f"  - {user.name}")

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Many2Many - Many to Many
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

class Tag(DotModel):
    __table__ = "tags"
    _dialect = POSTGRES

    id: int = Integer(primary_key=True)
    name: str = Char(max_length=50)

class Article(DotModel):
    __table__ = "articles"
    _dialect = POSTGRES

    id: int = Integer(primary_key=True)
    title: str = Char(max_length=200)
    tags: list[Tag] = Many2many(
        relation_table=lambda: Tag,
        many2many_table="article_tags",
        column1="tag_id",
        column2="article_id"
    )

# Get article with tags
article = await Article.get_with_relations(
    id=1,
    fields=["id", "title", "tags"]
)

# Link tags to article
await Article.link_many2many(
    field=Article.tags,
    values=[(article.id, 1), (article.id, 2), (article.id, 3)]
)

# Unlink tags
await Article.unlink_many2many(
    field=Article.tags,
    ids=[1, 2]
)

Transactions

from dotorm.databases.postgres import ContainerTransaction

async with ContainerTransaction(pool) as session:
    # All operations in single transaction
    role_id = await Role.create(
        Role(name="Admin"),
        session=session
    )
    
    user_id = await User.create(
        User(name="Admin User", role_id=role_id),
        session=session
    )
    
    # Auto commit on exit
    # Auto rollback on exception

Filters

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Supported Operators
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Comparison
filter=[("age", "=", 25)]
filter=[("age", "!=", 25)]
filter=[("age", ">", 18)]
filter=[("age", ">=", 18)]
filter=[("age", "<", 65)]
filter=[("age", "<=", 65)]

# String search
filter=[("name", "like", "John")]      # %John%
filter=[("name", "ilike", "john")]     # case-insensitive
filter=[("name", "not like", "test")]

# IN / NOT IN
filter=[("status", "in", ["active", "pending"])]
filter=[("id", "not in", [1, 2, 3])]

# NULL checks
filter=[("deleted_at", "is null", None)]
filter=[("email", "is not null", None)]

# BETWEEN
filter=[("created_at", "between", ["2024-01-01", "2024-12-31"])]

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Logical Operators
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# AND (default between conditions)
filter=[
    ("active", "=", True),
    ("verified", "=", True)
]

# OR
filter=[
    ("role", "=", "admin"),
    "or",
    ("role", "=", "moderator")
]

# Nested conditions
filter=[
    ("active", "=", True),
    "and",
    [
        ("role", "=", "admin"),
        "or",
        ("role", "=", "superuser")
    ]
]

# NOT
filter=[
    ("not", ("deleted", "=", True))
]

โšก Solving the N+1 Problem

The N+1 Problem

# โŒ BAD: N+1 queries
users = await User.search(fields=["id", "name", "role_id"], limit=100)
for user in users:
    # Each call = new DB query!
    role = await Role.get(user.role_id)
    print(f"{user.name} - {role.name}")
# Total: 1 + 100 = 101 queries!

DotORM Solution

1. Automatic Relation Loading in search()

# โœ… GOOD: 2 queries instead of 101
users = await User.search(
    fields=["id", "name", "role_id"],  # role_id is Many2one
    limit=100
)
# DotORM automatically:
# 1. Loads all users (1 query)
# 2. Collects unique role_ids
# 3. Loads all roles in one query (1 query)
# 4. Maps roles to users in memory

for user in users:
    print(f"{user.name} - {user.role_id.name}")  # No additional queries!

2. Batch Loading for Many2Many

# โœ… GOOD: Optimized M2M loading
articles = await Article.search(
    fields=["id", "title", "tags"],
    limit=50
)
# DotORM executes:
# 1. SELECT * FROM articles LIMIT 50
# 2. SELECT tags.*, article_tags.article_id as m2m_id
#    FROM tags
#    JOIN article_tags ON tags.id = article_tags.tag_id
#    WHERE article_tags.article_id IN (1, 2, 3, ..., 50)
# Total: 2 queries!

3. Batch Loading for One2Many

# โœ… GOOD: Optimized O2M loading
roles = await Role.search(
    fields=["id", "name", "users"],
    limit=10
)
# DotORM executes:
# 1. SELECT * FROM roles LIMIT 10
# 2. SELECT * FROM users WHERE role_id IN (1, 2, 3, ..., 10)
# Total: 2 queries!

N+1 Solution Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      ORM Layer                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                  search() method                      โ”‚    โ”‚
โ”‚  โ”‚  1. Execute main query                               โ”‚    โ”‚
โ”‚  โ”‚  2. Collect relation field IDs                       โ”‚    โ”‚
โ”‚  โ”‚  3. Call _records_list_get_relation()               โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                           โ”‚                                  โ”‚
โ”‚                           โ–ผ                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚         _records_list_get_relation()                 โ”‚    โ”‚
โ”‚  โ”‚  1. Build optimized queries for all relation types   โ”‚    โ”‚
โ”‚  โ”‚  2. Execute queries in parallel (asyncio.gather)    โ”‚    โ”‚
โ”‚  โ”‚  3. Map results back to parent records              โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                           โ”‚                                  โ”‚
โ”‚                           โ–ผ                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚              Builder Layer                           โ”‚    โ”‚
โ”‚  โ”‚  build_search_relation() - builds batch queries      โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚    โ”‚
โ”‚  โ”‚  โ”‚   Many2One  โ”‚  One2Many   โ”‚  Many2Many  โ”‚        โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  IN clause  โ”‚  IN clause  โ”‚  JOIN query โ”‚        โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Query Count Comparison

Scenario Naive Approach DotORM
100 users + roles (M2O) 101 queries 2 queries
50 articles + tags (M2M) 51 queries 2 queries
10 roles + users (O2M) 11 queries 2 queries
Combined 162 queries 4 queries

๐Ÿ“Š Benchmarks

Testing Methodology

  • Hardware: AMD Ryzen 7 5800X, 32GB RAM, NVMe SSD
  • Database: PostgreSQL 16, local
  • Python: 3.12.0
  • Data: 100,000 records in users table
  • Measurements: Average of 100 iterations

Comparison with Other ORMs

INSERT (1000 records)

ORM Time (ms) Queries Relative
DotORM 45 1 1.0x
SQLAlchemy 2.0 120 1000 2.7x
Tortoise ORM 89 1 2.0x
databases + raw SQL 42 1 0.9x
# DotORM - bulk insert
users = [User(name=f"User {i}", email=f"user{i}@test.com") for i in range(1000)]
await User.create_bulk(users)  # 1 query

SELECT (1000 records)

ORM Time (ms) Memory (MB) Relative
DotORM 12 8.2 1.0x
SQLAlchemy 2.0 28 15.4 2.3x
Tortoise ORM 22 12.1 1.8x
databases + raw SQL 10 6.5 0.8x

SELECT with JOIN (M2O, 1000 records)

ORM Time (ms) Queries Relative
DotORM 18 2 1.0x
SQLAlchemy (lazy) 1250 1001 69x
SQLAlchemy (eager) 35 1 1.9x
Tortoise ORM 45 2 2.5x

UPDATE (1000 records)

ORM Time (ms) Queries Relative
DotORM 38 1 1.0x
SQLAlchemy 2.0 95 1000 2.5x
Tortoise ORM 78 1 2.1x

Performance Chart

INSERT 1000 records (lower is better)
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
DotORM          โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  45ms
Tortoise        โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  89ms
SQLAlchemy      โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 120ms

SELECT 1000 records with M2O relation
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
DotORM          โ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  18ms (2 queries)
SQLAlchemy eagerโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  35ms (1 query)
Tortoise        โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘  45ms (2 queries)
SQLAlchemy lazy โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 1250ms (1001 queries)

Running Benchmarks

# Install benchmark dependencies
pip install pytest-benchmark memory_profiler

# Run all benchmarks
python -m pytest benchmarks/ -v --benchmark-only

# Run specific benchmark
python -m pytest benchmarks/test_insert.py -v

# With memory profiling
python -m memory_profiler benchmarks/memory_test.py

๐Ÿ—๏ธ Architecture

Overall Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                           Application Layer                              โ”‚
โ”‚                    (FastAPI, Django, Flask, etc.)                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                    โ”‚
                                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                              DotORM                                      โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
โ”‚  โ”‚                         Model Layer                             โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚   DotModel   โ”‚  โ”‚    Fields    โ”‚  โ”‚   Pydantic   โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚  (Base ORM)  โ”‚  โ”‚  (Type Def)  โ”‚  โ”‚ (Validation) โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
โ”‚                                    โ”‚                                     โ”‚
โ”‚                                    โ–ผ                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
โ”‚  โ”‚                          ORM Layer                              โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚ PrimaryMixin โ”‚  โ”‚ Many2Many    โ”‚  โ”‚  Relations   โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚  (CRUD ops)  โ”‚  โ”‚    Mixin     โ”‚  โ”‚    Mixin     โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                               โ”‚     โ”‚
โ”‚  โ”‚  โ”‚   DDLMixin   โ”‚                                               โ”‚     โ”‚
โ”‚  โ”‚  โ”‚(Table mgmt)  โ”‚                                               โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                               โ”‚     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
โ”‚                                    โ”‚                                     โ”‚
โ”‚                                    โ–ผ                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
โ”‚  โ”‚                        Builder Layer                            โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚  CRUDMixin   โ”‚  โ”‚  M2MMixin    โ”‚  โ”‚ RelationsMix โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚ (SQL CRUD)   โ”‚  โ”‚  (M2M SQL)   โ”‚  โ”‚  (Batch SQL) โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                             โ”‚     โ”‚
โ”‚  โ”‚  โ”‚ FilterParser โ”‚  โ”‚   Dialect    โ”‚                             โ”‚     โ”‚
โ”‚  โ”‚  โ”‚(WHERE build) โ”‚  โ”‚  (DB adapt)  โ”‚                             โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                             โ”‚     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
โ”‚                                    โ”‚                                     โ”‚
โ”‚                                    โ–ผ                                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚
โ”‚  โ”‚                       Database Layer                            โ”‚     โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚  PostgreSQL  โ”‚  โ”‚    MySQL     โ”‚  โ”‚  ClickHouse  โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ”‚   asyncpg    โ”‚  โ”‚   aiomysql   โ”‚  โ”‚    asynch    โ”‚          โ”‚     โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜          โ”‚     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ORM Layer Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                            ORM Layer                                     โ”‚
โ”‚                                                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                         DotModel                                  โ”‚    โ”‚
โ”‚  โ”‚                    (Main Model Class)                            โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚ Class Variables:                                         โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข __table__: str          - Table name                  โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข _pool: Pool             - Connection pool             โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข _dialect: Dialect       - Database dialect            โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข _builder: Builder       - SQL builder instance        โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข _no_transaction: Type   - Session factory             โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                              โ”‚ inherits                                  โ”‚
โ”‚          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                        โ”‚
โ”‚          โ–ผ                  โ–ผ                  โ–ผ                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚ OrmPrimary    โ”‚  โ”‚ OrmMany2many  โ”‚  โ”‚ OrmRelations  โ”‚               โ”‚
โ”‚  โ”‚    Mixin      โ”‚  โ”‚    Mixin      โ”‚  โ”‚    Mixin      โ”‚               โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค               โ”‚
โ”‚  โ”‚ โ€ข create()    โ”‚  โ”‚ โ€ข get_m2m()   โ”‚  โ”‚ โ€ข search()    โ”‚               โ”‚
โ”‚  โ”‚ โ€ข create_bulk โ”‚  โ”‚ โ€ข link_m2m()  โ”‚  โ”‚ โ€ข get_with_   โ”‚               โ”‚
โ”‚  โ”‚ โ€ข get()       โ”‚  โ”‚ โ€ข unlink_m2m()โ”‚  โ”‚   relations() โ”‚               โ”‚
โ”‚  โ”‚ โ€ข update()    โ”‚  โ”‚ โ€ข _records_   โ”‚  โ”‚ โ€ข update_with โ”‚               โ”‚
โ”‚  โ”‚ โ€ข update_bulk โ”‚  โ”‚   list_get_   โ”‚  โ”‚   _relations()โ”‚               โ”‚
โ”‚  โ”‚ โ€ข delete()    โ”‚  โ”‚   relation()  โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚ โ€ข delete_bulk โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚ โ€ข table_len() โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚
โ”‚          โ”‚                  โ”‚                  โ”‚                        โ”‚
โ”‚          โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                        โ”‚
โ”‚                             โ–ผ                                           โ”‚
โ”‚                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚                    โ”‚   DDLMixin    โ”‚                                    โ”‚
โ”‚                    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                                    โ”‚
โ”‚                    โ”‚ โ€ข __create_   โ”‚                                    โ”‚
โ”‚                    โ”‚   table__()   โ”‚                                    โ”‚
โ”‚                    โ”‚ โ€ข cache()     โ”‚                                    โ”‚
โ”‚                    โ”‚ โ€ข format_     โ”‚                                    โ”‚
โ”‚                    โ”‚   default()   โ”‚                                    โ”‚
โ”‚                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                                          โ”‚
โ”‚  Data Flow:                                                              โ”‚
โ”‚  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•    โ”‚
โ”‚  User.search() โ†’ OrmRelationsMixin.search()                             โ”‚
โ”‚       โ”‚                                                                  โ”‚
โ”‚       โ”œโ”€โ†’ _builder.build_search()          # Build SQL                  โ”‚
โ”‚       โ”œโ”€โ†’ session.execute()                 # Execute query             โ”‚
โ”‚       โ”œโ”€โ†’ prepare_list_ids()                # Deserialize               โ”‚
โ”‚       โ””โ”€โ†’ _records_list_get_relation()      # Load relations            โ”‚
โ”‚                โ”‚                                                         โ”‚
โ”‚                โ”œโ”€โ†’ _builder.build_search_relation()                     โ”‚
โ”‚                โ”œโ”€โ†’ asyncio.gather(*queries)  # Parallel execution       โ”‚
โ”‚                โ””โ”€โ†’ Map results to records                               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Builder Layer Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                           Builder Layer                                  โ”‚
โ”‚                                                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚                          Builder                                  โ”‚    โ”‚
โ”‚  โ”‚                   (Main Query Builder)                           โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚ Attributes:                                              โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข table: str              - Target table name           โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข fields: dict[str,Field] - Model fields                โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข dialect: Dialect        - SQL dialect config          โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  โ€ข filter_parser: Parser   - WHERE clause builder        โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                              โ”‚ inherits                                  โ”‚
โ”‚          โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                        โ”‚
โ”‚          โ–ผ                  โ–ผ                  โ–ผ                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚   CRUDMixin   โ”‚  โ”‚  Many2Many    โ”‚  โ”‚  Relations    โ”‚               โ”‚
โ”‚  โ”‚               โ”‚  โ”‚    Mixin      โ”‚  โ”‚    Mixin      โ”‚               โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค               โ”‚
โ”‚  โ”‚build_create() โ”‚  โ”‚build_get_m2m()โ”‚  โ”‚build_search_  โ”‚               โ”‚
โ”‚  โ”‚build_create_  โ”‚  โ”‚build_get_m2m_ โ”‚  โ”‚  relation()   โ”‚               โ”‚
โ”‚  โ”‚  bulk()       โ”‚  โ”‚  multiple()   โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚build_get()    โ”‚  โ”‚               โ”‚  โ”‚ Returns:      โ”‚               โ”‚
โ”‚  โ”‚build_search() โ”‚  โ”‚               โ”‚  โ”‚ List[Request  โ”‚               โ”‚
โ”‚  โ”‚build_update() โ”‚  โ”‚               โ”‚  โ”‚   Builder]    โ”‚               โ”‚
โ”‚  โ”‚build_update_  โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚  bulk()       โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚build_delete() โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚build_delete_  โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚  bulk()       โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚build_table_   โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ”‚  len()        โ”‚  โ”‚               โ”‚  โ”‚               โ”‚               โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜               โ”‚
โ”‚                                                                          โ”‚
โ”‚  Supporting Components:                                                  โ”‚
โ”‚  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•    โ”‚
โ”‚                                                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”‚
โ”‚  โ”‚       FilterParser        โ”‚    โ”‚         Dialect           โ”‚         โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค         โ”‚
โ”‚  โ”‚ โ€ข parse(filter_expr)      โ”‚    โ”‚ โ€ข name: str               โ”‚         โ”‚
โ”‚  โ”‚   โ†’ (sql, values)         โ”‚    โ”‚ โ€ข escape: str (", `)      โ”‚         โ”‚
โ”‚  โ”‚                           โ”‚    โ”‚ โ€ข placeholder: str ($, %) โ”‚         โ”‚
โ”‚  โ”‚ Supports:                 โ”‚    โ”‚ โ€ข supports_returning: boolโ”‚         โ”‚
โ”‚  โ”‚ โ€ข =, !=, >, <, >=, <=    โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ โ€ข like, ilike             โ”‚    โ”‚ Methods:                  โ”‚         โ”‚
โ”‚  โ”‚ โ€ข in, not in              โ”‚    โ”‚ โ€ข escape_identifier()     โ”‚         โ”‚
โ”‚  โ”‚ โ€ข is null, is not null    โ”‚    โ”‚ โ€ข make_placeholders()     โ”‚         โ”‚
โ”‚  โ”‚ โ€ข between                 โ”‚    โ”‚ โ€ข make_placeholder()      โ”‚         โ”‚
โ”‚  โ”‚ โ€ข and, or, not            โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚
โ”‚                                                                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”‚
โ”‚  โ”‚     RequestBuilder        โ”‚    โ”‚   RequestBuilderForm      โ”‚         โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค         โ”‚
โ”‚  โ”‚ Container for relation    โ”‚    โ”‚ Extended for form view    โ”‚         โ”‚
โ”‚  โ”‚ query parameters          โ”‚    โ”‚ with nested fields        โ”‚         โ”‚
โ”‚  โ”‚                           โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ โ€ข stmt: str               โ”‚    โ”‚ Overrides:                โ”‚         โ”‚
โ”‚  โ”‚ โ€ข value: tuple            โ”‚    โ”‚ โ€ข function_prepare        โ”‚         โ”‚
โ”‚  โ”‚ โ€ข field_name: str         โ”‚    โ”‚   โ†’ prepare_form_ids      โ”‚         โ”‚
โ”‚  โ”‚ โ€ข field: Field            โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ โ€ข fields: list[str]       โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚                           โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ Properties:               โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ โ€ข function_cursor         โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ”‚ โ€ข function_prepare        โ”‚    โ”‚                           โ”‚         โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚
โ”‚                                                                          โ”‚
โ”‚  Query Building Flow:                                                    โ”‚
โ”‚  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•    โ”‚
โ”‚                                                                          โ”‚
โ”‚  build_search(fields, filter, limit, order, sort)                       โ”‚
โ”‚       โ”‚                                                                  โ”‚
โ”‚       โ”œโ”€โ†’ Validate fields against store_fields                          โ”‚
โ”‚       โ”œโ”€โ†’ Build SELECT clause with escaped identifiers                  โ”‚
โ”‚       โ”œโ”€โ†’ filter_parser.parse(filter) โ†’ WHERE clause                    โ”‚
โ”‚       โ”œโ”€โ†’ Add ORDER BY, LIMIT, OFFSET                                   โ”‚
โ”‚       โ””โ”€โ†’ Return (sql_string, values_tuple)                             โ”‚
โ”‚                                                                          โ”‚
โ”‚  Example Output:                                                         โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€    โ”‚
โ”‚  Input:  fields=["id", "name"], filter=[("active", "=", True)]          โ”‚
โ”‚  Output: ('SELECT "id", "name" FROM users WHERE "active" = %s           โ”‚
โ”‚           ORDER BY id DESC LIMIT %s', (True, 80))                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

File Structure

dotorm/
โ”œโ”€โ”€ __init__.py              # Public API exports
โ”œโ”€โ”€ model.py                 # DotModel base class
โ”œโ”€โ”€ fields.py                # Field type definitions
โ”œโ”€โ”€ exceptions.py            # Custom exceptions
โ”œโ”€โ”€ pydantic.py              # Pydantic integration
โ”‚
โ”œโ”€โ”€ orm/                     # ORM Layer
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ protocol.py          # Type protocols
โ”‚   โ””โ”€โ”€ mixins/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ primary.py       # CRUD operations
โ”‚       โ”œโ”€โ”€ many2many.py     # M2M operations
โ”‚       โ”œโ”€โ”€ relations.py     # Relation loading
โ”‚       โ””โ”€โ”€ ddl.py           # Table management
โ”‚
โ”œโ”€โ”€ builder/                 # Builder Layer
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ builder.py           # Main Builder class
โ”‚   โ”œโ”€โ”€ protocol.py          # Builder protocol
โ”‚   โ”œโ”€โ”€ helpers.py           # SQL helpers
โ”‚   โ”œโ”€โ”€ request_builder.py   # Request containers
โ”‚   โ””โ”€โ”€ mixins/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ crud.py          # CRUD SQL builders
โ”‚       โ”œโ”€โ”€ m2m.py           # M2M SQL builders
โ”‚       โ””โ”€โ”€ relations.py     # Relation SQL builders
โ”‚
โ”œโ”€โ”€ components/              # Shared components
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ dialect.py           # Database dialects
โ”‚   โ””โ”€โ”€ filter_parser.py     # Filter expression parser
โ”‚
โ””โ”€โ”€ databases/               # Database Layer
    โ”œโ”€โ”€ abstract/
    โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ”œโ”€โ”€ pool.py          # Abstract pool
    โ”‚   โ”œโ”€โ”€ session.py       # Abstract session
    โ”‚   โ””โ”€โ”€ types.py         # Settings types
    โ”‚
    โ”œโ”€โ”€ postgres/
    โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ”œโ”€โ”€ pool.py          # PostgreSQL pool
    โ”‚   โ”œโ”€โ”€ session.py       # PostgreSQL sessions
    โ”‚   โ””โ”€โ”€ transaction.py   # Transaction manager
    โ”‚
    โ”œโ”€โ”€ mysql/
    โ”‚   โ”œโ”€โ”€ __init__.py
    โ”‚   โ”œโ”€โ”€ pool.py          # MySQL pool
    โ”‚   โ”œโ”€โ”€ session.py       # MySQL sessions
    โ”‚   โ””โ”€โ”€ transaction.py   # Transaction manager
    โ”‚
    โ””โ”€โ”€ clickhouse/
        โ”œโ”€โ”€ __init__.py
        โ”œโ”€โ”€ pool.py          # ClickHouse pool
        โ””โ”€โ”€ session.py       # ClickHouse session

๐Ÿงช Testing

Running Tests

# Install test dependencies
pip install pytest pytest-asyncio pytest-cov

# Run all tests
pytest

# Verbose output
pytest -v

# Unit tests only
pytest tests/unit/ -v

# Integration tests only (requires DB)
pytest tests/integration/ -v

# Specific file
pytest tests/unit/test_builder.py -v

# Specific test
pytest tests/unit/test_builder.py::TestCRUDBuilder::test_build_search -v

Test Coverage

# Generate coverage report
pytest --cov=dotorm --cov-report=html

# Open report
open htmlcov/index.html

# Console report
pytest --cov=dotorm --cov-report=term-missing

Current Coverage

Name                                    Stmts   Miss  Cover
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
dotorm/__init__.py                         45      0   100%
dotorm/model.py                           285     38    87%
dotorm/fields.py                          198     12    94%
dotorm/exceptions.py                        8      0   100%
dotorm/pydantic.py                        145     23    84%
dotorm/orm/mixins/primary.py              112      8    93%
dotorm/orm/mixins/many2many.py             89     11    88%
dotorm/orm/mixins/relations.py            156     19    88%
dotorm/orm/mixins/ddl.py                   87     15    83%
dotorm/builder/builder.py                  28      0   100%
dotorm/builder/mixins/crud.py             124      5    96%
dotorm/builder/mixins/m2m.py               56      3    95%
dotorm/builder/mixins/relations.py         67      8    88%
dotorm/components/dialect.py               52      2    96%
dotorm/components/filter_parser.py         98      4    96%
dotorm/databases/postgres/session.py       89     12    87%
dotorm/databases/postgres/pool.py          67      9    87%
dotorm/databases/mysql/session.py          78     14    82%
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
TOTAL                                    1784    183    87%

Test Structure

tests/
โ”œโ”€โ”€ conftest.py              # Pytest fixtures
โ”œโ”€โ”€ unit/
โ”‚   โ”œโ”€โ”€ test_fields.py       # Field type tests
โ”‚   โ”œโ”€โ”€ test_model.py        # Model tests
โ”‚   โ”œโ”€โ”€ test_builder.py      # Builder tests
โ”‚   โ”œโ”€โ”€ test_filter.py       # Filter parser tests
โ”‚   โ””โ”€โ”€ test_dialect.py      # Dialect tests
โ”‚
โ”œโ”€โ”€ integration/
โ”‚   โ”œโ”€โ”€ test_postgres.py     # PostgreSQL integration
โ”‚   โ”œโ”€โ”€ test_mysql.py        # MySQL integration
โ”‚   โ”œโ”€โ”€ test_crud.py         # CRUD operations
โ”‚   โ”œโ”€โ”€ test_relations.py    # Relation loading
โ”‚   โ””โ”€โ”€ test_transactions.py # Transaction tests
โ”‚
โ””โ”€โ”€ benchmarks/
    โ”œโ”€โ”€ test_insert.py       # Insert benchmarks
    โ”œโ”€โ”€ test_select.py       # Select benchmarks
    โ””โ”€โ”€ memory_test.py       # Memory profiling

Example Test

# tests/unit/test_builder.py
import pytest
from dotorm.builder import Builder
from dotorm.components import POSTGRES
from dotorm.fields import Integer, Char, Boolean

class TestCRUDBuilder:
    @pytest.fixture
    def builder(self):
        fields = {
            "id": Integer(primary_key=True),
            "name": Char(max_length=100),
            "email": Char(max_length=255),
            "active": Boolean(default=True),
        }
        return Builder(table="users", fields=fields, dialect=POSTGRES)

    def test_build_search(self, builder):
        """Test SELECT query building."""
        stmt, values = builder.build_search(
            fields=["id", "name"],
            filter=[("active", "=", True)],
            limit=10,
            order="ASC",
            sort="name"
        )

        assert "SELECT" in stmt
        assert '"id"' in stmt
        assert '"name"' in stmt
        assert "FROM users" in stmt
        assert "WHERE" in stmt
        assert "ORDER BY name ASC" in stmt
        assert "LIMIT" in stmt
        assert values == (True, 10)

    def test_build_create(self, builder):
        """Test INSERT query building."""
        payload = {"name": "John", "email": "john@example.com"}
        stmt, values = builder.build_create(payload)

        assert "INSERT INTO users" in stmt
        assert "name" in stmt
        assert "email" in stmt
        assert "VALUES" in stmt
        assert values == ("John", "john@example.com")

    def test_build_create_bulk(self, builder):
        """Test bulk INSERT."""
        payloads = [
            {"name": "John", "email": "john@example.com"},
            {"name": "Jane", "email": "jane@example.com"},
        ]
        stmt, all_values = builder.build_create_bulk(payloads)

        assert "INSERT INTO users" in stmt
        assert "(name, email)" in stmt
        assert len(all_values) == 4
        assert all_values == ["John", "john@example.com", "Jane", "jane@example.com"]

๐Ÿ“š API Reference

Fields

Field Python Type SQL Type (PG) Description
Integer int INTEGER 32-bit integer
BigInteger int BIGINT 64-bit integer
SmallInteger int SMALLINT 16-bit integer
Char str VARCHAR(n) String with max length
Text str TEXT Unlimited text
Boolean bool BOOL True/False
Float float DOUBLE PRECISION Floating point
Decimal Decimal DECIMAL(p,s) Precise decimal
Date date DATE Date only
Time time TIME Time only
Datetime datetime TIMESTAMPTZ Date and time
JSONField dict/list JSONB JSON data
Binary bytes BYTEA Binary data
Many2one Model INTEGER FK relation
One2many list[Model] - Reverse FK
Many2many list[Model] - M2M relation
One2one Model - 1:1 relation

Field Parameters

Field(
    primary_key=False,    # Is primary key?
    null=True,            # Allow NULL?
    required=False,       # Required (sets null=False)?
    unique=False,         # Unique constraint?
    index=False,          # Create index?
    default=None,         # Default value
    description=None,     # Field description
    store=True,           # Store in DB?
    compute=None,         # Compute function
)

Model Class Methods

Method Description Returns
create(payload) Create single record int (ID)
create_bulk(payloads) Create multiple records list[dict]
get(id, fields) Get by ID Model | None
search(...) Search with filters list[Model]
table_len() Count records int
get_with_relations(...) Get with relations Model | None
get_many2many(...) Get M2M related list[Model]
link_many2many(...) Create M2M links None
unlink_many2many(...) Remove M2M links None
__create_table__() Create DB table list[str]

Model Instance Methods

Method Description Returns
update(payload, fields) Update record None
delete() Delete record None
json(...) Serialize to dict dict
update_with_relations(...) Update with relations dict

๐Ÿ‘ค Author

ะั€ั‚ั‘ะผ ะจัƒั€ัˆะธะปะพะฒ

GitHub Telegram Email

Python Backend Developer | ORM Enthusiast | Open Source Contributor


๐Ÿค Contributing

We welcome contributions to the project!

# Fork the repository, then:
git clone https://github.com/YOUR_USERNAME/dotorm.git
cd dotorm

# Create virtual environment
python -m venv venv
source venv/bin/activate  # Linux/macOS
# or
.\venv\Scripts\activate   # Windows

# Install dev dependencies
pip install -e ".[dev]"

# Create feature branch
git checkout -b feature/amazing-feature

# After changes
pytest                    # Run tests
black dotorm/             # Format code
mypy dotorm/              # Type check

# Commit and PR
git commit -m "feat: add amazing feature"
git push origin feature/amazing-feature

๐Ÿ“„ License

MIT License

Copyright (c) 2024 Artem Shurshilov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

โญ If you find this project useful, give it a star! โญ

Made with โค๏ธ by Artem Shurshilov

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dotorm-2.0.5.tar.gz (54.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dotorm-2.0.5-py3-none-any.whl (62.1 kB view details)

Uploaded Python 3

File details

Details for the file dotorm-2.0.5.tar.gz.

File metadata

  • Download URL: dotorm-2.0.5.tar.gz
  • Upload date:
  • Size: 54.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dotorm-2.0.5.tar.gz
Algorithm Hash digest
SHA256 eef45425202647e988a70b8a0ec2a6bdde467b9e3df6efabd6eb14590aca8f6e
MD5 7b789612f22c78c50d98b0c36835ac2a
BLAKE2b-256 c389e344256cf780ea68ae8bf3768d0b0b737a949878f258ed489c09e702915e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dotorm-2.0.5.tar.gz:

Publisher: publish.yml on shurshilov/dotorm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dotorm-2.0.5-py3-none-any.whl.

File metadata

  • Download URL: dotorm-2.0.5-py3-none-any.whl
  • Upload date:
  • Size: 62.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dotorm-2.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 661e61ce6bdc23a217ce2f6e912ac16e9ba31925497d65d72905d7c4ba4a4ddb
MD5 ecdaef4344478931eda3be39f33eeaa2
BLAKE2b-256 79c7eb9fd6c6ae67d13e8ea3f6394159f0bc1232485a1a8878aff218657ed09e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dotorm-2.0.5-py3-none-any.whl:

Publisher: publish.yml on shurshilov/dotorm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page