Metadata-Version: 2.4
Name: ormx-py
Version: 4.4.3
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Typing :: Typed
Requires-Dist: pydantic>=2.6
Requires-Dist: typing-extensions>=4.0.0
Requires-Dist: pytest>=7.0 ; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21 ; extra == 'dev'
Requires-Dist: ruff>=0.4 ; extra == 'dev'
Requires-Dist: mypy>=1.10 ; extra == 'dev'
Requires-Dist: httpx>=0.27 ; extra == 'dev'
Requires-Dist: mkdocs-material>=9.5 ; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.26 ; extra == 'docs'
Provides-Extra: dev
Provides-Extra: docs
License-File: LICENSE
Summary: Pydantic-native async ORM with sharding, Rust core, and one-class-three-roles design
Keywords: orm,postgres,postgresql,asyncpg,sqlx,rust,pydantic
Author: ORMX Dev Team
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Documentation, https://ormx.dev/docs
Project-URL: Homepage, https://ormx.dev
Project-URL: Issues, https://github.com/vexarr-stack/python.ormx.vexarr.com/issues
Project-URL: Repository, https://github.com/vexarr-stack/python.ormx.vexarr.com

# ORMX (Python)

**Pydantic-native async ORM** for Postgres, with a Rust core for the
hot path and a real migration engine.

The headline trick: one class plays three roles — DB persistence
object, framework request body, OpenAPI response model — without a
translation layer. Drop into Rivex, FastAPI, Litestar, anything that
knows Pydantic.

```python
from rivex import Depends, Rivex
from ormx import Field, Model, TransactionMiddleware, connect, disconnect, get_db


class User(Model):
    __tablename__ = "users"
    id: int = Field(primary_key=True)
    email: str = Field(unique=True, db_index=True, pattern=r"^[^@\s]+@[^@\s]+$")
    name: str = Field(min_length=1, max_length=100)
    age: int = Field(ge=0, le=120)


app = Rivex()
app.add_middleware(TransactionMiddleware)


@app.on_event("startup")
async def init(): await connect("postgres://localhost/myapp")

@app.on_event("shutdown")
async def close(): await disconnect()


@app.get("/users/{id}", response_model=User)
async def get_user(id: int, db=Depends(get_db)):
    return await User.get(id)


@app.post("/users", response_model=User)
async def create_user(user: User, db=Depends(get_db)):
    return await user.save()
```

That's the entire stack.

## Status

`4.4.2` — stable. 345 tests passing.

| Feature | Status |
|---|---|
| Pydantic-native `Model` (every constraint, alias, validator) | ✅ |
| Framework-agnostic `TransactionMiddleware` | ✅ |
| Async-generator `get_db` dependency | ✅ |
| Rust transaction API (`Engine.begin()` / `tx.commit()` / `tx.rollback()`) | ✅ |
| Multi-statement DDL via simple-query protocol | ✅ |
| One-to-many eager loading via `.include()` | ✅ |
| Migration engine — CREATE/DROP TABLE, ADD/DROP COLUMN, ALTER COLUMN TYPE, indexes, foreign keys with `ON DELETE`/`ON UPDATE`, multi-schema, NULL ↔ NOT NULL | ✅ |
| Sharding — hash / range / geo / list / custom routing | ✅ |
| Multi-schema (`__schema__` on Model) | ✅ |
| Query builder — `where` / `order_by` / `limit` / `offset` / `select` / `distinct` / `group_by` / `having` / `count` / `exists` / `scalar` / `values` / `update` / `delete` / `stream` | ✅ |
| Streaming reads via server-side cursor (`.stream(chunk_size=)`) | ✅ |
| `Model.refresh()` for re-reading rows touched externally | ✅ |
| BYTEA / bytes round-trip | ✅ (since v3.0.2) |
| Many-to-many eager loading | 🚧 |
| Beyond Postgres (MySQL / SQLite drivers) | 🚧 |
| Composite (multi-column) indexes + foreign keys | 🚧 |

## Install

```bash
pip install ormx-py
```

> The PyPI distribution is **`ormx-py`** — the `ormx` and `pyormx`
> names there belong to unrelated packages. The import is `ormx`.

The Rust core is compiled into the wheel (vendored at
`vendor/ormx-core`, tracking
[core.ormx.vexarr.com](https://github.com/vexarr-stack/core.ormx.vexarr.com)),
with abi3 wheels for Linux (x86_64 + aarch64), macOS (Intel + Apple
Silicon), and Windows — one `pip install`, no separate core package.
Source builds need `rustc 1.75+` and a submodule-initialized checkout
(`git clone --recurse-submodules`). See [RELEASING.md](RELEASING.md)
for the full distribution story.

## Querying — the cookbook

ORMX query builders are chainable. Every chain method returns `self`;
terminal methods (`all`, `first`, `count`, `exists`, `scalar`,
`values`, `update`, `delete`, `stream`) execute the query and return.

```python
# Basic WHERE
users = await User.query().where(User.age > 18).all()

# Chained — AND
adults = await (
    User.query()
    .where(User.age > 18)
    .where(User.email.like("%@example.com"))
    .order_by("name")
    .limit(50)
    .all()
)

# Count / exists / scalar
total = await User.query().count()
has_admin = await User.query().where(User.email == "admin@x.com").exists()

# Aggregates with GROUP BY + HAVING (HAVING is a raw SQL fragment)
buckets = await (
    User.query()
    .select("age")
    .group_by("age")
    .having("count(*) > 5")
    .all(raw=True)
)
# → [{"age": 30}, {"age": 31}, ...]

# DISTINCT and DISTINCT ON
unique_ages = await User.query().select("age").distinct().values("age")
latest_per_user = await (
    Event.query()
    .order_by("user_id").order_by("created_at", "desc")
    .distinct("user_id")
    .all()
)

# Bulk update / delete (skip per-row hooks)
n = await User.query().where(User.age < 13).update(age=13)
n = await User.query().where(User.deleted_at.is_not_null()).delete()

# Streaming — memory-bounded, server-side cursor
async for user in User.query().where(User.age > 50).stream(chunk_size=1000):
    process(user)

# Refresh a stale instance after an external write
await user.refresh()  # re-reads by PK
```

## Relationships and eager loading

```python
class User(Model):
    __tablename__ = "users"
    id: int = Field(primary_key=True)
    posts = Relationship("Post", back_populates="user")

class Post(Model):
    __tablename__ = "posts"
    id: int = Field(primary_key=True)
    user_id: int = ForeignKey("users.id", on_delete="CASCADE")
    title: str = Field(db_index=True)
    user = Relationship("User", back_populates="posts")


# One IN-query per included relation — no N+1.
users = await User.query().include("posts").all()
for u in users:
    print(u.name, [p.title for p in u.posts])
```

## Transactions

Three flavours, pick what fits:

```python
# 1. Explicit context manager
async with ormx.transaction():
    await User.create(name="alice")
    await Order.create(user_id=1, total=10)
# Rollback automatic on exception; commit on clean exit.

# 2. Unit-of-Work (batched flush at the end)
async with ormx.uow_session():
    user = User(id=1, name="alice", age=30)
    user.age = 31         # implicitly dirty
    await user.save()      # flushed at context exit, all in one tx

# 3. Per-request middleware (the most common shape)
app.add_middleware(TransactionMiddleware)
# Every handler wrapped automatically.
# Default: rollback on 5xx responses + raised exceptions; configurable.
```

## Migrations

```bash
# Diff models vs DB, write a new timestamped migration file
ormx makemigrations

# Apply unapplied migrations in order
ormx migrate

# Roll back the most recently applied one
ormx migrate --rollback

# Drop orphan tables (off by default — safety against accidental data loss)
ormx makemigrations --allow-drop-tables
```

State lives in an `ormx_migrations` table the runner creates on first
use. Each migration runs in its own transaction; partial-failure
rolls back cleanly.

Detects: CREATE/DROP TABLE, ADD/DROP COLUMN, ALTER COLUMN TYPE (with
USING), CREATE/DROP INDEX, ADD/DROP FOREIGN KEY, SET/DROP NOT NULL,
CREATE SCHEMA for non-public-schema models. Multi-statement DDL +
PL/pgSQL with dollar-quoted bodies work via the Postgres simple-query
protocol — `executor.execute_many(sql)`.

## Multi-schema

```python
class AuditEvent(Model):
    __tablename__ = "events"
    __schema__ = "audit"   # ← non-public schema; CREATE SCHEMA emitted automatically
    id: int = Field(primary_key=True)
    event_type: str

# Cross-schema FK
class AuditRef(Model):
    __tablename__ = "refs"
    event_id: int = ForeignKey("audit.events.id")
```

## Sharding

```python
import ormx_core
from ormx import set_router
from ormx.sharding import Router, HashSharding

# Connect each shard
await ormx.connect("postgres://shard0/...", shard_name="shard0", is_default=True)
await ormx.connect("postgres://shard1/...", shard_name="shard1")

# Install a routing strategy
set_router(Router(shards=["shard0", "shard1"], strategy=HashSharding()))

# Pass ``sharding_value`` and ORMX routes to the right shard
await User.query().where(User.id == 42).first(sharding_value=42)
```

Hash / range / geo / list / custom routing all supported via
`ormx_core.ShardingConfig`.

## Settings management

ORMX itself doesn't ship one — use `pydantic-settings` (standard) or
write a small helper. Combined with Rivex's
[`Settings`](https://github.com/shregar1/rivex) base class:

```python
from rivex import Settings

class Config(Settings):
    database_url: str
    debug: bool = False

cfg = Config.from_env()
await ormx.connect(cfg.database_url)
```

## Upgrading from 2.x

See [`MIGRATION-3.0.md`](MIGRATION-3.0.md). TL;DR: type annotations on
fields are now required (Pydantic uses them); `ormx.fastapi` was
replaced by framework-agnostic primitives (`TransactionMiddleware` +
`get_db` work with FastAPI, Rivex, Litestar — anything that knows
async-generator dependencies).

## Examples

* [`examples/rivex_basic.py`](examples/rivex_basic.py) — full CRUD with Rivex (one file)
* [`examples/fastapi_basic.py`](examples/fastapi_basic.py) — same model, FastAPI
* [`examples/blog/`](examples/blog/) — full app: User + Post + Comment with relationships, eager loading, transactions, curl walkthrough

## Documentation

Full docs at [`docs/`](docs/) — `mkdocs serve` to read locally,
auto-publishes to `https://ormx.dev/docs` via the docs workflow.

## License

[MIT](LICENSE).

