# Public API — `mgf-sqlalchemy`

> ⚠️ **This file documents `master` HEAD.** Names listed here may
> be unreleased. To learn what your installed wheel actually
> ships, run `pip show mgf-sqlalchemy` and compare against the git
> tag matching the published version.

This document is the **contract list** for `mgf-sqlalchemy`. Every
name listed below is a public name that consumers MAY depend on.

The contract terms are defined in
[`mgf-standard/docs/standards/API_DESIGN.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/API_DESIGN.md). In
short:

- **Stability tiers:**
  - `experimental` — MAY change shape in any MINOR release
    (the 0.x window keeps everything experimental in practice).
  - `stable` — follows the deprecation cycle; removal only in MAJOR.
  - `deprecated` — slated for removal; emits `DeprecationWarning`.
- **Names not listed below are private.** Anything starting with
  `_` (underscore-prefixed module names like `_engine.py`) is
  internal and MAY change without notice.
- **The 0.x window applies.** Per [SemVer](https://semver.org/) 0.x
  semantics, MINOR releases MAY break the public API. Pin tightly:
  `mgf-sqlalchemy = ">=0.X.0,<0.Y"`.

---

## `mgf.sqlalchemy`

Top-level async SQLAlchemy helpers. Closed-box: depends on
`mgf-common` + `sqlalchemy[asyncio]` only — **no FastAPI / Starlette /
Django** in the runtime graph.

| Name | Tier | Description |
|---|---|---|
| `create_engine` | experimental | Async SQLAlchemy engine factory. Wraps `sqlalchemy.ext.asyncio.create_async_engine` with production-leaning pool defaults (`pool_pre_ping=True`, `pool_recycle=3600`, conservative `pool_size`/`max_overflow`). SQLite-aware (skips pool kwargs that StaticPool/NullPool reject). `**extra` forwarded for rare options like `connect_args`, `isolation_level`. Returns an unconnected `AsyncEngine`. |
| `create_sessionmaker` | experimental | Async sessionmaker factory. Wraps `async_sessionmaker` with SQLAlchemy 2's recommended `expire_on_commit=False` default. `expire_on_commit=True` opts back into the legacy synchronous semantics. |
| `tenant_session` | experimental | Async context manager for Postgres RLS multi-tenancy. Runs `SET LOCAL app.current_tenant = '<uuid>'` on the supplied session; the `LOCAL` qualifier scopes the setting to the current transaction (auto-resets on commit/rollback). `tenant_id` accepts a `UUID` object or a UUID-shaped string; non-UUID input raises `ValueError` before any SQL is emitted. SQLite raises at the SQL layer (no GUC support); Postgres needs the GUC declared either in `postgresql.conf` or via a migration. |
| `paginate_cursor` | experimental | Pure helper for the "fetch `limit + 1`" cursor-pagination idiom. Given rows fetched with `LIMIT = limit + 1`, returns `(items, truncated, next_cursor)`: `items` sliced to `≤ limit`, `truncated` true when a row was dropped, `next_cursor` (from a caller-supplied `cursor_of` extractor) pointing at the last included row. No session / no I/O — composes with any query shape. `limit <= 0` raises `ValueError`. |

### Behaviours guaranteed at the public surface

- `create_engine` accepts any SQLAlchemy URL and never connects
  eagerly — the first connection attempt happens lazily on the
  first `await engine.connect()` or sessionmaker call.
- `tenant_session` validates `tenant_id` is UUID-shaped BEFORE
  interpolating into the `SET LOCAL` SQL. Non-UUID input raises
  `ValueError`; the SQL never runs.
- `paginate_cursor` calls `cursor_of` only on a row that is being
  returned to the caller — a cursor derived from the dropped
  `(limit + 1)`-th row can never leak. `truncated` is exactly
  `len(rows) > limit`.

---

## What stays in `mgf-common`, NOT here

- **`AppError`, `OperationError`, `AppConfigError`** and the rest
  of the typed exception hierarchy — `mgf.common.exceptions`.
- **`bootstrap`, `app_name`, `app_version`, `current_context`** —
  `mgf.common`.

`mgf-sqlalchemy` reaches into none of mgf-common's private modules.
The runtime dep is `mgf-common>=0.30,<0.31`.

## What lives in `mgf-fastapi`, NOT here

- **`get_session`** — FastAPI-Depends-compatible session generator.
  Yields one `AsyncSession` per request from
  `request.app.state.mgf_db_sessionmaker`. Lives in
  `mgf.fastapi.db.get_session` (mgf-fastapi v0.2+).
- **`setup_db`** — FastAPI lifespan context manager that creates
  the engine + sessionmaker, stashes them on `app.state`, and
  disposes the engine cleanly at lifespan exit. Lives in
  `mgf.fastapi.db.setup_db` (mgf-fastapi v0.2+).

Install both siblings to get the FastAPI-shaped helpers:

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

## Additional public names

| Name | Tier | Description |
|---|---|---|
| `AuditTrailMixin` | experimental | declarative mixin adding created/updated audit columns |
| `SoftDeleteMixin` | experimental | declarative mixin adding a nullable deleted_at soft-delete column |
| `current_audit_user` | experimental | read the audit-user GUC set on the session |
| `make_safe_url` | experimental | return a connection URL with the password masked, for safe logging |
| `set_audit_user` | experimental | set the audit-user GUC on the session (for AuditTrailMixin) |
| `uuid7` | experimental | generate a UUIDv7 (time-ordered) primary-key value |
| `uuid7_pk_column` | experimental | a declarative UUIDv7 primary-key Column factory |

