svc-infra 0.1.589__py3-none-any.whl → 0.1.706__py3-none-any.whl

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.

Potentially problematic release.


This version of svc-infra might be problematic. Click here for more details.

Files changed (260) hide show
  1. svc_infra/__init__.py +58 -2
  2. svc_infra/apf_payments/README.md +732 -0
  3. svc_infra/apf_payments/models.py +133 -42
  4. svc_infra/apf_payments/provider/__init__.py +4 -0
  5. svc_infra/apf_payments/provider/aiydan.py +871 -0
  6. svc_infra/apf_payments/provider/base.py +30 -9
  7. svc_infra/apf_payments/provider/stripe.py +156 -62
  8. svc_infra/apf_payments/schemas.py +19 -10
  9. svc_infra/apf_payments/service.py +211 -68
  10. svc_infra/apf_payments/settings.py +27 -3
  11. svc_infra/api/__init__.py +61 -0
  12. svc_infra/api/fastapi/__init__.py +15 -0
  13. svc_infra/api/fastapi/admin/__init__.py +3 -0
  14. svc_infra/api/fastapi/admin/add.py +245 -0
  15. svc_infra/api/fastapi/apf_payments/router.py +145 -46
  16. svc_infra/api/fastapi/apf_payments/setup.py +26 -8
  17. svc_infra/api/fastapi/auth/__init__.py +65 -0
  18. svc_infra/api/fastapi/auth/_cookies.py +6 -2
  19. svc_infra/api/fastapi/auth/add.py +27 -14
  20. svc_infra/api/fastapi/auth/gaurd.py +104 -13
  21. svc_infra/api/fastapi/auth/mfa/models.py +3 -1
  22. svc_infra/api/fastapi/auth/mfa/pre_auth.py +10 -6
  23. svc_infra/api/fastapi/auth/mfa/router.py +15 -8
  24. svc_infra/api/fastapi/auth/mfa/security.py +1 -2
  25. svc_infra/api/fastapi/auth/mfa/utils.py +2 -1
  26. svc_infra/api/fastapi/auth/mfa/verify.py +9 -2
  27. svc_infra/api/fastapi/auth/policy.py +0 -1
  28. svc_infra/api/fastapi/auth/providers.py +3 -1
  29. svc_infra/api/fastapi/auth/routers/apikey_router.py +6 -6
  30. svc_infra/api/fastapi/auth/routers/oauth_router.py +214 -75
  31. svc_infra/api/fastapi/auth/routers/session_router.py +67 -0
  32. svc_infra/api/fastapi/auth/security.py +31 -10
  33. svc_infra/api/fastapi/auth/sender.py +8 -1
  34. svc_infra/api/fastapi/auth/settings.py +2 -0
  35. svc_infra/api/fastapi/auth/state.py +3 -1
  36. svc_infra/api/fastapi/auth/ws_security.py +275 -0
  37. svc_infra/api/fastapi/billing/router.py +73 -0
  38. svc_infra/api/fastapi/billing/setup.py +19 -0
  39. svc_infra/api/fastapi/cache/add.py +9 -5
  40. svc_infra/api/fastapi/db/__init__.py +5 -1
  41. svc_infra/api/fastapi/db/http.py +3 -1
  42. svc_infra/api/fastapi/db/nosql/__init__.py +39 -1
  43. svc_infra/api/fastapi/db/nosql/mongo/add.py +47 -32
  44. svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +30 -11
  45. svc_infra/api/fastapi/db/sql/__init__.py +5 -1
  46. svc_infra/api/fastapi/db/sql/add.py +71 -26
  47. svc_infra/api/fastapi/db/sql/crud_router.py +210 -22
  48. svc_infra/api/fastapi/db/sql/health.py +3 -1
  49. svc_infra/api/fastapi/db/sql/session.py +18 -0
  50. svc_infra/api/fastapi/db/sql/users.py +29 -5
  51. svc_infra/api/fastapi/dependencies/ratelimit.py +130 -0
  52. svc_infra/api/fastapi/docs/add.py +173 -0
  53. svc_infra/api/fastapi/docs/landing.py +4 -2
  54. svc_infra/api/fastapi/docs/scoped.py +62 -15
  55. svc_infra/api/fastapi/dual/__init__.py +12 -2
  56. svc_infra/api/fastapi/dual/dualize.py +1 -1
  57. svc_infra/api/fastapi/dual/protected.py +126 -4
  58. svc_infra/api/fastapi/dual/public.py +25 -0
  59. svc_infra/api/fastapi/dual/router.py +40 -13
  60. svc_infra/api/fastapi/dx.py +33 -2
  61. svc_infra/api/fastapi/ease.py +10 -2
  62. svc_infra/api/fastapi/http/concurrency.py +2 -1
  63. svc_infra/api/fastapi/http/conditional.py +3 -1
  64. svc_infra/api/fastapi/middleware/debug.py +4 -1
  65. svc_infra/api/fastapi/middleware/errors/catchall.py +6 -2
  66. svc_infra/api/fastapi/middleware/errors/exceptions.py +1 -1
  67. svc_infra/api/fastapi/middleware/errors/handlers.py +54 -8
  68. svc_infra/api/fastapi/middleware/graceful_shutdown.py +104 -0
  69. svc_infra/api/fastapi/middleware/idempotency.py +197 -70
  70. svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
  71. svc_infra/api/fastapi/middleware/optimistic_lock.py +42 -0
  72. svc_infra/api/fastapi/middleware/ratelimit.py +143 -31
  73. svc_infra/api/fastapi/middleware/ratelimit_store.py +111 -0
  74. svc_infra/api/fastapi/middleware/request_id.py +27 -11
  75. svc_infra/api/fastapi/middleware/request_size_limit.py +36 -0
  76. svc_infra/api/fastapi/middleware/timeout.py +177 -0
  77. svc_infra/api/fastapi/openapi/apply.py +5 -3
  78. svc_infra/api/fastapi/openapi/conventions.py +9 -2
  79. svc_infra/api/fastapi/openapi/mutators.py +165 -20
  80. svc_infra/api/fastapi/openapi/pipeline.py +1 -1
  81. svc_infra/api/fastapi/openapi/security.py +3 -1
  82. svc_infra/api/fastapi/ops/add.py +75 -0
  83. svc_infra/api/fastapi/pagination.py +47 -20
  84. svc_infra/api/fastapi/routers/__init__.py +43 -15
  85. svc_infra/api/fastapi/routers/ping.py +1 -0
  86. svc_infra/api/fastapi/setup.py +188 -56
  87. svc_infra/api/fastapi/tenancy/add.py +19 -0
  88. svc_infra/api/fastapi/tenancy/context.py +112 -0
  89. svc_infra/api/fastapi/versioned.py +101 -0
  90. svc_infra/app/README.md +5 -5
  91. svc_infra/app/__init__.py +3 -1
  92. svc_infra/app/env.py +69 -1
  93. svc_infra/app/logging/add.py +9 -2
  94. svc_infra/app/logging/formats.py +12 -5
  95. svc_infra/billing/__init__.py +23 -0
  96. svc_infra/billing/async_service.py +147 -0
  97. svc_infra/billing/jobs.py +241 -0
  98. svc_infra/billing/models.py +177 -0
  99. svc_infra/billing/quotas.py +103 -0
  100. svc_infra/billing/schemas.py +36 -0
  101. svc_infra/billing/service.py +123 -0
  102. svc_infra/bundled_docs/README.md +5 -0
  103. svc_infra/bundled_docs/__init__.py +1 -0
  104. svc_infra/bundled_docs/getting-started.md +6 -0
  105. svc_infra/cache/__init__.py +9 -0
  106. svc_infra/cache/add.py +170 -0
  107. svc_infra/cache/backend.py +7 -6
  108. svc_infra/cache/decorators.py +81 -15
  109. svc_infra/cache/demo.py +2 -2
  110. svc_infra/cache/keys.py +24 -4
  111. svc_infra/cache/recache.py +26 -14
  112. svc_infra/cache/resources.py +14 -5
  113. svc_infra/cache/tags.py +19 -44
  114. svc_infra/cache/utils.py +3 -1
  115. svc_infra/cli/__init__.py +52 -8
  116. svc_infra/cli/__main__.py +4 -0
  117. svc_infra/cli/cmds/__init__.py +39 -2
  118. svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +7 -4
  119. svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +7 -5
  120. svc_infra/cli/cmds/db/ops_cmds.py +270 -0
  121. svc_infra/cli/cmds/db/sql/alembic_cmds.py +103 -18
  122. svc_infra/cli/cmds/db/sql/sql_export_cmds.py +88 -0
  123. svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
  124. svc_infra/cli/cmds/docs/docs_cmds.py +142 -0
  125. svc_infra/cli/cmds/dx/__init__.py +12 -0
  126. svc_infra/cli/cmds/dx/dx_cmds.py +116 -0
  127. svc_infra/cli/cmds/health/__init__.py +179 -0
  128. svc_infra/cli/cmds/health/health_cmds.py +8 -0
  129. svc_infra/cli/cmds/help.py +4 -0
  130. svc_infra/cli/cmds/jobs/__init__.py +1 -0
  131. svc_infra/cli/cmds/jobs/jobs_cmds.py +47 -0
  132. svc_infra/cli/cmds/obs/obs_cmds.py +36 -15
  133. svc_infra/cli/cmds/sdk/__init__.py +0 -0
  134. svc_infra/cli/cmds/sdk/sdk_cmds.py +112 -0
  135. svc_infra/cli/foundation/runner.py +6 -2
  136. svc_infra/data/add.py +61 -0
  137. svc_infra/data/backup.py +58 -0
  138. svc_infra/data/erasure.py +45 -0
  139. svc_infra/data/fixtures.py +42 -0
  140. svc_infra/data/retention.py +61 -0
  141. svc_infra/db/__init__.py +15 -0
  142. svc_infra/db/crud_schema.py +9 -9
  143. svc_infra/db/inbox.py +67 -0
  144. svc_infra/db/nosql/__init__.py +3 -0
  145. svc_infra/db/nosql/core.py +30 -9
  146. svc_infra/db/nosql/indexes.py +3 -1
  147. svc_infra/db/nosql/management.py +1 -1
  148. svc_infra/db/nosql/mongo/README.md +13 -13
  149. svc_infra/db/nosql/mongo/client.py +19 -2
  150. svc_infra/db/nosql/mongo/settings.py +6 -2
  151. svc_infra/db/nosql/repository.py +35 -15
  152. svc_infra/db/nosql/resource.py +20 -3
  153. svc_infra/db/nosql/scaffold.py +9 -3
  154. svc_infra/db/nosql/service.py +3 -1
  155. svc_infra/db/nosql/types.py +6 -2
  156. svc_infra/db/ops.py +384 -0
  157. svc_infra/db/outbox.py +108 -0
  158. svc_infra/db/sql/apikey.py +37 -9
  159. svc_infra/db/sql/authref.py +9 -3
  160. svc_infra/db/sql/constants.py +12 -8
  161. svc_infra/db/sql/core.py +2 -2
  162. svc_infra/db/sql/management.py +11 -8
  163. svc_infra/db/sql/repository.py +99 -26
  164. svc_infra/db/sql/resource.py +5 -0
  165. svc_infra/db/sql/scaffold.py +6 -2
  166. svc_infra/db/sql/service.py +15 -5
  167. svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
  168. svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +1 -1
  169. svc_infra/db/sql/templates/setup/env_async.py.tmpl +34 -12
  170. svc_infra/db/sql/templates/setup/env_sync.py.tmpl +29 -7
  171. svc_infra/db/sql/tenant.py +88 -0
  172. svc_infra/db/sql/uniq_hooks.py +9 -3
  173. svc_infra/db/sql/utils.py +138 -51
  174. svc_infra/db/sql/versioning.py +14 -0
  175. svc_infra/deploy/__init__.py +538 -0
  176. svc_infra/documents/__init__.py +100 -0
  177. svc_infra/documents/add.py +264 -0
  178. svc_infra/documents/ease.py +233 -0
  179. svc_infra/documents/models.py +114 -0
  180. svc_infra/documents/storage.py +264 -0
  181. svc_infra/dx/add.py +65 -0
  182. svc_infra/dx/changelog.py +74 -0
  183. svc_infra/dx/checks.py +68 -0
  184. svc_infra/exceptions.py +141 -0
  185. svc_infra/health/__init__.py +864 -0
  186. svc_infra/http/__init__.py +13 -0
  187. svc_infra/http/client.py +105 -0
  188. svc_infra/jobs/builtins/outbox_processor.py +40 -0
  189. svc_infra/jobs/builtins/webhook_delivery.py +95 -0
  190. svc_infra/jobs/easy.py +33 -0
  191. svc_infra/jobs/loader.py +50 -0
  192. svc_infra/jobs/queue.py +116 -0
  193. svc_infra/jobs/redis_queue.py +256 -0
  194. svc_infra/jobs/runner.py +79 -0
  195. svc_infra/jobs/scheduler.py +53 -0
  196. svc_infra/jobs/worker.py +40 -0
  197. svc_infra/loaders/__init__.py +186 -0
  198. svc_infra/loaders/base.py +142 -0
  199. svc_infra/loaders/github.py +311 -0
  200. svc_infra/loaders/models.py +147 -0
  201. svc_infra/loaders/url.py +235 -0
  202. svc_infra/logging/__init__.py +374 -0
  203. svc_infra/mcp/svc_infra_mcp.py +91 -33
  204. svc_infra/obs/README.md +2 -0
  205. svc_infra/obs/add.py +65 -9
  206. svc_infra/obs/cloud_dash.py +2 -1
  207. svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
  208. svc_infra/obs/metrics/__init__.py +52 -0
  209. svc_infra/obs/metrics/asgi.py +13 -7
  210. svc_infra/obs/metrics/http.py +9 -5
  211. svc_infra/obs/metrics/sqlalchemy.py +13 -9
  212. svc_infra/obs/metrics.py +53 -0
  213. svc_infra/obs/settings.py +6 -2
  214. svc_infra/security/add.py +217 -0
  215. svc_infra/security/audit.py +212 -0
  216. svc_infra/security/audit_service.py +74 -0
  217. svc_infra/security/headers.py +52 -0
  218. svc_infra/security/hibp.py +101 -0
  219. svc_infra/security/jwt_rotation.py +105 -0
  220. svc_infra/security/lockout.py +102 -0
  221. svc_infra/security/models.py +287 -0
  222. svc_infra/security/oauth_models.py +73 -0
  223. svc_infra/security/org_invites.py +130 -0
  224. svc_infra/security/passwords.py +79 -0
  225. svc_infra/security/permissions.py +171 -0
  226. svc_infra/security/session.py +98 -0
  227. svc_infra/security/signed_cookies.py +100 -0
  228. svc_infra/storage/__init__.py +93 -0
  229. svc_infra/storage/add.py +253 -0
  230. svc_infra/storage/backends/__init__.py +11 -0
  231. svc_infra/storage/backends/local.py +339 -0
  232. svc_infra/storage/backends/memory.py +216 -0
  233. svc_infra/storage/backends/s3.py +353 -0
  234. svc_infra/storage/base.py +239 -0
  235. svc_infra/storage/easy.py +185 -0
  236. svc_infra/storage/settings.py +195 -0
  237. svc_infra/testing/__init__.py +685 -0
  238. svc_infra/utils.py +7 -3
  239. svc_infra/webhooks/__init__.py +69 -0
  240. svc_infra/webhooks/add.py +339 -0
  241. svc_infra/webhooks/encryption.py +115 -0
  242. svc_infra/webhooks/fastapi.py +39 -0
  243. svc_infra/webhooks/router.py +55 -0
  244. svc_infra/webhooks/service.py +70 -0
  245. svc_infra/webhooks/signing.py +34 -0
  246. svc_infra/websocket/__init__.py +79 -0
  247. svc_infra/websocket/add.py +140 -0
  248. svc_infra/websocket/client.py +282 -0
  249. svc_infra/websocket/config.py +69 -0
  250. svc_infra/websocket/easy.py +76 -0
  251. svc_infra/websocket/exceptions.py +61 -0
  252. svc_infra/websocket/manager.py +344 -0
  253. svc_infra/websocket/models.py +49 -0
  254. svc_infra-0.1.706.dist-info/LICENSE +21 -0
  255. svc_infra-0.1.706.dist-info/METADATA +356 -0
  256. svc_infra-0.1.706.dist-info/RECORD +357 -0
  257. svc_infra-0.1.589.dist-info/METADATA +0 -79
  258. svc_infra-0.1.589.dist-info/RECORD +0 -234
  259. {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/WHEEL +0 -0
  260. {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/entry_points.txt +0 -0
@@ -3,11 +3,24 @@ from __future__ import annotations
3
3
  import base64
4
4
  import contextvars
5
5
  import json
6
- from typing import Any, Callable, Generic, Iterable, List, Optional, Sequence, TypeVar
6
+ import logging
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Generic,
11
+ Iterable,
12
+ List,
13
+ Optional,
14
+ Sequence,
15
+ TypeVar,
16
+ cast,
17
+ )
7
18
 
8
19
  from fastapi import Query, Request
9
20
  from pydantic import BaseModel, Field
10
21
 
22
+ logger = logging.getLogger(__name__)
23
+
11
24
  T = TypeVar("T")
12
25
 
13
26
 
@@ -44,13 +57,13 @@ def _encode_cursor(payload: dict) -> str:
44
57
  return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
45
58
 
46
59
 
47
- def decode_cursor(token: Optional[str]) -> dict:
60
+ def decode_cursor(token: Optional[str]) -> dict[Any, Any]:
48
61
  """Public: decode an incoming cursor token for debugging/ops."""
49
62
  if not token:
50
63
  return {}
51
64
  s = token + "=" * (-len(token) % 4)
52
65
  raw = base64.urlsafe_b64decode(s.encode("ascii")).decode("utf-8")
53
- return json.loads(raw)
66
+ return cast(dict[Any, Any], json.loads(raw))
54
67
 
55
68
 
56
69
  # ---------- Context ----------
@@ -85,11 +98,15 @@ class PaginationContext(Generic[T]):
85
98
 
86
99
  @property
87
100
  def cursor(self) -> Optional[str]:
88
- return (self.cursor_params or CursorParams()).cursor if self.allow_cursor else None
101
+ return (
102
+ (self.cursor_params or CursorParams()).cursor if self.allow_cursor else None
103
+ )
89
104
 
90
105
  @property
91
106
  def limit(self) -> int:
92
- if self.allow_cursor and self.cursor_params and self.cursor_params.cursor is not None:
107
+ # For cursor-based pagination, always honor the requested limit, even on the first page
108
+ # (cursor may be None for the first page).
109
+ if self.allow_cursor and self.cursor_params:
93
110
  return self.cursor_params.limit
94
111
  if self.allow_page and self.page_params:
95
112
  return self.limit_override or self.page_params.page_size
@@ -101,7 +118,11 @@ class PaginationContext(Generic[T]):
101
118
 
102
119
  @property
103
120
  def page_size(self) -> Optional[int]:
104
- return self.page_params.page_size if (self.allow_page and self.page_params) else None
121
+ return (
122
+ self.page_params.page_size
123
+ if (self.allow_page and self.page_params)
124
+ else None
125
+ )
105
126
 
106
127
  @property
107
128
  def offset(self) -> int:
@@ -110,7 +131,11 @@ class PaginationContext(Generic[T]):
110
131
  return 0
111
132
 
112
133
  def wrap(
113
- self, items: list[T], *, next_cursor: Optional[str] = None, total: Optional[int] = None
134
+ self,
135
+ items: list[T],
136
+ *,
137
+ next_cursor: Optional[str] = None,
138
+ total: Optional[int] = None,
114
139
  ):
115
140
  if self.envelope:
116
141
  return Paginated[T](items=items, next_cursor=next_cursor, total=total)
@@ -125,8 +150,8 @@ class PaginationContext(Generic[T]):
125
150
  return _encode_cursor({"after": last_key})
126
151
 
127
152
 
128
- _pagination_ctx: contextvars.ContextVar[PaginationContext] = contextvars.ContextVar(
129
- "pagination_ctx", default=None
153
+ _pagination_ctx: contextvars.ContextVar[PaginationContext | None] = (
154
+ contextvars.ContextVar("pagination_ctx", default=None)
130
155
  )
131
156
 
132
157
 
@@ -146,7 +171,9 @@ def use_pagination() -> PaginationContext:
146
171
 
147
172
 
148
173
  # ---------- Utilities ----------
149
- def text_filter(items: Iterable[T], q: Optional[str], *getters: Callable[[T], str]) -> list[T]:
174
+ def text_filter(
175
+ items: Iterable[T], q: Optional[str], *getters: Callable[[T], str]
176
+ ) -> list[T]:
150
177
  if not q:
151
178
  return list(items)
152
179
  ql = q.lower()
@@ -157,8 +184,8 @@ def text_filter(items: Iterable[T], q: Optional[str], *getters: Callable[[T], st
157
184
  if ql in (g(it) or "").lower():
158
185
  out.append(it)
159
186
  break
160
- except Exception:
161
- pass
187
+ except Exception as e:
188
+ logger.debug("text_filter getter failed for item: %s", e)
162
189
  return out
163
190
 
164
191
 
@@ -215,7 +242,7 @@ def make_pagination_injector(
215
242
  # Cursor-only (common case)
216
243
  if allow_cursor and not allow_page and not include_filters:
217
244
 
218
- async def _inject(
245
+ async def _inject_cursor(
219
246
  request: Request,
220
247
  cursor: str | None = Query(None),
221
248
  limit: int = Query(default_limit, ge=1, le=max_limit),
@@ -233,12 +260,12 @@ def make_pagination_injector(
233
260
  )
234
261
  return None
235
262
 
236
- return _inject
263
+ return _inject_cursor
237
264
 
238
265
  # Cursor + filters
239
266
  if allow_cursor and not allow_page and include_filters:
240
267
 
241
- async def _inject(
268
+ async def _inject_cursor_with_filters(
242
269
  request: Request,
243
270
  cursor: str | None = Query(None),
244
271
  limit: int = Query(default_limit, ge=1, le=max_limit),
@@ -270,12 +297,12 @@ def make_pagination_injector(
270
297
  )
271
298
  return None
272
299
 
273
- return _inject
300
+ return _inject_cursor_with_filters
274
301
 
275
302
  # Page-only
276
303
  if not allow_cursor and allow_page:
277
304
 
278
- async def _inject(
305
+ async def _inject_page(
279
306
  request: Request,
280
307
  page: int = Query(1, ge=1),
281
308
  page_size: int = Query(default_limit, ge=1, le=max_limit),
@@ -293,10 +320,10 @@ def make_pagination_injector(
293
320
  )
294
321
  return None
295
322
 
296
- return _inject
323
+ return _inject_page
297
324
 
298
325
  # Both cursor + page (rare; exposes all)
299
- async def _inject(
326
+ async def _inject_all(
300
327
  request: Request,
301
328
  cursor: str | None = Query(None),
302
329
  limit: int = Query(default_limit, ge=1, le=max_limit),
@@ -336,7 +363,7 @@ def make_pagination_injector(
336
363
  )
337
364
  return None
338
365
 
339
- return _inject
366
+ return _inject_all
340
367
 
341
368
 
342
369
  # ----- Convenience helpers for routers -----
@@ -4,12 +4,18 @@ import importlib
4
4
  import logging
5
5
  import pkgutil
6
6
  from types import ModuleType
7
- from typing import Optional
7
+ from typing import Any, Optional
8
8
 
9
9
  from fastapi import FastAPI
10
10
  from fastapi.routing import APIRoute
11
11
 
12
- from svc_infra.app.env import ALL_ENVIRONMENTS, CURRENT_ENVIRONMENT, DEV_ENV, LOCAL_ENV, Environment
12
+ from svc_infra.app.env import (
13
+ ALL_ENVIRONMENTS,
14
+ CURRENT_ENVIRONMENT,
15
+ DEV_ENV,
16
+ LOCAL_ENV,
17
+ Environment,
18
+ )
13
19
 
14
20
  logger = logging.getLogger(__name__)
15
21
 
@@ -49,7 +55,9 @@ def _validate_base_package(base_package: str) -> ModuleType:
49
55
  try:
50
56
  package_module: ModuleType = importlib.import_module(base_package)
51
57
  except Exception as exc:
52
- raise RuntimeError(f"Could not import base_package '{base_package}': {exc}") from exc
58
+ raise RuntimeError(
59
+ f"Could not import base_package '{base_package}': {exc}"
60
+ ) from exc
53
61
 
54
62
  if not hasattr(package_module, "__path__"):
55
63
  raise RuntimeError(
@@ -64,7 +72,11 @@ def _normalize_environment(environment: Optional[Environment | str]) -> Environm
64
72
  return (
65
73
  CURRENT_ENVIRONMENT
66
74
  if environment is None
67
- else (Environment(environment) if not isinstance(environment, Environment) else environment)
75
+ else (
76
+ Environment(environment)
77
+ if not isinstance(environment, Environment)
78
+ else environment
79
+ )
68
80
  )
69
81
 
70
82
 
@@ -87,9 +99,12 @@ def _is_router_excluded_by_environment(
87
99
 
88
100
  # Support ALL_ENVIRONMENTS as a special value
89
101
  if router_excluded_envs is ALL_ENVIRONMENTS or (
90
- isinstance(router_excluded_envs, set) and router_excluded_envs == ALL_ENVIRONMENTS
102
+ isinstance(router_excluded_envs, set)
103
+ and router_excluded_envs == ALL_ENVIRONMENTS
91
104
  ):
92
- logger.debug(f"Skipping router module {module_name} due to ALL_ENVIRONMENTS exclusion.")
105
+ logger.debug(
106
+ f"Skipping router module {module_name} due to ALL_ENVIRONMENTS exclusion."
107
+ )
93
108
  return True
94
109
 
95
110
  # Normalize to set of Environment or str
@@ -99,14 +114,19 @@ def _is_router_excluded_by_environment(
99
114
  )
100
115
  return False
101
116
 
102
- normalized_excluded_envs = set()
117
+ normalized_excluded_envs: set[Environment | str] = set()
103
118
  for e in router_excluded_envs:
104
119
  try:
105
- normalized_excluded_envs.add(Environment(e) if not isinstance(e, Environment) else e)
120
+ normalized_excluded_envs.add(
121
+ Environment(e) if not isinstance(e, Environment) else e
122
+ )
106
123
  except Exception:
107
124
  normalized_excluded_envs.add(str(e))
108
125
 
109
- if environment in normalized_excluded_envs or str(environment) in normalized_excluded_envs:
126
+ if (
127
+ environment in normalized_excluded_envs
128
+ or str(environment) in normalized_excluded_envs
129
+ ):
110
130
  logger.debug(
111
131
  f"Skipping router module {module_name} due to ROUTER_EXCLUDED_ENVIRONMENTS restriction: {router_excluded_envs}"
112
132
  )
@@ -126,7 +146,7 @@ def _is_router_included_by_environment(
126
146
  f"ROUTER_ENVIRONMENTS in {module_name} must be a set/list/tuple, got {type(router_envs)}"
127
147
  )
128
148
  return True
129
- normalized = set()
149
+ normalized: set[Environment | str] = set()
130
150
  for e in router_envs:
131
151
  try:
132
152
  normalized.add(Environment(e) if not isinstance(e, Environment) else e)
@@ -163,7 +183,7 @@ def _build_include_kwargs(module: ModuleType, prefix: str, force_include: bool)
163
183
  router_tag = getattr(module, "ROUTER_TAG", None)
164
184
  include_in_schema = getattr(module, "INCLUDE_ROUTER_IN_SCHEMA", True)
165
185
 
166
- include_kwargs = {"prefix": prefix}
186
+ include_kwargs: dict[str, Any] = {"prefix": prefix}
167
187
  if router_prefix:
168
188
  include_kwargs["prefix"] = prefix.rstrip("/") + router_prefix
169
189
  if router_tag:
@@ -230,18 +250,24 @@ def register_all_routers(
230
250
  """
231
251
  if base_package is None:
232
252
  if __package__ is None:
233
- raise RuntimeError("Cannot derive base_package; please pass base_package explicitly.")
253
+ raise RuntimeError(
254
+ "Cannot derive base_package; please pass base_package explicitly."
255
+ )
234
256
  base_package = __package__
235
257
 
236
258
  package_module = _validate_base_package(base_package)
237
259
  environment = _normalize_environment(environment)
238
- force_include = _should_force_include_in_schema(environment, force_include_in_schema)
260
+ force_include = _should_force_include_in_schema(
261
+ environment, force_include_in_schema
262
+ )
239
263
 
240
264
  for _, module_name, _ in pkgutil.walk_packages(
241
265
  package_module.__path__, prefix=f"{base_package}."
242
266
  ):
243
267
  if _should_skip_module(module_name):
244
- logger.debug("Skipping router module due to exclusion/private: %s", module_name)
268
+ logger.debug(
269
+ "Skipping router module due to exclusion/private: %s", module_name
270
+ )
245
271
  continue
246
272
 
247
273
  try:
@@ -250,4 +276,6 @@ def register_all_routers(
250
276
  logger.exception("Failed to import router module %s: %s", module_name, exc)
251
277
  continue
252
278
 
253
- _process_router_module(app, module, module_name, prefix, environment, force_include)
279
+ _process_router_module(
280
+ app, module, module_name, prefix, environment, force_include
281
+ )
@@ -14,6 +14,7 @@ router = public_router(tags=["Health Check"])
14
14
  PING_PATH,
15
15
  status_code=status.HTTP_200_OK,
16
16
  description="Operation to check if the service is up and running",
17
+ operation_id="health_ping_get",
17
18
  )
18
19
  def ping():
19
20
  logging.info("Health check: /ping endpoint accessed. Service is responsive.")