# mgf-sqlalchemy v0.1.0 — Maiden voyage cutover guide

> **Audience:** consumer agents (and humans relaying for them)
> migrating from `mgf-common[db]` (v0.29 and earlier) to
> `mgf-sqlalchemy` v0.1.0 + `mgf-common` v0.30.0.
> **What this is:** the canonical migration document for the
> SQLAlchemy half of the v0.30 paired federation extraction. The
> companion mgf-alembic guide is at
> [`mgf-alembic/docs/cutover/v0.1.0.md`](https://codeberg.org/magogi-admin/mgf_alembic/src/branch/main/docs/cutover/v0.1.0.md);
> the mgf-common-side guide is at
> [`mgf-common/docs/cutover/v0.30.0.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/cutover/v0.30.0.md).

This is the **third sibling extraction** under the federation split
plan recorded in
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
mgf-fastapi shipped first at v0.1.0 / mgf-common v0.28; mgf-http
shipped second at v0.1.0 / mgf-common v0.29; mgf-sqlalchemy +
mgf-alembic ship now as a paired release at v0.1.0 / mgf-common
v0.30. mgf-fastapi simultaneously bumps to v0.2.0 to host the
FastAPI-shaped DB helpers extracted out of `mgf.common.db`.

---

## At-a-glance

| Was (≤ mgf-common v0.29) | Is (mgf-common v0.30 + mgf-sqlalchemy v0.1 + mgf-fastapi v0.2) |
|---|---|
| `pip install 'mgf-common[db]'` | `pip install mgf-sqlalchemy` |
| `pip install 'mgf-common[db-test]'` | `pip install 'mgf-sqlalchemy[test]'` |
| `from mgf.common.db import create_engine` | `from mgf.sqlalchemy import create_engine` |
| `from mgf.common.db import create_sessionmaker` | `from mgf.sqlalchemy import create_sessionmaker` |
| `from mgf.common.db import tenant_session` | `from mgf.sqlalchemy import tenant_session` |
| `from mgf.common.db import get_session` ⚠️ | `from mgf.fastapi.db import get_session` (sibling moved) |
| `from mgf.common.db import setup_db` ⚠️ | `from mgf.fastapi.db import setup_db` (sibling moved) |

**Two BREAKING relocations:** `get_session` and `setup_db` move to
the **mgf-fastapi** sibling (v0.2.0), not mgf-sqlalchemy. They're
inherently FastAPI-shaped (Depends + lifespan + `app.state`) and a
pure-SQLAlchemy sibling shouldn't drag Starlette/FastAPI into its
dep graph. See [Why FastAPI helpers moved to mgf-fastapi](#why-fastapi-helpers-moved-to-mgf-fastapi).

**No code body changes** otherwise. The `create_engine` /
`create_sessionmaker` / `tenant_session` implementations are
byte-identical to what shipped in mgf-common v0.29.0 (modulo the
import path).

---

## What landed in mgf-sqlalchemy v0.1.0

| Submodule | What |
|---|---|
| `mgf.sqlalchemy` | `create_engine`, `create_sessionmaker`, `tenant_session`. |

Three names total — pure SQLAlchemy 2 helpers, no Starlette / no
FastAPI / no Django.

## What landed in mgf-fastapi v0.2.0 (paired ship)

| Submodule | What |
|---|---|
| `mgf.fastapi.db` | `get_session`, `setup_db`. New optional extra: `mgf-fastapi[sqlalchemy]` pulls `mgf-sqlalchemy>=0.1,<0.2`. |

The submodule is **gated**: importing `mgf.fastapi.db` raises
`ImportError` if `mgf-sqlalchemy` isn't installed (ship the
`[sqlalchemy]` extra explicitly).

---

## Why FastAPI helpers moved to mgf-fastapi

Pre-v0.30, `mgf.common.db._session.get_session` imported
`starlette.requests.Request` at runtime, and `_lifespan.setup_db`
took a FastAPI `app: FastAPI` parameter. The federation roadmap
specifies mgf-sqlalchemy as **framework-agnostic** — pulling
Starlette into a "pure SQLAlchemy" sibling would break that promise.

The fix: split by shape. Pure SQLAlchemy goes to mgf-sqlalchemy
(no framework deps). FastAPI-shaped helpers go to mgf-fastapi's
new `mgf.fastapi.db` submodule, which imports from mgf.sqlalchemy
via the `[sqlalchemy]` optional extra.

**Net result for consumers:** one extra dep line in pyproject.toml
when you use the FastAPI helpers (you install both
`mgf-sqlalchemy` and `mgf-fastapi[sqlalchemy]`), and a one-line
import-path rewrite. Pure-SQLAlchemy consumers (data pipelines,
CLI workers, non-FastAPI services) shed a transitive Starlette
import they never asked for.

---

## Migration steps

### 1. Update your `pyproject.toml`

For consumers using only `create_engine` / `create_sessionmaker` /
`tenant_session` (pure SQLAlchemy, no FastAPI helpers):

```toml
# Before
dependencies = [
    "mgf-common[db]>=0.29,<0.30",
]

# After
dependencies = [
    "mgf-common>=0.30,<0.31",
    "mgf-sqlalchemy>=0.1,<0.2",
]
```

For consumers using the FastAPI-shaped helpers (`get_session`,
`setup_db`):

```toml
# Before
dependencies = [
    "mgf-common[db,fastapi]>=0.29,<0.30",  # or [db] + [fastapi] separately
]

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

For test consumers (using aiosqlite for in-memory tests):

```toml
# Before
[project.optional-dependencies]
test = [
    "mgf-common[db-test]>=0.29,<0.30",
]

# After
test = [
    "mgf-common>=0.30,<0.31",
    "mgf-sqlalchemy[test]>=0.1,<0.2",
]
```

### 2. Rewrite imports

```bash
# At your repo root:
git ls-files '*.py' | xargs sed -i \
    -e 's|from mgf\.common\.db import \(get_session\|setup_db\)|from mgf.fastapi.db import \1|g' \
    -e 's|from mgf\.common\.db\b|from mgf.sqlalchemy|g' \
    -e 's|import mgf\.common\.db\b|import mgf.sqlalchemy|g'
```

The first sed handles the FastAPI-shaped helpers (they migrate to
`mgf.fastapi.db`); the second handles the pure-SQLAlchemy helpers
(they migrate to `mgf.sqlalchemy`). Order matters — handle the
FastAPI helpers first because their full-line pattern is more
specific.

For mixed imports like
`from mgf.common.db import create_engine, get_session`,
split by hand into two lines first, then re-run sed:

```python
# Before
from mgf.common.db import create_engine, get_session, setup_db, tenant_session

# After
from mgf.sqlalchemy import create_engine, tenant_session
from mgf.fastapi.db import get_session, setup_db
```

### 3. Re-run the four gates

```bash
uv sync
uv run ruff check
uv run mypy --strict src/
uv run pytest
```

If your test suite was green against mgf-common v0.29, it should be
green against mgf-common v0.30 + mgf-sqlalchemy v0.1 + mgf-fastapi
v0.2 immediately after the import rewrite.

### 4. Re-run `mgf-common catch-up`

```bash
mgf-common catch-up
```

The audit's pin-checker recognises `mgf-sqlalchemy` and
`mgf-fastapi[sqlalchemy]` as expected sibling deps. The
closed-box-leak checker also understands `mgf.sqlalchemy` and
`mgf.fastapi.db` as sibling import paths.

---

## What changed internally

- **PEP 420 namespace.** `mgf-sqlalchemy` ships `src/mgf/` without
  a top-level `__init__.py`, matching mgf-common, mgf-fastapi,
  mgf-http. `mgf.sqlalchemy`, `mgf.fastapi`, `mgf.http`,
  `mgf.common` coexist as siblings in the same Python process.
- **Same code body.** `create_engine`, `create_sessionmaker`,
  `tenant_session` move byte-identical (modulo import paths). No
  behaviour change for consumers.
- **No starlette in mgf-sqlalchemy's runtime graph.** Pre-v0.30
  `mgf.common.db._session` imported `starlette.requests.Request` for
  the `get_session` Depends signature; post-v0.30 mgf-sqlalchemy has
  no Starlette import anywhere.

---

## Why this split happened

Per the federation roadmap. The reasoning is shared across all
sibling extractions, but for `mgf-sqlalchemy` specifically:

- The `sqlalchemy[asyncio]` runtime dep is heavy; CLI tools and
  data-pipeline workers using only `mgf.common.config` +
  `mgf.common.observability` shouldn't pay the install cost. The
  `[db]` extra already enforced that, but the sibling split is a
  cleaner barrier.
- The "framework-agnostic" intent in the roadmap was at odds with
  the actual code shape (Starlette imports). Splitting by shape —
  pure helpers in mgf-sqlalchemy, FastAPI helpers in mgf-fastapi —
  delivers what the roadmap originally promised.
- Future async-DB-shaped concretes (custom session subclasses,
  bulk-insert helpers, etc.) land here, not in mgf-common —
  keeping mgf-common as the federation leaf with hierarchy roots
  only.

This is the **third** of four planned extractions:

| mgf-common release | Extracts | Status |
|---|---|---|
| v0.28 | mgf-fastapi | shipped |
| v0.29 | mgf-http | shipped |
| v0.30 (this one) | mgf-sqlalchemy + mgf-alembic + mgf-fastapi v0.2 | this release |
| v0.31 | mgf-django | future |
| v0.32+ | stabilization | future |
| v1.0 | lock the federated surface | future |

The full plan is in
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).

---

## Per-consumer migration notes

### PlasmaMapper (FastAPI scientific platform)

**Migration touches DB session injection at every endpoint.** Steps:

1. Bump pins: drop `mgf-common[db,fastapi]`; add `mgf-sqlalchemy`,
   `mgf-fastapi[sqlalchemy]`, and `mgf-common>=0.30,<0.31`.
2. Run the sed pair from
   [Step 2](#2-rewrite-imports). Most endpoints will rewrite
   cleanly; mixed-import lines need manual splitting.
3. Re-run unit + integration tests. Wire shape unchanged.
4. PostGIS schemas (already filing `include_object` callables via
   the v0.19 PAPER-22 ship of `configure_env`) — the alembic-side
   migration is a separate step; see
   `mgf-alembic/docs/cutover/v0.1.0.md`.

### inthewords (Django web)

**Not affected.** Django consumer doesn't import `mgf.common.db`
(uses Django's ORM). Bump pin to `mgf-common>=0.30,<0.31`. No
rewrite.

### vm-vmanager-app (Qt desktop)

**Not affected.** No DB layer in vm-vmanager-app. Bump pin to
`mgf-common>=0.30,<0.31`. No rewrite.

### mgf-apiprobe

**Not affected.** No DB layer in mgf-apiprobe. Bump pin to
`mgf-common>=0.30,<0.31` if version-floor allows.

---

## What's deliberately NOT in v0.1.0

The first mgf-sqlalchemy release is **byte-identical** to
mgf-common v0.29.0's `mgf.common.db` non-FastAPI surface (modulo
the import path). New surface waits for v0.2.0+:

- **Sync engine factory.** Async-only today; sync wrapper is a
  follow-up paper if a consumer asks.
- **Multi-tenant helper for non-Postgres dialects.** Today's
  `tenant_session` is Postgres-specific (`SET LOCAL`). MySQL /
  SQL Server equivalents land if a consumer files.
- **Bulk-insert helper.** Use `session.execute(insert(...).values([...]))`
  today; promotion to typed helper if a second consumer signals
  need.
- **Read-replica aware sessionmaker.** Out of scope for v0.1.

---

## Engineering note

mgf-sqlalchemy v0.1.0 carries 11 tests parity-preserved from
mgf-common v0.29.0 (the `test_engine` suite + the non-FastAPI
portions of `test_session`). Coverage threshold ≥80% matches
mgf-common's standard.

The PEP 420 namespace package (no top-level `mgf/__init__.py` in
any sibling repo) means `mgf.sqlalchemy`, `mgf.alembic`,
`mgf.fastapi`, `mgf.http`, and `mgf.common` coexist seamlessly in
the same Python process. No `__init__` conflicts; no namespace
collision; consumers can install the full federation in one venv
without surprise.
