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

[![Registry](https://img.shields.io/badge/registry-Codeberg-blue)](https://codeberg.org/magogi-admin/mgf-sqlalchemy)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://codeberg.org/magogi-admin/mgf-sqlalchemy)

> **Shape:** Federation sibling of `mgf-common`. The current
> `mgf-common` pin window is shown in this package's
> [Codeberg registry](https://codeberg.org/magogi-admin/mgf-sqlalchemy) —
> sourced from `pyproject.toml` at release time, so this README
> can't go stale. (Per v2.6 DOC-01 / project-shape taxonomy.
> PAPER-38.)
>
> **Sibling of [`mgf-common`](https://codeberg.org/magogi-admin/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://codeberg.org/magogi-admin/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-standard/docs/standards/`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/).
This sibling inherits them by reference; the standards
source-of-truth lives in mgf-common.

> **Conformance: L2** per [`mgf-standard/docs/standards/`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/). Per-rule audit ledger: [`docs/inprogress/MGF_STANDARDS_CONFORMANCE.md`](docs/inprogress/MGF_STANDARDS_CONFORMANCE.md). L2 means every federation **MUST** rule applies, evidenced by the tracker.

## 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://codeberg.org/magogi-admin/mgf-alembic) — async-aware alembic env.py helper (paired ship at v0.30; depends on this sibling).
