Skip to main content

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 upon sqlmodel). Define schema using sqlmodel.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, and ormodel.database_context for setup/teardown, but the application calls them.
  • Session Scoping: Library provides ormodel.get_session async context manager. Application must use this (e.g., in middleware) for the implicit Model.objects manager 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.

  1. Clone the repository (or create files from source):

    # git clone https://github.com/yourusername/ormodel.git
    cd ormodel
    
  2. Create and activate virtual environment:

    uv venv .venv
    source .venv/bin/activate  # or .\venv\Scripts\activate on Windows
    
  3. 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: Set sqlalchemy.url = %(SQLA_URL)s.
  • Configure examples/alembic/env.py:
    • Import metadata from ormodel.
    • 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.
  • 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

  1. Ensure dependencies are installed (uv pip install -e ".[dev]").

  2. Navigate to the examples/ directory.

  3. Create/configure your .env file.

  4. Run database migrations: alembic upgrade head.

  5. 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

ormodel-0.2.0.tar.gz (10.8 kB view details)

Uploaded Source

Built Distribution

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

ormodel-0.2.0-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

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

Hashes for ormodel-0.2.0.tar.gz
Algorithm Hash digest
SHA256 43c3ea286884c753d4383d7829af456e482c1ad6fef25e4ed4ae4eaa6b737235
MD5 e8770c5e2e19c6e57322d7811e3b35f7
BLAKE2b-256 00c24cf9852e7000d0d7cc160a7fdc11935c2ecc4e47aaab897d11d34172ebe0

See more details on using hashes here.

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

Hashes for ormodel-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 edf15404ca7bc6ee6f4cd7e2225bba9e04c9e0bd255777f0a25273459d815d12
MD5 73e2f4922dc5f039d0f97fa5dbbf44c2
BLAKE2b-256 ee81d46430e1629264918f8d3d1adead1198bbd907b16ce4704bc1fc38b7aa21

See more details on using hashes here.

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