Metadata-Version: 2.4
Name: duo-orm
Version: 0.1.4
Summary: An opinionated, modern ORM for Python combining the power of SQLAlchemy 2.0 with a clean, symmetrical API for sync and async operations.
Author-email: Siddhanth Bhimakari <siddhanth.n.b@gmail.com>
Project-URL: Homepage, https://github.com/SiddhanthNB/duo-orm
Project-URL: Bug Tracker, https://github.com/SiddhanthNB/duo-orm/issues
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: sqlalchemy>=2.0
Requires-Dist: alembic>=1.8
Requires-Dist: click>=8.0
Requires-Dist: toml>=0.10
Requires-Dist: aiosqlite>=0.17
Requires-Dist: pydantic<3,>=2.6
Provides-Extra: all
Requires-Dist: psycopg[binary]>=3.1; extra == "all"
Requires-Dist: oracledb>=2.0; extra == "all"
Requires-Dist: pymysql>=1.1; extra == "all"
Requires-Dist: asyncmy>=0.2; extra == "all"
Requires-Dist: pyodbc>=5.0; extra == "all"
Requires-Dist: aioodbc>=0.5; extra == "all"
Requires-Dist: aiosqlite>=0.20; extra == "all"
Provides-Extra: postgresql
Requires-Dist: psycopg[binary]>=3.1; extra == "postgresql"
Provides-Extra: oracle
Requires-Dist: oracledb>=2.0; extra == "oracle"
Provides-Extra: mysql
Requires-Dist: pymysql>=1.1; extra == "mysql"
Requires-Dist: asyncmy>=0.2; extra == "mysql"
Provides-Extra: mssql
Requires-Dist: pyodbc>=5.0; extra == "mssql"
Requires-Dist: aioodbc>=0.5; extra == "mssql"
Provides-Extra: sqlite
Requires-Dist: aiosqlite>=0.20; extra == "sqlite"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.20; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: black>=23.0; extra == "dev"
Requires-Dist: mkdocs>=1.5; extra == "dev"
Requires-Dist: mkdocs-material>=9.0; extra == "dev"
Requires-Dist: mkdocstrings-python>=1.0; extra == "dev"
Dynamic: license-file

<p align="center">
<a href="https://duo-orm.readthedocs.io/">
  <img src="https://raw.githubusercontent.com/SiddhanthNB/duo-orm/main/docs/assets/logo.svg" width="200" alt="DuoORM Logo">
</a>
</p>
<p align="center">
    <a href="https://pypi.org/project/duo-orm/">
        <img src="https://img.shields.io/pypi/v/duo-orm.svg?cacheSeconds=300" alt="PyPI version">
    </a>
    <a href="https://pypi.org/project/duo-orm/">
        <img src="https://img.shields.io/pypi/pyversions/duo-orm.svg?cacheSeconds=300" alt="Python versions">
    </a>
    <a href="https://duo-orm.readthedocs.io/">
        <img src="https://img.shields.io/badge/docs-readthedocs-blue.svg" alt="Docs">
    </a>
</p>

# DuoORM

DuoORM is a modern ORM built on SQLAlchemy 2.0 that empowers your data models, turning them into a powerful and expressive query interface. It's designed for developers who love clean, symmetrical sync/async APIs and explicit control over their unit of work, without sacrificing the power of the underlying SQLAlchemy Core.

## Key Features

- **Symmetrical Sync & Async API:** Write your queries once. Use `await` in an async context or call directly in a sync context. The API is identical.
- **Fluent, Model-Centric API:** Chainable methods like `.where()`, `.order_by()`, and `.limit()` flow directly from your models. CRUD is intuitive with methods like `Model.create()` and `instance.save()`.
- **Explicit Unit of Work:** By default, every operation is a single, isolated statement. For complex workflows, use the `db.transaction()` context manager to share a single session and guarantee atomicity.
- **Automated Driver Management:** Use clean, driverless database URLs (e.g., `postgresql://...`). DuoORM automatically injects the correct, high-performance sync and async drivers for you.
- **First-Class Pydantic Integration:** Use Pydantic models for validated data creation and updates right out of the box, or use plain dictionaries. The choice is yours.
- **Powerful Escape Hatch:** Never get blocked. Drop down to raw SQLAlchemy at any time by calling `.alchemize()` on any query to get the underlying `Select` object for advanced SQL needs.

## Installation

```bash
# Core library with SQLite support
pip install duo-orm

# Or install with a specific database driver
pip install "duo-orm[postgresql]"
pip install "duo-orm[mysql]"
```

## Quickstart

Getting started with DuoORM is a simple four-step process.

### 0. Initialize your Project

DuoORM includes a handy CLI to set up a recommended project structure for your database code.

```bash
# This creates a `db/` directory with models, schemas, and migrations.
$ duo-orm init
```

### 1. Define your Models

First, configure your database and define your ORM models in a Python file (e.g., `db/models.py`).

```python
from duo_orm import Database, Mapped, mapped_column

# Configure the database with a clean, driverless URL
db = Database("sqlite:///app.db")

# Define an ORM model
class User(db.Model):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    age: Mapped[int]
```

### 2. Create Database Tables

Next, use the `duo-orm` CLI in your terminal to create the database tables from your models. This provides a safe, version-controlled schema for your application.

```bash
# First, generate a migration script from your models
$ duo-orm migration create "Initial user schema"

# Then, apply the migration to the database
$ duo-orm migration upgrade
```

### 3. Query your Data

With your tables created, you can now use DuoORM's fluent API to interact with your database in any Python script.

```python
import asyncio
from db.models import User # Import your model from the file you created

async def query_users():
    # Create a user (in an async context)
    ada = await User.create({"name": "Ada Lovelace", "age": 35})
    print(f"Created: {ada.name}")

    # Find a user (the API is the same in sync contexts, just without `await`)
    found_user = await User.where(User.name == "Ada Lovelace").first()
    print(f"Found: {found_user.name}")

# Run the async function
asyncio.run(query_users())
```

## Full Documentation

For detailed guides on all features, including transactions, Pydantic integration, database migrations, and advanced queries, please see the full documentation on **[ReadTheDocs](https://duo-orm.readthedocs.io/)**.

## Contributing

Contributions are welcome! Please feel free to open an issue or submit a pull request.

## License

This project is licensed under the MIT License.
