svc-infra 0.1.595__py3-none-any.whl → 1.1.0__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 (274) hide show
  1. svc_infra/__init__.py +58 -2
  2. svc_infra/apf_payments/models.py +68 -38
  3. svc_infra/apf_payments/provider/__init__.py +2 -2
  4. svc_infra/apf_payments/provider/aiydan.py +39 -23
  5. svc_infra/apf_payments/provider/base.py +8 -3
  6. svc_infra/apf_payments/provider/registry.py +3 -5
  7. svc_infra/apf_payments/provider/stripe.py +74 -52
  8. svc_infra/apf_payments/schemas.py +84 -83
  9. svc_infra/apf_payments/service.py +27 -16
  10. svc_infra/apf_payments/settings.py +12 -11
  11. svc_infra/api/__init__.py +61 -0
  12. svc_infra/api/fastapi/__init__.py +34 -0
  13. svc_infra/api/fastapi/admin/__init__.py +3 -0
  14. svc_infra/api/fastapi/admin/add.py +240 -0
  15. svc_infra/api/fastapi/apf_payments/router.py +94 -73
  16. svc_infra/api/fastapi/apf_payments/setup.py +10 -9
  17. svc_infra/api/fastapi/auth/__init__.py +65 -0
  18. svc_infra/api/fastapi/auth/_cookies.py +1 -3
  19. svc_infra/api/fastapi/auth/add.py +14 -15
  20. svc_infra/api/fastapi/auth/gaurd.py +32 -20
  21. svc_infra/api/fastapi/auth/mfa/models.py +3 -4
  22. svc_infra/api/fastapi/auth/mfa/pre_auth.py +13 -9
  23. svc_infra/api/fastapi/auth/mfa/router.py +9 -8
  24. svc_infra/api/fastapi/auth/mfa/security.py +4 -7
  25. svc_infra/api/fastapi/auth/mfa/utils.py +5 -3
  26. svc_infra/api/fastapi/auth/policy.py +0 -1
  27. svc_infra/api/fastapi/auth/providers.py +3 -3
  28. svc_infra/api/fastapi/auth/routers/apikey_router.py +19 -21
  29. svc_infra/api/fastapi/auth/routers/oauth_router.py +98 -52
  30. svc_infra/api/fastapi/auth/routers/session_router.py +6 -5
  31. svc_infra/api/fastapi/auth/security.py +25 -15
  32. svc_infra/api/fastapi/auth/sender.py +5 -0
  33. svc_infra/api/fastapi/auth/settings.py +18 -19
  34. svc_infra/api/fastapi/auth/state.py +5 -4
  35. svc_infra/api/fastapi/auth/ws_security.py +275 -0
  36. svc_infra/api/fastapi/billing/router.py +71 -0
  37. svc_infra/api/fastapi/billing/setup.py +19 -0
  38. svc_infra/api/fastapi/cache/add.py +9 -5
  39. svc_infra/api/fastapi/db/__init__.py +5 -1
  40. svc_infra/api/fastapi/db/http.py +10 -9
  41. svc_infra/api/fastapi/db/nosql/__init__.py +39 -1
  42. svc_infra/api/fastapi/db/nosql/mongo/add.py +35 -30
  43. svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +39 -21
  44. svc_infra/api/fastapi/db/sql/__init__.py +5 -1
  45. svc_infra/api/fastapi/db/sql/add.py +62 -25
  46. svc_infra/api/fastapi/db/sql/crud_router.py +205 -30
  47. svc_infra/api/fastapi/db/sql/session.py +19 -2
  48. svc_infra/api/fastapi/db/sql/users.py +18 -9
  49. svc_infra/api/fastapi/dependencies/ratelimit.py +76 -14
  50. svc_infra/api/fastapi/docs/add.py +163 -0
  51. svc_infra/api/fastapi/docs/landing.py +6 -6
  52. svc_infra/api/fastapi/docs/scoped.py +75 -36
  53. svc_infra/api/fastapi/dual/__init__.py +12 -2
  54. svc_infra/api/fastapi/dual/dualize.py +2 -2
  55. svc_infra/api/fastapi/dual/protected.py +123 -10
  56. svc_infra/api/fastapi/dual/public.py +25 -0
  57. svc_infra/api/fastapi/dual/router.py +18 -8
  58. svc_infra/api/fastapi/dx.py +33 -2
  59. svc_infra/api/fastapi/ease.py +59 -7
  60. svc_infra/api/fastapi/http/concurrency.py +2 -1
  61. svc_infra/api/fastapi/http/conditional.py +2 -2
  62. svc_infra/api/fastapi/middleware/debug.py +4 -1
  63. svc_infra/api/fastapi/middleware/errors/exceptions.py +2 -5
  64. svc_infra/api/fastapi/middleware/errors/handlers.py +50 -10
  65. svc_infra/api/fastapi/middleware/graceful_shutdown.py +95 -0
  66. svc_infra/api/fastapi/middleware/idempotency.py +190 -68
  67. svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
  68. svc_infra/api/fastapi/middleware/optimistic_lock.py +39 -0
  69. svc_infra/api/fastapi/middleware/ratelimit.py +125 -28
  70. svc_infra/api/fastapi/middleware/ratelimit_store.py +45 -13
  71. svc_infra/api/fastapi/middleware/request_id.py +24 -10
  72. svc_infra/api/fastapi/middleware/request_size_limit.py +3 -3
  73. svc_infra/api/fastapi/middleware/timeout.py +176 -0
  74. svc_infra/api/fastapi/object_router.py +1060 -0
  75. svc_infra/api/fastapi/openapi/apply.py +4 -3
  76. svc_infra/api/fastapi/openapi/conventions.py +13 -6
  77. svc_infra/api/fastapi/openapi/mutators.py +144 -17
  78. svc_infra/api/fastapi/openapi/pipeline.py +2 -2
  79. svc_infra/api/fastapi/openapi/responses.py +4 -6
  80. svc_infra/api/fastapi/openapi/security.py +1 -1
  81. svc_infra/api/fastapi/ops/add.py +73 -0
  82. svc_infra/api/fastapi/pagination.py +47 -32
  83. svc_infra/api/fastapi/routers/__init__.py +16 -10
  84. svc_infra/api/fastapi/routers/ping.py +1 -0
  85. svc_infra/api/fastapi/setup.py +167 -54
  86. svc_infra/api/fastapi/tenancy/add.py +20 -0
  87. svc_infra/api/fastapi/tenancy/context.py +113 -0
  88. svc_infra/api/fastapi/versioned.py +102 -0
  89. svc_infra/app/README.md +5 -5
  90. svc_infra/app/__init__.py +3 -1
  91. svc_infra/app/env.py +70 -4
  92. svc_infra/app/logging/add.py +10 -2
  93. svc_infra/app/logging/filter.py +1 -1
  94. svc_infra/app/logging/formats.py +13 -5
  95. svc_infra/app/root.py +3 -3
  96. svc_infra/billing/__init__.py +40 -0
  97. svc_infra/billing/async_service.py +167 -0
  98. svc_infra/billing/jobs.py +231 -0
  99. svc_infra/billing/models.py +146 -0
  100. svc_infra/billing/quotas.py +101 -0
  101. svc_infra/billing/schemas.py +34 -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 +21 -5
  106. svc_infra/cache/add.py +167 -0
  107. svc_infra/cache/backend.py +9 -7
  108. svc_infra/cache/decorators.py +75 -20
  109. svc_infra/cache/demo.py +2 -2
  110. svc_infra/cache/keys.py +26 -6
  111. svc_infra/cache/recache.py +26 -27
  112. svc_infra/cache/resources.py +6 -5
  113. svc_infra/cache/tags.py +19 -44
  114. svc_infra/cache/ttl.py +2 -3
  115. svc_infra/cache/utils.py +4 -3
  116. svc_infra/cli/__init__.py +44 -8
  117. svc_infra/cli/__main__.py +4 -0
  118. svc_infra/cli/cmds/__init__.py +39 -2
  119. svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +18 -14
  120. svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +9 -10
  121. svc_infra/cli/cmds/db/ops_cmds.py +267 -0
  122. svc_infra/cli/cmds/db/sql/alembic_cmds.py +97 -29
  123. svc_infra/cli/cmds/db/sql/sql_export_cmds.py +80 -0
  124. svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +13 -13
  125. svc_infra/cli/cmds/docs/docs_cmds.py +139 -0
  126. svc_infra/cli/cmds/dx/__init__.py +12 -0
  127. svc_infra/cli/cmds/dx/dx_cmds.py +110 -0
  128. svc_infra/cli/cmds/health/__init__.py +179 -0
  129. svc_infra/cli/cmds/health/health_cmds.py +8 -0
  130. svc_infra/cli/cmds/help.py +4 -0
  131. svc_infra/cli/cmds/jobs/__init__.py +1 -0
  132. svc_infra/cli/cmds/jobs/jobs_cmds.py +42 -0
  133. svc_infra/cli/cmds/obs/obs_cmds.py +31 -13
  134. svc_infra/cli/cmds/sdk/__init__.py +0 -0
  135. svc_infra/cli/cmds/sdk/sdk_cmds.py +102 -0
  136. svc_infra/cli/foundation/runner.py +4 -5
  137. svc_infra/cli/foundation/typer_bootstrap.py +1 -2
  138. svc_infra/data/__init__.py +83 -0
  139. svc_infra/data/add.py +61 -0
  140. svc_infra/data/backup.py +56 -0
  141. svc_infra/data/erasure.py +46 -0
  142. svc_infra/data/fixtures.py +42 -0
  143. svc_infra/data/retention.py +56 -0
  144. svc_infra/db/__init__.py +15 -0
  145. svc_infra/db/crud_schema.py +14 -13
  146. svc_infra/db/inbox.py +67 -0
  147. svc_infra/db/nosql/__init__.py +2 -0
  148. svc_infra/db/nosql/constants.py +1 -1
  149. svc_infra/db/nosql/core.py +19 -5
  150. svc_infra/db/nosql/indexes.py +12 -9
  151. svc_infra/db/nosql/management.py +4 -4
  152. svc_infra/db/nosql/mongo/README.md +13 -13
  153. svc_infra/db/nosql/mongo/client.py +21 -4
  154. svc_infra/db/nosql/mongo/settings.py +1 -1
  155. svc_infra/db/nosql/repository.py +46 -27
  156. svc_infra/db/nosql/resource.py +28 -16
  157. svc_infra/db/nosql/scaffold.py +14 -12
  158. svc_infra/db/nosql/service.py +2 -1
  159. svc_infra/db/nosql/service_with_hooks.py +4 -3
  160. svc_infra/db/nosql/utils.py +4 -4
  161. svc_infra/db/ops.py +380 -0
  162. svc_infra/db/outbox.py +105 -0
  163. svc_infra/db/sql/apikey.py +34 -15
  164. svc_infra/db/sql/authref.py +8 -6
  165. svc_infra/db/sql/constants.py +5 -1
  166. svc_infra/db/sql/core.py +13 -13
  167. svc_infra/db/sql/management.py +5 -6
  168. svc_infra/db/sql/repository.py +92 -26
  169. svc_infra/db/sql/resource.py +18 -12
  170. svc_infra/db/sql/scaffold.py +11 -11
  171. svc_infra/db/sql/service.py +2 -1
  172. svc_infra/db/sql/service_with_hooks.py +4 -3
  173. svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
  174. svc_infra/db/sql/templates/setup/env_async.py.tmpl +34 -12
  175. svc_infra/db/sql/templates/setup/env_sync.py.tmpl +29 -7
  176. svc_infra/db/sql/tenant.py +80 -0
  177. svc_infra/db/sql/uniq.py +8 -7
  178. svc_infra/db/sql/uniq_hooks.py +12 -11
  179. svc_infra/db/sql/utils.py +105 -47
  180. svc_infra/db/sql/versioning.py +14 -0
  181. svc_infra/db/utils.py +3 -3
  182. svc_infra/deploy/__init__.py +531 -0
  183. svc_infra/documents/__init__.py +100 -0
  184. svc_infra/documents/add.py +263 -0
  185. svc_infra/documents/ease.py +233 -0
  186. svc_infra/documents/models.py +114 -0
  187. svc_infra/documents/storage.py +262 -0
  188. svc_infra/dx/__init__.py +58 -0
  189. svc_infra/dx/add.py +63 -0
  190. svc_infra/dx/changelog.py +74 -0
  191. svc_infra/dx/checks.py +68 -0
  192. svc_infra/exceptions.py +141 -0
  193. svc_infra/health/__init__.py +863 -0
  194. svc_infra/http/__init__.py +13 -0
  195. svc_infra/http/client.py +101 -0
  196. svc_infra/jobs/__init__.py +79 -0
  197. svc_infra/jobs/builtins/outbox_processor.py +38 -0
  198. svc_infra/jobs/builtins/webhook_delivery.py +93 -0
  199. svc_infra/jobs/easy.py +33 -0
  200. svc_infra/jobs/loader.py +49 -0
  201. svc_infra/jobs/queue.py +106 -0
  202. svc_infra/jobs/redis_queue.py +242 -0
  203. svc_infra/jobs/runner.py +75 -0
  204. svc_infra/jobs/scheduler.py +53 -0
  205. svc_infra/jobs/worker.py +40 -0
  206. svc_infra/loaders/__init__.py +186 -0
  207. svc_infra/loaders/base.py +143 -0
  208. svc_infra/loaders/github.py +309 -0
  209. svc_infra/loaders/models.py +147 -0
  210. svc_infra/loaders/url.py +229 -0
  211. svc_infra/logging/__init__.py +375 -0
  212. svc_infra/mcp/__init__.py +82 -0
  213. svc_infra/mcp/svc_infra_mcp.py +91 -33
  214. svc_infra/obs/README.md +2 -0
  215. svc_infra/obs/add.py +68 -11
  216. svc_infra/obs/cloud_dash.py +2 -1
  217. svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
  218. svc_infra/obs/metrics/__init__.py +6 -7
  219. svc_infra/obs/metrics/asgi.py +8 -7
  220. svc_infra/obs/metrics/base.py +13 -13
  221. svc_infra/obs/metrics/http.py +3 -3
  222. svc_infra/obs/metrics/sqlalchemy.py +14 -13
  223. svc_infra/obs/metrics.py +9 -8
  224. svc_infra/resilience/__init__.py +44 -0
  225. svc_infra/resilience/circuit_breaker.py +328 -0
  226. svc_infra/resilience/retry.py +289 -0
  227. svc_infra/security/__init__.py +167 -0
  228. svc_infra/security/add.py +213 -0
  229. svc_infra/security/audit.py +97 -18
  230. svc_infra/security/audit_service.py +10 -9
  231. svc_infra/security/headers.py +15 -2
  232. svc_infra/security/hibp.py +14 -7
  233. svc_infra/security/jwt_rotation.py +78 -29
  234. svc_infra/security/lockout.py +23 -16
  235. svc_infra/security/models.py +77 -44
  236. svc_infra/security/oauth_models.py +73 -0
  237. svc_infra/security/org_invites.py +12 -12
  238. svc_infra/security/passwords.py +3 -3
  239. svc_infra/security/permissions.py +31 -7
  240. svc_infra/security/session.py +7 -8
  241. svc_infra/security/signed_cookies.py +26 -6
  242. svc_infra/storage/__init__.py +93 -0
  243. svc_infra/storage/add.py +250 -0
  244. svc_infra/storage/backends/__init__.py +11 -0
  245. svc_infra/storage/backends/local.py +331 -0
  246. svc_infra/storage/backends/memory.py +213 -0
  247. svc_infra/storage/backends/s3.py +334 -0
  248. svc_infra/storage/base.py +239 -0
  249. svc_infra/storage/easy.py +181 -0
  250. svc_infra/storage/settings.py +193 -0
  251. svc_infra/testing/__init__.py +682 -0
  252. svc_infra/utils.py +170 -5
  253. svc_infra/webhooks/__init__.py +69 -0
  254. svc_infra/webhooks/add.py +327 -0
  255. svc_infra/webhooks/encryption.py +115 -0
  256. svc_infra/webhooks/fastapi.py +37 -0
  257. svc_infra/webhooks/router.py +55 -0
  258. svc_infra/webhooks/service.py +69 -0
  259. svc_infra/webhooks/signing.py +34 -0
  260. svc_infra/websocket/__init__.py +79 -0
  261. svc_infra/websocket/add.py +139 -0
  262. svc_infra/websocket/client.py +283 -0
  263. svc_infra/websocket/config.py +57 -0
  264. svc_infra/websocket/easy.py +76 -0
  265. svc_infra/websocket/exceptions.py +61 -0
  266. svc_infra/websocket/manager.py +343 -0
  267. svc_infra/websocket/models.py +49 -0
  268. svc_infra-1.1.0.dist-info/LICENSE +21 -0
  269. svc_infra-1.1.0.dist-info/METADATA +362 -0
  270. svc_infra-1.1.0.dist-info/RECORD +364 -0
  271. svc_infra-0.1.595.dist-info/METADATA +0 -80
  272. svc_infra-0.1.595.dist-info/RECORD +0 -253
  273. {svc_infra-0.1.595.dist-info → svc_infra-1.1.0.dist-info}/WHEEL +0 -0
  274. {svc_infra-0.1.595.dist-info → svc_infra-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from functools import partial
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
 
6
6
  import anyio
7
7
 
@@ -44,7 +44,7 @@ from .base import ProviderAdapter
44
44
  try:
45
45
  import stripe
46
46
  except Exception: # pragma: no cover
47
- stripe = None # type: ignore
47
+ stripe = None # type: ignore[assignment]
48
48
 
49
49
 
50
50
  async def _acall(fn, /, *args, **kwargs):
@@ -224,7 +224,7 @@ class StripeAdapter(ProviderAdapter):
224
224
  name=c.get("name"),
225
225
  )
226
226
 
227
- async def get_customer(self, provider_customer_id: str) -> Optional[CustomerOut]:
227
+ async def get_customer(self, provider_customer_id: str) -> CustomerOut | None:
228
228
  c = await _acall(stripe.Customer.retrieve, provider_customer_id)
229
229
  return CustomerOut(
230
230
  id=c.id,
@@ -235,9 +235,14 @@ class StripeAdapter(ProviderAdapter):
235
235
  )
236
236
 
237
237
  async def list_customers(
238
- self, *, provider: str | None, user_id: str | None, limit: int, cursor: str | None
238
+ self,
239
+ *,
240
+ provider: str | None,
241
+ user_id: str | None,
242
+ limit: int,
243
+ cursor: str | None,
239
244
  ) -> tuple[list[CustomerOut], str | None]:
240
- params = {"limit": int(limit)}
245
+ params: dict[str, Any] = {"limit": int(limit)}
241
246
  if cursor:
242
247
  params["starting_after"] = cursor
243
248
  # Stripe has no direct filter for our custom user_id; many teams store mapping in DB.
@@ -271,13 +276,21 @@ class StripeAdapter(ProviderAdapter):
271
276
  invoice_settings={"default_payment_method": pm.id},
272
277
  )
273
278
  is_default = (
274
- getattr(getattr(cust, "invoice_settings", None), "default_payment_method", None)
279
+ getattr(
280
+ getattr(cust, "invoice_settings", None),
281
+ "default_payment_method",
282
+ None,
283
+ )
275
284
  == pm.id
276
285
  )
277
286
  else:
278
287
  cust = await _acall(stripe.Customer.retrieve, data.customer_provider_id)
279
288
  is_default = (
280
- getattr(getattr(cust, "invoice_settings", None), "default_payment_method", None)
289
+ getattr(
290
+ getattr(cust, "invoice_settings", None),
291
+ "default_payment_method",
292
+ None,
293
+ )
281
294
  == pm.id
282
295
  )
283
296
  return _pm_to_out(pm, is_default=is_default)
@@ -366,7 +379,7 @@ class StripeAdapter(ProviderAdapter):
366
379
  async def list_products(
367
380
  self, *, active: bool | None, limit: int, cursor: str | None
368
381
  ) -> tuple[list[ProductOut], str | None]:
369
- params = {"limit": int(limit)}
382
+ params: dict[str, Any] = {"limit": int(limit)}
370
383
  if active is not None:
371
384
  params["active"] = bool(active)
372
385
  if cursor:
@@ -390,12 +403,12 @@ class StripeAdapter(ProviderAdapter):
390
403
  return _product_to_out(p)
391
404
 
392
405
  async def create_price(self, data: PriceCreateIn) -> PriceOut:
393
- kwargs: dict[str, Any] = dict(
394
- product=data.provider_product_id,
395
- currency=data.currency.lower(),
396
- unit_amount=int(data.unit_amount),
397
- active=bool(data.active),
398
- )
406
+ kwargs: dict[str, Any] = {
407
+ "product": data.provider_product_id,
408
+ "currency": data.currency.lower(),
409
+ "unit_amount": int(data.unit_amount),
410
+ "active": bool(data.active),
411
+ }
399
412
  if data.interval:
400
413
  kwargs["recurring"] = {"interval": data.interval}
401
414
  if data.trial_days is not None:
@@ -415,7 +428,7 @@ class StripeAdapter(ProviderAdapter):
415
428
  limit: int,
416
429
  cursor: str | None,
417
430
  ) -> tuple[list[PriceOut], str | None]:
418
- params = {"limit": int(limit)}
431
+ params: dict[str, Any] = {"limit": int(limit)}
419
432
  if provider_product_id:
420
433
  params["product"] = provider_product_id
421
434
  if active is not None:
@@ -441,11 +454,11 @@ class StripeAdapter(ProviderAdapter):
441
454
 
442
455
  # -------- Subscriptions --------
443
456
  async def create_subscription(self, data: SubscriptionCreateIn) -> SubscriptionOut:
444
- kwargs: dict[str, Any] = dict(
445
- customer=data.customer_provider_id,
446
- items=[{"price": data.price_provider_id, "quantity": int(data.quantity)}],
447
- proration_behavior=data.proration_behavior,
448
- )
457
+ kwargs: dict[str, Any] = {
458
+ "customer": data.customer_provider_id,
459
+ "items": [{"price": data.price_provider_id, "quantity": int(data.quantity)}],
460
+ "proration_behavior": data.proration_behavior,
461
+ }
449
462
  if data.trial_days is not None:
450
463
  kwargs["trial_period_days"] = int(data.trial_days)
451
464
  s = await _acall(stripe.Subscription.create, **kwargs)
@@ -493,7 +506,7 @@ class StripeAdapter(ProviderAdapter):
493
506
  limit: int,
494
507
  cursor: str | None,
495
508
  ) -> tuple[list[SubscriptionOut], str | None]:
496
- params = {"limit": int(limit)}
509
+ params: dict[str, Any] = {"limit": int(limit)}
497
510
  if customer_provider_id:
498
511
  params["customer"] = customer_provider_id
499
512
  if status:
@@ -530,19 +543,20 @@ class StripeAdapter(ProviderAdapter):
530
543
  self, provider_invoice_id: str, data: InvoiceLineItemIn
531
544
  ) -> InvoiceOut:
532
545
  # attach an item to a DRAFT invoice
533
- kwargs: dict[str, Any] = dict(
534
- invoice=provider_invoice_id,
535
- customer=data.customer_provider_id,
536
- quantity=int(data.quantity or 1),
537
- currency=data.currency.lower(),
538
- description=data.description or None,
539
- )
546
+ kwargs: dict[str, Any] = {
547
+ "invoice": provider_invoice_id,
548
+ "customer": data.customer_provider_id,
549
+ "quantity": int(data.quantity or 1),
550
+ "currency": data.currency.lower(),
551
+ "description": data.description or None,
552
+ }
540
553
  if data.provider_price_id:
541
554
  kwargs["price"] = data.provider_price_id
542
555
  else:
543
556
  kwargs["unit_amount"] = int(data.unit_amount)
544
557
  await _acall(
545
- stripe.InvoiceItem.create, **{k: v for k, v in kwargs.items() if v is not None}
558
+ stripe.InvoiceItem.create,
559
+ **{k: v for k, v in kwargs.items() if v is not None},
546
560
  )
547
561
  inv = await _acall(stripe.Invoice.retrieve, provider_invoice_id)
548
562
  return _inv_to_out(inv)
@@ -555,7 +569,7 @@ class StripeAdapter(ProviderAdapter):
555
569
  limit: int,
556
570
  cursor: str | None,
557
571
  ) -> tuple[list[InvoiceOut], str | None]:
558
- params = {"limit": int(limit)}
572
+ params: dict[str, Any] = {"limit": int(limit)}
559
573
  if customer_provider_id:
560
574
  params["customer"] = customer_provider_id
561
575
  if status:
@@ -577,13 +591,13 @@ class StripeAdapter(ProviderAdapter):
577
591
  params = {"customer": customer_provider_id}
578
592
  if subscription_id:
579
593
  params["subscription"] = subscription_id
580
- inv = await _acall(stripe.Invoice.upcoming, **params)
594
+ inv = await _acall(stripe.Invoice.upcoming, **params) # type: ignore[attr-defined]
581
595
  return _inv_to_out(inv)
582
596
 
583
597
  async def list_invoice_line_items(
584
598
  self, provider_invoice_id: str, *, limit: int, cursor: str | None
585
599
  ) -> tuple[list[InvoiceLineItemOut], str | None]:
586
- params = {"limit": int(limit)}
600
+ params: dict[str, Any] = {"limit": int(limit)}
587
601
  if cursor:
588
602
  params["starting_after"] = cursor
589
603
  res = await _acall(stripe.Invoice.list_lines, provider_invoice_id, **params)
@@ -607,17 +621,20 @@ class StripeAdapter(ProviderAdapter):
607
621
 
608
622
  # -------- Intents --------
609
623
  async def create_intent(self, data: IntentCreateIn, *, user_id: str | None) -> IntentOut:
610
- kwargs: dict[str, Any] = dict(
611
- amount=int(data.amount),
612
- currency=data.currency.lower(),
613
- description=data.description or None,
614
- capture_method="manual" if data.capture_method == "manual" else "automatic",
615
- automatic_payment_methods={"enabled": True} if not data.payment_method_types else None,
616
- )
624
+ kwargs: dict[str, Any] = {
625
+ "amount": int(data.amount),
626
+ "currency": data.currency.lower(),
627
+ "description": data.description or None,
628
+ "capture_method": "manual" if data.capture_method == "manual" else "automatic",
629
+ "automatic_payment_methods": {"enabled": True}
630
+ if not data.payment_method_types
631
+ else None,
632
+ }
617
633
  if data.payment_method_types:
618
634
  kwargs["payment_method_types"] = data.payment_method_types
619
635
  pi = await _acall(
620
- stripe.PaymentIntent.create, **{k: v for k, v in kwargs.items() if v is not None}
636
+ stripe.PaymentIntent.create,
637
+ **{k: v for k, v in kwargs.items() if v is not None},
621
638
  )
622
639
  return _pi_to_out(pi)
623
640
 
@@ -663,7 +680,7 @@ class StripeAdapter(ProviderAdapter):
663
680
  limit: int,
664
681
  cursor: str | None,
665
682
  ) -> tuple[list[IntentOut], str | None]:
666
- params = {"limit": int(limit)}
683
+ params: dict[str, Any] = {"limit": int(limit)}
667
684
  if customer_provider_id:
668
685
  params["customer"] = customer_provider_id
669
686
  if status:
@@ -678,7 +695,8 @@ class StripeAdapter(ProviderAdapter):
678
695
  # ---- Setup Intents (off-session readiness) ----
679
696
  async def create_setup_intent(self, data: SetupIntentCreateIn) -> SetupIntentOut:
680
697
  si = await _acall(
681
- stripe.SetupIntent.create, payment_method_types=data.payment_method_types or ["card"]
698
+ stripe.SetupIntent.create,
699
+ payment_method_types=data.payment_method_types or ["card"],
682
700
  )
683
701
  return SetupIntentOut(
684
702
  id=si.id,
@@ -720,7 +738,7 @@ class StripeAdapter(ProviderAdapter):
720
738
  async def list_disputes(
721
739
  self, *, status: str | None, limit: int, cursor: str | None
722
740
  ) -> tuple[list[DisputeOut], str | None]:
723
- params = {"limit": int(limit)}
741
+ params: dict[str, Any] = {"limit": int(limit)}
724
742
  if status:
725
743
  params["status"] = status
726
744
  if cursor:
@@ -738,7 +756,7 @@ class StripeAdapter(ProviderAdapter):
738
756
  d = await _acall(stripe.Dispute.modify, provider_dispute_id, evidence=evidence)
739
757
  # Some disputes require explicit submit call:
740
758
  try:
741
- d = await _acall(stripe.Dispute.submit, provider_dispute_id)
759
+ d = await _acall(stripe.Dispute.submit, provider_dispute_id) # type: ignore[attr-defined]
742
760
  except Exception:
743
761
  pass
744
762
  return _dispute_to_out(d)
@@ -761,7 +779,7 @@ class StripeAdapter(ProviderAdapter):
761
779
  async def list_payouts(
762
780
  self, *, limit: int, cursor: str | None
763
781
  ) -> tuple[list[PayoutOut], str | None]:
764
- params = {"limit": int(limit)}
782
+ params: dict[str, Any] = {"limit": int(limit)}
765
783
  if cursor:
766
784
  params["starting_after"] = cursor
767
785
  res = await _acall(stripe.Payout.list, **params)
@@ -777,7 +795,7 @@ class StripeAdapter(ProviderAdapter):
777
795
  async def list_refunds(
778
796
  self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
779
797
  ) -> tuple[list[RefundOut], str | None]:
780
- params = {"limit": int(limit)}
798
+ params: dict[str, Any] = {"limit": int(limit)}
781
799
  if provider_payment_intent_id:
782
800
  params["payment_intent"] = provider_payment_intent_id
783
801
  if cursor:
@@ -810,7 +828,7 @@ class StripeAdapter(ProviderAdapter):
810
828
  }
811
829
  if data.timestamp:
812
830
  body["timestamp"] = int(data.timestamp)
813
- rec = await _acall(stripe.UsageRecord.create, **body)
831
+ rec = await _acall(stripe.UsageRecord.create, **body) # type: ignore[attr-defined]
814
832
  return UsageRecordOut(
815
833
  id=rec.id,
816
834
  quantity=int(rec.quantity),
@@ -829,15 +847,19 @@ class StripeAdapter(ProviderAdapter):
829
847
  sub_item = items.data[0].id if items.data else None
830
848
  if not sub_item:
831
849
  return [], None
832
- params = {"limit": int(f.limit or 50)}
850
+ params: dict[str, Any] = {"limit": int(f.limit or 50)}
833
851
  if f.cursor:
834
852
  params["starting_after"] = f.cursor
835
- res = await _acall(stripe.SubscriptionItem.list_usage_record_summaries, sub_item, **params)
836
- items: list[UsageRecordOut] = []
853
+ res = await _acall(
854
+ stripe.SubscriptionItem.list_usage_record_summaries, # type: ignore[attr-defined]
855
+ sub_item,
856
+ **params,
857
+ )
858
+ usage_records: list[UsageRecordOut] = []
837
859
  for s in res.data:
838
860
  # No record id in summaries—synthesize a stable id from period start.
839
861
  synthesized_id = f"{sub_item}:{getattr(s, 'period', {}).get('start')}"
840
- items.append(
862
+ usage_records.append(
841
863
  UsageRecordOut(
842
864
  id=synthesized_id,
843
865
  quantity=int(getattr(s, "total_usage", 0)),
@@ -851,7 +873,7 @@ class StripeAdapter(ProviderAdapter):
851
873
  if getattr(res, "has_more", False) and res.data and hasattr(res.data[-1], "id")
852
874
  else None
853
875
  )
854
- return items, next_cursor
876
+ return usage_records, next_cursor
855
877
 
856
878
  async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
857
879
  # Stripe has no direct "retrieve usage record by id" API.
@@ -1,38 +1,39 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Literal, Optional
3
+ from typing import Annotated, Any, Literal
4
4
 
5
- from pydantic import BaseModel, Field, conint, constr
5
+ from pydantic import BaseModel, Field, StringConstraints
6
6
 
7
- Currency = constr(pattern=r"^[A-Z]{3}$")
8
- AmountMinor = conint(ge=0) # minor units (cents)
7
+ # Type aliases for payment fields using Annotated with proper type hints
8
+ Currency = Annotated[str, StringConstraints(pattern=r"^[A-Z]{3}$")]
9
+ AmountMinor = Annotated[int, Field(ge=0)] # minor units (cents)
9
10
 
10
11
 
11
12
  class CustomerUpsertIn(BaseModel):
12
- user_id: Optional[str] = None
13
- email: Optional[str] = None
14
- name: Optional[str] = None
13
+ user_id: str | None = None
14
+ email: str | None = None
15
+ name: str | None = None
15
16
 
16
17
 
17
18
  class CustomerOut(BaseModel):
18
19
  id: str
19
20
  provider: str
20
21
  provider_customer_id: str
21
- email: Optional[str] = None
22
- name: Optional[str] = None
22
+ email: str | None = None
23
+ name: str | None = None
23
24
 
24
25
 
25
26
  class IntentCreateIn(BaseModel):
26
27
  amount: AmountMinor = Field(..., description="Minor units (e.g., cents)")
27
28
  currency: Currency = Field(..., json_schema_extra={"example": "USD"})
28
- description: Optional[str] = None
29
+ description: str | None = None
29
30
  capture_method: Literal["automatic", "manual"] = "automatic"
30
31
  payment_method_types: list[str] = Field(default_factory=list) # let provider default
31
32
 
32
33
 
33
34
  class NextAction(BaseModel):
34
- type: Optional[str] = None
35
- data: Optional[dict[str, Any]] = None
35
+ type: str | None = None
36
+ data: dict[str, Any] | None = None
36
37
 
37
38
 
38
39
  class IntentOut(BaseModel):
@@ -42,13 +43,13 @@ class IntentOut(BaseModel):
42
43
  status: str
43
44
  amount: AmountMinor
44
45
  currency: Currency
45
- client_secret: Optional[str] = None
46
- next_action: Optional[NextAction] = None
46
+ client_secret: str | None = None
47
+ next_action: NextAction | None = None
47
48
 
48
49
 
49
50
  class RefundIn(BaseModel):
50
- amount: Optional[AmountMinor] = None
51
- reason: Optional[str] = None
51
+ amount: AmountMinor | None = None
52
+ reason: str | None = None
52
53
 
53
54
 
54
55
  class TransactionRow(BaseModel):
@@ -60,9 +61,9 @@ class TransactionRow(BaseModel):
60
61
  status: str
61
62
  provider: str
62
63
  provider_ref: str
63
- user_id: Optional[str] = None
64
- net: Optional[int] = None
65
- fee: Optional[int] = None
64
+ user_id: str | None = None
65
+ net: int | None = None
66
+ fee: int | None = None
66
67
 
67
68
 
68
69
  class StatementRow(BaseModel):
@@ -87,10 +88,10 @@ class PaymentMethodOut(BaseModel):
87
88
  provider: str
88
89
  provider_customer_id: str
89
90
  provider_method_id: str
90
- brand: Optional[str] = None
91
- last4: Optional[str] = None
92
- exp_month: Optional[int] = None
93
- exp_year: Optional[int] = None
91
+ brand: str | None = None
92
+ last4: str | None = None
93
+ exp_month: int | None = None
94
+ exp_year: int | None = None
94
95
  is_default: bool = False
95
96
 
96
97
 
@@ -111,8 +112,8 @@ class PriceCreateIn(BaseModel):
111
112
  provider_product_id: str
112
113
  currency: Currency
113
114
  unit_amount: AmountMinor
114
- interval: Optional[Literal["day", "week", "month", "year"]] = None
115
- trial_days: Optional[int] = None
115
+ interval: Literal["day", "week", "month", "year"] | None = None
116
+ trial_days: int | None = None
116
117
  active: bool = True
117
118
 
118
119
 
@@ -123,8 +124,8 @@ class PriceOut(BaseModel):
123
124
  provider_product_id: str
124
125
  currency: Currency
125
126
  unit_amount: AmountMinor
126
- interval: Optional[str] = None
127
- trial_days: Optional[int] = None
127
+ interval: str | None = None
128
+ trial_days: int | None = None
128
129
  active: bool = True
129
130
 
130
131
 
@@ -132,14 +133,14 @@ class SubscriptionCreateIn(BaseModel):
132
133
  customer_provider_id: str
133
134
  price_provider_id: str
134
135
  quantity: int = 1
135
- trial_days: Optional[int] = None
136
+ trial_days: int | None = None
136
137
  proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
137
138
 
138
139
 
139
140
  class SubscriptionUpdateIn(BaseModel):
140
- price_provider_id: Optional[str] = None
141
- quantity: Optional[int] = None
142
- cancel_at_period_end: Optional[bool] = None
141
+ price_provider_id: str | None = None
142
+ quantity: int | None = None
143
+ cancel_at_period_end: bool | None = None
143
144
  proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
144
145
 
145
146
 
@@ -151,7 +152,7 @@ class SubscriptionOut(BaseModel):
151
152
  status: str
152
153
  quantity: int
153
154
  cancel_at_period_end: bool
154
- current_period_end: Optional[str] = None
155
+ current_period_end: str | None = None
155
156
 
156
157
 
157
158
  class InvoiceCreateIn(BaseModel):
@@ -167,45 +168,45 @@ class InvoiceOut(BaseModel):
167
168
  status: str
168
169
  amount_due: AmountMinor
169
170
  currency: Currency
170
- hosted_invoice_url: Optional[str] = None
171
- pdf_url: Optional[str] = None
171
+ hosted_invoice_url: str | None = None
172
+ pdf_url: str | None = None
172
173
 
173
174
 
174
175
  class CaptureIn(BaseModel):
175
- amount: Optional[AmountMinor] = None # partial capture supported
176
+ amount: AmountMinor | None = None # partial capture supported
176
177
 
177
178
 
178
179
  class IntentListFilter(BaseModel):
179
- customer_provider_id: Optional[str] = None
180
- status: Optional[str] = None
181
- limit: Optional[int] = Field(default=50, ge=1, le=200)
182
- cursor: Optional[str] = None # opaque provider cursor when supported
180
+ customer_provider_id: str | None = None
181
+ status: str | None = None
182
+ limit: int | None = Field(default=50, ge=1, le=200)
183
+ cursor: str | None = None # opaque provider cursor when supported
183
184
 
184
185
 
185
186
  class UsageRecordIn(BaseModel):
186
187
  # Stripe: subscription_item is the target for metered billing.
187
188
  # If provider doesn't use subscription_item, allow provider_price_id as fallback.
188
- subscription_item: Optional[str] = None
189
- provider_price_id: Optional[str] = None
190
- quantity: conint(ge=0)
191
- timestamp: Optional[int] = None # Unix seconds; provider defaults to "now"
192
- action: Optional[Literal["increment", "set"]] = "increment"
189
+ subscription_item: str | None = None
190
+ provider_price_id: str | None = None
191
+ quantity: Annotated[int, Field(ge=0)]
192
+ timestamp: int | None = None # Unix seconds; provider defaults to "now"
193
+ action: Literal["increment", "set"] | None = "increment"
193
194
 
194
195
 
195
196
  class InvoiceLineItemIn(BaseModel):
196
197
  customer_provider_id: str
197
- description: Optional[str] = None
198
+ description: str | None = None
198
199
  unit_amount: AmountMinor
199
200
  currency: Currency
200
- quantity: Optional[int] = 1
201
- provider_price_id: Optional[str] = None # if linked to a price, unit_amount may be ignored
201
+ quantity: int | None = 1
202
+ provider_price_id: str | None = None # if linked to a price, unit_amount may be ignored
202
203
 
203
204
 
204
205
  class InvoicesListFilter(BaseModel):
205
- customer_provider_id: Optional[str] = None
206
- status: Optional[str] = None
207
- limit: Optional[int] = Field(default=50, ge=1, le=200)
208
- cursor: Optional[str] = None
206
+ customer_provider_id: str | None = None
207
+ status: str | None = None
208
+ limit: int | None = Field(default=50, ge=1, le=200)
209
+ cursor: str | None = None
209
210
 
210
211
 
211
212
  class SetupIntentOut(BaseModel):
@@ -213,8 +214,8 @@ class SetupIntentOut(BaseModel):
213
214
  provider: str
214
215
  provider_setup_intent_id: str
215
216
  status: str
216
- client_secret: Optional[str] = None
217
- next_action: Optional[NextAction] = None
217
+ client_secret: str | None = None
218
+ next_action: NextAction | None = None
218
219
 
219
220
 
220
221
  class DisputeOut(BaseModel):
@@ -223,10 +224,10 @@ class DisputeOut(BaseModel):
223
224
  provider_dispute_id: str
224
225
  amount: AmountMinor
225
226
  currency: Currency
226
- reason: Optional[str] = None
227
+ reason: str | None = None
227
228
  status: str
228
- evidence_due_by: Optional[str] = None
229
- created_at: Optional[str] = None
229
+ evidence_due_by: str | None = None
230
+ created_at: str | None = None
230
231
 
231
232
 
232
233
  class PayoutOut(BaseModel):
@@ -236,8 +237,8 @@ class PayoutOut(BaseModel):
236
237
  amount: AmountMinor
237
238
  currency: Currency
238
239
  status: str
239
- arrival_date: Optional[str] = None
240
- type: Optional[str] = None
240
+ arrival_date: str | None = None
241
+ type: str | None = None
241
242
 
242
243
 
243
244
  class BalanceAmount(BaseModel):
@@ -255,7 +256,7 @@ class SetupIntentCreateIn(BaseModel):
255
256
 
256
257
 
257
258
  class WebhookReplayIn(BaseModel):
258
- event_ids: Optional[list[str]] = None
259
+ event_ids: list[str] | None = None
259
260
 
260
261
 
261
262
  class WebhookReplayOut(BaseModel):
@@ -269,36 +270,36 @@ class WebhookAckOut(BaseModel):
269
270
  class UsageRecordOut(BaseModel):
270
271
  id: str
271
272
  quantity: int
272
- timestamp: Optional[int] = None
273
- subscription_item: Optional[str] = None
274
- provider_price_id: Optional[str] = None
275
- action: Optional[Literal["increment", "set"]] = None
273
+ timestamp: int | None = None
274
+ subscription_item: str | None = None
275
+ provider_price_id: str | None = None
276
+ action: Literal["increment", "set"] | None = None
276
277
 
277
278
 
278
279
  # -------- Customers list filter ----------
279
280
  class CustomersListFilter(BaseModel):
280
- provider: Optional[str] = None
281
- user_id: Optional[str] = None
282
- limit: Optional[int] = Field(default=50, ge=1, le=200)
283
- cursor: Optional[str] = None # we’ll paginate on provider_customer_id asc
281
+ provider: str | None = None
282
+ user_id: str | None = None
283
+ limit: int | None = Field(default=50, ge=1, le=200)
284
+ cursor: str | None = None # we’ll paginate on provider_customer_id asc
284
285
 
285
286
 
286
287
  # -------- Products / Prices updates ----------
287
288
  class ProductUpdateIn(BaseModel):
288
- name: Optional[str] = None
289
- active: Optional[bool] = None
289
+ name: str | None = None
290
+ active: bool | None = None
290
291
 
291
292
 
292
293
  class PriceUpdateIn(BaseModel):
293
- active: Optional[bool] = None
294
+ active: bool | None = None
294
295
 
295
296
 
296
297
  # -------- Payment Method update ----------
297
298
  class PaymentMethodUpdateIn(BaseModel):
298
299
  # keep minimal + commonly supported card fields
299
- name: Optional[str] = None
300
- exp_month: Optional[int] = None
301
- exp_year: Optional[int] = None
300
+ name: str | None = None
301
+ exp_month: int | None = None
302
+ exp_year: int | None = None
302
303
  # extend here later with address fields (line1, city, etc.)
303
304
 
304
305
 
@@ -307,27 +308,27 @@ class RefundOut(BaseModel):
307
308
  id: str
308
309
  provider: str
309
310
  provider_refund_id: str
310
- provider_payment_intent_id: Optional[str] = None
311
+ provider_payment_intent_id: str | None = None
311
312
  amount: AmountMinor
312
313
  currency: Currency
313
314
  status: str
314
- reason: Optional[str] = None
315
- created_at: Optional[str] = None
315
+ reason: str | None = None
316
+ created_at: str | None = None
316
317
 
317
318
 
318
319
  # -------- Invoice line items (list) ----------
319
320
  class InvoiceLineItemOut(BaseModel):
320
321
  id: str
321
- description: Optional[str] = None
322
+ description: str | None = None
322
323
  amount: AmountMinor
323
324
  currency: Currency
324
- quantity: Optional[int] = 1
325
- provider_price_id: Optional[str] = None
325
+ quantity: int | None = 1
326
+ provider_price_id: str | None = None
326
327
 
327
328
 
328
329
  # -------- Usage records list/get ----------
329
330
  class UsageRecordListFilter(BaseModel):
330
- subscription_item: Optional[str] = None
331
- provider_price_id: Optional[str] = None
332
- limit: Optional[int] = Field(default=50, ge=1, le=200)
333
- cursor: Optional[str] = None
331
+ subscription_item: str | None = None
332
+ provider_price_id: str | None = None
333
+ limit: int | None = Field(default=50, ge=1, le=200)
334
+ cursor: str | None = None