Async Python ORM for PostgreSQL, MySQL and ClickHouse with dot-notation access
Project description
๐ DotORM
High-performance async ORM for Python with PostgreSQL, MySQL and ClickHouse support
Simple, Fast, Type-safe
๐ Table of Contents
- โจ Features
- ๐ฆ Installation
- ๐ Quick Start
- ๐ Usage Examples
- โก Solving the N+1 Problem
- ๐ Benchmarks
- ๐๏ธ Architecture
- ๐งช Testing
- ๐ API Reference
- ๐ค Author
- ๐ License
โจ 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
ะัััะผ ะจัััะธะปะพะฒ
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dotorm-2.0.9.tar.gz.
File metadata
- Download URL: dotorm-2.0.9.tar.gz
- Upload date:
- Size: 67.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eaef92ee5b7ccc3e68abd2a856d9fa88c141c885459551e6326a004d422c60a3
|
|
| MD5 |
cd16f02032490a3fc12096959a4eeb4b
|
|
| BLAKE2b-256 |
4782c3828b5ebaa70d703f480a85c0d4b5135dd9767e8e80911bb1279eecc1a7
|
Provenance
The following attestation bundles were made for dotorm-2.0.9.tar.gz:
Publisher:
publish.yml on shurshilov/dotorm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dotorm-2.0.9.tar.gz -
Subject digest:
eaef92ee5b7ccc3e68abd2a856d9fa88c141c885459551e6326a004d422c60a3 - Sigstore transparency entry: 854573198
- Sigstore integration time:
-
Permalink:
shurshilov/dotorm@250dd8635f402258a045d084e4649c9c732cc321 -
Branch / Tag:
refs/tags/2.0.9 - Owner: https://github.com/shurshilov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@250dd8635f402258a045d084e4649c9c732cc321 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dotorm-2.0.9-py3-none-any.whl.
File metadata
- Download URL: dotorm-2.0.9-py3-none-any.whl
- Upload date:
- Size: 78.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4294d63eb7b4fc9d9fd08824a1361c6d2a512b3d8d63273a8ea2fd4321670cc0
|
|
| MD5 |
d6337deb63051865a4c575ad9e2f1868
|
|
| BLAKE2b-256 |
2ed157a7277ce2822a77120a45d3d9e712dcc014204857a38251929d9405c2ec
|
Provenance
The following attestation bundles were made for dotorm-2.0.9-py3-none-any.whl:
Publisher:
publish.yml on shurshilov/dotorm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dotorm-2.0.9-py3-none-any.whl -
Subject digest:
4294d63eb7b4fc9d9fd08824a1361c6d2a512b3d8d63273a8ea2fd4321670cc0 - Sigstore transparency entry: 854573201
- Sigstore integration time:
-
Permalink:
shurshilov/dotorm@250dd8635f402258a045d084e4649c9c732cc321 -
Branch / Tag:
refs/tags/2.0.9 - Owner: https://github.com/shurshilov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@250dd8635f402258a045d084e4649c9c732cc321 -
Trigger Event:
release
-
Statement type: