Universal, framework-independent ORM for Python.
Project description
AkronORM
Universal, framework-independent ORM for Python 🚀
Akron is a modern Python ORM that provides a unified interface for working with multiple databases. Whether you're using SQLite, MySQL, PostgreSQL, or MongoDB, Akron gives you the same clean, type-safe API across all platforms.
✨ Key Features
Core Capabilities
- 🔄 Universal Database Support - One API for SQLite, MySQL, PostgreSQL, and MongoDB
- 🛡️ Type Safety - Full Pydantic integration for type-safe models and validation
- 🔧 Zero Configuration - Works out of the box with simple connection strings
- � Framework Independent - Works with any Python framework or standalone scripts
Advanced ORM Features
- 📊 Advanced Querying - QueryBuilder with filtering, sorting, pagination, and joins
- 🔍 Smart Operators - Support for
gt,lt,in,like,isnulland more - 📈 Aggregations - Built-in
sum,count,avg,min,maxwith GROUP BY - 🔄 Transactions - Context managers and manual transaction control
- ⚡ Bulk Operations - Efficient bulk insert, update, and delete operations
- 🗂️ Indexing - Create and manage database indexes for performance
- 🔗 Relationships - Foreign key constraints and multi-table operations
- �️ Raw SQL - Execute custom SQL when needed
- 💾 Serialization - Convert results to JSON and dictionaries
Developer Experience
- 📦 Schema Management - Declarative schema with automatic migrations via
akron.json - ⚡ CLI Tools - Modern command-line interface (
akron db init,migrate, etc.) - 🧪 Well Tested - Comprehensive test coverage across all database drivers
- � Rich Documentation - Complete guides and examples for all features
🚀 Quick Start
Installation
pip install akron
Basic Usage
from akron import Akron
# Initialize database connection
db = Akron("sqlite:///example.db")
# Create table with relationships
db.create_table("users", {
"id": "int",
"name": "str",
"email": "str",
"age": "int",
"active": "bool"
})
db.create_table("posts", {
"id": "int",
"title": "str",
"content": "str",
"user_id": "int->users.id", # Foreign key to users table
"published": "bool"
})
# Insert data
user_id = db.insert("users", {
"name": "Alice Johnson",
"email": "alice@example.com",
"age": 28,
"active": True
})
# Bulk insert
post_ids = db.bulk_insert("posts", [
{"title": "Hello World", "content": "My first post", "user_id": user_id, "published": True},
{"title": "Python Tips", "content": "Some useful tips", "user_id": user_id, "published": False}
])
# Advanced querying with QueryBuilder
published_posts = db.query("posts").where(
published=True,
user_id=user_id
).order_by("-created_at").limit(10).all()
# Find with operators
young_users = db.query("users").where(age__lt=30, active=True).all()
# Aggregations
user_stats = db.aggregate("posts", {
"post_count": "count",
"avg_views": "avg"
}, group_by=["user_id"])
# Transactions
with db.transaction():
new_user_id = db.insert("users", {"name": "Bob", "email": "bob@example.com", "age": 25, "active": True})
db.insert("posts", {"title": "Bob's First Post", "user_id": new_user_id, "published": True})
# Count and existence checks
total_users = db.count("users")
has_admin = db.exists("users", {"email": "admin@example.com"})
# Raw SQL for complex queries
user_post_stats = db.raw("""
SELECT u.name, COUNT(p.id) as post_count
FROM users u LEFT JOIN posts p ON u.id = p.user_id
GROUP BY u.id, u.name
ORDER BY post_count DESC
""")
# Close connection
db.close()
Type-Safe Models with Pydantic
from pydantic import BaseModel
from akron import Akron
from akron.models import ModelMixin
from typing import Optional
class User(BaseModel, ModelMixin):
id: Optional[int] = None
name: str
email: str
age: int
is_active: bool = True
# Initialize database
db = Akron("sqlite:///users.db")
# Create table from model
User.create_table(db)
# Insert with type safety
new_user = User(name="Bob Smith", email="bob@example.com", age=25)
user_id = User.insert(db, new_user)
# Query with automatic deserialization
users = User.find(db) # Returns List[User]
active_users = User.find(db, {"is_active": True})
# Update and delete
User.update(db, {"id": user_id}, {"age": 26})
User.delete(db, {"email": "bob@example.com"})
Django-like Foreign Keys
from pydantic import BaseModel
from akron.models import ModelMixin
from typing import Optional
class User(BaseModel, ModelMixin):
id: Optional[int] = None
name: str
email: str
class Post(BaseModel, ModelMixin):
id: Optional[int] = None
title: str
content: str
author_id: int
category_id: Optional[int] = None
published: bool = False
# 🔥 Django-like foreign key definitions - Simple and clean!
__foreign_keys__ = {
'author_id': 'users.id',
'category_id': 'categories.id'
}
class Comment(BaseModel, ModelMixin):
id: Optional[int] = None
post_id: int
author_name: str
content: str
__foreign_keys__ = {
'post_id': 'posts.id'
}
# Create tables with automatic foreign key constraints
db = Akron("sqlite:///blog.db")
User.create_table(db) # Creates users table
Post.create_table(db) # Creates posts table with foreign keys to users
Comment.create_table(db) # Creates comments table with foreign key to posts
# Use normally with full type safety and relationship validation
user = User(name="Alice", email="alice@example.com")
user_id = User.insert(db, user)
post = Post(title="Hello World", content="...", author_id=user_id, published=True)
Post.insert(db, post)
🗄️ Database Support
| Database | Connection String Example | CRUD | Foreign Keys | Migrations | CLI Support |
|---|---|---|---|---|---|
| SQLite | sqlite:///path/to/db.db |
✅ | ✅ | ✅ | ✅ |
| MySQL | mysql://user:pass@host:port/dbname |
✅ | ✅ | ✅ | ✅ |
| PostgreSQL | postgres://user:pass@host:port/dbname |
✅ | ✅ | ✅ | ✅ |
| MongoDB | mongodb://host:port/dbname |
✅ | ❌* | Schemaless | ✅ |
*MongoDB doesn't support traditional foreign keys but maintains the same API for document references.
Connection Examples
# SQLite (file-based)
db = Akron("sqlite:///myapp.db")
# SQLite (in-memory)
db = Akron("sqlite:///:memory:")
# MySQL
db = Akron("mysql://username:password@localhost:3306/mydatabase")
# PostgreSQL
db = Akron("postgres://username:password@localhost:5432/mydatabase")
# MongoDB
db = Akron("mongodb://localhost:27017/mydatabase")
🔗 Advanced Features
Foreign Key Relationships
# Create tables with foreign key relationships
db.create_table("users", {
"id": "int",
"name": "str",
"email": "str"
})
db.create_table("orders", {
"id": "int",
"user_id": "int->users.id", # Foreign key syntax
"product_name": "str",
"amount": "float",
"status": "str"
})
# Insert related data
user_id = db.insert("users", {"name": "Alice", "email": "alice@example.com"})
order_id = db.insert("orders", {
"user_id": user_id,
"product_name": "Laptop",
"amount": 999.99,
"status": "pending"
})
# Query with relationships
user_orders = db.find("orders", {"user_id": user_id})
Complex Queries with QueryBuilder
# Advanced filtering with operators
results = db.query("users").where(
age__gte=25, # age >= 25
name__like="John%", # name starts with "John"
active=True # active = True
).order_by("-created_at").limit(10).all()
# Pagination made simple
page_1 = db.query("posts").paginate(page=1, per_page=20)
page_2 = db.query("posts").paginate(page=2, per_page=20)
# Joins and aggregations
user_stats = db.query("users").join(
"posts", on="users.id = posts.user_id"
).select([
"users.name",
"COUNT(posts.id) as post_count"
]).group_by("users.id").all()
# Bulk operations for performance
db.bulk_insert("products", [
{"name": "Product 1", "price": 19.99},
{"name": "Product 2", "price": 29.99},
{"name": "Product 3", "price": 39.99}
])
# Atomic transactions
with db.transaction():
user_id = db.insert("users", {"name": "Charlie", "email": "charlie@example.com"})
db.insert("profiles", {"user_id": user_id, "bio": "New user profile"})
# All operations committed together or rolled back on error
Performance and Indexing
# Create indexes for faster queries
db.create_index("users", ["email"]) # Single column index
db.create_index("orders", ["user_id", "status"]) # Composite index
# Performance optimization with exists()
if db.exists("users", {"email": "admin@example.com"}):
print("Admin user found")
# Count records efficiently
total_active_users = db.count("users", {"active": True})
# Raw SQL for complex operations
results = db.raw("""
SELECT category, AVG(price) as avg_price, COUNT(*) as item_count
FROM products
WHERE active = 1
GROUP BY category
HAVING COUNT(*) > 5
ORDER BY avg_price DESC
""")
Pydantic Model Relationships
class User(BaseModel, ModelMixin):
id: int
name: str
email: str
age: int
class Order(BaseModel, ModelMixin):
id: int
user_id: int # Foreign key reference
product_name: str
amount: float
status: str
# Create tables
User.create_table(db)
Order.create_table(db)
# Work with related models
user = User(id=1, name="Charlie", email="charlie@example.com", age=30)
User.insert(db, user)
order = Order(id=1, user_id=1, product_name="Book", amount=24.99, status="shipped")
Order.insert(db, order)
# Query relationships
user_orders = Order.find(db, {"user_id": 1})
🔄 Schema Management (Prisma-like)
Akron now supports Prisma-like schema management with akron.json configuration files:
Initialize a New Project
# Initialize with SQLite (default)
akron db init
# Initialize with specific database
akron db init --provider postgresql --url "postgres://user:pass@localhost:5432/mydb"
akron db init --provider mysql --url "mysql://user:pass@localhost:3306/mydb"
akron db init --provider mongodb --url "mongodb://localhost:27017/mydb"
Define Your Schema
Edit the generated akron.json file:
{
"database": {
"provider": "sqlite",
"url": "sqlite:///app.db"
},
"tables": {
"users": {
"columns": {
"id": {
"type": "int",
"primary_key": true,
"auto_increment": true
},
"email": {
"type": "str",
"unique": true,
"nullable": false
},
"username": {
"type": "str",
"unique": true,
"nullable": false,
"max_length": 50
},
"created_at": {
"type": "datetime",
"default": "CURRENT_TIMESTAMP"
}
}
},
"posts": {
"columns": {
"id": {
"type": "int",
"primary_key": true,
"auto_increment": true
},
"title": {
"type": "str",
"nullable": false,
"max_length": 200
},
"content": {
"type": "text",
"nullable": true
},
"author_id": {
"type": "int",
"nullable": false
},
"published": {
"type": "bool",
"default": false
}
},
"foreign_keys": {
"author_id": {
"references": "users",
"column": "id",
"on_delete": "CASCADE"
}
}
}
}
}
Generate and Apply Migrations
# Generate migrations from schema changes
akron db makemigrations --name "add_user_posts"
# Preview what will be migrated
akron db migrate --dry-run
# Apply migrations
akron db migrate
# Check migration status
akron db status
Schema Management Workflow
- Initialize:
akron db initcreatesakron.jsonand.akron/directory - Define: Edit
akron.jsonto define your database schema - Generate:
akron db makemigrationscreates migration files - Apply:
akron db migrateapplies pending migrations - Monitor:
akron db statusshows current state
🔄 Legacy Migration System
For backward compatibility, Akron still supports the original migration commands:
Auto-Generate Migrations
# Create a migration for schema changes
akron makemigrations users --db sqlite:///app.db --schema '{"id": "int", "name": "str", "email": "str", "created_at": "str"}'
# Apply migrations
akron migrate users --db sqlite:///app.db
# View migration history
akron showmigrations users --db sqlite:///app.db
Migration Features
- Automatic Schema Diffing - Compares current vs target schema
- Migration File Generation - Creates JSON migration files in
migrations/directory - Version Tracking - Maintains migration history in
_akron_migrationstable - Rollback Support - Track applied migrations for potential rollbacks
🛠️ CLI Commands
Akron provides two command interfaces: modern schema management and legacy commands.
Modern Schema Management Commands
# Initialize a new Akron project
akron db init # SQLite default
akron db init --provider postgresql --url "..." # PostgreSQL
akron db init --provider mysql --url "..." # MySQL
akron db init --provider mongodb --url "..." # MongoDB
# Generate migrations from schema changes
akron db makemigrations # Auto-named migration
akron db makemigrations --name "add_user_table" # Custom name
# Apply migrations
akron db migrate # Apply all pending
akron db migrate --dry-run # Preview changes
# Check status
akron db status # Show schema and migration status
# Reset database (planned)
akron db reset --force # Drop all and reapply
Legacy Commands (Backward Compatibility)
# Table Management
akron create-table users --db sqlite:///app.db --schema '{"id": "int", "name": "str"}'
akron drop-table users --db sqlite:///app.db
akron inspect-schema users --db sqlite:///app.db
# Data Management
akron seed users --db sqlite:///app.db --data '{"name": "John", "email": "john@example.com"}'
akron raw-sql --db sqlite:///app.db --sql "SELECT COUNT(*) FROM users"
# Legacy Migration Commands
akron makemigrations orders --db mysql://user:pass@localhost/shop --schema '{"id": "int"}'
akron migrate orders --db mysql://user:pass@localhost/shop
akron showmigrations orders --db mysql://user:pass@localhost/shop
📊 Type System
Akron provides a flexible type system that maps Python types to database-specific types:
Supported Python Types
| Python Type | SQL Databases | MongoDB |
|---|---|---|
int |
INTEGER | Number |
str |
VARCHAR/TEXT | String |
float |
REAL/DOUBLE | Number |
bool |
BOOLEAN | Boolean |
Custom Type Mapping
# Define custom field types in Pydantic models
from pydantic import Field
from datetime import datetime
class User(BaseModel, ModelMixin):
id: int
name: str
email: str = Field(..., max_length=255)
age: int = Field(..., ge=0, le=150)
created_at: str # Store as ISO string
is_premium: bool = False
🔧 Configuration & Best Practices
Connection Pooling
# For production use, consider connection pooling
class DatabaseManager:
def __init__(self, db_url: str):
self.db_url = db_url
self._db = None
def get_db(self):
if self._db is None:
self._db = Akron(self.db_url)
return self._db
def close(self):
if self._db:
self._db.close()
self._db = None
# Usage
db_manager = DatabaseManager("sqlite:///app.db")
db = db_manager.get_db()
Error Handling
from akron.exceptions import AkronError, TableNotFoundError, SchemaError
try:
db = Akron("sqlite:///myapp.db")
users = db.find("nonexistent_table")
except TableNotFoundError:
print("Table doesn't exist - creating it...")
db.create_table("users", {"id": "int", "name": "str"})
except AkronError as e:
print(f"Database error: {e}")
Environment-Based Configuration
import os
from akron import Akron
# Use environment variables for database configuration
DATABASE_URL = os.getenv(
"DATABASE_URL",
"sqlite:///default.db" # fallback for development
)
db = Akron(DATABASE_URL)
🧪 Testing
Akron includes comprehensive test coverage and provides utilities for testing:
Test Database Setup
import pytest
from akron import Akron
from akron.models import ModelMixin
from pydantic import BaseModel
class User(BaseModel, ModelMixin):
id: int
name: str
email: str
@pytest.fixture
def test_db():
# Use in-memory database for testing
db = Akron("sqlite:///:memory:")
User.create_table(db)
yield db
db.close()
def test_user_creation(test_db):
user = User(id=1, name="Test User", email="test@example.com")
User.insert(test_db, user)
users = User.find(test_db, {"email": "test@example.com"})
assert len(users) == 1
assert users[0].name == "Test User"
Running Tests
# Run the full test suite
pytest tests/
# Run specific database tests
pytest tests/test_sqlite_pytest.py
pytest tests/test_mysql_pytest.py
📈 Performance Considerations
Batch Operations
# For large datasets, consider batch insertions
users_data = [
{"name": f"User {i}", "email": f"user{i}@example.com", "age": 20 + i}
for i in range(1000)
]
for user_data in users_data:
db.insert("users", user_data)
Connection Management
# Always close connections in production
try:
db = Akron("mysql://user:pass@localhost/prod_db")
# ... database operations ...
finally:
db.close()
# Or use context managers (if implementing __enter__/__exit__)
🔍 Examples
Check out the examples/ directory for more comprehensive examples:
basic_crud.py- Basic CRUD operationssqlite_multi_table.py- Multi-table relationships with SQLitepostgres_multi_table.py- PostgreSQL with foreign keys
🆚 Version Information
- Current Version: v0.1.5
- Python Requirements: Python 3.7+
- Dependencies:
pydantic- Type safety and validationmysql-connector-python- MySQL supportpsycopg2- PostgreSQL supportpymongo- MongoDB support
Changelog
See CHANGELOG.md for detailed version history and updates.
📚 Quick Reference
Essential Operations Cheat Sheet
# Basic CRUD
db.insert("table", {"field": "value"})
db.find("table", {"field": "value"})
db.update("table", {"id": 1}, {"field": "new_value"})
db.delete("table", {"field": "value"})
# Advanced Querying
db.query("table").where(age__gte=25, active=True).order_by("-created_at").limit(10).all()
db.query("table").paginate(page=1, per_page=20)
# Aggregations
db.count("table", {"active": True})
db.aggregate("table", {"total": "sum", "avg_price": "avg"})
# Bulk Operations
db.bulk_insert("table", [{"name": "A"}, {"name": "B"}])
db.bulk_update("table", {"active": False}, {"status": "inactive"})
# Transactions
with db.transaction():
# Multiple operations here
# Performance
db.exists("table", {"email": "user@example.com"})
db.create_index("table", ["email", "status"])
db.raw("SELECT * FROM table WHERE custom_condition")
QueryBuilder Operators
| Operator | Example | SQL Equivalent |
|---|---|---|
field |
where(age=25) |
WHERE age = 25 |
field__gt |
where(age__gt=18) |
WHERE age > 18 |
field__gte |
where(age__gte=18) |
WHERE age >= 18 |
field__lt |
where(age__lt=65) |
WHERE age < 65 |
field__lte |
where(age__lte=65) |
WHERE age <= 65 |
field__in |
where(status__in=["active", "pending"]) |
WHERE status IN (...) |
field__like |
where(name__like="John%") |
WHERE name LIKE 'John%' |
field__isnull |
where(deleted_at__isnull=True) |
WHERE deleted_at IS NULL |
🤝 Contributing
We welcome contributions! Please feel free to:
- Report Issues - Found a bug? Let us know!
- Feature Requests - Have an idea? We'd love to hear it!
- Pull Requests - Code contributions are always welcome!
Development Setup
# Clone the repository
git clone https://github.com/Akash-nath29/akron.git
cd akron
# Install development dependencies
pip install -e .
pip install pytest
# Run tests
pytest tests/
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Links
- PyPI Package: https://pypi.org/project/akron/
- GitHub Repository: https://github.com/Akash-nath29/akron
- Documentation: https://akron-website.vercel.app/docs
- Issues & Support: https://github.com/Akash-nath29/akron/issues
Made with ❤️ by the Akron team
⭐ Star us on GitHub if you find Akron useful! ⭐
🔄 Transactions & Atomic Operations
Transactions let you group multiple database operations into a single, all-or-nothing unit. This means either all changes succeed together, or none are applied if something fails. This is essential for keeping your data safe and consistent.
Why Use Transactions?
- Data Integrity: Prevents partial updates and keeps your database consistent.
- Automatic Rollback: If any operation fails, all changes are undone automatically.
- Business Logic: Ensures complex operations (like money transfers, order processing) are atomic.
Example Usage
with db.transaction():
user_id = db.insert("users", {"name": "Alice"})
db.insert("profiles", {"user_id": user_id})
# If any step fails, all changes are rolled back!
Real-World Scenarios
- Money Transfer: Deduct from one account, add to another, log the transaction. If any step fails, no money is lost.
- Order Processing: Charge customer, reduce inventory, create order record. If payment fails, inventory isn't reduced.
- User Registration: Create account, profile, send email. If any step fails, no partial user is created.
How It Works
- Akron starts a transaction when you enter the
with db.transaction()block. - If all operations succeed, changes are committed.
- If any operation fails, Akron automatically rolls back all changes.
Best Practices
- Use transactions for any set of operations that must succeed together.
- Don't use transactions for simple, single-step reads or writes.
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 akron-0.3.1.tar.gz.
File metadata
- Download URL: akron-0.3.1.tar.gz
- Upload date:
- Size: 41.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92710939002cffdd251b31354c136c04f2da7a042793f27107d16881d4533b03
|
|
| MD5 |
5aeee452d4b553047a863c27ea84dbbd
|
|
| BLAKE2b-256 |
1e2574ef8f1eb606b5def1804e77b8f2c542899d87bd151926e3f312e08bc59d
|
File details
Details for the file akron-0.3.1-py3-none-any.whl.
File metadata
- Download URL: akron-0.3.1-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e0bbd7113037c58cee1e12ce7c4eeeb3baf42aa478d8501c26b321b11abc71a7
|
|
| MD5 |
5da1107373c4d2ed340578a366113231
|
|
| BLAKE2b-256 |
e3eb905d97771dc407f36cee718c1eca9c1d1389f4f3b22ce98aae61a6b552bb
|