An async ORM using SQLModel with a Django-like query syntax.
Project description
ORModel
An asynchronous ORM library leveraging SQLModel features, providing a Django ORM-like query syntax (Model.objects). Built for use with asyncio and frameworks like FastAPI. Managed with uv.
Features
- Model Definition: Inherit from
ormodel.ORModel(built uponsqlmodel). Define schema usingsqlmodel.Field. - Django-Style Manager: Access database operations via
YourModel.objects. - Async Queries:
.all(),.filter(),.get(),.create(),.get_or_create(),.update_or_create(),.count(),.delete(). - Application-Managed DB Lifecycle: Library provides
ormodel.init_database,ormodel.shutdown_database, andormodel.database_contextfor setup/teardown, but the application calls them. - Session Scoping: Library provides
ormodel.get_sessionasync context manager. Application must use this (e.g., in middleware) for the implicitModel.objectsmanager to work. - Uses SQLAlchemy 2.0+: Leverages modern async SQLAlchemy.
- Alembic Compatible: Designed for use with Alembic for database migrations (managed by the application).
Installation
Requires Python 3.11+ and uv.
-
Clone the repository (or create files from source):
# git clone https://github.com/yourusername/ormodel.git cd ormodel
-
Create and activate virtual environment:
uv venv .venv source .venv/bin/activate # or .\venv\Scripts\activate on Windows
-
Install:
# Install in editable mode with development dependencies uv pip install -e ".[dev]"
Configuration (Application Responsibility)
Example (examples/.env):
# Async database connection string for the application
DATABASE_URL="sqlite+aiosqlite:///./example_app.db"
# Example: DATABASE_URL="postgresql+asyncpg://user:pass@host/db"
# Sync database connection string for Alembic migrations
ALEMBIC_DATABASE_URL="sqlite:///./example_app.db"
# Example: ALEMBIC_DATABASE_URL="postgresql+psycopg2://user:pass@host/db"
# Optional: Echo SQL statements
ECHO_SQL=False
Usage Guide
1. Database Initialization & Shutdown (Application)
Your application must initialize the ORModel database connection pool on startup and shut it down gracefully on exit.
For Servers (e.g., FastAPI Lifespan):
# examples/api.py (FastAPI Lifespan Snippet)
from contextlib import asynccontextmanager
from fastapi import FastAPI
# Import library functions and application's config loader
from ormodel import init_database, shutdown_database
from examples.config import get_settings
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings() # Load app config
print(f"Initializing database: {settings.DATABASE_URL}")
init_database(database_url=settings.DATABASE_URL, echo_sql=settings.ECHO_SQL)
yield # Application runs
print("Shutting down database...")
await shutdown_database()
app = FastAPI(lifespan=lifespan)
For Standalone Scripts:
Use the ormodel.database_context manager.
# examples/standalone.py (Snippet)
import asyncio
from ormodel import database_context, get_session # ... other imports
from examples.config import get_settings
from examples.models import Team # ...
async def main():
settings = get_settings()
async with database_context(settings.DATABASE_URL, echo_sql=settings.ECHO_SQL):
# Database is initialized here and shutdown automatically on exit
async with get_session() as session:
# Perform ORM operations
count = await Team.objects.count()
print(f"Team count: {count}")
asyncio.run(main_script())
2. Define Models
Inherit from ormodel.ORModel and use sqlmodel.Field / sqlmodel.Relationship.
# examples/models.py
from typing import Optional, List
from sqlmodel import Field, Relationship
from ormodel import ORModel # Import the base class
class Team(ORModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
heroes: List["Hero"] = Relationship(back_populates="team")
class Hero(ORModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
age: Optional[int] = Field(default=None, index=True)
team_id: Optional[int] = Field(default=None, foreign_key="team.id")
team: Optional[Team] = Relationship(back_populates="heroes")
3. Database Migrations (Alembic - Application Responsibility)
Use Alembic within your application's structure (e.g., examples/) to manage schema changes.
- Initialize:
cd examples && alembic init alembic - Configure
examples/alembic.ini: Setsqlalchemy.url = %(SQLA_URL)s. - Configure
examples/alembic/env.py:- Import
metadatafromormodel. - Import your application's models (e.g.,
import examples.models). - Import your application's settings loader (e.g.,
from examples.config import get_settings). - Set
target_metadata = metadata. - Load settings and configure Alembic context with
settings.ALEMBIC_DATABASE_URL.
- Import
- Generate:
alembic revision --autogenerate -m "..." - Apply:
alembic upgrade head
4. Session Management (Application Middleware / Context)
To use the implicit Model.objects manager, the application must manage the session context using ormodel.get_session.
FastAPI Middleware Example:
# examples/api.py (Middleware Snippet)
from fastapi import FastAPI, Request
from ormodel import get_session # Import library's context manager
app = FastAPI(...) # Lifespan should call init_database
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
try:
async with get_session() as session: # Use library's session manager
response = await call_next(request)
# Commit handled by get_session context manager on success
except Exception as e:
# Rollback handled by get_session context manager on error
# Re-raise or return appropriate error response
raise e
return response
Direct Context Usage (e.g., in standalone scripts):
# examples/standalone.py (Snippet inside database_context)
from ormodel import get_session
async with get_session() as session:
# Use Model.objects or session directly here
hero = await Hero.objects.create(...)
# Commit/rollback handled by 'async with get_session()'
5. Querying Examples
Use YourModel.objects within an active session scope managed by ormodel.get_session.
# Assuming code runs inside an 'async with get_session():' block
# Create
hero = await Hero.objects.create(name="Flash", secret_name="Barry", age=28)
# Get (Raises DoesNotExist or MultipleObjectsReturned)
the_flash = await Hero.objects.get(name="Flash")
# Filter (Returns a Query object)
query_young = Hero.objects.filter(Hero.age < 30) # Use SQLAlchemy expressions
# Execute Query
young_heroes = await query_young.all()
first_young = await query_young.first()
num_young = await query_young.count()
# Chaining
preventers = await Team.objects.get(name="Preventers")
preventer_heroes = await Hero.objects.filter(team_id=preventers.id).order_by(Hero.name).all()
# Get or Create
team, created = await Team.objects.get_or_create(name="Titans", defaults={"headquarters": "Titans Tower"})
# Update or Create
hero, created = await Hero.objects.update_or_create(name="Flash", defaults={"age": 29})
# Delete
await Hero.objects.delete(hero)
Running the Example Application
-
Ensure dependencies are installed (
uv pip install -e ".[dev]"). -
Navigate to the
examples/directory. -
Create/configure your
.envfile. -
Run database migrations:
alembic upgrade head. -
Run the desired example:
-
API Server:
# From the project root directory (ormodel/) python examples/api.py # OR using uvicorn directly for more options # uvicorn examples.api:app --reload --host 0.0.0.0 --port 8000
Access the API docs at
http://localhost:8000/docs. -
Standalone Script:
# From the project root directory (ormodel/) python examples/standalone.py
-
Testing
The library includes tests using pytest, pytest-asyncio, and httpx. Tests are configured in tests/conftest.py to use an in-memory SQLite database, ensuring isolation.
# Run tests from the project root
pytest -v
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
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 ormodel-0.2.0.tar.gz.
File metadata
- Download URL: ormodel-0.2.0.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43c3ea286884c753d4383d7829af456e482c1ad6fef25e4ed4ae4eaa6b737235
|
|
| MD5 |
e8770c5e2e19c6e57322d7811e3b35f7
|
|
| BLAKE2b-256 |
00c24cf9852e7000d0d7cc160a7fdc11935c2ecc4e47aaab897d11d34172ebe0
|
File details
Details for the file ormodel-0.2.0-py3-none-any.whl.
File metadata
- Download URL: ormodel-0.2.0-py3-none-any.whl
- Upload date:
- Size: 12.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
edf15404ca7bc6ee6f4cd7e2225bba9e04c9e0bd255777f0a25273459d815d12
|
|
| MD5 |
73e2f4922dc5f039d0f97fa5dbbf44c2
|
|
| BLAKE2b-256 |
ee81d46430e1629264918f8d3d1adead1198bbd907b16ce4704bc1fc38b7aa21
|