mgf-fastapi 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.
- mgf_fastapi-0.1.0/.gitignore +64 -0
- mgf_fastapi-0.1.0/CHANGELOG.md +80 -0
- mgf_fastapi-0.1.0/LICENSE +21 -0
- mgf_fastapi-0.1.0/PKG-INFO +130 -0
- mgf_fastapi-0.1.0/PUBLIC_API.md +138 -0
- mgf_fastapi-0.1.0/README.md +87 -0
- mgf_fastapi-0.1.0/docs/cutover/v0.1.0.md +229 -0
- mgf_fastapi-0.1.0/docs/recipes/fastapi.md +275 -0
- mgf_fastapi-0.1.0/docs/recipes/webhooks.md +265 -0
- mgf_fastapi-0.1.0/pyproject.toml +196 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/__init__.py +73 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/_dependencies.py +93 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/_exceptions.py +200 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/_lifespan.py +72 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/_request_id.py +87 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/exceptions.py +43 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/py.typed +0 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/security.py +137 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/testing.py +130 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/webhooks/__init__.py +43 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/webhooks/_request.py +57 -0
- mgf_fastapi-0.1.0/src/mgf/fastapi/webhooks/_verifier.py +188 -0
- mgf_fastapi-0.1.0/tests/unit/test_dependencies.py +149 -0
- mgf_fastapi-0.1.0/tests/unit/test_exceptions.py +290 -0
- mgf_fastapi-0.1.0/tests/unit/test_lifespan.py +47 -0
- mgf_fastapi-0.1.0/tests/unit/test_request_id.py +70 -0
- mgf_fastapi-0.1.0/tests/unit/test_security.py +195 -0
- mgf_fastapi-0.1.0/tests/unit/test_testing.py +127 -0
- mgf_fastapi-0.1.0/tests/unit/test_webhooks.py +318 -0
|
@@ -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,80 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `mgf-fastapi` 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-fastapi = ">=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/master/docs/standards/RELEASING.md).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [Unreleased]
|
|
21
|
+
|
|
22
|
+
Nothing yet.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [0.1.0] — 2026-05-09
|
|
27
|
+
|
|
28
|
+
PyPI: <https://pypi.org/project/mgf-fastapi/0.1.0/> · Tag: `v0.1.0`
|
|
29
|
+
|
|
30
|
+
> **Maiden voyage** — extracted from `mgf-common` v0.28.0 per the
|
|
31
|
+
> federation split plan ([mgf-common/docs/release/federation_roadmap.md](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/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.fastapi`** — top-level FastAPI integration. `bootstrap` ↔
|
|
37
|
+
FastAPI lifespan (`setup_lifespan`); `RequestIdMiddleware` +
|
|
38
|
+
`current_request_id()`; `ExceptionTranslationMiddleware` +
|
|
39
|
+
`DEFAULT_STATUS_MAP`; `Depends()` helpers (`get_app_context`,
|
|
40
|
+
`get_settings`, `get_request_id`); `REQUEST_ID_HEADER` constant.
|
|
41
|
+
- **`mgf.fastapi.webhooks`** — Svix-shape HMAC webhook verification.
|
|
42
|
+
`WebhookHeaderSchema` (frozen dataclass; case-folds header names);
|
|
43
|
+
`SVIX_SCHEMA` (Svix preset; used verbatim by Clerk);
|
|
44
|
+
`HmacWebhookVerifier` (transport-agnostic core; thread-safe;
|
|
45
|
+
`verify(headers, body) -> (event_id, ts)`); `verify_request`
|
|
46
|
+
(async FastAPI request adapter).
|
|
47
|
+
- **`mgf.fastapi.security`** — `IpAllowlist` FastAPI dependency.
|
|
48
|
+
CIDR allowlist with IPv4 + IPv6 dual-stack; cross-version matches
|
|
49
|
+
deliberately rejected. `trust_proxy=False` default concentrates
|
|
50
|
+
the proxy-trust footgun. Default `cidrs=LOOPBACK_CIDRS` so tests
|
|
51
|
+
work out-of-the-box.
|
|
52
|
+
- **`mgf.fastapi.testing`** — `run_test_app` async context manager.
|
|
53
|
+
Starts uvicorn for a FastAPI app on a free port; yields the base
|
|
54
|
+
URL; tears down cleanly on context exit. Surfaces server-startup
|
|
55
|
+
exceptions immediately instead of timing out silently. Optional
|
|
56
|
+
`[testing]` extra pulls in `uvicorn>=0.30` + `httpx>=0.27`.
|
|
57
|
+
- **`mgf.fastapi.exceptions`** — `HmacVerificationError(HttpUnauthorized)`.
|
|
58
|
+
Inherits the HTTP-01 status leaf from `mgf.common.exceptions` so
|
|
59
|
+
PAPER-24's `http_status` resolution maps to 401 in
|
|
60
|
+
`ExceptionTranslationMiddleware` without an explicit `status_map`
|
|
61
|
+
entry. (Other concrete leaves can be added here in subsequent
|
|
62
|
+
minors per the federation rule "siblings own their domain
|
|
63
|
+
concretes; mgf-common owns hierarchy roots + status leaves.")
|
|
64
|
+
|
|
65
|
+
### Engineering
|
|
66
|
+
|
|
67
|
+
- 83 tests carried over from `mgf-common/tests/unit/fastapi/` —
|
|
68
|
+
every pre-extraction failure mode and integration path stays
|
|
69
|
+
green at parity. Coverage threshold ≥80% (matches mgf-common's
|
|
70
|
+
standard).
|
|
71
|
+
- Imports from `mgf.common.fastapi.*` rewrite to `mgf.fastapi.*`.
|
|
72
|
+
`HmacVerificationError` re-exported from `mgf.fastapi.exceptions`
|
|
73
|
+
rather than `mgf.common.exceptions` (the latter no longer ships
|
|
74
|
+
it as of mgf-common v0.28).
|
|
75
|
+
- `mgf-common>=0.28,<0.29` runtime dependency. AP-03 0.x window
|
|
76
|
+
pin discipline applies — bump in lock-step with mgf-common's
|
|
77
|
+
next minor.
|
|
78
|
+
- PEP 420 namespace package (no top-level `mgf/__init__.py`); the
|
|
79
|
+
wheel ships `src/mgf/` as-is so `mgf.fastapi` and `mgf.common`
|
|
80
|
+
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,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mgf-fastapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI integration adapters for mgf-common — request-id + exception translation + lifespan + webhooks (Svix HMAC) + IpAllowlist + run_test_app. Sibling of mgf-common under the mgf.* namespace.
|
|
5
|
+
Project-URL: Homepage, https://codeberg.org/magogi-admin/mgf-fastapi
|
|
6
|
+
Project-URL: Issues, https://codeberg.org/magogi-admin/mgf-fastapi/issues
|
|
7
|
+
Project-URL: Changelog, https://codeberg.org/magogi-admin/mgf-fastapi/src/branch/master/CHANGELOG.md
|
|
8
|
+
Author: Bassam Alsanie, mgf-fastapi contributors
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: fastapi,ip-allowlist,middleware,request-id,svix,webhooks
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: FastAPI
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: fastapi>=0.110
|
|
28
|
+
Requires-Dist: mgf-common<0.29,>=0.28
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
31
|
+
Requires-Dist: hypothesis>=6.100; extra == 'dev'
|
|
32
|
+
Requires-Dist: import-linter>=2.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
38
|
+
Requires-Dist: uvicorn>=0.30; extra == 'dev'
|
|
39
|
+
Provides-Extra: testing
|
|
40
|
+
Requires-Dist: httpx>=0.27; extra == 'testing'
|
|
41
|
+
Requires-Dist: uvicorn>=0.30; extra == 'testing'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# `mgf-fastapi` — FastAPI integration adapters for mgf-common
|
|
45
|
+
|
|
46
|
+
[](https://pypi.org/project/mgf-fastapi/)
|
|
47
|
+
[](https://pypi.org/project/mgf-fastapi/)
|
|
48
|
+
|
|
49
|
+
> **Sibling of [`mgf-common`](https://pypi.org/project/mgf-common/)
|
|
50
|
+
> under the `mgf.*` namespace.** Houses every FastAPI-specific
|
|
51
|
+
> adapter that previously lived under `mgf.common.fastapi.*` —
|
|
52
|
+
> extracted at mgf-common v0.28 / mgf-fastapi v0.1 per the
|
|
53
|
+
> [federation split plan](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md).
|
|
54
|
+
|
|
55
|
+
## What this provides
|
|
56
|
+
|
|
57
|
+
| Submodule | What |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `mgf.fastapi` | `bootstrap` ↔ FastAPI lifespan; `RequestIdMiddleware`; `ExceptionTranslationMiddleware`; `Depends()` helpers (`get_app_context`, `get_settings`, `get_request_id`); `setup_lifespan`. |
|
|
60
|
+
| `mgf.fastapi.webhooks` | Svix-shape HMAC webhook verification (`HmacWebhookVerifier`, `WebhookHeaderSchema`, `SVIX_SCHEMA`, `verify_request`). |
|
|
61
|
+
| `mgf.fastapi.security` | `IpAllowlist` FastAPI dependency with proxy-trust opt-in. |
|
|
62
|
+
| `mgf.fastapi.testing` | `run_test_app` async context manager — start uvicorn for a FastAPI app on a free port; yield base URL; clean shutdown. |
|
|
63
|
+
| `mgf.fastapi.exceptions` | `HmacVerificationError(HttpUnauthorized)` and other framework-domain concrete leaves. (HTTP-01 hierarchy roots stay in `mgf.common.exceptions`.) |
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install mgf-fastapi
|
|
69
|
+
# Or with the test-helper extra (uvicorn + httpx for run_test_app):
|
|
70
|
+
pip install 'mgf-fastapi[testing]'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Pulls in `mgf-common` and `fastapi` automatically.
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from fastapi import FastAPI, Request
|
|
79
|
+
from mgf.fastapi import (
|
|
80
|
+
ExceptionTranslationMiddleware,
|
|
81
|
+
RequestIdMiddleware,
|
|
82
|
+
setup_lifespan,
|
|
83
|
+
)
|
|
84
|
+
from mgf.fastapi.webhooks import HmacWebhookVerifier, verify_request
|
|
85
|
+
|
|
86
|
+
app = FastAPI(lifespan=setup_lifespan(app_name="my-service", app_version="0.1.0"))
|
|
87
|
+
app.add_middleware(ExceptionTranslationMiddleware)
|
|
88
|
+
app.add_middleware(RequestIdMiddleware)
|
|
89
|
+
|
|
90
|
+
webhook = HmacWebhookVerifier(secret=settings.webhook_secret)
|
|
91
|
+
|
|
92
|
+
@app.post("/webhooks/clerk")
|
|
93
|
+
async def clerk_webhook(request: Request) -> dict:
|
|
94
|
+
event_id, ts = await verify_request(request, webhook)
|
|
95
|
+
payload = await request.json()
|
|
96
|
+
# ... process the (now-trusted) payload ...
|
|
97
|
+
return {"ok": True}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
|
+
- [`docs/recipes/fastapi.md`](docs/recipes/fastapi.md) — full FastAPI service walkthrough.
|
|
103
|
+
- [`docs/recipes/webhooks.md`](docs/recipes/webhooks.md) — HMAC webhook verification.
|
|
104
|
+
- [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md) — maiden voyage migration story (the v0.28 split).
|
|
105
|
+
- [`PUBLIC_API.md`](PUBLIC_API.md) — full public surface contract.
|
|
106
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — release history.
|
|
107
|
+
|
|
108
|
+
For the federation-wide engineering standards (DESIGN_PRINCIPLES,
|
|
109
|
+
ERROR_HANDLING, SECURITY, etc.) see
|
|
110
|
+
[`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/standards/).
|
|
111
|
+
This sibling inherits them by reference; the standards source-of-truth
|
|
112
|
+
lives in mgf-common.
|
|
113
|
+
|
|
114
|
+
## Status
|
|
115
|
+
|
|
116
|
+
🚧 **Experimental** — every public name is `experimental` per AP-09.
|
|
117
|
+
Promotion to `stable` happens release-by-release as consumer feedback
|
|
118
|
+
in [`FEEDBACK.md`](FEEDBACK.md) converges. The 0.x window applies.
|
|
119
|
+
Pin tightly: `mgf-fastapi = ">=0.X.0,<0.Y"`.
|
|
120
|
+
|
|
121
|
+
## Cross-references
|
|
122
|
+
|
|
123
|
+
- **Filing process for sharp edges**: open an entry on
|
|
124
|
+
[`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md)
|
|
125
|
+
with `[mgf-fastapi]` prefix, OR file directly on this repo's
|
|
126
|
+
Issues → maintainer mirrors into the canonical FEEDBACK.md.
|
|
127
|
+
- **Federation pattern**:
|
|
128
|
+
[`mgf-common/docs/design/federation.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/design/federation.md).
|
|
129
|
+
- **The split that created this sibling**:
|
|
130
|
+
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Public API — `mgf-fastapi`
|
|
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-fastapi` and compare against the
|
|
6
|
+
> `__version__` field at the top of `PUBLIC_API.json`. The git
|
|
7
|
+
> tag matching the published version is authoritative.
|
|
8
|
+
|
|
9
|
+
This document is the **contract list** for `mgf-fastapi`. Every name
|
|
10
|
+
listed below is a public name that consumers MAY depend on.
|
|
11
|
+
|
|
12
|
+
The contract terms are defined in
|
|
13
|
+
[`mgf-common/docs/standards/API_DESIGN.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/standards/API_DESIGN.md). In
|
|
14
|
+
short:
|
|
15
|
+
|
|
16
|
+
- **Stability tiers:**
|
|
17
|
+
- `experimental` — MAY change shape in any MINOR release
|
|
18
|
+
(the 0.x window keeps everything experimental in practice).
|
|
19
|
+
- `stable` — follows the deprecation cycle; removal only in MAJOR.
|
|
20
|
+
- `deprecated` — slated for removal; emits `DeprecationWarning`.
|
|
21
|
+
- **Names not listed below are private.** Anything starting with
|
|
22
|
+
`_` (underscore-prefixed module names like `_lifespan.py`) is
|
|
23
|
+
internal and MAY change without notice.
|
|
24
|
+
- **The 0.x window applies.** Per [SemVer](https://semver.org/) 0.x
|
|
25
|
+
semantics, MINOR releases MAY break the public API. Pin tightly:
|
|
26
|
+
`mgf-fastapi = ">=0.X.0,<0.Y"`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## `mgf.fastapi`
|
|
31
|
+
|
|
32
|
+
Top-level FastAPI integration adapters. Re-exports the load-bearing
|
|
33
|
+
public names from the submodules. Closed-box: depends on
|
|
34
|
+
`mgf-common` + `fastapi` only.
|
|
35
|
+
|
|
36
|
+
| Name | Tier | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `setup_lifespan(*, app_name=None, app_version=None, settings=None)` | experimental | Build a FastAPI lifespan that runs `mgf.common.bootstrap()` at app startup. The resulting `AppContext` is stashed on `app.state.mgf_context` for dependency injection. LIFO unwind on shutdown. |
|
|
39
|
+
| `RequestIdMiddleware` | experimental | ASGI middleware. Injects/forwards `X-Request-Id` header (case-insensitive); generates UUID4 if absent. Sets `request.state.request_id` AND a contextvar shared with `mgf.common._request_id` (so cross-framework processes correlate end-to-end). Echoes in response. |
|
|
40
|
+
| `current_request_id() -> str` | experimental | Read the per-request id from the contextvar. Returns empty string outside a request. PEP 567 propagates across async children. |
|
|
41
|
+
| `REQUEST_ID_HEADER = "X-Request-Id"` | experimental | Canonical header name. Inbound case-insensitive; outbound canonical. |
|
|
42
|
+
| `ExceptionTranslationMiddleware` | experimental | ASGI middleware. Catches `AppError` from handlers; maps to typed JSON responses via `status_map` (default per category) AND falls back to `HttpError.http_status` (PAPER-24 resolution) for HTTP-01 leaves like `HttpUnauthorized`, `HttpForbidden`, `HttpNotFound`, etc. — including sibling-domain concretes like `HmacVerificationError` (401 via inheritance). Configurable via `status_map=` and `response_factory=` kwargs. Includes `request_id` in error body when `RequestIdMiddleware` is installed. |
|
|
43
|
+
| `DEFAULT_STATUS_MAP: dict[type[AppError], int]` | experimental | Default exception → HTTP status mapping. `SchemaValidationError`→400, `ResourceNotFoundError`→404, `ResourceAlreadyExistsError`→409, `HostEnvironmentError`→503, `ConfigError`/`OperationError`/`VaultError`/`AppError`→500. |
|
|
44
|
+
| `get_app_context(request) -> AppContext` | experimental | FastAPI dependency. Reads `request.app.state.mgf_context`; raises `AppConfigError` if unset (lifespan not wired). |
|
|
45
|
+
| `get_settings(request) -> MgfSettings` | experimental | FastAPI dependency. Returns the `MgfSettings` from the active context. |
|
|
46
|
+
| `get_request_id(request) -> str` | experimental | FastAPI dependency. Reads `request.state.request_id`; returns `""` if `RequestIdMiddleware` is not installed. |
|
|
47
|
+
| `HmacVerificationError` | experimental | Re-exported from `mgf.fastapi.exceptions`. Convenience top-level alias for the most-commonly-caught sibling-domain exception. |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## `mgf.fastapi.exceptions`
|
|
52
|
+
|
|
53
|
+
Sibling-domain concrete exception leaves. Per the federation rule
|
|
54
|
+
(decision **D3** in the [federation roadmap](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md)),
|
|
55
|
+
siblings own their domain concretes; mgf-common owns hierarchy
|
|
56
|
+
roots + generic status-code leaves.
|
|
57
|
+
|
|
58
|
+
| Name | Tier | Description |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `HmacVerificationError` | experimental | A webhook HMAC signature failed verification. Inherits `mgf.common.exceptions.HttpUnauthorized` (status 401). Raised by `mgf.fastapi.webhooks.HmacWebhookVerifier` on any failure mode (missing header, unparseable timestamp, replay-window miss, signature mismatch). PAPER-24's `http_status` resolution maps it to 401 in `ExceptionTranslationMiddleware` without an explicit `status_map` entry. |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## `mgf.fastapi.webhooks`
|
|
65
|
+
|
|
66
|
+
Svix-shape HMAC webhook verification. Pulls the per-consumer
|
|
67
|
+
verification dance into one tested primitive. Closes
|
|
68
|
+
[`mgf-common`'s PAPER-26](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md)
|
|
69
|
+
(originally shipped in mgf-common v0.26; moved to this sibling at
|
|
70
|
+
mgf-common v0.28 / mgf-fastapi v0.1).
|
|
71
|
+
|
|
72
|
+
| Name | Tier | Description |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `WebhookHeaderSchema(id_header, timestamp_header, signature_header)` | experimental | Frozen dataclass naming the three Svix-style headers a provider uses. Header names are case-folded to lowercase on construction. |
|
|
75
|
+
| `SVIX_SCHEMA: WebhookHeaderSchema` | experimental | Pre-built schema for Svix's canonical headers (`svix-id` / `svix-timestamp` / `svix-signature`). Used by Clerk verbatim. Stripe / GitHub / Slack are deferred to follow-up papers. |
|
|
76
|
+
| `HmacWebhookVerifier(*, secret, schema=SVIX_SCHEMA, replay_window_seconds=300, signature_prefix="v1,", now=time.time)` | experimental | Transport-agnostic verifier. `verify(headers, body) -> (event_id, ts)`. Raises `HmacVerificationError` on any failure (missing header, unparseable timestamp, replay-window miss, signature mismatch). Stateless and thread-safe. |
|
|
77
|
+
| `verify_request(request, verifier) -> (event_id, ts)` | experimental | Async FastAPI request adapter. Reads `await request.body()`, lifts `request.headers` into the verifier's input, returns `verifier.verify(...)`. Caller MUST NOT have consumed the body yet. |
|
|
78
|
+
|
|
79
|
+
See [`docs/recipes/webhooks.md`](docs/recipes/webhooks.md)
|
|
80
|
+
for the full walkthrough including key rotation, custom schemas,
|
|
81
|
+
and the consumer pattern for replacing per-provider verifiers.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## `mgf.fastapi.security`
|
|
86
|
+
|
|
87
|
+
Request-side security primitives for FastAPI. Closes
|
|
88
|
+
[`mgf-common`'s PAPER-27](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md)
|
|
89
|
+
(originally shipped in mgf-common v0.27; moved to this sibling at
|
|
90
|
+
mgf-common v0.28 / mgf-fastapi v0.1).
|
|
91
|
+
|
|
92
|
+
| Name | Tier | Description |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `IpAllowlist(cidrs=LOOPBACK_CIDRS, *, trust_proxy=False)` | experimental | FastAPI dependency that 403s requests whose client IP isn't in `cidrs`. Concentrates the proxy-trust footgun in one knob — `trust_proxy=False` default; honouring `X-Forwarded-For` requires explicit opt-in. IPv4 + IPv6 dual-stack with cross-version matches deliberately rejected. Misconfigured CIDRs raise at construction. |
|
|
95
|
+
| `LOOPBACK_CIDRS: tuple[str, ...]` | experimental | The default `cidrs=` for `IpAllowlist` — `("127.0.0.0/8", "::1/128")`. Loopback-only so tests work out-of-the-box; consumers OVERRIDE for production. |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## `mgf.fastapi.testing`
|
|
100
|
+
|
|
101
|
+
Test-server lifecycle helper. Closes
|
|
102
|
+
[`mgf-common`'s PAPER-28](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md)
|
|
103
|
+
(originally shipped in mgf-common v0.27; moved to this sibling at
|
|
104
|
+
mgf-common v0.28 / mgf-fastapi v0.1).
|
|
105
|
+
|
|
106
|
+
Optional `[testing]` extra: `pip install 'mgf-fastapi[testing]'`
|
|
107
|
+
(pulls `uvicorn>=0.30` + `httpx>=0.27`).
|
|
108
|
+
|
|
109
|
+
| Name | Tier | Description |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `run_test_app(app, *, port=None, host="127.0.0.1", log_level="warning", startup_timeout_seconds=5.0)` | experimental | Async context manager. Starts uvicorn for `app` on a free port; yields `f"http://{host}:{port}"`; tears down cleanly on context exit (including under exception). `port=None` picks a free ephemeral port. Surfaces server-startup exceptions immediately instead of timing out silently. Raises `TimeoutError` if uvicorn doesn't reach `started` within `startup_timeout_seconds`. `CancelledError` from the serve task is suppressed on shutdown (expected). |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Summary
|
|
116
|
+
|
|
117
|
+
| Module | Public name count |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `mgf.fastapi` | 10 |
|
|
120
|
+
| `mgf.fastapi.exceptions` | 1 |
|
|
121
|
+
| `mgf.fastapi.webhooks` | 4 |
|
|
122
|
+
| `mgf.fastapi.security` | 2 |
|
|
123
|
+
| `mgf.fastapi.testing` | 1 |
|
|
124
|
+
| **Total** | **18** |
|
|
125
|
+
|
|
126
|
+
(`HmacVerificationError` counted once — at `mgf.fastapi.exceptions`. The
|
|
127
|
+
re-export at `mgf.fastapi` is a convenience alias.)
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Cross-references
|
|
132
|
+
|
|
133
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — release history.
|
|
134
|
+
- [`docs/recipes/`](docs/recipes/) — full how-to for each surface.
|
|
135
|
+
- [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md) — maiden voyage migration story (the v0.28 split).
|
|
136
|
+
- [`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md) — design conversation queue.
|
|
137
|
+
- [`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/standards/) — the federation-wide engineering standards.
|
|
138
|
+
- [`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md) — the split plan that created this sibling.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# `mgf-fastapi` — FastAPI integration adapters for mgf-common
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/mgf-fastapi/)
|
|
4
|
+
[](https://pypi.org/project/mgf-fastapi/)
|
|
5
|
+
|
|
6
|
+
> **Sibling of [`mgf-common`](https://pypi.org/project/mgf-common/)
|
|
7
|
+
> under the `mgf.*` namespace.** Houses every FastAPI-specific
|
|
8
|
+
> adapter that previously lived under `mgf.common.fastapi.*` —
|
|
9
|
+
> extracted at mgf-common v0.28 / mgf-fastapi v0.1 per the
|
|
10
|
+
> [federation split plan](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md).
|
|
11
|
+
|
|
12
|
+
## What this provides
|
|
13
|
+
|
|
14
|
+
| Submodule | What |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `mgf.fastapi` | `bootstrap` ↔ FastAPI lifespan; `RequestIdMiddleware`; `ExceptionTranslationMiddleware`; `Depends()` helpers (`get_app_context`, `get_settings`, `get_request_id`); `setup_lifespan`. |
|
|
17
|
+
| `mgf.fastapi.webhooks` | Svix-shape HMAC webhook verification (`HmacWebhookVerifier`, `WebhookHeaderSchema`, `SVIX_SCHEMA`, `verify_request`). |
|
|
18
|
+
| `mgf.fastapi.security` | `IpAllowlist` FastAPI dependency with proxy-trust opt-in. |
|
|
19
|
+
| `mgf.fastapi.testing` | `run_test_app` async context manager — start uvicorn for a FastAPI app on a free port; yield base URL; clean shutdown. |
|
|
20
|
+
| `mgf.fastapi.exceptions` | `HmacVerificationError(HttpUnauthorized)` and other framework-domain concrete leaves. (HTTP-01 hierarchy roots stay in `mgf.common.exceptions`.) |
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install mgf-fastapi
|
|
26
|
+
# Or with the test-helper extra (uvicorn + httpx for run_test_app):
|
|
27
|
+
pip install 'mgf-fastapi[testing]'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Pulls in `mgf-common` and `fastapi` automatically.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from fastapi import FastAPI, Request
|
|
36
|
+
from mgf.fastapi import (
|
|
37
|
+
ExceptionTranslationMiddleware,
|
|
38
|
+
RequestIdMiddleware,
|
|
39
|
+
setup_lifespan,
|
|
40
|
+
)
|
|
41
|
+
from mgf.fastapi.webhooks import HmacWebhookVerifier, verify_request
|
|
42
|
+
|
|
43
|
+
app = FastAPI(lifespan=setup_lifespan(app_name="my-service", app_version="0.1.0"))
|
|
44
|
+
app.add_middleware(ExceptionTranslationMiddleware)
|
|
45
|
+
app.add_middleware(RequestIdMiddleware)
|
|
46
|
+
|
|
47
|
+
webhook = HmacWebhookVerifier(secret=settings.webhook_secret)
|
|
48
|
+
|
|
49
|
+
@app.post("/webhooks/clerk")
|
|
50
|
+
async def clerk_webhook(request: Request) -> dict:
|
|
51
|
+
event_id, ts = await verify_request(request, webhook)
|
|
52
|
+
payload = await request.json()
|
|
53
|
+
# ... process the (now-trusted) payload ...
|
|
54
|
+
return {"ok": True}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Documentation
|
|
58
|
+
|
|
59
|
+
- [`docs/recipes/fastapi.md`](docs/recipes/fastapi.md) — full FastAPI service walkthrough.
|
|
60
|
+
- [`docs/recipes/webhooks.md`](docs/recipes/webhooks.md) — HMAC webhook verification.
|
|
61
|
+
- [`docs/cutover/v0.1.0.md`](docs/cutover/v0.1.0.md) — maiden voyage migration story (the v0.28 split).
|
|
62
|
+
- [`PUBLIC_API.md`](PUBLIC_API.md) — full public surface contract.
|
|
63
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — release history.
|
|
64
|
+
|
|
65
|
+
For the federation-wide engineering standards (DESIGN_PRINCIPLES,
|
|
66
|
+
ERROR_HANDLING, SECURITY, etc.) see
|
|
67
|
+
[`mgf-common/docs/standards/`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/standards/).
|
|
68
|
+
This sibling inherits them by reference; the standards source-of-truth
|
|
69
|
+
lives in mgf-common.
|
|
70
|
+
|
|
71
|
+
## Status
|
|
72
|
+
|
|
73
|
+
🚧 **Experimental** — every public name is `experimental` per AP-09.
|
|
74
|
+
Promotion to `stable` happens release-by-release as consumer feedback
|
|
75
|
+
in [`FEEDBACK.md`](FEEDBACK.md) converges. The 0.x window applies.
|
|
76
|
+
Pin tightly: `mgf-fastapi = ">=0.X.0,<0.Y"`.
|
|
77
|
+
|
|
78
|
+
## Cross-references
|
|
79
|
+
|
|
80
|
+
- **Filing process for sharp edges**: open an entry on
|
|
81
|
+
[`mgf-common/FEEDBACK.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/FEEDBACK.md)
|
|
82
|
+
with `[mgf-fastapi]` prefix, OR file directly on this repo's
|
|
83
|
+
Issues → maintainer mirrors into the canonical FEEDBACK.md.
|
|
84
|
+
- **Federation pattern**:
|
|
85
|
+
[`mgf-common/docs/design/federation.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/design/federation.md).
|
|
86
|
+
- **The split that created this sibling**:
|
|
87
|
+
[`mgf-common/docs/release/federation_roadmap.md`](https://codeberg.org/magogi-admin/mgf_common/src/branch/master/docs/release/federation_roadmap.md).
|