mgf-sqlalchemy 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ # IDE
2
+ .idea/
3
+ .vscode/
4
+ *.swp
5
+ *.swo
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ *.so
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+
29
+ # Virtual environments
30
+ .venv/
31
+ venv/
32
+ env/
33
+ ENV/
34
+
35
+ # Testing and coverage
36
+ .pytest_cache/
37
+ .hypothesis/
38
+ .coverage
39
+ .coverage.*
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ coverage.xml
44
+ *.cover
45
+
46
+ # Type checking
47
+ .mypy_cache/
48
+ .pyre/
49
+ .pytype/
50
+
51
+ # Ruff
52
+ .ruff_cache/
53
+
54
+ # Packaging
55
+ MANIFEST
56
+
57
+ # Claude Code — keep settings.json (committed for the team), ignore
58
+ # per-machine local overrides + runtime locks/state.
59
+ .claude/settings.local.json
60
+ .claude/*.lock
61
+ .claude/scheduled_tasks*
62
+
63
+ # Maintainer planning notes (per-machine; never commit)
64
+ my_stuff/
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ All notable changes to `mgf-sqlalchemy` are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
6
+ the project follows [SemVer](https://semver.org/spec/v2.0.0.html). Per
7
+ the 0.x window ([SemVer 0.x](https://semver.org/#spec-item-4) and rule
8
+ **AP-03** from `mgf-common/docs/standards/API_DESIGN.md`), MINOR
9
+ releases MAY break the public API. Pin tightly:
10
+
11
+ ```toml
12
+ mgf-sqlalchemy = ">=0.X.0,<0.Y" # one minor at a time
13
+ ```
14
+
15
+ The release-engineering discipline that produces this changelog is in
16
+ [`mgf-common/docs/standards/RELEASING.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/RELEASING.md).
17
+
18
+ ---
19
+
20
+ ## [Unreleased]
21
+
22
+ Nothing yet.
23
+
24
+ ---
25
+
26
+ ## [0.1.0] — 2026-05-10
27
+
28
+ PyPI: <https://pypi.org/project/mgf-sqlalchemy/0.1.0/> · Tag: `v0.1.0`
29
+
30
+ > **Maiden voyage** — extracted from `mgf-common` v0.29.0 per the
31
+ > federation split plan ([mgf-common/docs/release/federation_roadmap.md](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md)).
32
+ > Cutover guide: [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md).
33
+
34
+ ### Added
35
+
36
+ - **`mgf.sqlalchemy.create_engine`** — async SQLAlchemy engine
37
+ factory wrapping `sqlalchemy.ext.asyncio.create_async_engine` with
38
+ production-leaning defaults: `pool_pre_ping=True` (catches stale
39
+ connections after DB restart), `pool_recycle=3600` (avoids MySQL
40
+ `wait_timeout` surprise), conservative `pool_size`/`max_overflow`.
41
+ SQLite-aware: detects `sqlite://` / `sqlite+` URLs and omits pool
42
+ kwargs that `StaticPool` / `NullPool` reject. `**extra` forwarded
43
+ to `create_async_engine` for rare options.
44
+ - **`mgf.sqlalchemy.create_sessionmaker`** — async sessionmaker
45
+ factory wrapping `async_sessionmaker` with SQLAlchemy 2's
46
+ recommended `expire_on_commit=False` default for async sessions.
47
+ `expire_on_commit=True` opts back into the legacy synchronous
48
+ semantics.
49
+ - **`mgf.sqlalchemy.tenant_session`** — async context manager that
50
+ runs `SET LOCAL app.current_tenant = '<uuid>'` for Postgres
51
+ RLS-based multi-tenancy. The `LOCAL` qualifier scopes to the
52
+ current transaction (auto-resets on commit/rollback) so
53
+ cross-tenant leakage between requests is impossible at the SQL
54
+ layer. `tenant_id` is UUID-validated before interpolation
55
+ (Postgres `SET LOCAL` doesn't accept bind params, so literal
56
+ interpolation is the only path; UUID validation makes it safe).
57
+
58
+ ### Removed (BREAKING vs the pre-extraction `mgf.common.db` shape)
59
+
60
+ - **`get_session` and `setup_db`** are NOT in this sibling. They
61
+ were FastAPI-Depends and FastAPI-lifespan shaped respectively in
62
+ mgf-common — pulling Starlette/FastAPI into a "framework-agnostic
63
+ SQLAlchemy" sibling would violate the closed-box rule. They moved
64
+ to `mgf.fastapi.db.get_session` / `mgf.fastapi.db.setup_db` in
65
+ mgf-fastapi v0.2.0 (gated behind the `mgf-fastapi[sqlalchemy]`
66
+ optional extra). Consumers needing them install both
67
+ `mgf-sqlalchemy` and `mgf-fastapi[sqlalchemy]`.
68
+
69
+ ### Engineering
70
+
71
+ - 11 tests carried over from `mgf-common/tests/unit/db/test_engine.py`
72
+ + the non-FastAPI portions of `test_session.py` — every
73
+ pre-extraction failure mode for the helpers in this sibling stays
74
+ green at parity. Coverage threshold ≥80% (matches mgf-common's
75
+ standard).
76
+ - Imports rewrite from `mgf.common.db.*` to `mgf.sqlalchemy.*`. No
77
+ private-import leaks; the sibling reaches into mgf-common only
78
+ through public names.
79
+ - `mgf-common>=0.30,<0.31` runtime dependency. AP-03 0.x window pin
80
+ discipline applies — bump in lock-step with mgf-common's next
81
+ minor.
82
+ - PEP 420 namespace package (no top-level `mgf/__init__.py`); the
83
+ wheel ships `src/mgf/` as-is so `mgf.sqlalchemy`, `mgf.alembic`,
84
+ `mgf.fastapi`, `mgf.http`, and `mgf.common` coexist as siblings.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bassam Alsanie and mgf-common contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: mgf-sqlalchemy
3
+ Version: 0.1.0
4
+ Summary: Async SQLAlchemy helpers for mgf-common consumers — typed engine factory, sessionmaker, Postgres RLS tenant-scoping. Sibling of mgf-common under the mgf.* namespace.
5
+ Project-URL: Homepage, https://codeberg.org/magogi-admin/mgf-sqlalchemy
6
+ Project-URL: Issues, https://codeberg.org/magogi-admin/mgf-sqlalchemy/issues
7
+ Project-URL: Changelog, https://codeberg.org/magogi-admin/mgf-sqlalchemy/src/branch/main/CHANGELOG.md
8
+ Author: Bassam Alsanie, mgf-sqlalchemy contributors
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: async,asyncpg,engine,multi-tenant,rls,sqlalchemy
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Database
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: mgf-common<0.31,>=0.30
27
+ Requires-Dist: sqlalchemy[asyncio]>=2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: aiosqlite>=0.20; extra == 'dev'
30
+ Requires-Dist: import-linter>=2.0; extra == 'dev'
31
+ Requires-Dist: mypy>=1.10; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
33
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
34
+ Requires-Dist: pytest>=8.0; extra == 'dev'
35
+ Requires-Dist: ruff>=0.4; extra == 'dev'
36
+ Provides-Extra: test
37
+ Requires-Dist: aiosqlite>=0.20; extra == 'test'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # `mgf-sqlalchemy` — async SQLAlchemy helpers for mgf-common consumers
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)
44
+
45
+ > **Sibling of [`mgf-common`](https://pypi.org/project/mgf-common/)
46
+ > under the `mgf.*` namespace.** Houses the async-SQLAlchemy helpers
47
+ > that previously lived under `mgf.common.db.*` — extracted at
48
+ > mgf-common v0.30 / mgf-sqlalchemy v0.1 per the
49
+ > [federation split plan](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
50
+
51
+ ## What this provides
52
+
53
+ | Submodule | What |
54
+ |---|---|
55
+ | `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. |
56
+
57
+ **FastAPI helpers live elsewhere.** The `get_session`
58
+ FastAPI-Depends generator and the `setup_db` lifespan helper that
59
+ previously co-located with these in mgf-common moved to
60
+ [`mgf.fastapi.db`](https://pypi.org/project/mgf-fastapi/) in
61
+ mgf-fastapi v0.2.0 (which depends on mgf-sqlalchemy via its
62
+ `[sqlalchemy]` extra). This sibling stays framework-agnostic — no
63
+ Starlette / no FastAPI in the dependency graph.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install mgf-sqlalchemy
69
+ # Or with the test extra (aiosqlite for in-memory SQLite tests):
70
+ pip install 'mgf-sqlalchemy[test]'
71
+ ```
72
+
73
+ Pulls in `mgf-common` + `sqlalchemy[asyncio]` automatically. Production
74
+ consumers also need an async driver of their own (asyncpg / aiomysql /
75
+ asyncmy); we don't pin one — the consumer picks based on their
76
+ database.
77
+
78
+ ## Quick start
79
+
80
+ ```python
81
+ import asyncio
82
+ from mgf.sqlalchemy import create_engine, create_sessionmaker
83
+
84
+ async def main() -> None:
85
+ engine = create_engine(
86
+ "postgresql+asyncpg://user:pass@localhost/myapp",
87
+ echo=False,
88
+ )
89
+ sessionmaker = create_sessionmaker(engine)
90
+ try:
91
+ async with sessionmaker() as session:
92
+ from sqlalchemy import text
93
+ row = (await session.execute(text("SELECT 1"))).scalar_one()
94
+ print(row)
95
+ finally:
96
+ await engine.dispose()
97
+
98
+ asyncio.run(main())
99
+ ```
100
+
101
+ ## Postgres RLS multi-tenancy
102
+
103
+ ```python
104
+ from uuid import UUID
105
+ from mgf.sqlalchemy import tenant_session
106
+
107
+ tenant_id = UUID("550e8400-e29b-41d4-a716-446655440000")
108
+
109
+ async with sessionmaker() as session:
110
+ async with tenant_session(session, tenant_id) as scoped:
111
+ # Every query on `scoped` (same session, just tenant-scoped)
112
+ # gets `app.current_tenant` set in the current transaction.
113
+ # RLS policies in your schema can read from
114
+ # `current_setting('app.current_tenant')`.
115
+ rows = await scoped.execute(text("SELECT ..."))
116
+ ```
117
+
118
+ ## FastAPI integration
119
+
120
+ The FastAPI-shaped helpers (request-scoped session injection +
121
+ lifespan) live in mgf-fastapi:
122
+
123
+ ```python
124
+ # pyproject.toml
125
+ dependencies = [
126
+ "mgf-common>=0.30,<0.31",
127
+ "mgf-sqlalchemy>=0.1,<0.2",
128
+ "mgf-fastapi[sqlalchemy]>=0.2,<0.3",
129
+ ]
130
+ ```
131
+
132
+ ```python
133
+ from typing import Annotated
134
+ from contextlib import asynccontextmanager
135
+ from fastapi import FastAPI, Depends
136
+ from sqlalchemy.ext.asyncio import AsyncSession
137
+ from mgf.fastapi.db import get_session, setup_db
138
+
139
+ @asynccontextmanager
140
+ async def lifespan(app: FastAPI):
141
+ async with setup_db(app, database_url="postgresql+asyncpg://..."):
142
+ yield
143
+
144
+ app = FastAPI(lifespan=lifespan)
145
+
146
+ @app.get("/users")
147
+ async def list_users(
148
+ session: Annotated[AsyncSession, Depends(get_session)],
149
+ ) -> list[dict]:
150
+ ...
151
+ ```
152
+
153
+ ## Documentation
154
+
155
+ - [`docs/recipes/sqlalchemy.md`](docs/recipes/sqlalchemy.md) — full async-SQLAlchemy walkthrough.
156
+ - [`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).
157
+ - [`PUBLIC_API.md`](PUBLIC_API.md) — full public surface contract.
158
+ - [`CHANGELOG.md`](CHANGELOG.md) — release history.
159
+
160
+ For the federation-wide engineering standards (DESIGN_PRINCIPLES,
161
+ ERROR_HANDLING, SECURITY, etc.) see
162
+ [`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/).
163
+ This sibling inherits them by reference; the standards
164
+ source-of-truth lives in mgf-common.
165
+
166
+ ## Status
167
+
168
+ 🚧 **Experimental** — every public name is `experimental` per AP-09.
169
+ Promotion to `stable` happens release-by-release as consumer feedback
170
+ in [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
171
+ converges. The 0.x window applies. Pin tightly:
172
+ `mgf-sqlalchemy = ">=0.X.0,<0.Y"`.
173
+
174
+ ## Cross-references
175
+
176
+ - **Filing process for sharp edges**: open an entry on
177
+ [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
178
+ with `[mgf-sqlalchemy]` prefix, OR file directly on this repo's
179
+ Issues → maintainer mirrors into the canonical FEEDBACK.md.
180
+ - **Federation pattern**:
181
+ [`mgf-common/docs/design/federation.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/design/federation.md).
182
+ - **The split that created this sibling**:
183
+ [`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
184
+ - **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).
@@ -0,0 +1,81 @@
1
+ # Public API — `mgf-sqlalchemy`
2
+
3
+ > ⚠️ **This file documents `master` HEAD.** Names listed here may
4
+ > be unreleased. To learn what your installed wheel actually
5
+ > ships, run `pip show mgf-sqlalchemy` and compare against the git
6
+ > tag matching the published version.
7
+
8
+ This document is the **contract list** for `mgf-sqlalchemy`. Every
9
+ name listed below is a public name that consumers MAY depend on.
10
+
11
+ The contract terms are defined in
12
+ [`mgf-common/docs/standards/API_DESIGN.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/API_DESIGN.md). In
13
+ short:
14
+
15
+ - **Stability tiers:**
16
+ - `experimental` — MAY change shape in any MINOR release
17
+ (the 0.x window keeps everything experimental in practice).
18
+ - `stable` — follows the deprecation cycle; removal only in MAJOR.
19
+ - `deprecated` — slated for removal; emits `DeprecationWarning`.
20
+ - **Names not listed below are private.** Anything starting with
21
+ `_` (underscore-prefixed module names like `_engine.py`) is
22
+ internal and MAY change without notice.
23
+ - **The 0.x window applies.** Per [SemVer](https://semver.org/) 0.x
24
+ semantics, MINOR releases MAY break the public API. Pin tightly:
25
+ `mgf-sqlalchemy = ">=0.X.0,<0.Y"`.
26
+
27
+ ---
28
+
29
+ ## `mgf.sqlalchemy`
30
+
31
+ Top-level async SQLAlchemy helpers. Closed-box: depends on
32
+ `mgf-common` + `sqlalchemy[asyncio]` only — **no FastAPI / Starlette /
33
+ Django** in the runtime graph.
34
+
35
+ | Name | Tier | Description |
36
+ |---|---|---|
37
+ | `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`. |
38
+ | `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. |
39
+ | `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. |
40
+
41
+ ### Behaviours guaranteed at the public surface
42
+
43
+ - `create_engine` accepts any SQLAlchemy URL and never connects
44
+ eagerly — the first connection attempt happens lazily on the
45
+ first `await engine.connect()` or sessionmaker call.
46
+ - `tenant_session` validates `tenant_id` is UUID-shaped BEFORE
47
+ interpolating into the `SET LOCAL` SQL. Non-UUID input raises
48
+ `ValueError`; the SQL never runs.
49
+
50
+ ---
51
+
52
+ ## What stays in `mgf-common`, NOT here
53
+
54
+ - **`AppError`, `OperationError`, `AppConfigError`** and the rest
55
+ of the typed exception hierarchy — `mgf.common.exceptions`.
56
+ - **`bootstrap`, `app_name`, `app_version`, `current_context`** —
57
+ `mgf.common`.
58
+
59
+ `mgf-sqlalchemy` reaches into none of mgf-common's private modules.
60
+ The runtime dep is `mgf-common>=0.30,<0.31`.
61
+
62
+ ## What lives in `mgf-fastapi`, NOT here
63
+
64
+ - **`get_session`** — FastAPI-Depends-compatible session generator.
65
+ Yields one `AsyncSession` per request from
66
+ `request.app.state.mgf_db_sessionmaker`. Lives in
67
+ `mgf.fastapi.db.get_session` (mgf-fastapi v0.2+).
68
+ - **`setup_db`** — FastAPI lifespan context manager that creates
69
+ the engine + sessionmaker, stashes them on `app.state`, and
70
+ disposes the engine cleanly at lifespan exit. Lives in
71
+ `mgf.fastapi.db.setup_db` (mgf-fastapi v0.2+).
72
+
73
+ Install both siblings to get the FastAPI-shaped helpers:
74
+
75
+ ```toml
76
+ dependencies = [
77
+ "mgf-common>=0.30,<0.31",
78
+ "mgf-sqlalchemy>=0.1,<0.2",
79
+ "mgf-fastapi[sqlalchemy]>=0.2,<0.3",
80
+ ]
81
+ ```
@@ -0,0 +1,145 @@
1
+ # `mgf-sqlalchemy` — async SQLAlchemy helpers for mgf-common consumers
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/mgf-sqlalchemy)](https://pypi.org/project/mgf-sqlalchemy/)
5
+
6
+ > **Sibling of [`mgf-common`](https://pypi.org/project/mgf-common/)
7
+ > under the `mgf.*` namespace.** Houses the async-SQLAlchemy helpers
8
+ > that previously lived under `mgf.common.db.*` — extracted at
9
+ > mgf-common v0.30 / mgf-sqlalchemy v0.1 per the
10
+ > [federation split plan](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
11
+
12
+ ## What this provides
13
+
14
+ | Submodule | What |
15
+ |---|---|
16
+ | `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. |
17
+
18
+ **FastAPI helpers live elsewhere.** The `get_session`
19
+ FastAPI-Depends generator and the `setup_db` lifespan helper that
20
+ previously co-located with these in mgf-common moved to
21
+ [`mgf.fastapi.db`](https://pypi.org/project/mgf-fastapi/) in
22
+ mgf-fastapi v0.2.0 (which depends on mgf-sqlalchemy via its
23
+ `[sqlalchemy]` extra). This sibling stays framework-agnostic — no
24
+ Starlette / no FastAPI in the dependency graph.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install mgf-sqlalchemy
30
+ # Or with the test extra (aiosqlite for in-memory SQLite tests):
31
+ pip install 'mgf-sqlalchemy[test]'
32
+ ```
33
+
34
+ Pulls in `mgf-common` + `sqlalchemy[asyncio]` automatically. Production
35
+ consumers also need an async driver of their own (asyncpg / aiomysql /
36
+ asyncmy); we don't pin one — the consumer picks based on their
37
+ database.
38
+
39
+ ## Quick start
40
+
41
+ ```python
42
+ import asyncio
43
+ from mgf.sqlalchemy import create_engine, create_sessionmaker
44
+
45
+ async def main() -> None:
46
+ engine = create_engine(
47
+ "postgresql+asyncpg://user:pass@localhost/myapp",
48
+ echo=False,
49
+ )
50
+ sessionmaker = create_sessionmaker(engine)
51
+ try:
52
+ async with sessionmaker() as session:
53
+ from sqlalchemy import text
54
+ row = (await session.execute(text("SELECT 1"))).scalar_one()
55
+ print(row)
56
+ finally:
57
+ await engine.dispose()
58
+
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ## Postgres RLS multi-tenancy
63
+
64
+ ```python
65
+ from uuid import UUID
66
+ from mgf.sqlalchemy import tenant_session
67
+
68
+ tenant_id = UUID("550e8400-e29b-41d4-a716-446655440000")
69
+
70
+ async with sessionmaker() as session:
71
+ async with tenant_session(session, tenant_id) as scoped:
72
+ # Every query on `scoped` (same session, just tenant-scoped)
73
+ # gets `app.current_tenant` set in the current transaction.
74
+ # RLS policies in your schema can read from
75
+ # `current_setting('app.current_tenant')`.
76
+ rows = await scoped.execute(text("SELECT ..."))
77
+ ```
78
+
79
+ ## FastAPI integration
80
+
81
+ The FastAPI-shaped helpers (request-scoped session injection +
82
+ lifespan) live in mgf-fastapi:
83
+
84
+ ```python
85
+ # pyproject.toml
86
+ dependencies = [
87
+ "mgf-common>=0.30,<0.31",
88
+ "mgf-sqlalchemy>=0.1,<0.2",
89
+ "mgf-fastapi[sqlalchemy]>=0.2,<0.3",
90
+ ]
91
+ ```
92
+
93
+ ```python
94
+ from typing import Annotated
95
+ from contextlib import asynccontextmanager
96
+ from fastapi import FastAPI, Depends
97
+ from sqlalchemy.ext.asyncio import AsyncSession
98
+ from mgf.fastapi.db import get_session, setup_db
99
+
100
+ @asynccontextmanager
101
+ async def lifespan(app: FastAPI):
102
+ async with setup_db(app, database_url="postgresql+asyncpg://..."):
103
+ yield
104
+
105
+ app = FastAPI(lifespan=lifespan)
106
+
107
+ @app.get("/users")
108
+ async def list_users(
109
+ session: Annotated[AsyncSession, Depends(get_session)],
110
+ ) -> list[dict]:
111
+ ...
112
+ ```
113
+
114
+ ## Documentation
115
+
116
+ - [`docs/recipes/sqlalchemy.md`](docs/recipes/sqlalchemy.md) — full async-SQLAlchemy walkthrough.
117
+ - [`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).
118
+ - [`PUBLIC_API.md`](PUBLIC_API.md) — full public surface contract.
119
+ - [`CHANGELOG.md`](CHANGELOG.md) — release history.
120
+
121
+ For the federation-wide engineering standards (DESIGN_PRINCIPLES,
122
+ ERROR_HANDLING, SECURITY, etc.) see
123
+ [`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/standards/).
124
+ This sibling inherits them by reference; the standards
125
+ source-of-truth lives in mgf-common.
126
+
127
+ ## Status
128
+
129
+ 🚧 **Experimental** — every public name is `experimental` per AP-09.
130
+ Promotion to `stable` happens release-by-release as consumer feedback
131
+ in [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
132
+ converges. The 0.x window applies. Pin tightly:
133
+ `mgf-sqlalchemy = ">=0.X.0,<0.Y"`.
134
+
135
+ ## Cross-references
136
+
137
+ - **Filing process for sharp edges**: open an entry on
138
+ [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/FEEDBACK.md)
139
+ with `[mgf-sqlalchemy]` prefix, OR file directly on this repo's
140
+ Issues → maintainer mirrors into the canonical FEEDBACK.md.
141
+ - **Federation pattern**:
142
+ [`mgf-common/docs/design/federation.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/design/federation.md).
143
+ - **The split that created this sibling**:
144
+ [`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/main/docs/release/federation_roadmap.md).
145
+ - **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).