fastapi-m8 4.2.0__tar.gz → 4.2.2__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.
Files changed (65) hide show
  1. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.github/workflows/CI.yaml +9 -9
  2. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.github/workflows/PiPy.yml +5 -5
  3. fastapi_m8-4.2.2/CLAUDE.md +8 -0
  4. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/PKG-INFO +29 -2
  5. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/README.md +27 -0
  6. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/constraints-all.txt +1 -1
  7. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/constraints.txt +1 -1
  8. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_version.py +1 -1
  9. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/pyproject.toml +2 -2
  10. fastapi_m8-4.2.0/CLAUDE.md +0 -7
  11. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.codacy.yml +0 -0
  12. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.dockerignore +0 -0
  13. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.env.example +0 -0
  14. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.gitattributes +0 -0
  15. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.github/FUNDING.yml +0 -0
  16. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.github/dependabot.yml +0 -0
  17. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.gitignore +0 -0
  18. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.gitleaks.toml +0 -0
  19. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/.pydocstyle +0 -0
  20. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/AGENTS.md +0 -0
  21. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/CHANGELOG.md +0 -0
  22. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/LICENSE +0 -0
  23. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/REPOSITORY_CONTEXT.md +0 -0
  24. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/SECURITY.md +0 -0
  25. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/__init__.py +0 -0
  26. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_api_key.py +0 -0
  27. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_app.py +0 -0
  28. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_async_stub.py +0 -0
  29. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_compat.py +0 -0
  30. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_deps.py +0 -0
  31. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_engine.py +0 -0
  32. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_events.py +0 -0
  33. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_health.py +0 -0
  34. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_internal_auth.py +0 -0
  35. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_revocation.py +0 -0
  36. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/_route_audit.py +0 -0
  37. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/config.py +0 -0
  38. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/scripts/__init__.py +0 -0
  39. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/scripts/docker_start.sh +0 -0
  40. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/fastapi_m8/scripts/pre_start.py +0 -0
  41. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/__init__.py +0 -0
  42. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/conftest.py +0 -0
  43. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_api_key.py +0 -0
  44. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_api_key_deps.py +0 -0
  45. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_app.py +0 -0
  46. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_app_extra.py +0 -0
  47. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_async_stub.py +0 -0
  48. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_ci_policy.py +0 -0
  49. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_compat.py +0 -0
  50. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_config.py +0 -0
  51. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_config_file_secrets.py +0 -0
  52. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_deps.py +0 -0
  53. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_engine.py +0 -0
  54. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_event_signing_gate.py +0 -0
  55. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_events.py +0 -0
  56. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_fixture_matrix_contract.py +0 -0
  57. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_health.py +0 -0
  58. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_host_header_routing.py +0 -0
  59. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_internal_auth.py +0 -0
  60. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_meta.py +0 -0
  61. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_packaging.py +0 -0
  62. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_pre_start.py +0 -0
  63. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_public_typing.py +0 -0
  64. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_revocation.py +0 -0
  65. {fastapi_m8-4.2.0 → fastapi_m8-4.2.2}/tests/test_route_audit.py +0 -0
@@ -16,10 +16,10 @@ jobs:
16
16
  lint:
17
17
  runs-on: ubuntu-latest
18
18
  steps:
19
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
20
 
21
21
  - name: Set up Python
22
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
22
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
23
23
  with:
24
24
  python-version: "3.14"
25
25
  cache: "pip"
@@ -36,10 +36,10 @@ jobs:
36
36
  typecheck:
37
37
  runs-on: ubuntu-latest
38
38
  steps:
39
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
39
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40
40
 
41
41
  - name: Set up Python
42
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
42
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
43
43
  with:
44
44
  python-version: "3.14"
45
45
  cache: "pip"
@@ -53,10 +53,10 @@ jobs:
53
53
  security:
54
54
  runs-on: ubuntu-latest
55
55
  steps:
56
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
56
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
57
57
 
58
58
  - name: Set up Python
59
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
59
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
60
60
  with:
61
61
  python-version: "3.14"
62
62
 
@@ -87,10 +87,10 @@ jobs:
87
87
  python-version: ["3.12", "3.13", "3.14"]
88
88
 
89
89
  steps:
90
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
90
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
91
91
 
92
92
  - name: Set up Python ${{ matrix.python-version }}
93
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
93
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
94
94
  with:
95
95
  python-version: ${{ matrix.python-version }}
96
96
  cache: "pip"
@@ -121,7 +121,7 @@ jobs:
121
121
  runs-on: ubuntu-latest
122
122
 
123
123
  steps:
124
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
124
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
125
125
  with:
126
126
  fetch-depth: 0
127
127
 
@@ -12,9 +12,9 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
15
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
16
16
 
17
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
17
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
18
18
  with:
19
19
  python-version: "3.x"
20
20
 
@@ -51,7 +51,7 @@ jobs:
51
51
  path: dist/
52
52
 
53
53
  - name: Publish release distributions to PyPI
54
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
54
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
55
55
 
56
56
  sbom:
57
57
  name: Generate & attach SBOM
@@ -63,9 +63,9 @@ jobs:
63
63
  id-token: write
64
64
 
65
65
  steps:
66
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
66
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
67
67
 
68
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
68
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
69
69
  with:
70
70
  python-version: "3.x"
71
71
 
@@ -0,0 +1,8 @@
1
+ # fastapi-m8
2
+
3
+ ## Claude instructions
4
+
5
+ @REPOSITORY_CONTEXT.md
6
+
7
+ A verified nearest workspace may provide optional supported enhancement; it is
8
+ never required for standalone work.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-m8
3
- Version: 4.2.0
3
+ Version: 4.2.2
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.0
218
+ Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<4.0.0,>=3.1.2
219
219
  Requires-Dist: fastapi>=0.136.3
220
220
  Requires-Dist: httpx>=0.27.0
221
221
  Requires-Dist: packaging>=24.0
@@ -659,6 +659,18 @@ untouched.
659
659
  | `SERVICE_TOKEN_SCOPES` | No | `["introspection"]` | Scopes requested when minting a service token; fa-auth narrows to the subset the bootstrap credential was granted. |
660
660
  | `SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS` | No | `30` | Refresh a cached service token this many seconds before its `exp` so a call never races expiry. |
661
661
 
662
+ > **Ordinary expiry never surfaces as a `503`.** The refresh check runs lazily
663
+ > on every call, before the private request is sent: once past
664
+ > `exp - SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS`, the client re-exchanges first.
665
+ > Continuous traffic therefore never presents an actually expired service
666
+ > token to the issuer. The one case where a `503` legitimately occurs is a
667
+ > withdrawn/rotated consumer secret, or a request whose latency races the
668
+ > leeway margin — the issuer answers `401`, the cached token is dropped, and
669
+ > that single request fails closed while the next one re-mints and succeeds
670
+ > (never retried automatically — introspection consumes the caller's quota).
671
+ > Accepted operational behavior; documented rather than pre-empted by a
672
+ > second re-mint mechanism, since the proactive one above already covers it.
673
+
662
674
  ### Remote API-Key Principal
663
675
 
664
676
  Resolves a user API key presented by an external client to its owner's current
@@ -1168,6 +1180,21 @@ The value must match across the stack.
1168
1180
  **Stateless** — maximum scalability, simplest setup. Logout does not invalidate
1169
1181
  in-flight access tokens; they expire naturally.
1170
1182
 
1183
+ **Hybrid** — refresh-session state is revoked immediately (so a revoked user
1184
+ cannot mint a new access token), but an already-issued access token is not
1185
+ checked against the issuer and stays valid for the rest of its own lifetime.
1186
+ This is not a weaker or open-ended window: `TokenValidator.validate_access_token`
1187
+ enforces the JWT `exp` claim unconditionally, on every request, regardless of
1188
+ `TOKEN_MODE` (`auth_sdk_m8/security/token_validator.py`), and no `hybrid`/
1189
+ `stateless` code path in `build_auth_deps` skips or extends that check — only
1190
+ the *additional* revocation-client call is gated on `settings.is_stateful`. The
1191
+ stale-access-token window is therefore bounded by exactly
1192
+ `ACCESS_TOKEN_EXPIRE_MINUTES` and nothing longer; it must never be read as
1193
+ "immediate revocation" for the access token itself. Consumers that need
1194
+ immediate enforcement of a role/privilege downgrade — not just eventual
1195
+ expiry — must run `stateful` with `ACCESS_REVOCATION_FAILURE_MODE=fail_closed`
1196
+ instead of relying on hybrid/stateless.
1197
+
1171
1198
  **Stateful** — highest security. On each request a consumer performs an HTTP call to
1172
1199
  `fa-auth-m8` to verify the JWT's JTI has not been revoked. Requires `INTROSPECTION_URL`
1173
1200
  and `PRIVATE_API_SECRET` in consumer settings.
@@ -402,6 +402,18 @@ untouched.
402
402
  | `SERVICE_TOKEN_SCOPES` | No | `["introspection"]` | Scopes requested when minting a service token; fa-auth narrows to the subset the bootstrap credential was granted. |
403
403
  | `SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS` | No | `30` | Refresh a cached service token this many seconds before its `exp` so a call never races expiry. |
404
404
 
405
+ > **Ordinary expiry never surfaces as a `503`.** The refresh check runs lazily
406
+ > on every call, before the private request is sent: once past
407
+ > `exp - SERVICE_TOKEN_REFRESH_LEEWAY_SECONDS`, the client re-exchanges first.
408
+ > Continuous traffic therefore never presents an actually expired service
409
+ > token to the issuer. The one case where a `503` legitimately occurs is a
410
+ > withdrawn/rotated consumer secret, or a request whose latency races the
411
+ > leeway margin — the issuer answers `401`, the cached token is dropped, and
412
+ > that single request fails closed while the next one re-mints and succeeds
413
+ > (never retried automatically — introspection consumes the caller's quota).
414
+ > Accepted operational behavior; documented rather than pre-empted by a
415
+ > second re-mint mechanism, since the proactive one above already covers it.
416
+
405
417
  ### Remote API-Key Principal
406
418
 
407
419
  Resolves a user API key presented by an external client to its owner's current
@@ -911,6 +923,21 @@ The value must match across the stack.
911
923
  **Stateless** — maximum scalability, simplest setup. Logout does not invalidate
912
924
  in-flight access tokens; they expire naturally.
913
925
 
926
+ **Hybrid** — refresh-session state is revoked immediately (so a revoked user
927
+ cannot mint a new access token), but an already-issued access token is not
928
+ checked against the issuer and stays valid for the rest of its own lifetime.
929
+ This is not a weaker or open-ended window: `TokenValidator.validate_access_token`
930
+ enforces the JWT `exp` claim unconditionally, on every request, regardless of
931
+ `TOKEN_MODE` (`auth_sdk_m8/security/token_validator.py`), and no `hybrid`/
932
+ `stateless` code path in `build_auth_deps` skips or extends that check — only
933
+ the *additional* revocation-client call is gated on `settings.is_stateful`. The
934
+ stale-access-token window is therefore bounded by exactly
935
+ `ACCESS_TOKEN_EXPIRE_MINUTES` and nothing longer; it must never be read as
936
+ "immediate revocation" for the access token itself. Consumers that need
937
+ immediate enforcement of a role/privilege downgrade — not just eventual
938
+ expiry — must run `stateful` with `ACCESS_REVOCATION_FAILURE_MODE=fail_closed`
939
+ instead of relying on hybrid/stateless.
940
+
914
941
  **Stateful** — highest security. On each request a consumer performs an HTTP call to
915
942
  `fa-auth-m8` to verify the JWT's JTI has not been revoked. Requires `INTROSPECTION_URL`
916
943
  and `PRIVATE_API_SECRET` in consumer settings.
@@ -25,7 +25,7 @@ attrs==26.1.0
25
25
  # via
26
26
  # outcome
27
27
  # trio
28
- auth-sdk-m8[config,events,fastapi,observability,security]==3.1.0
28
+ auth-sdk-m8[config,events,fastapi,observability,security]==3.1.2
29
29
  # via
30
30
  # fastapi-m8
31
31
  # fastapi-m8 (pyproject.toml)
@@ -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.0
19
+ auth-sdk-m8[config,events,fastapi,observability,security]==3.1.2
20
20
  # via
21
21
  # fastapi-m8
22
22
  # fastapi-m8 (pyproject.toml)
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "4.2.0"
3
+ __version__ = "4.2.2"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fastapi-m8"
7
- version = "4.2.0"
7
+ version = "4.2.2"
8
8
  description = "FastAPI application framework for m8 consumer microservices."
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -26,7 +26,7 @@ dependencies = [
26
26
  "httpx>=0.27.0",
27
27
  "packaging>=24.0",
28
28
  "anyio>=4.0",
29
- "auth-sdk-m8[config,security,fastapi,observability,events]>=3.1.0,<4.0.0",
29
+ "auth-sdk-m8[config,security,fastapi,observability,events]>=3.1.2,<4.0.0",
30
30
  ]
31
31
 
32
32
  [project.optional-dependencies]
@@ -1,7 +0,0 @@
1
- # fastapi-m8
2
-
3
- # Claude instructions
4
-
5
- Read `REPOSITORY_CONTEXT.md` for the repository-local context. A verified nearest
6
- workspace may provide optional supported enhancement; it is never required for
7
- standalone work.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes