Metadata-Version: 2.4
Name: mgf-sqlalchemy
Version: 0.1.0
Summary: Async SQLAlchemy helpers for mgf-common consumers — typed engine factory, sessionmaker, Postgres RLS tenant-scoping. Sibling of mgf-common under the mgf.* namespace.
Project-URL: Homepage, https://codeberg.org/magogi-admin/mgf-sqlalchemy
Project-URL: Issues, https://codeberg.org/magogi-admin/mgf-sqlalchemy/issues
Project-URL: Changelog, https://codeberg.org/magogi-admin/mgf-sqlalchemy/src/branch/main/CHANGELOG.md
Author: Bassam Alsanie, mgf-sqlalchemy contributors
License: MIT
License-File: LICENSE
Keywords: async,asyncpg,engine,multi-tenant,rls,sqlalchemy
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: mgf-common<0.31,>=0.30
Requires-Dist: sqlalchemy[asyncio]>=2.0
Provides-Extra: dev
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
Requires-Dist: import-linter>=2.0; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: test
Requires-Dist: aiosqlite>=0.20; extra == 'test'
Description-Content-Type: text/markdown

# `mgf-sqlalchemy` — async SQLAlchemy helpers for mgf-common consumers

[![PyPI](https://img.shields.io/pypi/v/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)
[![Python](https://img.shields.io/pypi/pyversions/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)

> **Sibling of [`mgf-common`](https://pypi.org/project/mgf-common/)
> under the `mgf.*` namespace.** Houses the async-SQLAlchemy helpers
> that previously lived under `mgf.common.db.*` — extracted at
> mgf-common v0.30 / mgf-sqlalchemy v0.1 per the
> [federation split plan](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).

## What this provides

| Submodule | What |
|---|---|
| `mgf.sqlalchemy` | `create_engine` — async SQLAlchemy engine factory with production-leaning pool defaults (`pool_pre_ping`, `pool_recycle`, sane `pool_size`/`max_overflow`). SQLite-aware (skips pool kwargs that StaticPool/NullPool reject). `create_sessionmaker` — async sessionmaker factory with SQLAlchemy 2's recommended `expire_on_commit=False` default. `tenant_session` — context manager for Postgres RLS multi-tenancy (`SET LOCAL app.current_tenant = '<uuid>'`); UUID-validated to prevent SQL injection. |

**FastAPI helpers live elsewhere.** The `get_session`
FastAPI-Depends generator and the `setup_db` lifespan helper that
previously co-located with these in mgf-common moved to
[`mgf.fastapi.db`](https://pypi.org/project/mgf-fastapi/) in
mgf-fastapi v0.2.0 (which depends on mgf-sqlalchemy via its
`[sqlalchemy]` extra). This sibling stays framework-agnostic — no
Starlette / no FastAPI in the dependency graph.

## Install

```bash
pip install mgf-sqlalchemy
# Or with the test extra (aiosqlite for in-memory SQLite tests):
pip install 'mgf-sqlalchemy[test]'
```

Pulls in `mgf-common` + `sqlalchemy[asyncio]` automatically. Production
consumers also need an async driver of their own (asyncpg / aiomysql /
asyncmy); we don't pin one — the consumer picks based on their
database.

## Quick start

```python
import asyncio
from mgf.sqlalchemy import create_engine, create_sessionmaker

async def main() -> None:
    engine = create_engine(
        "postgresql+asyncpg://user:pass@localhost/myapp",
        echo=False,
    )
    sessionmaker = create_sessionmaker(engine)
    try:
        async with sessionmaker() as session:
            from sqlalchemy import text
            row = (await session.execute(text("SELECT 1"))).scalar_one()
            print(row)
    finally:
        await engine.dispose()

asyncio.run(main())
```

## Postgres RLS multi-tenancy

```python
from uuid import UUID
from mgf.sqlalchemy import tenant_session

tenant_id = UUID("550e8400-e29b-41d4-a716-446655440000")

async with sessionmaker() as session:
    async with tenant_session(session, tenant_id) as scoped:
        # Every query on `scoped` (same session, just tenant-scoped)
        # gets `app.current_tenant` set in the current transaction.
        # RLS policies in your schema can read from
        # `current_setting('app.current_tenant')`.
        rows = await scoped.execute(text("SELECT ..."))
```

## FastAPI integration

The FastAPI-shaped helpers (request-scoped session injection +
lifespan) live in mgf-fastapi:

```python
# pyproject.toml
dependencies = [
    "mgf-common>=0.30,<0.31",
    "mgf-sqlalchemy>=0.1,<0.2",
    "mgf-fastapi[sqlalchemy]>=0.2,<0.3",
]
```

```python
from typing import Annotated
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from mgf.fastapi.db import get_session, setup_db

@asynccontextmanager
async def lifespan(app: FastAPI):
    async with setup_db(app, database_url="postgresql+asyncpg://..."):
        yield

app = FastAPI(lifespan=lifespan)

@app.get("/users")
async def list_users(
    session: Annotated[AsyncSession, Depends(get_session)],
) -> list[dict]:
    ...
```

## Documentation

- [`docs/recipes/sqlalchemy.md`](docs/recipes/sqlalchemy.md) — full async-SQLAlchemy walkthrough.
- [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md) — maiden voyage migration story (the v0.30 split + the get_session/setup_db relocation to mgf-fastapi).
- [`PUBLIC_API.md`](PUBLIC_API.md) — full public surface contract.
- [`CHANGELOG.md`](CHANGELOG.md) — release history.

For the federation-wide engineering standards (DESIGN_PRINCIPLES,
ERROR_HANDLING, SECURITY, etc.) see
[`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/).
This sibling inherits them by reference; the standards
source-of-truth lives in mgf-common.

## Status

🚧 **Experimental** — every public name is `experimental` per AP-09.
Promotion to `stable` happens release-by-release as consumer feedback
in [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
converges. The 0.x window applies. Pin tightly:
`mgf-sqlalchemy = ">=0.X.0,<0.Y"`.

## Cross-references

- **Filing process for sharp edges**: open an entry on
  [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
  with `[mgf-sqlalchemy]` prefix, OR file directly on this repo's
  Issues → maintainer mirrors into the canonical FEEDBACK.md.
- **Federation pattern**:
  [`mgf-common/docs/design/federation.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/design/federation.md).
- **The split that created this sibling**:
  [`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
- **Companion sibling**: [`mgf-alembic`](https://pypi.org/project/mgf-alembic/) — async-aware alembic env.py helper (paired ship at v0.30; depends on this sibling).
