svc-infra 0.1.706__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 (227) hide show
  1. svc_infra/apf_payments/models.py +47 -108
  2. svc_infra/apf_payments/provider/__init__.py +2 -2
  3. svc_infra/apf_payments/provider/aiydan.py +42 -100
  4. svc_infra/apf_payments/provider/base.py +10 -26
  5. svc_infra/apf_payments/provider/registry.py +3 -5
  6. svc_infra/apf_payments/provider/stripe.py +63 -135
  7. svc_infra/apf_payments/schemas.py +82 -90
  8. svc_infra/apf_payments/service.py +40 -86
  9. svc_infra/apf_payments/settings.py +10 -13
  10. svc_infra/api/__init__.py +13 -13
  11. svc_infra/api/fastapi/__init__.py +19 -0
  12. svc_infra/api/fastapi/admin/add.py +13 -18
  13. svc_infra/api/fastapi/apf_payments/router.py +47 -84
  14. svc_infra/api/fastapi/apf_payments/setup.py +7 -13
  15. svc_infra/api/fastapi/auth/__init__.py +1 -1
  16. svc_infra/api/fastapi/auth/_cookies.py +3 -9
  17. svc_infra/api/fastapi/auth/add.py +4 -8
  18. svc_infra/api/fastapi/auth/gaurd.py +9 -26
  19. svc_infra/api/fastapi/auth/mfa/models.py +4 -7
  20. svc_infra/api/fastapi/auth/mfa/pre_auth.py +3 -3
  21. svc_infra/api/fastapi/auth/mfa/router.py +9 -15
  22. svc_infra/api/fastapi/auth/mfa/security.py +3 -5
  23. svc_infra/api/fastapi/auth/mfa/utils.py +3 -2
  24. svc_infra/api/fastapi/auth/mfa/verify.py +2 -9
  25. svc_infra/api/fastapi/auth/providers.py +4 -6
  26. svc_infra/api/fastapi/auth/routers/apikey_router.py +16 -18
  27. svc_infra/api/fastapi/auth/routers/oauth_router.py +37 -85
  28. svc_infra/api/fastapi/auth/routers/session_router.py +3 -6
  29. svc_infra/api/fastapi/auth/security.py +17 -28
  30. svc_infra/api/fastapi/auth/sender.py +1 -3
  31. svc_infra/api/fastapi/auth/settings.py +18 -19
  32. svc_infra/api/fastapi/auth/state.py +6 -7
  33. svc_infra/api/fastapi/auth/ws_security.py +2 -2
  34. svc_infra/api/fastapi/billing/router.py +6 -8
  35. svc_infra/api/fastapi/db/http.py +10 -11
  36. svc_infra/api/fastapi/db/nosql/mongo/add.py +5 -15
  37. svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +14 -15
  38. svc_infra/api/fastapi/db/sql/add.py +6 -14
  39. svc_infra/api/fastapi/db/sql/crud_router.py +27 -40
  40. svc_infra/api/fastapi/db/sql/health.py +1 -3
  41. svc_infra/api/fastapi/db/sql/session.py +4 -5
  42. svc_infra/api/fastapi/db/sql/users.py +8 -11
  43. svc_infra/api/fastapi/dependencies/ratelimit.py +4 -6
  44. svc_infra/api/fastapi/docs/add.py +13 -23
  45. svc_infra/api/fastapi/docs/landing.py +6 -8
  46. svc_infra/api/fastapi/docs/scoped.py +34 -42
  47. svc_infra/api/fastapi/dual/dualize.py +1 -1
  48. svc_infra/api/fastapi/dual/protected.py +12 -21
  49. svc_infra/api/fastapi/dual/router.py +14 -31
  50. svc_infra/api/fastapi/ease.py +57 -13
  51. svc_infra/api/fastapi/http/conditional.py +3 -5
  52. svc_infra/api/fastapi/middleware/errors/catchall.py +2 -6
  53. svc_infra/api/fastapi/middleware/errors/exceptions.py +1 -4
  54. svc_infra/api/fastapi/middleware/errors/handlers.py +12 -18
  55. svc_infra/api/fastapi/middleware/graceful_shutdown.py +4 -13
  56. svc_infra/api/fastapi/middleware/idempotency.py +11 -16
  57. svc_infra/api/fastapi/middleware/idempotency_store.py +14 -14
  58. svc_infra/api/fastapi/middleware/optimistic_lock.py +5 -8
  59. svc_infra/api/fastapi/middleware/ratelimit.py +8 -8
  60. svc_infra/api/fastapi/middleware/ratelimit_store.py +7 -8
  61. svc_infra/api/fastapi/middleware/request_id.py +1 -3
  62. svc_infra/api/fastapi/middleware/timeout.py +9 -10
  63. svc_infra/api/fastapi/object_router.py +1060 -0
  64. svc_infra/api/fastapi/openapi/apply.py +5 -6
  65. svc_infra/api/fastapi/openapi/conventions.py +4 -4
  66. svc_infra/api/fastapi/openapi/mutators.py +13 -31
  67. svc_infra/api/fastapi/openapi/pipeline.py +2 -2
  68. svc_infra/api/fastapi/openapi/responses.py +4 -6
  69. svc_infra/api/fastapi/openapi/security.py +1 -3
  70. svc_infra/api/fastapi/ops/add.py +7 -9
  71. svc_infra/api/fastapi/pagination.py +25 -37
  72. svc_infra/api/fastapi/routers/__init__.py +16 -38
  73. svc_infra/api/fastapi/setup.py +13 -31
  74. svc_infra/api/fastapi/tenancy/add.py +3 -2
  75. svc_infra/api/fastapi/tenancy/context.py +8 -7
  76. svc_infra/api/fastapi/versioned.py +3 -2
  77. svc_infra/app/env.py +5 -7
  78. svc_infra/app/logging/add.py +2 -1
  79. svc_infra/app/logging/filter.py +1 -1
  80. svc_infra/app/logging/formats.py +3 -2
  81. svc_infra/app/root.py +3 -3
  82. svc_infra/billing/__init__.py +19 -2
  83. svc_infra/billing/async_service.py +27 -7
  84. svc_infra/billing/jobs.py +23 -33
  85. svc_infra/billing/models.py +21 -52
  86. svc_infra/billing/quotas.py +5 -7
  87. svc_infra/billing/schemas.py +4 -6
  88. svc_infra/cache/__init__.py +12 -5
  89. svc_infra/cache/add.py +6 -9
  90. svc_infra/cache/backend.py +6 -5
  91. svc_infra/cache/decorators.py +17 -28
  92. svc_infra/cache/keys.py +2 -2
  93. svc_infra/cache/recache.py +22 -35
  94. svc_infra/cache/resources.py +8 -16
  95. svc_infra/cache/ttl.py +2 -3
  96. svc_infra/cache/utils.py +5 -6
  97. svc_infra/cli/__init__.py +4 -12
  98. svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +11 -10
  99. svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +6 -9
  100. svc_infra/cli/cmds/db/ops_cmds.py +3 -6
  101. svc_infra/cli/cmds/db/sql/alembic_cmds.py +24 -41
  102. svc_infra/cli/cmds/db/sql/sql_export_cmds.py +9 -17
  103. svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +10 -10
  104. svc_infra/cli/cmds/docs/docs_cmds.py +7 -10
  105. svc_infra/cli/cmds/dx/dx_cmds.py +5 -11
  106. svc_infra/cli/cmds/jobs/jobs_cmds.py +2 -7
  107. svc_infra/cli/cmds/obs/obs_cmds.py +4 -7
  108. svc_infra/cli/cmds/sdk/sdk_cmds.py +5 -15
  109. svc_infra/cli/foundation/runner.py +6 -11
  110. svc_infra/cli/foundation/typer_bootstrap.py +1 -2
  111. svc_infra/data/__init__.py +83 -0
  112. svc_infra/data/add.py +5 -5
  113. svc_infra/data/backup.py +8 -10
  114. svc_infra/data/erasure.py +3 -2
  115. svc_infra/data/fixtures.py +3 -3
  116. svc_infra/data/retention.py +8 -13
  117. svc_infra/db/crud_schema.py +9 -8
  118. svc_infra/db/nosql/__init__.py +0 -1
  119. svc_infra/db/nosql/constants.py +1 -1
  120. svc_infra/db/nosql/core.py +7 -14
  121. svc_infra/db/nosql/indexes.py +11 -10
  122. svc_infra/db/nosql/management.py +3 -3
  123. svc_infra/db/nosql/mongo/client.py +3 -3
  124. svc_infra/db/nosql/mongo/settings.py +2 -6
  125. svc_infra/db/nosql/repository.py +27 -28
  126. svc_infra/db/nosql/resource.py +15 -20
  127. svc_infra/db/nosql/scaffold.py +13 -17
  128. svc_infra/db/nosql/service.py +3 -4
  129. svc_infra/db/nosql/service_with_hooks.py +4 -3
  130. svc_infra/db/nosql/types.py +2 -6
  131. svc_infra/db/nosql/utils.py +4 -4
  132. svc_infra/db/ops.py +14 -18
  133. svc_infra/db/outbox.py +15 -18
  134. svc_infra/db/sql/apikey.py +12 -21
  135. svc_infra/db/sql/authref.py +3 -7
  136. svc_infra/db/sql/constants.py +9 -9
  137. svc_infra/db/sql/core.py +11 -11
  138. svc_infra/db/sql/management.py +2 -6
  139. svc_infra/db/sql/repository.py +17 -24
  140. svc_infra/db/sql/resource.py +14 -13
  141. svc_infra/db/sql/scaffold.py +13 -17
  142. svc_infra/db/sql/service.py +7 -16
  143. svc_infra/db/sql/service_with_hooks.py +4 -3
  144. svc_infra/db/sql/tenant.py +6 -14
  145. svc_infra/db/sql/uniq.py +8 -7
  146. svc_infra/db/sql/uniq_hooks.py +14 -19
  147. svc_infra/db/sql/utils.py +24 -53
  148. svc_infra/db/utils.py +3 -3
  149. svc_infra/deploy/__init__.py +8 -15
  150. svc_infra/documents/add.py +7 -8
  151. svc_infra/documents/ease.py +8 -8
  152. svc_infra/documents/models.py +3 -3
  153. svc_infra/documents/storage.py +11 -13
  154. svc_infra/dx/__init__.py +58 -0
  155. svc_infra/dx/add.py +1 -3
  156. svc_infra/dx/changelog.py +2 -2
  157. svc_infra/dx/checks.py +1 -1
  158. svc_infra/health/__init__.py +15 -16
  159. svc_infra/http/client.py +10 -14
  160. svc_infra/jobs/__init__.py +79 -0
  161. svc_infra/jobs/builtins/outbox_processor.py +3 -5
  162. svc_infra/jobs/builtins/webhook_delivery.py +1 -3
  163. svc_infra/jobs/loader.py +4 -5
  164. svc_infra/jobs/queue.py +14 -24
  165. svc_infra/jobs/redis_queue.py +20 -34
  166. svc_infra/jobs/runner.py +7 -11
  167. svc_infra/jobs/scheduler.py +5 -5
  168. svc_infra/jobs/worker.py +1 -1
  169. svc_infra/loaders/base.py +5 -4
  170. svc_infra/loaders/github.py +1 -3
  171. svc_infra/loaders/url.py +3 -9
  172. svc_infra/logging/__init__.py +7 -6
  173. svc_infra/mcp/__init__.py +82 -0
  174. svc_infra/mcp/svc_infra_mcp.py +2 -2
  175. svc_infra/obs/add.py +4 -3
  176. svc_infra/obs/cloud_dash.py +1 -1
  177. svc_infra/obs/metrics/__init__.py +3 -3
  178. svc_infra/obs/metrics/asgi.py +9 -14
  179. svc_infra/obs/metrics/base.py +13 -13
  180. svc_infra/obs/metrics/http.py +5 -9
  181. svc_infra/obs/metrics/sqlalchemy.py +9 -12
  182. svc_infra/obs/metrics.py +3 -3
  183. svc_infra/obs/settings.py +2 -6
  184. svc_infra/resilience/__init__.py +44 -0
  185. svc_infra/resilience/circuit_breaker.py +328 -0
  186. svc_infra/resilience/retry.py +289 -0
  187. svc_infra/security/__init__.py +167 -0
  188. svc_infra/security/add.py +5 -9
  189. svc_infra/security/audit.py +14 -17
  190. svc_infra/security/audit_service.py +9 -9
  191. svc_infra/security/hibp.py +3 -6
  192. svc_infra/security/jwt_rotation.py +7 -10
  193. svc_infra/security/lockout.py +12 -11
  194. svc_infra/security/models.py +37 -46
  195. svc_infra/security/oauth_models.py +8 -8
  196. svc_infra/security/org_invites.py +11 -13
  197. svc_infra/security/passwords.py +4 -6
  198. svc_infra/security/permissions.py +8 -7
  199. svc_infra/security/session.py +6 -7
  200. svc_infra/security/signed_cookies.py +9 -9
  201. svc_infra/storage/add.py +5 -8
  202. svc_infra/storage/backends/local.py +13 -21
  203. svc_infra/storage/backends/memory.py +4 -7
  204. svc_infra/storage/backends/s3.py +17 -36
  205. svc_infra/storage/base.py +2 -2
  206. svc_infra/storage/easy.py +4 -8
  207. svc_infra/storage/settings.py +16 -18
  208. svc_infra/testing/__init__.py +36 -39
  209. svc_infra/utils.py +169 -8
  210. svc_infra/webhooks/__init__.py +1 -1
  211. svc_infra/webhooks/add.py +17 -29
  212. svc_infra/webhooks/encryption.py +2 -2
  213. svc_infra/webhooks/fastapi.py +2 -4
  214. svc_infra/webhooks/router.py +3 -3
  215. svc_infra/webhooks/service.py +5 -6
  216. svc_infra/webhooks/signing.py +5 -5
  217. svc_infra/websocket/add.py +2 -3
  218. svc_infra/websocket/client.py +3 -2
  219. svc_infra/websocket/config.py +6 -18
  220. svc_infra/websocket/manager.py +9 -10
  221. {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/METADATA +11 -5
  222. svc_infra-1.1.0.dist-info/RECORD +364 -0
  223. svc_infra/billing/service.py +0 -123
  224. svc_infra-0.1.706.dist-info/RECORD +0 -357
  225. {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/LICENSE +0 -0
  226. {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/WHEEL +0 -0
  227. {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Annotated, Any, Literal, Optional
3
+ from typing import Annotated, Any, Literal
4
4
 
5
5
  from pydantic import BaseModel, Field, StringConstraints
6
6
 
@@ -10,32 +10,30 @@ AmountMinor = Annotated[int, Field(ge=0)] # minor units (cents)
10
10
 
11
11
 
12
12
  class CustomerUpsertIn(BaseModel):
13
- user_id: Optional[str] = None
14
- email: Optional[str] = None
15
- name: Optional[str] = None
13
+ user_id: str | None = None
14
+ email: str | None = None
15
+ name: str | None = None
16
16
 
17
17
 
18
18
  class CustomerOut(BaseModel):
19
19
  id: str
20
20
  provider: str
21
21
  provider_customer_id: str
22
- email: Optional[str] = None
23
- name: Optional[str] = None
22
+ email: str | None = None
23
+ name: str | None = None
24
24
 
25
25
 
26
26
  class IntentCreateIn(BaseModel):
27
27
  amount: AmountMinor = Field(..., description="Minor units (e.g., cents)")
28
28
  currency: Currency = Field(..., json_schema_extra={"example": "USD"})
29
- description: Optional[str] = None
29
+ description: str | None = None
30
30
  capture_method: Literal["automatic", "manual"] = "automatic"
31
- payment_method_types: list[str] = Field(
32
- default_factory=list
33
- ) # let provider default
31
+ payment_method_types: list[str] = Field(default_factory=list) # let provider default
34
32
 
35
33
 
36
34
  class NextAction(BaseModel):
37
- type: Optional[str] = None
38
- data: Optional[dict[str, Any]] = None
35
+ type: str | None = None
36
+ data: dict[str, Any] | None = None
39
37
 
40
38
 
41
39
  class IntentOut(BaseModel):
@@ -45,13 +43,13 @@ class IntentOut(BaseModel):
45
43
  status: str
46
44
  amount: AmountMinor
47
45
  currency: Currency
48
- client_secret: Optional[str] = None
49
- next_action: Optional[NextAction] = None
46
+ client_secret: str | None = None
47
+ next_action: NextAction | None = None
50
48
 
51
49
 
52
50
  class RefundIn(BaseModel):
53
- amount: Optional[AmountMinor] = None
54
- reason: Optional[str] = None
51
+ amount: AmountMinor | None = None
52
+ reason: str | None = None
55
53
 
56
54
 
57
55
  class TransactionRow(BaseModel):
@@ -63,9 +61,9 @@ class TransactionRow(BaseModel):
63
61
  status: str
64
62
  provider: str
65
63
  provider_ref: str
66
- user_id: Optional[str] = None
67
- net: Optional[int] = None
68
- fee: Optional[int] = None
64
+ user_id: str | None = None
65
+ net: int | None = None
66
+ fee: int | None = None
69
67
 
70
68
 
71
69
  class StatementRow(BaseModel):
@@ -90,10 +88,10 @@ class PaymentMethodOut(BaseModel):
90
88
  provider: str
91
89
  provider_customer_id: str
92
90
  provider_method_id: str
93
- brand: Optional[str] = None
94
- last4: Optional[str] = None
95
- exp_month: Optional[int] = None
96
- 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
97
95
  is_default: bool = False
98
96
 
99
97
 
@@ -114,8 +112,8 @@ class PriceCreateIn(BaseModel):
114
112
  provider_product_id: str
115
113
  currency: Currency
116
114
  unit_amount: AmountMinor
117
- interval: Optional[Literal["day", "week", "month", "year"]] = None
118
- trial_days: Optional[int] = None
115
+ interval: Literal["day", "week", "month", "year"] | None = None
116
+ trial_days: int | None = None
119
117
  active: bool = True
120
118
 
121
119
 
@@ -126,8 +124,8 @@ class PriceOut(BaseModel):
126
124
  provider_product_id: str
127
125
  currency: Currency
128
126
  unit_amount: AmountMinor
129
- interval: Optional[str] = None
130
- trial_days: Optional[int] = None
127
+ interval: str | None = None
128
+ trial_days: int | None = None
131
129
  active: bool = True
132
130
 
133
131
 
@@ -135,19 +133,15 @@ class SubscriptionCreateIn(BaseModel):
135
133
  customer_provider_id: str
136
134
  price_provider_id: str
137
135
  quantity: int = 1
138
- trial_days: Optional[int] = None
139
- proration_behavior: Literal["create_prorations", "none", "always_invoice"] = (
140
- "create_prorations"
141
- )
136
+ trial_days: int | None = None
137
+ proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
142
138
 
143
139
 
144
140
  class SubscriptionUpdateIn(BaseModel):
145
- price_provider_id: Optional[str] = None
146
- quantity: Optional[int] = None
147
- cancel_at_period_end: Optional[bool] = None
148
- proration_behavior: Literal["create_prorations", "none", "always_invoice"] = (
149
- "create_prorations"
150
- )
141
+ price_provider_id: str | None = None
142
+ quantity: int | None = None
143
+ cancel_at_period_end: bool | None = None
144
+ proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
151
145
 
152
146
 
153
147
  class SubscriptionOut(BaseModel):
@@ -158,7 +152,7 @@ class SubscriptionOut(BaseModel):
158
152
  status: str
159
153
  quantity: int
160
154
  cancel_at_period_end: bool
161
- current_period_end: Optional[str] = None
155
+ current_period_end: str | None = None
162
156
 
163
157
 
164
158
  class InvoiceCreateIn(BaseModel):
@@ -174,47 +168,45 @@ class InvoiceOut(BaseModel):
174
168
  status: str
175
169
  amount_due: AmountMinor
176
170
  currency: Currency
177
- hosted_invoice_url: Optional[str] = None
178
- pdf_url: Optional[str] = None
171
+ hosted_invoice_url: str | None = None
172
+ pdf_url: str | None = None
179
173
 
180
174
 
181
175
  class CaptureIn(BaseModel):
182
- amount: Optional[AmountMinor] = None # partial capture supported
176
+ amount: AmountMinor | None = None # partial capture supported
183
177
 
184
178
 
185
179
  class IntentListFilter(BaseModel):
186
- customer_provider_id: Optional[str] = None
187
- status: Optional[str] = None
188
- limit: Optional[int] = Field(default=50, ge=1, le=200)
189
- 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
190
184
 
191
185
 
192
186
  class UsageRecordIn(BaseModel):
193
187
  # Stripe: subscription_item is the target for metered billing.
194
188
  # If provider doesn't use subscription_item, allow provider_price_id as fallback.
195
- subscription_item: Optional[str] = None
196
- provider_price_id: Optional[str] = None
189
+ subscription_item: str | None = None
190
+ provider_price_id: str | None = None
197
191
  quantity: Annotated[int, Field(ge=0)]
198
- timestamp: Optional[int] = None # Unix seconds; provider defaults to "now"
199
- action: Optional[Literal["increment", "set"]] = "increment"
192
+ timestamp: int | None = None # Unix seconds; provider defaults to "now"
193
+ action: Literal["increment", "set"] | None = "increment"
200
194
 
201
195
 
202
196
  class InvoiceLineItemIn(BaseModel):
203
197
  customer_provider_id: str
204
- description: Optional[str] = None
198
+ description: str | None = None
205
199
  unit_amount: AmountMinor
206
200
  currency: Currency
207
- quantity: Optional[int] = 1
208
- provider_price_id: Optional[str] = (
209
- None # if linked to a price, unit_amount may be ignored
210
- )
201
+ quantity: int | None = 1
202
+ provider_price_id: str | None = None # if linked to a price, unit_amount may be ignored
211
203
 
212
204
 
213
205
  class InvoicesListFilter(BaseModel):
214
- customer_provider_id: Optional[str] = None
215
- status: Optional[str] = None
216
- limit: Optional[int] = Field(default=50, ge=1, le=200)
217
- 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
218
210
 
219
211
 
220
212
  class SetupIntentOut(BaseModel):
@@ -222,8 +214,8 @@ class SetupIntentOut(BaseModel):
222
214
  provider: str
223
215
  provider_setup_intent_id: str
224
216
  status: str
225
- client_secret: Optional[str] = None
226
- next_action: Optional[NextAction] = None
217
+ client_secret: str | None = None
218
+ next_action: NextAction | None = None
227
219
 
228
220
 
229
221
  class DisputeOut(BaseModel):
@@ -232,10 +224,10 @@ class DisputeOut(BaseModel):
232
224
  provider_dispute_id: str
233
225
  amount: AmountMinor
234
226
  currency: Currency
235
- reason: Optional[str] = None
227
+ reason: str | None = None
236
228
  status: str
237
- evidence_due_by: Optional[str] = None
238
- created_at: Optional[str] = None
229
+ evidence_due_by: str | None = None
230
+ created_at: str | None = None
239
231
 
240
232
 
241
233
  class PayoutOut(BaseModel):
@@ -245,8 +237,8 @@ class PayoutOut(BaseModel):
245
237
  amount: AmountMinor
246
238
  currency: Currency
247
239
  status: str
248
- arrival_date: Optional[str] = None
249
- type: Optional[str] = None
240
+ arrival_date: str | None = None
241
+ type: str | None = None
250
242
 
251
243
 
252
244
  class BalanceAmount(BaseModel):
@@ -264,7 +256,7 @@ class SetupIntentCreateIn(BaseModel):
264
256
 
265
257
 
266
258
  class WebhookReplayIn(BaseModel):
267
- event_ids: Optional[list[str]] = None
259
+ event_ids: list[str] | None = None
268
260
 
269
261
 
270
262
  class WebhookReplayOut(BaseModel):
@@ -278,36 +270,36 @@ class WebhookAckOut(BaseModel):
278
270
  class UsageRecordOut(BaseModel):
279
271
  id: str
280
272
  quantity: int
281
- timestamp: Optional[int] = None
282
- subscription_item: Optional[str] = None
283
- provider_price_id: Optional[str] = None
284
- 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
285
277
 
286
278
 
287
279
  # -------- Customers list filter ----------
288
280
  class CustomersListFilter(BaseModel):
289
- provider: Optional[str] = None
290
- user_id: Optional[str] = None
291
- limit: Optional[int] = Field(default=50, ge=1, le=200)
292
- 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
293
285
 
294
286
 
295
287
  # -------- Products / Prices updates ----------
296
288
  class ProductUpdateIn(BaseModel):
297
- name: Optional[str] = None
298
- active: Optional[bool] = None
289
+ name: str | None = None
290
+ active: bool | None = None
299
291
 
300
292
 
301
293
  class PriceUpdateIn(BaseModel):
302
- active: Optional[bool] = None
294
+ active: bool | None = None
303
295
 
304
296
 
305
297
  # -------- Payment Method update ----------
306
298
  class PaymentMethodUpdateIn(BaseModel):
307
299
  # keep minimal + commonly supported card fields
308
- name: Optional[str] = None
309
- exp_month: Optional[int] = None
310
- exp_year: Optional[int] = None
300
+ name: str | None = None
301
+ exp_month: int | None = None
302
+ exp_year: int | None = None
311
303
  # extend here later with address fields (line1, city, etc.)
312
304
 
313
305
 
@@ -316,27 +308,27 @@ class RefundOut(BaseModel):
316
308
  id: str
317
309
  provider: str
318
310
  provider_refund_id: str
319
- provider_payment_intent_id: Optional[str] = None
311
+ provider_payment_intent_id: str | None = None
320
312
  amount: AmountMinor
321
313
  currency: Currency
322
314
  status: str
323
- reason: Optional[str] = None
324
- created_at: Optional[str] = None
315
+ reason: str | None = None
316
+ created_at: str | None = None
325
317
 
326
318
 
327
319
  # -------- Invoice line items (list) ----------
328
320
  class InvoiceLineItemOut(BaseModel):
329
321
  id: str
330
- description: Optional[str] = None
322
+ description: str | None = None
331
323
  amount: AmountMinor
332
324
  currency: Currency
333
- quantity: Optional[int] = 1
334
- provider_price_id: Optional[str] = None
325
+ quantity: int | None = 1
326
+ provider_price_id: str | None = None
335
327
 
336
328
 
337
329
  # -------- Usage records list/get ----------
338
330
  class UsageRecordListFilter(BaseModel):
339
- subscription_item: Optional[str] = None
340
- provider_price_id: Optional[str] = None
341
- limit: Optional[int] = Field(default=50, ge=1, le=200)
342
- 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
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional
4
-
5
3
  from sqlalchemy import select
6
4
  from sqlalchemy.ext.asyncio import AsyncSession
7
5
 
@@ -78,7 +76,7 @@ class PaymentsService:
78
76
  session: AsyncSession,
79
77
  *,
80
78
  tenant_id: str,
81
- provider_name: Optional[str] = None,
79
+ provider_name: str | None = None,
82
80
  ):
83
81
  if not tenant_id:
84
82
  raise ValueError("tenant_id is required for PaymentsService")
@@ -144,9 +142,7 @@ class PaymentsService:
144
142
 
145
143
  # --- Intents --------------------------------------------------------------
146
144
 
147
- async def create_intent(
148
- self, user_id: Optional[str], data: IntentCreateIn
149
- ) -> IntentOut:
145
+ async def create_intent(self, user_id: str | None, data: IntentCreateIn) -> IntentOut:
150
146
  adapter = self._get_adapter()
151
147
  out = await adapter.create_intent(data, user_id=user_id)
152
148
  self.session.add(
@@ -217,9 +213,7 @@ class PaymentsService:
217
213
 
218
214
  # --- Webhooks -------------------------------------------------------------
219
215
 
220
- async def handle_webhook(
221
- self, provider: str, signature: str | None, payload: bytes
222
- ) -> dict:
216
+ async def handle_webhook(self, provider: str, signature: str | None, payload: bytes) -> dict:
223
217
  adapter = self._get_adapter()
224
218
  parsed = await adapter.verify_and_parse_webhook(signature, payload)
225
219
  self.session.add(
@@ -317,9 +311,7 @@ class PaymentsService:
317
311
  )
318
312
  )
319
313
 
320
- async def attach_payment_method(
321
- self, data: PaymentMethodAttachIn
322
- ) -> PaymentMethodOut:
314
+ async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
323
315
  out = await self._get_adapter().attach_payment_method(data)
324
316
  # Upsert locally for quick listing
325
317
  pm = PayPaymentMethod(
@@ -336,9 +328,7 @@ class PaymentsService:
336
328
  self.session.add(pm)
337
329
  return out
338
330
 
339
- async def list_payment_methods(
340
- self, provider_customer_id: str
341
- ) -> list[PaymentMethodOut]:
331
+ async def list_payment_methods(self, provider_customer_id: str) -> list[PaymentMethodOut]:
342
332
  return await self._get_adapter().list_payment_methods(provider_customer_id)
343
333
 
344
334
  async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
@@ -401,18 +391,14 @@ class PaymentsService:
401
391
  async def update_subscription(
402
392
  self, provider_subscription_id: str, data: SubscriptionUpdateIn
403
393
  ) -> SubscriptionOut:
404
- out = await self._get_adapter().update_subscription(
405
- provider_subscription_id, data
406
- )
394
+ out = await self._get_adapter().update_subscription(provider_subscription_id, data)
407
395
  # Optionally reflect status/quantity locally (query + update if exists)
408
396
  return out
409
397
 
410
398
  async def cancel_subscription(
411
399
  self, provider_subscription_id: str, at_period_end: bool = True
412
400
  ) -> SubscriptionOut:
413
- out = await self._get_adapter().cancel_subscription(
414
- provider_subscription_id, at_period_end
415
- )
401
+ out = await self._get_adapter().cancel_subscription(provider_subscription_id, at_period_end)
416
402
  return out
417
403
 
418
404
  # --- Invoices ---
@@ -457,15 +443,15 @@ class PaymentsService:
457
443
  q = select(
458
444
  func.date_trunc("day", LedgerEntry.ts).label("day"),
459
445
  LedgerEntry.currency,
460
- func.sum(
461
- func.case((LedgerEntry.kind == "payment", LedgerEntry.amount), else_=0)
462
- ).label("gross"),
463
- func.sum(
464
- func.case((LedgerEntry.kind == "refund", LedgerEntry.amount), else_=0)
465
- ).label("refunds"),
466
- func.sum(
467
- func.case((LedgerEntry.kind == "fee", LedgerEntry.amount), else_=0)
468
- ).label("fees"),
446
+ func.sum(func.case((LedgerEntry.kind == "payment", LedgerEntry.amount), else_=0)).label(
447
+ "gross"
448
+ ),
449
+ func.sum(func.case((LedgerEntry.kind == "refund", LedgerEntry.amount), else_=0)).label(
450
+ "refunds"
451
+ ),
452
+ func.sum(func.case((LedgerEntry.kind == "fee", LedgerEntry.amount), else_=0)).label(
453
+ "fees"
454
+ ),
469
455
  func.count().label("count"),
470
456
  )
471
457
  if date_from:
@@ -478,9 +464,9 @@ class PaymentsService:
478
464
  q = q.where(LedgerEntry.ts <= datetime.fromisoformat(date_to))
479
465
  except Exception:
480
466
  pass
481
- q = q.group_by(
482
- func.date_trunc("day", LedgerEntry.ts), LedgerEntry.currency
483
- ).order_by(func.date_trunc("day", LedgerEntry.ts).desc())
467
+ q = q.group_by(func.date_trunc("day", LedgerEntry.ts), LedgerEntry.currency).order_by(
468
+ func.date_trunc("day", LedgerEntry.ts).desc()
469
+ )
484
470
 
485
471
  rows = (await self.session.execute(q)).all()
486
472
  out: list[StatementRow] = []
@@ -502,9 +488,7 @@ class PaymentsService:
502
488
  )
503
489
  return out
504
490
 
505
- async def capture_intent(
506
- self, provider_intent_id: str, data: CaptureIn
507
- ) -> IntentOut:
491
+ async def capture_intent(self, provider_intent_id: str, data: CaptureIn) -> IntentOut:
508
492
  out = await self._get_adapter().capture_intent(
509
493
  provider_intent_id,
510
494
  amount=int(data.amount) if data.amount is not None else None,
@@ -539,9 +523,7 @@ class PaymentsService:
539
523
  )
540
524
  return out
541
525
 
542
- async def list_intents(
543
- self, f: IntentListFilter
544
- ) -> tuple[list[IntentOut], str | None]:
526
+ async def list_intents(self, f: IntentListFilter) -> tuple[list[IntentOut], str | None]:
545
527
  return await self._get_adapter().list_intents(
546
528
  customer_provider_id=f.customer_provider_id,
547
529
  status=f.status,
@@ -553,13 +535,9 @@ class PaymentsService:
553
535
  async def add_invoice_line_item(
554
536
  self, provider_invoice_id: str, data: InvoiceLineItemIn
555
537
  ) -> InvoiceOut:
556
- return await self._get_adapter().add_invoice_line_item(
557
- provider_invoice_id, data
558
- )
538
+ return await self._get_adapter().add_invoice_line_item(provider_invoice_id, data)
559
539
 
560
- async def list_invoices(
561
- self, f: InvoicesListFilter
562
- ) -> tuple[list[InvoiceOut], str | None]:
540
+ async def list_invoices(self, f: InvoicesListFilter) -> tuple[list[InvoiceOut], str | None]:
563
541
  return await self._get_adapter().list_invoices(
564
542
  customer_provider_id=f.customer_provider_id,
565
543
  status=f.status,
@@ -596,9 +574,7 @@ class PaymentsService:
596
574
  )
597
575
  return out
598
576
 
599
- async def confirm_setup_intent(
600
- self, provider_setup_intent_id: str
601
- ) -> SetupIntentOut:
577
+ async def confirm_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
602
578
  out = await self._get_adapter().confirm_setup_intent(provider_setup_intent_id)
603
579
  row = await self.session.scalar(
604
580
  select(PaySetupIntent).where(
@@ -647,19 +623,15 @@ class PaymentsService:
647
623
 
648
624
  # --- Disputes -------------------------------------------------------------
649
625
  async def list_disputes(
650
- self, *, status: Optional[str], limit: int, cursor: Optional[str]
651
- ) -> tuple[list[DisputeOut], Optional[str]]:
652
- return await self._get_adapter().list_disputes(
653
- status=status, limit=limit, cursor=cursor
654
- )
626
+ self, *, status: str | None, limit: int, cursor: str | None
627
+ ) -> tuple[list[DisputeOut], str | None]:
628
+ return await self._get_adapter().list_disputes(status=status, limit=limit, cursor=cursor)
655
629
 
656
630
  async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
657
631
  out = await self._get_adapter().get_dispute(provider_dispute_id)
658
632
  # Upsert locally
659
633
  row = await self.session.scalar(
660
- select(PayDispute).where(
661
- PayDispute.provider_dispute_id == provider_dispute_id
662
- )
634
+ select(PayDispute).where(PayDispute.provider_dispute_id == provider_dispute_id)
663
635
  )
664
636
  if row:
665
637
  row.status = out.status
@@ -680,17 +652,11 @@ class PaymentsService:
680
652
  )
681
653
  return out
682
654
 
683
- async def submit_dispute_evidence(
684
- self, provider_dispute_id: str, evidence: dict
685
- ) -> DisputeOut:
686
- out = await self._get_adapter().submit_dispute_evidence(
687
- provider_dispute_id, evidence
688
- )
655
+ async def submit_dispute_evidence(self, provider_dispute_id: str, evidence: dict) -> DisputeOut:
656
+ out = await self._get_adapter().submit_dispute_evidence(provider_dispute_id, evidence)
689
657
  # reflect status
690
658
  row = await self.session.scalar(
691
- select(PayDispute).where(
692
- PayDispute.provider_dispute_id == provider_dispute_id
693
- )
659
+ select(PayDispute).where(PayDispute.provider_dispute_id == provider_dispute_id)
694
660
  )
695
661
  if row:
696
662
  row.status = out.status
@@ -702,8 +668,8 @@ class PaymentsService:
702
668
 
703
669
  # --- Payouts --------------------------------------------------------------
704
670
  async def list_payouts(
705
- self, *, limit: int, cursor: Optional[str]
706
- ) -> tuple[list[PayoutOut], Optional[str]]:
671
+ self, *, limit: int, cursor: str | None
672
+ ) -> tuple[list[PayoutOut], str | None]:
707
673
  return await self._get_adapter().list_payouts(limit=limit, cursor=cursor)
708
674
 
709
675
  async def get_payout(self, provider_payout_id: str) -> PayoutOut:
@@ -733,7 +699,7 @@ class PaymentsService:
733
699
 
734
700
  # --- Webhook replay -------------------------------------------------------
735
701
  async def replay_webhooks(
736
- self, since: Optional[str], until: Optional[str], event_ids: list[str]
702
+ self, since: str | None, until: str | None, event_ids: list[str]
737
703
  ) -> int:
738
704
  from datetime import datetime
739
705
 
@@ -760,9 +726,7 @@ class PaymentsService:
760
726
  return len(rows)
761
727
 
762
728
  # ---- Customers ----
763
- async def list_customers(
764
- self, f: CustomersListFilter
765
- ) -> tuple[list[CustomerOut], str | None]:
729
+ async def list_customers(self, f: CustomersListFilter) -> tuple[list[CustomerOut], str | None]:
766
730
  adapter = self._get_adapter()
767
731
  try:
768
732
  return await adapter.list_customers(
@@ -805,9 +769,7 @@ class PaymentsService:
805
769
  raise RuntimeError("Customer not found")
806
770
  # upsert locally
807
771
  row = await self.session.scalar(
808
- select(PayCustomer).where(
809
- PayCustomer.provider_customer_id == provider_customer_id
810
- )
772
+ select(PayCustomer).where(PayCustomer.provider_customer_id == provider_customer_id)
811
773
  )
812
774
  if not row:
813
775
  self.session.add(
@@ -827,19 +789,13 @@ class PaymentsService:
827
789
  async def list_products(
828
790
  self, *, active: bool | None, limit: int, cursor: str | None
829
791
  ) -> tuple[list[ProductOut], str | None]:
830
- return await self._get_adapter().list_products(
831
- active=active, limit=limit, cursor=cursor
832
- )
792
+ return await self._get_adapter().list_products(active=active, limit=limit, cursor=cursor)
833
793
 
834
- async def update_product(
835
- self, provider_product_id: str, data: ProductUpdateIn
836
- ) -> ProductOut:
794
+ async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
837
795
  out = await self._get_adapter().update_product(provider_product_id, data)
838
796
  # reflect DB
839
797
  row = await self.session.scalar(
840
- select(PayProduct).where(
841
- PayProduct.provider_product_id == provider_product_id
842
- )
798
+ select(PayProduct).where(PayProduct.provider_product_id == provider_product_id)
843
799
  )
844
800
  if row:
845
801
  if data.name is not None:
@@ -866,9 +822,7 @@ class PaymentsService:
866
822
  cursor=cursor,
867
823
  )
868
824
 
869
- async def update_price(
870
- self, provider_price_id: str, data: PriceUpdateIn
871
- ) -> PriceOut:
825
+ async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
872
826
  out = await self._get_adapter().update_price(provider_price_id, data)
873
827
  row = await self.session.scalar(
874
828
  select(PayPrice).where(PayPrice.provider_price_id == provider_price_id)