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.8+ 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.1.1.tar.gz (12.0 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.1.1-py3-none-any.whl (13.6 kB view details)

Uploaded Python 3

File details

Details for the file ormodel-0.1.1.tar.gz.

File metadata

  • Download URL: ormodel-0.1.1.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for ormodel-0.1.1.tar.gz
Algorithm Hash digest
SHA256 bf00067b9d0a207d7c6d63bf485c495a90c88dc6e3ec7e34016b360b0ffc02ef
MD5 75cb2f41a92c7ce5e440e967f9f63087
BLAKE2b-256 b592dfa0537197b4f773c6e0aa430808c6b516e7255dbeb3417f169ae5f574d9

See more details on using hashes here.

File details

Details for the file ormodel-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: ormodel-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 13.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.11

File hashes

Hashes for ormodel-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 750bc1d3c49921006d9c52aec106b42fe60699681094b8acd35959c0f77f28f6
MD5 c9ea068aa40d1459f7c6aabaa687cba8
BLAKE2b-256 1b1e548c56122f5f95371115f4cc030fd8e8016c7e027973b50af0994459da0a

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