fastapi-m8 4.2.2__tar.gz → 4.4.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.
- fastapi_m8-4.4.0/.markdownlint.yaml +43 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/CHANGELOG.md +198 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/PKG-INFO +77 -6
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/README.md +74 -3
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/constraints-all.txt +1 -1
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/constraints.txt +1 -1
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/__init__.py +83 -6
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_compat.py +39 -2
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_version.py +1 -1
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/pyproject.toml +2 -2
- fastapi_m8-4.4.0/tests/test_changelog_version_parity.py +41 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_compat.py +44 -1
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_packaging.py +145 -1
- fastapi_m8-4.4.0/tests/test_public_typing.py +275 -0
- fastapi_m8-4.4.0/tests/test_version_source_parity.py +32 -0
- fastapi_m8-4.2.2/tests/test_public_typing.py +0 -136
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.codacy.yml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.dockerignore +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.env.example +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.gitattributes +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.github/FUNDING.yml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.github/dependabot.yml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.github/workflows/CI.yaml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.github/workflows/PiPy.yml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.gitignore +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.gitleaks.toml +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/.pydocstyle +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/AGENTS.md +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/CLAUDE.md +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/LICENSE +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/REPOSITORY_CONTEXT.md +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/SECURITY.md +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_api_key.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_app.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_async_stub.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_deps.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_engine.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_events.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_health.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_internal_auth.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_revocation.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/_route_audit.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/config.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/scripts/__init__.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/scripts/docker_start.sh +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/fastapi_m8/scripts/pre_start.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/__init__.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/conftest.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_api_key.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_api_key_deps.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_app.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_app_extra.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_async_stub.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_ci_policy.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_config.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_config_file_secrets.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_deps.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_engine.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_event_signing_gate.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_events.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_fixture_matrix_contract.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_health.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_host_header_routing.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_internal_auth.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_meta.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_pre_start.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_revocation.py +0 -0
- {fastapi_m8-4.2.2 → fastapi_m8-4.4.0}/tests/test_route_audit.py +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
# markdownlint configuration, also consumed by Codacy's markdownlint engine when
|
|
3
|
+
# the repository is set to use its own configuration files.
|
|
4
|
+
#
|
|
5
|
+
# Scope note: a config file supplies markdownlint's *defaults* for every rule it
|
|
6
|
+
# does not mention, so this file deliberately restates the rules this repository
|
|
7
|
+
# already does not enforce. Without them, adding this file would turn one
|
|
8
|
+
# reported rule into five and ~534 findings across README.md + CHANGELOG.md.
|
|
9
|
+
|
|
10
|
+
# MD024/no-duplicate-heading — restrict to sibling headings.
|
|
11
|
+
#
|
|
12
|
+
# CHANGELOG.md follows Keep a Changelog, where every release section repeats the
|
|
13
|
+
# same subsection names (### Added / ### Changed / ### Security / ### Docs …).
|
|
14
|
+
# Under the default setting that makes each release after the first a stack of
|
|
15
|
+
# duplicate-heading errors — 28 of them at 4.2.2, purely as a function of how
|
|
16
|
+
# many releases the file records, and growing by ~3 with every release. The
|
|
17
|
+
# duplication is the format, not a defect, and renaming the sections to be
|
|
18
|
+
# unique would break the convention every reader of this changelog relies on.
|
|
19
|
+
#
|
|
20
|
+
# siblings_only flags a duplicate only when it shares a parent heading, so
|
|
21
|
+
# "### Added" under [4.3.0] and "### Added" under [4.2.0] both pass, while two
|
|
22
|
+
# "### Added" blocks inside one release — a real editing mistake — still fail.
|
|
23
|
+
MD024:
|
|
24
|
+
siblings_only: true
|
|
25
|
+
|
|
26
|
+
# MD013/line-length — off. README.md and CHANGELOG.md wrap prose for readability
|
|
27
|
+
# rather than to a fixed column, and carry long table rows, URLs and code lines
|
|
28
|
+
# that cannot be wrapped at all (328 findings). Python line length is enforced
|
|
29
|
+
# separately and for real, by ruff (line-length = 88 in pyproject.toml).
|
|
30
|
+
MD013: false
|
|
31
|
+
|
|
32
|
+
# MD060/table-column-style — off. The documentation tables use compact pipes
|
|
33
|
+
# (194 findings); this is a presentation preference with no effect on rendering.
|
|
34
|
+
MD060: false
|
|
35
|
+
|
|
36
|
+
# The three below are off to preserve the status quo, NOT because they are
|
|
37
|
+
# wrong — each flags something worth fixing, and the count is small enough to
|
|
38
|
+
# fix in one pass: MD031 blanks-around-fences (7), MD040 fenced-code-language
|
|
39
|
+
# (3), MD028 no-blanks-blockquote (2). Left as a follow-up rather than folded
|
|
40
|
+
# into an unrelated release commit; re-enable once those 12 spots are cleaned.
|
|
41
|
+
MD031: false
|
|
42
|
+
MD040: false
|
|
43
|
+
MD028: false
|
|
@@ -5,6 +5,204 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · Versioning:
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [4.4.0] — 2026-08-15 · Fail closed on a missing compatibility row; make the bare install importable
|
|
9
|
+
|
|
10
|
+
Two independent release-integrity fixes ship together.
|
|
11
|
+
|
|
12
|
+
`_assert_compat()` read its requirements with
|
|
13
|
+
`COMPAT_MATRIX.get(minor, {})`. A `fastapi-m8` minor with no row yielded an
|
|
14
|
+
empty dict, the validation loop ran zero times, and the function returned
|
|
15
|
+
**successfully** — so a release that forgot its row disabled the
|
|
16
|
+
`auth-sdk-m8` version check entirely, silently, for every consumer on that
|
|
17
|
+
minor. That is fail-**open** on an authorization-adjacent dependency guard.
|
|
18
|
+
|
|
19
|
+
Separately, the package's own minimal install had never been importable — see
|
|
20
|
+
*Fixed*, below.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`_assert_compat()` now fails closed.** An unlisted minor raises
|
|
25
|
+
`RuntimeError` naming the missing row (`"… has no COMPAT_MATRIX row for
|
|
26
|
+
minor 'X.Y' …"`) instead of booting unchecked. The behavior for a *listed*
|
|
27
|
+
minor is unchanged.
|
|
28
|
+
|
|
29
|
+
- **`auth-sdk-m8` floor raised to `>=3.1.3,<4.0.0`** (was `>=3.1.2`) in
|
|
30
|
+
`pyproject.toml`, `constraints.txt`/`constraints-all.txt` (pinned to
|
|
31
|
+
`3.1.3`), and `COMPAT_MATRIX["4.4"]`, updated in place to match — `3.1.3` is
|
|
32
|
+
published, dependency maintenance only (no new SDK API, no source change on
|
|
33
|
+
either side). Folded into this still-unpublished release rather than a
|
|
34
|
+
separate bump, per the same one-bump-per-unpublished-release rule the
|
|
35
|
+
*Notes* below already applies to the bare-install fix.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`COMPAT_MATRIX["4.4"] = {"auth-sdk-m8": ">=3.1.3,<4.0.0"}`** — shipped in
|
|
40
|
+
the same commit as the fail-closed change, and load-bearing: without it this
|
|
41
|
+
release would be unbootable for every consumer resolving
|
|
42
|
+
`fastapi-m8>=4.3.0,<5.0.0`. No new SDK API is consumed; the floor tracks
|
|
43
|
+
`pyproject.toml`'s (`>=3.1.2` at the time this row was first added, raised to
|
|
44
|
+
`>=3.1.3` before publish — see the dependency-maintenance bullet above).
|
|
45
|
+
- **`tests/test_compat.py::test_assert_compat_fails_closed_on_unlisted_minor`**
|
|
46
|
+
— reproduces the boot failure on an unlisted minor.
|
|
47
|
+
- **`tests/test_compat.py::test_compat_matrix_current_minor_row_matches_pyproject_floor`**
|
|
48
|
+
— generalizes the existing `4.0`-specific row-vs-floor test to the *current*
|
|
49
|
+
minor, so a stale copy-pasted row can no longer pass.
|
|
50
|
+
`test_compat_matrix_40_row_matches_pyproject_floor` is retained: it also
|
|
51
|
+
backs the `4.0`-gate accept/reject tests.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **`pip install fastapi-m8` with no extras now produces an importable
|
|
56
|
+
package.** Since `4.2.1` — when the SDK re-export block landed — a no-extras
|
|
57
|
+
install succeeded and then `import fastapi_m8` raised
|
|
58
|
+
`ModuleNotFoundError: No module named 'sqlalchemy'`, from `__init__.py`'s
|
|
59
|
+
module-level `from auth_sdk_m8.controllers.base import BaseController`.
|
|
60
|
+
SQLAlchemy arrives only through the `db` extra, so the "minimal (no database)"
|
|
61
|
+
install the README documents had never worked. Reproduced against the
|
|
62
|
+
published wheels for `4.2.0`, `4.2.1`, `4.2.2` and `4.3.0` in a clean
|
|
63
|
+
`python:3.14-slim` container. **No consumer in the fleet was exposed**: all
|
|
64
|
+
three declare `fastapi-m8[db,postgres(,mysql)]`, so they resolve SQLAlchemy
|
|
65
|
+
through the extra.
|
|
66
|
+
|
|
67
|
+
`BaseController` and `TimestampMixin` — the only two re-exports that need the
|
|
68
|
+
`db` extra — are now resolved by a module-level `__getattr__` (PEP 562)
|
|
69
|
+
instead of at import time. Both remain in `__all__`, remain importable as
|
|
70
|
+
`from fastapi_m8 import BaseController`, and **keep their object identity**:
|
|
71
|
+
the accessor returns the SDK object itself and caches it in the module
|
|
72
|
+
globals, so ORM models mixing in the re-exported `TimestampMixin` register
|
|
73
|
+
byte-identical metadata. Touching either without the extra now raises a
|
|
74
|
+
`ModuleNotFoundError` naming `fastapi-m8[db]` rather than a bare
|
|
75
|
+
`No module named 'sqlalchemy'`.
|
|
76
|
+
|
|
77
|
+
Option (a) of three was chosen — deferring the imports, rather than moving
|
|
78
|
+
`sqlalchemy` into the base dependencies or withdrawing the minimal-install
|
|
79
|
+
claim — because it keeps the SDK import boundary intact without forcing an ORM
|
|
80
|
+
on a service that has no database. **PEP 562 moves the failure from
|
|
81
|
+
`import fastapi_m8` to first attribute access; for these two names that is
|
|
82
|
+
still the consumer's own module-import time** (`BaseController` is subclassed
|
|
83
|
+
and `TimestampMixin` is mixed in at class-definition time), so the deferral
|
|
84
|
+
does not push an `ImportError` into a request path. This is recorded in the
|
|
85
|
+
`__getattr__` docstring so the laziness is not later mistaken for a
|
|
86
|
+
runtime-path hazard.
|
|
87
|
+
|
|
88
|
+
- **`tests/test_packaging.py::test_package_imports_without_the_db_extra`** — a
|
|
89
|
+
no-extras clean-install probe alongside the existing one. The existing probe
|
|
90
|
+
installs `--no-deps` into a target dir and therefore runs against whatever the
|
|
91
|
+
development environment already has on `sys.path`, so it is structurally blind
|
|
92
|
+
to this class of defect; the new probe installs the same built wheel and runs
|
|
93
|
+
with `sqlalchemy`/`sqlmodel`/`alembic` made unimportable, asserting first that
|
|
94
|
+
the blocker really blocks. `tests/test_public_typing.py` gains four tests
|
|
95
|
+
covering lazy-lookup stability, the unknown-name `AttributeError`, and the
|
|
96
|
+
actionable missing-extra message.
|
|
97
|
+
|
|
98
|
+
### Notes
|
|
99
|
+
|
|
100
|
+
- The bare-install fix and the `auth-sdk-m8 3.1.3` floor raise **ship no
|
|
101
|
+
version bump of their own**: both ride this same `4.4.0`, per the wave's
|
|
102
|
+
version-bump rule (one bump per unpublished release).
|
|
103
|
+
- `COMPAT_MATRIX` and `_assert_compat` exist in `fastapi-m8` only; a sweep of
|
|
104
|
+
the other nine Python repositories in the fleet found no second copy, so this
|
|
105
|
+
guard has exactly one implementation.
|
|
106
|
+
- Historical rows are **not** revised by this release. The `"4.2"` row states
|
|
107
|
+
`>=3.1.0` where `pyproject.toml` at the time declared `>=3.1.2`; the new
|
|
108
|
+
row-vs-floor test is scoped to the current minor, and whether historical rows
|
|
109
|
+
are corrected or grandfathered remains an open decision.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## [4.3.0] — 2026-08-14 · Complete the SDK re-export surface
|
|
114
|
+
|
|
115
|
+
Adds the five `auth-sdk-m8` primitives the consumer fleet still imports
|
|
116
|
+
directly, so a consumer service can depend on `fastapi-m8` alone and reach
|
|
117
|
+
**zero** `auth_sdk_m8` imports. Additive only: no existing export, signature or
|
|
118
|
+
behavior changes, and no new `auth-sdk-m8` API is required.
|
|
119
|
+
|
|
120
|
+
Until this release the boundary was literally unsatisfiable — five of the twelve
|
|
121
|
+
symbols the fleet imports had no `fastapi_m8` re-export, so every consumer had
|
|
122
|
+
to keep a direct SDK import or invent a local shim.
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- **`has_minimum_role`** (from `auth_sdk_m8.authorization`) — the canonical
|
|
127
|
+
role-ordering predicate. One implementation of the hierarchy, re-exported
|
|
128
|
+
rather than re-derived at a call site.
|
|
129
|
+
- **`RoleType`** (from `auth_sdk_m8.schemas.base`) — the role enum those
|
|
130
|
+
thresholds are expressed in, and the argument type of
|
|
131
|
+
`AuthDeps.require_role()`.
|
|
132
|
+
- **`ValidationConstants`** (from `auth_sdk_m8.schemas.shared`) — the shared
|
|
133
|
+
field-length/format constants consumer schemas validate against.
|
|
134
|
+
- **`make_scrape_credential_guard`** (from `auth_sdk_m8.security.guards`) — the
|
|
135
|
+
`/metrics` scrape-credential guard factory.
|
|
136
|
+
- **`REGISTRY`** (from `auth_sdk_m8.observability.metrics`) — the shared
|
|
137
|
+
Prometheus collector registry that `render_metrics` (already re-exported)
|
|
138
|
+
renders.
|
|
139
|
+
|
|
140
|
+
All five are documented in the module docstring's *Reusable SDK primitives*
|
|
141
|
+
block and listed in `__all__`; `has_superuser_privileges`, `BaseController`,
|
|
142
|
+
`ResponseModelBase`, `ResponseMessage`, `TimestampMixin`, `UserModel`,
|
|
143
|
+
`find_dotenv` and `render_metrics` were already re-exported and are unchanged.
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- `COMPAT_MATRIX` gains its `"4.3"` row (`auth-sdk-m8 >=3.1.2,<4.0.0`). Without
|
|
148
|
+
it `_assert_compat()` would find no requirement for the new minor and silently
|
|
149
|
+
skip the startup check. The floor is stated as `>=3.1.2` — the floor
|
|
150
|
+
`pyproject.toml` already declares — rather than `4.2`'s looser `>=3.1.0`.
|
|
151
|
+
|
|
152
|
+
### Documentation
|
|
153
|
+
|
|
154
|
+
- `README.md` gains a **Reusable SDK Primitives** section listing every
|
|
155
|
+
re-exported symbol, its SDK origin and its purpose, and states the boundary
|
|
156
|
+
rule: a consumer service imports these from `fastapi-m8`, never from
|
|
157
|
+
`auth-sdk-m8`. The database example now imports `TimestampMixin` from
|
|
158
|
+
`fastapi_m8` accordingly, and the compatibility table gains the `4.3.0` row.
|
|
159
|
+
|
|
160
|
+
### Tests
|
|
161
|
+
|
|
162
|
+
- `tests/test_public_typing.py` type-checks all twelve re-exported primitives
|
|
163
|
+
through the public surface and asserts each one **is** the SDK object it
|
|
164
|
+
claims to re-export (identity, not just importability).
|
|
165
|
+
- `tests/test_packaging.py`'s clean-install probe imports the five new names
|
|
166
|
+
from the built wheel, which is the acceptance condition for this release.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## [4.2.2] — 2026-07-31 · Dependency maintenance
|
|
171
|
+
|
|
172
|
+
Reconstructed from Git history (`ed259a1`, `0c3123b`) — the release shipped
|
|
173
|
+
without a changelog entry.
|
|
174
|
+
|
|
175
|
+
### Changed
|
|
176
|
+
|
|
177
|
+
- `auth-sdk-m8` floor raised to `>=3.1.2,<4.0.0` in `pyproject.toml` and pinned
|
|
178
|
+
to `3.1.2` in the compiled constraints files. No source change.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## [4.2.1] — 2026-07-31 · Re-exports, Python floor, docs
|
|
183
|
+
|
|
184
|
+
Reconstructed from Git history (`86830ee` and the commits it bumped for:
|
|
185
|
+
`b105e01`, `23020fe`, `be5c382`, `a57811c`) — the release shipped without a
|
|
186
|
+
changelog entry.
|
|
187
|
+
|
|
188
|
+
### Added
|
|
189
|
+
|
|
190
|
+
- Re-export of the reusable `auth-sdk-m8` primitives (`has_superuser_privileges`,
|
|
191
|
+
`BaseController`, `ResponseModelBase`, `ResponseMessage`, `TimestampMixin`,
|
|
192
|
+
`UserModel`, `find_dotenv`, `render_metrics`) — the first half of the surface
|
|
193
|
+
`4.3.0` completes.
|
|
194
|
+
|
|
195
|
+
### Changed
|
|
196
|
+
|
|
197
|
+
- Python floor raised to `>=3.12`; truncated lockfiles regenerated.
|
|
198
|
+
|
|
199
|
+
### Documentation
|
|
200
|
+
|
|
201
|
+
- Documented hybrid mode's expiry-bounded revocation contract; reformatted the
|
|
202
|
+
embedded Python snippets in `CHANGELOG.md`/`README.md`.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
8
206
|
## [4.2.0] — 2026-07-23 · Role-capability demonstration surface (Phase 7)
|
|
9
207
|
|
|
10
208
|
Adds a centralized `require_role(required_role: RoleType)` JWT dependency
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-m8
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.0
|
|
4
4
|
Summary: FastAPI application framework for m8 consumer microservices.
|
|
5
5
|
Author-email: Eli Serra <e.serra173@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -215,7 +215,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
215
215
|
Classifier: Topic :: Software Development :: Libraries
|
|
216
216
|
Requires-Python: >=3.12
|
|
217
217
|
Requires-Dist: anyio>=4.0
|
|
218
|
-
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<4.0.0,>=3.1.
|
|
218
|
+
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<4.0.0,>=3.1.3
|
|
219
219
|
Requires-Dist: fastapi>=0.136.3
|
|
220
220
|
Requires-Dist: httpx>=0.27.0
|
|
221
221
|
Requires-Dist: packaging>=24.0
|
|
@@ -284,6 +284,7 @@ boilerplate from every consumer service.
|
|
|
284
284
|
- [build_auth_deps()](#build_auth_deps)
|
|
285
285
|
- [create_db_engine()](#create_db_engine)
|
|
286
286
|
- [Health Checks](#health-checks)
|
|
287
|
+
- [Reusable SDK Primitives](#reusable-sdk-primitives)
|
|
287
288
|
7. [Authentication](#authentication)
|
|
288
289
|
- [Token Modes](#token-modes)
|
|
289
290
|
- [Role System](#role-system)
|
|
@@ -389,6 +390,20 @@ pip install "fastapi-m8[all]"
|
|
|
389
390
|
|
|
390
391
|
**Runtime requirements:** Python 3.12+
|
|
391
392
|
|
|
393
|
+
**What the minimal install gives you (since 4.4.0):** everything except the two
|
|
394
|
+
SQLModel-backed re-exports. `create_app`, `build_auth_deps`,
|
|
395
|
+
`ConsumerServiceSettings`, the health building blocks, the API-key and event-stream
|
|
396
|
+
clients, and the extra-free SDK primitives (`RoleType`, `has_minimum_role`,
|
|
397
|
+
`UserModel`, `REGISTRY`, …) all work with no extras. `BaseController` and
|
|
398
|
+
`TimestampMixin` are resolved lazily and raise a `ModuleNotFoundError` naming
|
|
399
|
+
`fastapi-m8[db]` if you touch them without that extra — install `[db]` (plus a
|
|
400
|
+
driver) as soon as your service has a database.
|
|
401
|
+
|
|
402
|
+
> Before 4.4.0 the minimal install did not actually work: `import fastapi_m8`
|
|
403
|
+
> raised `ModuleNotFoundError: No module named 'sqlalchemy'` on any release from
|
|
404
|
+
> 4.2.1 onward, because those two names were imported at module level. If you are
|
|
405
|
+
> on 4.2.1–4.3.0 and want a database-free install, upgrade to 4.4.0.
|
|
406
|
+
|
|
392
407
|
---
|
|
393
408
|
|
|
394
409
|
## Quick Start
|
|
@@ -1162,6 +1177,53 @@ class RedisCheck:
|
|
|
1162
1177
|
| `LENIENT` (default) | Any check is `fail` |
|
|
1163
1178
|
| `STRICT` | Any check is `fail` or `unknown` |
|
|
1164
1179
|
|
|
1180
|
+
### Reusable SDK Primitives
|
|
1181
|
+
|
|
1182
|
+
`fastapi-m8` already depends on `auth-sdk-m8` and pins its major range, so it
|
|
1183
|
+
re-exports the SDK primitives a consumer service actually needs. **Import them
|
|
1184
|
+
from `fastapi_m8`, never from `auth_sdk_m8`.** A consumer service that imports
|
|
1185
|
+
the SDK directly takes on a second dependency whose major range it does not
|
|
1186
|
+
control, and can be broken by an SDK change `fastapi-m8` has already absorbed.
|
|
1187
|
+
|
|
1188
|
+
| Re-export | SDK origin | Purpose |
|
|
1189
|
+
|---|---|---|
|
|
1190
|
+
| `has_superuser_privileges` | `auth_sdk_m8` | Dual-evidence superuser predicate (`role == SUPERADMIN` **and** `is_superuser`) |
|
|
1191
|
+
| `has_minimum_role` | `auth_sdk_m8.authorization` | Canonical role-ordering predicate — the single implementation of the hierarchy |
|
|
1192
|
+
| `RoleType` | `auth_sdk_m8.schemas.base` | Role enum; the argument type of `AuthDeps.require_role()` |
|
|
1193
|
+
| `BaseController` | `auth_sdk_m8.controllers.base` | CRUD controller base class |
|
|
1194
|
+
| `ResponseModelBase` | `auth_sdk_m8.schemas.base` | Base response schema |
|
|
1195
|
+
| `ResponseMessage` | `auth_sdk_m8.schemas.base` | Simple `{"message": ...}` response schema |
|
|
1196
|
+
| `ValidationConstants` | `auth_sdk_m8.schemas.shared` | Shared field length/format constants for consumer schemas |
|
|
1197
|
+
| `TimestampMixin` | `auth_sdk_m8.models.shared` | `created_at` / `updated_at` UTC columns for SQLModel tables |
|
|
1198
|
+
| `UserModel` | `auth_sdk_m8.schemas.user` | The authenticated principal every JWT dependency resolves to |
|
|
1199
|
+
| `find_dotenv` | `auth_sdk_m8.utils.paths` | Locate the service `.env` file |
|
|
1200
|
+
| `render_metrics` | `auth_sdk_m8.observability.metrics` | Render the Prometheus exposition payload |
|
|
1201
|
+
| `REGISTRY` | `auth_sdk_m8.observability.metrics` | The shared Prometheus collector registry `render_metrics` renders |
|
|
1202
|
+
| `make_scrape_credential_guard` | `auth_sdk_m8.security.guards` | Build the `/metrics` scrape-credential guard |
|
|
1203
|
+
|
|
1204
|
+
```python
|
|
1205
|
+
from fastapi_m8 import (
|
|
1206
|
+
REGISTRY,
|
|
1207
|
+
BaseController,
|
|
1208
|
+
ResponseMessage,
|
|
1209
|
+
ResponseModelBase,
|
|
1210
|
+
RoleType,
|
|
1211
|
+
TimestampMixin,
|
|
1212
|
+
UserModel,
|
|
1213
|
+
ValidationConstants,
|
|
1214
|
+
find_dotenv,
|
|
1215
|
+
has_minimum_role,
|
|
1216
|
+
has_superuser_privileges,
|
|
1217
|
+
make_scrape_credential_guard,
|
|
1218
|
+
render_metrics,
|
|
1219
|
+
)
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
Every name above is the SDK object itself, not a wrapper — `fastapi_m8.RoleType
|
|
1223
|
+
is auth_sdk_m8.schemas.base.RoleType` — so rerouting an import changes nothing
|
|
1224
|
+
at runtime, including SQLModel/SQLAlchemy table metadata built on
|
|
1225
|
+
`TimestampMixin`.
|
|
1226
|
+
|
|
1165
1227
|
---
|
|
1166
1228
|
|
|
1167
1229
|
## Authentication
|
|
@@ -1420,13 +1482,15 @@ TABLES_PREFIX=app
|
|
|
1420
1482
|
`SQLALCHEMY_DATABASE_URI` is assembled automatically. You can also set it directly
|
|
1421
1483
|
to override the assembly.
|
|
1422
1484
|
|
|
1423
|
-
Define models with `TimestampMixin`
|
|
1424
|
-
`
|
|
1485
|
+
Define models with `TimestampMixin` (adds `created_at` / `updated_at` UTC
|
|
1486
|
+
columns). It originates in `auth-sdk-m8` and is
|
|
1487
|
+
[re-exported by `fastapi-m8`](#reusable-sdk-primitives) — import it from here,
|
|
1488
|
+
not from the SDK:
|
|
1425
1489
|
|
|
1426
1490
|
```python
|
|
1427
1491
|
import uuid
|
|
1428
1492
|
from sqlmodel import SQLModel, Field
|
|
1429
|
-
from
|
|
1493
|
+
from fastapi_m8 import TimestampMixin
|
|
1430
1494
|
|
|
1431
1495
|
|
|
1432
1496
|
class Item(TimestampMixin, SQLModel, table=True):
|
|
@@ -1636,6 +1700,8 @@ async def test_health(client):
|
|
|
1636
1700
|
|
|
1637
1701
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1638
1702
|
|---|---|---|
|
|
1703
|
+
| `4.4.0` | `>=3.1.3, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1704
|
+
| `4.3.0` | `>=3.1.2, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1639
1705
|
| `4.2.0` | `>=3.1.0, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1640
1706
|
| `4.1.0` | `>=3.1.0, <4.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1641
1707
|
| `4.0.0` | `>=3.0.0, <4.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
@@ -1658,6 +1724,11 @@ The compatibility matrix is enforced at startup via `COMPAT_MATRIX`. A
|
|
|
1658
1724
|
`RuntimeError` is raised immediately if the installed `auth-sdk-m8` version is
|
|
1659
1725
|
outside the supported range.
|
|
1660
1726
|
|
|
1727
|
+
Since `4.4.0` the check **fails closed**: if the running `fastapi-m8` minor has
|
|
1728
|
+
no `COMPAT_MATRIX` row at all, startup raises a `RuntimeError` naming the
|
|
1729
|
+
missing row instead of skipping validation. Before `4.4.0` an unlisted minor
|
|
1730
|
+
booted with the dependency check silently disabled.
|
|
1731
|
+
|
|
1661
1732
|
Check at runtime:
|
|
1662
1733
|
|
|
1663
1734
|
```python
|
|
@@ -27,6 +27,7 @@ boilerplate from every consumer service.
|
|
|
27
27
|
- [build_auth_deps()](#build_auth_deps)
|
|
28
28
|
- [create_db_engine()](#create_db_engine)
|
|
29
29
|
- [Health Checks](#health-checks)
|
|
30
|
+
- [Reusable SDK Primitives](#reusable-sdk-primitives)
|
|
30
31
|
7. [Authentication](#authentication)
|
|
31
32
|
- [Token Modes](#token-modes)
|
|
32
33
|
- [Role System](#role-system)
|
|
@@ -132,6 +133,20 @@ pip install "fastapi-m8[all]"
|
|
|
132
133
|
|
|
133
134
|
**Runtime requirements:** Python 3.12+
|
|
134
135
|
|
|
136
|
+
**What the minimal install gives you (since 4.4.0):** everything except the two
|
|
137
|
+
SQLModel-backed re-exports. `create_app`, `build_auth_deps`,
|
|
138
|
+
`ConsumerServiceSettings`, the health building blocks, the API-key and event-stream
|
|
139
|
+
clients, and the extra-free SDK primitives (`RoleType`, `has_minimum_role`,
|
|
140
|
+
`UserModel`, `REGISTRY`, …) all work with no extras. `BaseController` and
|
|
141
|
+
`TimestampMixin` are resolved lazily and raise a `ModuleNotFoundError` naming
|
|
142
|
+
`fastapi-m8[db]` if you touch them without that extra — install `[db]` (plus a
|
|
143
|
+
driver) as soon as your service has a database.
|
|
144
|
+
|
|
145
|
+
> Before 4.4.0 the minimal install did not actually work: `import fastapi_m8`
|
|
146
|
+
> raised `ModuleNotFoundError: No module named 'sqlalchemy'` on any release from
|
|
147
|
+
> 4.2.1 onward, because those two names were imported at module level. If you are
|
|
148
|
+
> on 4.2.1–4.3.0 and want a database-free install, upgrade to 4.4.0.
|
|
149
|
+
|
|
135
150
|
---
|
|
136
151
|
|
|
137
152
|
## Quick Start
|
|
@@ -905,6 +920,53 @@ class RedisCheck:
|
|
|
905
920
|
| `LENIENT` (default) | Any check is `fail` |
|
|
906
921
|
| `STRICT` | Any check is `fail` or `unknown` |
|
|
907
922
|
|
|
923
|
+
### Reusable SDK Primitives
|
|
924
|
+
|
|
925
|
+
`fastapi-m8` already depends on `auth-sdk-m8` and pins its major range, so it
|
|
926
|
+
re-exports the SDK primitives a consumer service actually needs. **Import them
|
|
927
|
+
from `fastapi_m8`, never from `auth_sdk_m8`.** A consumer service that imports
|
|
928
|
+
the SDK directly takes on a second dependency whose major range it does not
|
|
929
|
+
control, and can be broken by an SDK change `fastapi-m8` has already absorbed.
|
|
930
|
+
|
|
931
|
+
| Re-export | SDK origin | Purpose |
|
|
932
|
+
|---|---|---|
|
|
933
|
+
| `has_superuser_privileges` | `auth_sdk_m8` | Dual-evidence superuser predicate (`role == SUPERADMIN` **and** `is_superuser`) |
|
|
934
|
+
| `has_minimum_role` | `auth_sdk_m8.authorization` | Canonical role-ordering predicate — the single implementation of the hierarchy |
|
|
935
|
+
| `RoleType` | `auth_sdk_m8.schemas.base` | Role enum; the argument type of `AuthDeps.require_role()` |
|
|
936
|
+
| `BaseController` | `auth_sdk_m8.controllers.base` | CRUD controller base class |
|
|
937
|
+
| `ResponseModelBase` | `auth_sdk_m8.schemas.base` | Base response schema |
|
|
938
|
+
| `ResponseMessage` | `auth_sdk_m8.schemas.base` | Simple `{"message": ...}` response schema |
|
|
939
|
+
| `ValidationConstants` | `auth_sdk_m8.schemas.shared` | Shared field length/format constants for consumer schemas |
|
|
940
|
+
| `TimestampMixin` | `auth_sdk_m8.models.shared` | `created_at` / `updated_at` UTC columns for SQLModel tables |
|
|
941
|
+
| `UserModel` | `auth_sdk_m8.schemas.user` | The authenticated principal every JWT dependency resolves to |
|
|
942
|
+
| `find_dotenv` | `auth_sdk_m8.utils.paths` | Locate the service `.env` file |
|
|
943
|
+
| `render_metrics` | `auth_sdk_m8.observability.metrics` | Render the Prometheus exposition payload |
|
|
944
|
+
| `REGISTRY` | `auth_sdk_m8.observability.metrics` | The shared Prometheus collector registry `render_metrics` renders |
|
|
945
|
+
| `make_scrape_credential_guard` | `auth_sdk_m8.security.guards` | Build the `/metrics` scrape-credential guard |
|
|
946
|
+
|
|
947
|
+
```python
|
|
948
|
+
from fastapi_m8 import (
|
|
949
|
+
REGISTRY,
|
|
950
|
+
BaseController,
|
|
951
|
+
ResponseMessage,
|
|
952
|
+
ResponseModelBase,
|
|
953
|
+
RoleType,
|
|
954
|
+
TimestampMixin,
|
|
955
|
+
UserModel,
|
|
956
|
+
ValidationConstants,
|
|
957
|
+
find_dotenv,
|
|
958
|
+
has_minimum_role,
|
|
959
|
+
has_superuser_privileges,
|
|
960
|
+
make_scrape_credential_guard,
|
|
961
|
+
render_metrics,
|
|
962
|
+
)
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
Every name above is the SDK object itself, not a wrapper — `fastapi_m8.RoleType
|
|
966
|
+
is auth_sdk_m8.schemas.base.RoleType` — so rerouting an import changes nothing
|
|
967
|
+
at runtime, including SQLModel/SQLAlchemy table metadata built on
|
|
968
|
+
`TimestampMixin`.
|
|
969
|
+
|
|
908
970
|
---
|
|
909
971
|
|
|
910
972
|
## Authentication
|
|
@@ -1163,13 +1225,15 @@ TABLES_PREFIX=app
|
|
|
1163
1225
|
`SQLALCHEMY_DATABASE_URI` is assembled automatically. You can also set it directly
|
|
1164
1226
|
to override the assembly.
|
|
1165
1227
|
|
|
1166
|
-
Define models with `TimestampMixin`
|
|
1167
|
-
`
|
|
1228
|
+
Define models with `TimestampMixin` (adds `created_at` / `updated_at` UTC
|
|
1229
|
+
columns). It originates in `auth-sdk-m8` and is
|
|
1230
|
+
[re-exported by `fastapi-m8`](#reusable-sdk-primitives) — import it from here,
|
|
1231
|
+
not from the SDK:
|
|
1168
1232
|
|
|
1169
1233
|
```python
|
|
1170
1234
|
import uuid
|
|
1171
1235
|
from sqlmodel import SQLModel, Field
|
|
1172
|
-
from
|
|
1236
|
+
from fastapi_m8 import TimestampMixin
|
|
1173
1237
|
|
|
1174
1238
|
|
|
1175
1239
|
class Item(TimestampMixin, SQLModel, table=True):
|
|
@@ -1379,6 +1443,8 @@ async def test_health(client):
|
|
|
1379
1443
|
|
|
1380
1444
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1381
1445
|
|---|---|---|
|
|
1446
|
+
| `4.4.0` | `>=3.1.3, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1447
|
+
| `4.3.0` | `>=3.1.2, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1382
1448
|
| `4.2.0` | `>=3.1.0, <4.0.0` | 3.12, 3.13, 3.14 |
|
|
1383
1449
|
| `4.1.0` | `>=3.1.0, <4.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1384
1450
|
| `4.0.0` | `>=3.0.0, <4.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
@@ -1401,6 +1467,11 @@ The compatibility matrix is enforced at startup via `COMPAT_MATRIX`. A
|
|
|
1401
1467
|
`RuntimeError` is raised immediately if the installed `auth-sdk-m8` version is
|
|
1402
1468
|
outside the supported range.
|
|
1403
1469
|
|
|
1470
|
+
Since `4.4.0` the check **fails closed**: if the running `fastapi-m8` minor has
|
|
1471
|
+
no `COMPAT_MATRIX` row at all, startup raises a `RuntimeError` naming the
|
|
1472
|
+
missing row instead of skipping validation. Before `4.4.0` an unlisted minor
|
|
1473
|
+
booted with the dependency check silently disabled.
|
|
1474
|
+
|
|
1404
1475
|
Check at runtime:
|
|
1405
1476
|
|
|
1406
1477
|
```python
|
|
@@ -16,7 +16,7 @@ anyio==4.14.1
|
|
|
16
16
|
# fastapi-m8 (pyproject.toml)
|
|
17
17
|
# httpx
|
|
18
18
|
# starlette
|
|
19
|
-
auth-sdk-m8[config,events,fastapi,observability,security]==3.1.
|
|
19
|
+
auth-sdk-m8[config,events,fastapi,observability,security]==3.1.3
|
|
20
20
|
# via
|
|
21
21
|
# fastapi-m8
|
|
22
22
|
# fastapi-m8 (pyproject.toml)
|
|
@@ -27,11 +27,17 @@ Tier 2 — health building blocks::
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
Reusable SDK primitives (re-exported from auth-sdk-m8, so consumers depend
|
|
30
|
-
only on fastapi-m8
|
|
30
|
+
only on fastapi-m8 — a consumer service must never import ``auth_sdk_m8``
|
|
31
|
+
directly)::
|
|
31
32
|
|
|
32
|
-
from fastapi_m8 import has_superuser_privileges
|
|
33
|
+
from fastapi_m8 import has_superuser_privileges, has_minimum_role, RoleType
|
|
33
34
|
from fastapi_m8 import BaseController, ResponseModelBase, ResponseMessage
|
|
34
|
-
from fastapi_m8 import TimestampMixin, UserModel,
|
|
35
|
+
from fastapi_m8 import TimestampMixin, UserModel, ValidationConstants
|
|
36
|
+
from fastapi_m8 import find_dotenv, render_metrics, REGISTRY
|
|
37
|
+
from fastapi_m8 import make_scrape_credential_guard
|
|
38
|
+
|
|
39
|
+
``BaseController`` and ``TimestampMixin`` are the only two of those that need
|
|
40
|
+
the ``[db]`` extra; both are resolved lazily — see ``__getattr__`` below.
|
|
35
41
|
|
|
36
42
|
Tier 3 — informational / future::
|
|
37
43
|
|
|
@@ -39,15 +45,19 @@ Tier 3 — informational / future::
|
|
|
39
45
|
from fastapi_m8 import COMPAT_MATRIX, __version__
|
|
40
46
|
"""
|
|
41
47
|
|
|
48
|
+
from typing import TYPE_CHECKING, Any
|
|
49
|
+
|
|
42
50
|
# Tier 1
|
|
43
51
|
# Reusable SDK primitives — re-exported so consumers only need fastapi-m8,
|
|
44
52
|
# never a direct auth-sdk-m8 dependency.
|
|
45
53
|
from auth_sdk_m8 import has_superuser_privileges
|
|
46
|
-
from auth_sdk_m8.
|
|
47
|
-
from auth_sdk_m8.
|
|
54
|
+
from auth_sdk_m8.authorization import has_minimum_role
|
|
55
|
+
from auth_sdk_m8.observability.metrics import REGISTRY
|
|
48
56
|
from auth_sdk_m8.observability.metrics import render as render_metrics
|
|
49
|
-
from auth_sdk_m8.schemas.base import ResponseMessage, ResponseModelBase
|
|
57
|
+
from auth_sdk_m8.schemas.base import ResponseMessage, ResponseModelBase, RoleType
|
|
58
|
+
from auth_sdk_m8.schemas.shared import ValidationConstants
|
|
50
59
|
from auth_sdk_m8.schemas.user import UserModel
|
|
60
|
+
from auth_sdk_m8.security.guards import make_scrape_credential_guard
|
|
51
61
|
from auth_sdk_m8.utils.paths import find_dotenv
|
|
52
62
|
|
|
53
63
|
from fastapi_m8._api_key import (
|
|
@@ -92,6 +102,68 @@ from fastapi_m8._route_audit import BareApiKeyDependency, audit_api_key_routes
|
|
|
92
102
|
from fastapi_m8._version import __version__
|
|
93
103
|
from fastapi_m8.config import ConsumerServiceSettings
|
|
94
104
|
|
|
105
|
+
if TYPE_CHECKING: # pragma: no cover - type-checker-only, never executed
|
|
106
|
+
# Imported eagerly for type checkers and IDEs only. At runtime these two
|
|
107
|
+
# names are served by ``__getattr__`` below, so a bare install can import
|
|
108
|
+
# the package without the ``[db]`` extra.
|
|
109
|
+
from auth_sdk_m8.controllers.base import BaseController
|
|
110
|
+
from auth_sdk_m8.models.shared import TimestampMixin
|
|
111
|
+
|
|
112
|
+
# The SDK re-exports that require the ``[db]`` extra, mapped to their source
|
|
113
|
+
# module. ``auth_sdk_m8.controllers.base`` imports ``sqlalchemy.exc`` and
|
|
114
|
+
# ``sqlmodel``; ``auth_sdk_m8.models.shared`` imports ``sqlalchemy`` and
|
|
115
|
+
# ``sqlmodel``. Every other re-export above is extra-free.
|
|
116
|
+
_DB_REEXPORTS: dict[str, str] = {
|
|
117
|
+
"BaseController": "auth_sdk_m8.controllers.base",
|
|
118
|
+
"TimestampMixin": "auth_sdk_m8.models.shared",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def __getattr__(name: str) -> Any:
|
|
123
|
+
"""
|
|
124
|
+
Resolve the ``[db]``-extra SDK re-exports on first access (PEP 562).
|
|
125
|
+
|
|
126
|
+
``pip install fastapi-m8`` with no extras must yield an importable
|
|
127
|
+
package. Importing ``BaseController`` / ``TimestampMixin`` at module level
|
|
128
|
+
made ``import fastapi_m8`` raise ``ModuleNotFoundError: No module named
|
|
129
|
+
'sqlalchemy'`` on a bare install, because both come from SQLModel-backed
|
|
130
|
+
SDK modules and SQLAlchemy arrives only through the ``db`` extra.
|
|
131
|
+
|
|
132
|
+
Deferring them costs a bare install nothing and keeps the import boundary
|
|
133
|
+
intact: a consumer still writes ``from fastapi_m8 import BaseController``.
|
|
134
|
+
The object returned **is** the SDK object — identity is preserved, so ORM
|
|
135
|
+
models mixing in the re-exported ``TimestampMixin`` register exactly the
|
|
136
|
+
same metadata as before, and the resolved value is cached in the module
|
|
137
|
+
globals so later lookups skip this function entirely.
|
|
138
|
+
|
|
139
|
+
Note on where the failure now surfaces: PEP 562 moves it from
|
|
140
|
+
``import fastapi_m8`` to first attribute access. For both of these names
|
|
141
|
+
that is still the consumer's own module-import time — ``BaseController``
|
|
142
|
+
is subclassed and ``TimestampMixin`` is mixed in at class-definition
|
|
143
|
+
time — so the deferral does **not** push an ``ImportError`` into a request
|
|
144
|
+
path. It is a packaging fix, not a runtime-path hazard.
|
|
145
|
+
"""
|
|
146
|
+
module_path = _DB_REEXPORTS.get(name)
|
|
147
|
+
if module_path is None:
|
|
148
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
149
|
+
from importlib import import_module # noqa: PLC0415
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
value = getattr(import_module(module_path), name)
|
|
153
|
+
except ModuleNotFoundError as exc:
|
|
154
|
+
raise ModuleNotFoundError(
|
|
155
|
+
f"fastapi_m8.{name} is re-exported from {module_path}, which requires"
|
|
156
|
+
f" the 'db' extra: install fastapi-m8[db] (or fastapi-m8[all])."
|
|
157
|
+
) from exc
|
|
158
|
+
globals()[name] = value
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def __dir__() -> list[str]:
|
|
163
|
+
"""Keep the lazy names discoverable by ``dir()`` and tab completion."""
|
|
164
|
+
return sorted(set(globals()) | set(_DB_REEXPORTS))
|
|
165
|
+
|
|
166
|
+
|
|
95
167
|
__all__ = [
|
|
96
168
|
"__version__",
|
|
97
169
|
# Tier 1
|
|
@@ -127,13 +199,18 @@ __all__ = [
|
|
|
127
199
|
"HealthAggregatePolicy",
|
|
128
200
|
# Reusable SDK primitives (from auth-sdk-m8)
|
|
129
201
|
"has_superuser_privileges",
|
|
202
|
+
"has_minimum_role",
|
|
203
|
+
"RoleType",
|
|
130
204
|
"BaseController",
|
|
131
205
|
"ResponseModelBase",
|
|
132
206
|
"ResponseMessage",
|
|
133
207
|
"TimestampMixin",
|
|
134
208
|
"UserModel",
|
|
209
|
+
"ValidationConstants",
|
|
135
210
|
"find_dotenv",
|
|
136
211
|
"render_metrics",
|
|
212
|
+
"REGISTRY",
|
|
213
|
+
"make_scrape_credential_guard",
|
|
137
214
|
# Tier 3
|
|
138
215
|
"create_async_app",
|
|
139
216
|
"CAPABILITIES",
|