svc-infra 0.1.618__tar.gz → 0.1.672__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (375) hide show
  1. {svc_infra-0.1.618 → svc_infra-0.1.672}/PKG-INFO +46 -17
  2. svc_infra-0.1.672/README.md +80 -0
  3. {svc_infra-0.1.618 → svc_infra-0.1.672}/pyproject.toml +14 -2
  4. svc_infra-0.1.672/src/svc_infra/api/fastapi/admin/__init__.py +3 -0
  5. svc_infra-0.1.672/src/svc_infra/api/fastapi/admin/add.py +231 -0
  6. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -2
  7. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/add.py +0 -4
  8. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +19 -4
  9. svc_infra-0.1.672/src/svc_infra/api/fastapi/billing/router.py +64 -0
  10. svc_infra-0.1.672/src/svc_infra/api/fastapi/billing/setup.py +19 -0
  11. svc_infra-0.1.672/src/svc_infra/api/fastapi/cache/add.py +18 -0
  12. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
  13. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/add.py +8 -5
  14. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/crud_router.py +21 -21
  15. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/session.py +16 -0
  16. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/landing.py +1 -1
  17. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/scoped.py +41 -6
  18. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +15 -0
  19. svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/graceful_shutdown.py +87 -0
  20. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/idempotency.py +7 -0
  21. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/ratelimit.py +20 -3
  22. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +12 -6
  23. svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/request_id.py +37 -0
  24. svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/timeout.py +180 -0
  25. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/mutators.py +3 -0
  26. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/ops/add.py +9 -1
  27. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/setup.py +38 -18
  28. svc_infra-0.1.672/src/svc_infra/api/fastapi/versioned.py +101 -0
  29. svc_infra-0.1.672/src/svc_infra/billing/async_service.py +147 -0
  30. svc_infra-0.1.672/src/svc_infra/billing/jobs.py +230 -0
  31. svc_infra-0.1.672/src/svc_infra/billing/quotas.py +101 -0
  32. svc_infra-0.1.672/src/svc_infra/billing/schemas.py +33 -0
  33. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/__init__.py +4 -0
  34. svc_infra-0.1.672/src/svc_infra/cache/add.py +158 -0
  35. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/__init__.py +17 -11
  36. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +4 -3
  37. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +4 -4
  38. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +28 -12
  39. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/sql_export_cmds.py +2 -4
  40. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
  41. svc_infra-0.1.672/src/svc_infra/cli/cmds/docs/docs_cmds.py +140 -0
  42. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/help.py +4 -0
  43. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/obs/obs_cmds.py +4 -3
  44. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/README.md +13 -13
  45. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/repository.py +2 -0
  46. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
  47. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +25 -11
  48. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +20 -5
  49. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/utils.py +18 -4
  50. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/acceptance-matrix.md +17 -0
  51. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/acceptance.md +1 -1
  52. svc_infra-0.1.672/src/svc_infra/docs/admin.md +425 -0
  53. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0005-data-lifecycle.md +1 -1
  54. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0008-billing-primitives.md +34 -0
  55. svc_infra-0.1.672/src/svc_infra/docs/adr/0010-timeouts-and-resource-limits.md +54 -0
  56. svc_infra-0.1.672/src/svc_infra/docs/adr/0011-admin-scope-and-impersonation.md +73 -0
  57. svc_infra-0.1.672/src/svc_infra/docs/adr/0012-generic-file-storage.md +498 -0
  58. svc_infra-0.1.672/src/svc_infra/docs/api.md +215 -0
  59. svc_infra-0.1.672/src/svc_infra/docs/billing.md +190 -0
  60. svc_infra-0.1.672/src/svc_infra/docs/cache.md +76 -0
  61. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/cli.md +5 -5
  62. svc_infra-0.1.672/src/svc_infra/docs/documents.md +697 -0
  63. svc_infra-0.1.618/README.md → svc_infra-0.1.672/src/svc_infra/docs/getting-started.md +25 -16
  64. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/ops.md +4 -0
  65. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/rate-limiting.md +4 -0
  66. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/security.md +23 -2
  67. svc_infra-0.1.672/src/svc_infra/docs/storage.md +982 -0
  68. svc_infra-0.1.672/src/svc_infra/docs/timeouts-and-resource-limits.md +147 -0
  69. svc_infra-0.1.672/src/svc_infra/docs/versioned-integrations.md +146 -0
  70. svc_infra-0.1.672/src/svc_infra/documents/__init__.py +100 -0
  71. svc_infra-0.1.672/src/svc_infra/documents/add.py +258 -0
  72. svc_infra-0.1.672/src/svc_infra/documents/ease.py +233 -0
  73. svc_infra-0.1.672/src/svc_infra/documents/models.py +114 -0
  74. svc_infra-0.1.672/src/svc_infra/documents/storage.py +262 -0
  75. svc_infra-0.1.672/src/svc_infra/http/__init__.py +13 -0
  76. svc_infra-0.1.672/src/svc_infra/http/client.py +72 -0
  77. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/builtins/webhook_delivery.py +14 -2
  78. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/queue.py +9 -1
  79. svc_infra-0.1.672/src/svc_infra/jobs/runner.py +75 -0
  80. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/worker.py +17 -1
  81. svc_infra-0.1.672/src/svc_infra/mcp/svc_infra_mcp.py +124 -0
  82. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/headers.py +15 -2
  83. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/hibp.py +6 -2
  84. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/models.py +27 -7
  85. svc_infra-0.1.672/src/svc_infra/security/oauth_models.py +59 -0
  86. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/permissions.py +1 -0
  87. svc_infra-0.1.672/src/svc_infra/storage/__init__.py +93 -0
  88. svc_infra-0.1.672/src/svc_infra/storage/add.py +250 -0
  89. svc_infra-0.1.672/src/svc_infra/storage/backends/__init__.py +11 -0
  90. svc_infra-0.1.672/src/svc_infra/storage/backends/local.py +331 -0
  91. svc_infra-0.1.672/src/svc_infra/storage/backends/memory.py +214 -0
  92. svc_infra-0.1.672/src/svc_infra/storage/backends/s3.py +329 -0
  93. svc_infra-0.1.672/src/svc_infra/storage/base.py +239 -0
  94. svc_infra-0.1.672/src/svc_infra/storage/easy.py +182 -0
  95. svc_infra-0.1.672/src/svc_infra/storage/settings.py +193 -0
  96. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/service.py +10 -2
  97. svc_infra-0.1.618/docs/api.md +0 -59
  98. svc_infra-0.1.618/docs/cache.md +0 -18
  99. svc_infra-0.1.618/src/svc_infra/api/fastapi/cache/add.py +0 -14
  100. svc_infra-0.1.618/src/svc_infra/api/fastapi/middleware/request_id.py +0 -23
  101. svc_infra-0.1.618/src/svc_infra/cli/cmds/docs/docs_cmds.py +0 -188
  102. svc_infra-0.1.618/src/svc_infra/mcp/svc_infra_mcp.py +0 -67
  103. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/__init__.py +0 -0
  104. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/README.md +0 -0
  105. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/__init__.py +0 -0
  106. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/alembic.py +0 -0
  107. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/models.py +0 -0
  108. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
  109. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
  110. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/base.py +0 -0
  111. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/registry.py +0 -0
  112. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
  113. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/schemas.py +0 -0
  114. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/service.py +0 -0
  115. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/settings.py +0 -0
  116. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/__init__.py +0 -0
  117. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/__init__.py +0 -0
  118. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
  119. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
  120. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
  121. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
  122. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
  123. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
  124. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
  125. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
  126. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
  127. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
  128. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
  129. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
  130. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
  131. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
  132. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
  133. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
  134. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
  135. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
  136. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/security.py +0 -0
  137. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
  138. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
  139. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/state.py +0 -0
  140. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
  141. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
  142. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/http.py +0 -0
  143. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
  144. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
  145. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
  146. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
  147. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
  148. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
  149. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
  150. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
  151. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
  152. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
  153. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/add.py +0 -0
  154. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
  155. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
  156. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
  157. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/public.py +0 -0
  158. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/router.py +0 -0
  159. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
  160. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dx.py +0 -0
  161. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/ease.py +0 -0
  162. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
  163. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
  164. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
  165. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
  166. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
  167. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
  168. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
  169. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
  170. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
  171. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
  172. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
  173. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
  174. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
  175. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
  176. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
  177. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
  178. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
  179. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
  180. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
  181. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/pagination.py +0 -0
  182. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
  183. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
  184. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
  185. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
  186. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/user.py +0 -0
  187. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
  188. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
  189. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/tenancy/add.py +0 -0
  190. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/tenancy/context.py +0 -0
  191. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/README.md +0 -0
  192. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/__init__.py +0 -0
  193. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/env.py +0 -0
  194. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/__init__.py +0 -0
  195. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/add.py +0 -0
  196. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/filter.py +0 -0
  197. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/formats.py +0 -0
  198. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/root.py +0 -0
  199. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/__init__.py +0 -0
  200. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/models.py +0 -0
  201. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/service.py +0 -0
  202. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/README.md +0 -0
  203. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/__init__.py +0 -0
  204. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/getting-started.md +0 -0
  205. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/README.md +0 -0
  206. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/backend.py +0 -0
  207. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/decorators.py +0 -0
  208. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/demo.py +0 -0
  209. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/keys.py +0 -0
  210. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/recache.py +0 -0
  211. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/resources.py +0 -0
  212. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/tags.py +0 -0
  213. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/ttl.py +0 -0
  214. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/utils.py +0 -0
  215. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/__main__.py +0 -0
  216. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/__init__.py +0 -0
  217. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
  218. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
  219. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
  220. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
  221. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
  222. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/dx/__init__.py +0 -0
  223. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/dx/dx_cmds.py +0 -0
  224. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
  225. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
  226. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
  227. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/sdk/__init__.py +0 -0
  228. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/sdk/sdk_cmds.py +0 -0
  229. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/__init__.py +0 -0
  230. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/runner.py +0 -0
  231. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
  232. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/add.py +0 -0
  233. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/backup.py +0 -0
  234. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/erasure.py +0 -0
  235. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/fixtures.py +0 -0
  236. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/retention.py +0 -0
  237. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/__init__.py +0 -0
  238. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/crud_schema.py +0 -0
  239. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/inbox.py +0 -0
  240. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/__init__.py +0 -0
  241. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/base.py +0 -0
  242. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/constants.py +0 -0
  243. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/core.py +0 -0
  244. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/indexes.py +0 -0
  245. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/management.py +0 -0
  246. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
  247. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/client.py +0 -0
  248. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
  249. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
  250. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
  251. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
  252. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
  253. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/repository.py +0 -0
  254. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/resource.py +0 -0
  255. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/scaffold.py +0 -0
  256. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/service.py +0 -0
  257. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
  258. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/types.py +0 -0
  259. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/utils.py +0 -0
  260. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/outbox.py +0 -0
  261. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/README.md +0 -0
  262. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/__init__.py +0 -0
  263. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/apikey.py +0 -0
  264. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/authref.py +0 -0
  265. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/base.py +0 -0
  266. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/constants.py +0 -0
  267. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/core.py +0 -0
  268. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/management.py +0 -0
  269. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/resource.py +0 -0
  270. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/scaffold.py +0 -0
  271. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/service.py +0 -0
  272. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
  273. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/__init__.py +0 -0
  274. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
  275. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
  276. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
  277. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
  278. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
  279. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
  280. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
  281. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/tenant.py +0 -0
  282. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/types.py +0 -0
  283. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/uniq.py +0 -0
  284. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
  285. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/versioning.py +0 -0
  286. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/utils.py +0 -0
  287. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0002-background-jobs-and-scheduling.md +0 -0
  288. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0003-webhooks-framework.md +0 -0
  289. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0004-tenancy-model.md +0 -0
  290. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0006-ops-slos-and-metrics.md +0 -0
  291. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0007-docs-and-sdks.md +0 -0
  292. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0009-acceptance-harness.md +0 -0
  293. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/auth.md +0 -0
  294. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/contributing.md +0 -0
  295. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/data-lifecycle.md +0 -0
  296. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/database.md +0 -0
  297. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/docs-and-sdks.md +0 -0
  298. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/environment.md +0 -0
  299. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/idempotency.md +0 -0
  300. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/jobs.md +0 -0
  301. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/observability.md +0 -0
  302. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/repo-review.md +0 -0
  303. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/tenancy.md +0 -0
  304. {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/webhooks.md +0 -0
  305. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/add.py +0 -0
  306. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/changelog.py +0 -0
  307. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/checks.py +0 -0
  308. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
  309. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/easy.py +0 -0
  310. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/loader.py +0 -0
  311. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/redis_queue.py +0 -0
  312. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/scheduler.py +0 -0
  313. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/mcp/__init__.py +0 -0
  314. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/README.md +0 -0
  315. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/__init__.py +0 -0
  316. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/add.py +0 -0
  317. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/cloud_dash.py +0 -0
  318. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/grafana/dashboards/http-overview.json +0 -0
  319. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/__init__.py +0 -0
  320. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/asgi.py +0 -0
  321. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/base.py +0 -0
  322. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/http.py +0 -0
  323. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
  324. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics.py +0 -0
  325. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/__init__.py +0 -0
  326. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
  327. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
  328. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
  329. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/templates/docker-compose.cloud.yml.tmpl +0 -0
  330. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
  331. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
  332. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
  333. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
  334. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
  335. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
  336. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
  337. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
  338. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
  339. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
  340. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
  341. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
  342. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
  343. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/settings.py +0 -0
  344. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
  345. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
  346. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
  347. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
  348. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
  349. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
  350. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
  351. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
  352. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
  353. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
  354. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
  355. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
  356. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
  357. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
  358. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
  359. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
  360. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/py.typed +0 -0
  361. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/add.py +0 -0
  362. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/audit.py +0 -0
  363. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/audit_service.py +0 -0
  364. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/jwt_rotation.py +0 -0
  365. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/lockout.py +0 -0
  366. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/org_invites.py +0 -0
  367. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/passwords.py +0 -0
  368. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/session.py +0 -0
  369. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/signed_cookies.py +0 -0
  370. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/utils.py +0 -0
  371. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/__init__.py +0 -0
  372. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/add.py +0 -0
  373. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/fastapi.py +0 -0
  374. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/router.py +0 -0
  375. {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/signing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: svc-infra
3
- Version: 0.1.618
3
+ Version: 0.1.672
4
4
  Summary: Infrastructure for building and deploying prod-ready services
5
5
  License: MIT
6
6
  Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
@@ -24,10 +24,13 @@ Provides-Extra: mysql
24
24
  Provides-Extra: pg
25
25
  Provides-Extra: pg2
26
26
  Provides-Extra: redshift
27
+ Provides-Extra: s3
27
28
  Provides-Extra: snowflake
28
29
  Provides-Extra: sqlite
29
30
  Requires-Dist: adyen (>=13.4.0,<14.0.0)
30
31
  Requires-Dist: ai-infra (>=0.1.63,<0.2.0)
32
+ Requires-Dist: aioboto3 (>=13.0.0,<14.0.0) ; extra == "s3"
33
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
31
34
  Requires-Dist: aiosqlite (>=0.20.0,<0.21.0) ; extra == "sqlite"
32
35
  Requires-Dist: alembic (>=1.13.2,<2.0.0)
33
36
  Requires-Dist: asyncpg (>=0.30.0,<0.31.0) ; extra == "pg"
@@ -77,25 +80,50 @@ Description-Content-Type: text/markdown
77
80
  # svc-infra
78
81
 
79
82
  [![PyPI](https://img.shields.io/pypi/v/svc-infra.svg)](https://pypi.org/project/svc-infra/)
80
- [![Docs](https://img.shields.io/badge/docs-reference-blue)](docs/)
83
+ [![Docs](https://img.shields.io/badge/docs-reference-blue)](.)
81
84
 
82
85
  svc-infra packages the shared building blocks we use to ship production FastAPI services fast—HTTP APIs with secure auth, durable persistence, background execution, cache, observability, and webhook plumbing that all share the same batteries-included defaults.
83
86
 
84
87
  ## Helper index
85
88
 
86
- | Helper | What it covers | Guide |
89
+ | Area | What it covers | Guide |
87
90
  | --- | --- | --- |
88
- | API | FastAPI bootstrap, envelopes, middleware, docs wiring | [FastAPI guide](docs/api.md) |
89
- | Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth settings](docs/auth.md) |
90
- | Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database guide](docs/database.md) |
91
- | Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
92
- | Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
93
- | Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
94
- | Ops | Probes, breaker, SLOs & dashboards | [SLOs & Ops](docs/ops.md) |
95
- | Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
96
- | Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
97
- | Contributing | Dev setup and quality gates | [Contributing guide](docs/contributing.md) |
98
- | Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](docs/data-lifecycle.md) |
91
+ | Getting Started | Overview and entry points | [This page](src/svc_infra/docs/getting-started.md) |
92
+ | Environment | Feature switches and env vars | [Environment](src/svc_infra/docs/environment.md) |
93
+ | API | FastAPI bootstrap, middleware, docs wiring | [API guide](src/svc_infra/docs/api.md) |
94
+ | Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth](src/svc_infra/docs/auth.md) |
95
+ | Security | Password policy, lockout, signed cookies, headers | [Security](src/svc_infra/docs/security.md) |
96
+ | Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database](src/svc_infra/docs/database.md) |
97
+ | Storage | File storage with S3, local, memory backends | [Storage](src/svc_infra/docs/storage.md) |
98
+ | Documents | Generic document management with metadata | [Documents](src/svc_infra/docs/documents.md) |
99
+ | Tenancy | Multi-tenant boundaries and helpers | [Tenancy](src/svc_infra/docs/tenancy.md) |
100
+ | Idempotency | Idempotent endpoints and middleware | [Idempotency](src/svc_infra/docs/idempotency.md) |
101
+ | Rate Limiting | Middleware, dependency limiter, headers | [Rate limiting](src/svc_infra/docs/rate-limiting.md) |
102
+ | Cache | cashews decorators, namespace management, TTL helpers | [Cache](src/svc_infra/docs/cache.md) |
103
+ | Jobs | JobQueue, scheduler, CLI worker | [Jobs](src/svc_infra/docs/jobs.md) |
104
+ | Observability | Prometheus, Grafana, OpenTelemetry | [Observability](src/svc_infra/docs/observability.md) |
105
+ | Ops | Probes, breakers, SLOs & dashboards | [Ops](src/svc_infra/docs/ops.md) |
106
+ | Webhooks | Subscription store, signing, retry worker | [Webhooks](src/svc_infra/docs/webhooks.md) |
107
+ | CLI | Command groups for sql/mongo/obs/docs/dx/sdk/jobs | [CLI](src/svc_infra/docs/cli.md) |
108
+ | Docs & SDKs | Publishing docs, generating SDKs | [Docs & SDKs](src/svc_infra/docs/docs-and-sdks.md) |
109
+ | Acceptance | Acceptance harness and flows | [Acceptance](src/svc_infra/docs/acceptance.md), [Matrix](src/svc_infra/docs/acceptance-matrix.md) |
110
+ | Contributing | Dev setup and quality gates | [Contributing](src/svc_infra/docs/contributing.md) |
111
+ | Repo Review | Checklist for releasing/PRs | [Repo review](src/svc_infra/docs/repo-review.md) |
112
+ | Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](src/svc_infra/docs/data-lifecycle.md) |
113
+
114
+ ## Quick Start with Template Example
115
+
116
+ See **ALL** svc-infra features working together in a complete example:
117
+
118
+ ```bash
119
+ # One-time setup (from repo root)
120
+ make setup-template # Scaffolds models, runs migrations
121
+
122
+ # Run the example server
123
+ make run-template # Starts at http://localhost:8001
124
+ ```
125
+
126
+ See [`examples/README.md`](examples/README.md) for full documentation and manual setup options.
99
127
 
100
128
  ## Minimal FastAPI bootstrap
101
129
 
@@ -123,9 +151,10 @@ async def handle_webhook(payload = Depends(require_signature(lambda: ["current",
123
151
  - **API** – toggle logging/observability and docs exposure with `ENABLE_LOGGING`, `LOG_LEVEL`, `LOG_FORMAT`, `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and `CORS_ALLOW_ORIGINS`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/api/fastapi/setup.py†L47-L88】
124
152
  - **Auth** – configure JWT secrets, SMTP, cookies, and policy using the `AUTH_…` settings family (e.g., `AUTH_JWT__SECRET`, `AUTH_SMTP_HOST`, `AUTH_SESSION_COOKIE_SECURE`). 【F:src/svc_infra/api/fastapi/auth/settings.py†L23-L91】
125
153
  - **Database** – set connection URLs or components via `SQL_URL`/`SQL_URL_FILE`, `DB_DIALECT`, `DB_HOST`, `DB_USER`, `DB_PASSWORD`, plus Mongo knobs like `MONGO_URL`, `MONGO_DB`, and `MONGO_URL_FILE`. 【F:src/svc_infra/api/fastapi/db/sql/add.py†L55-L114】【F:src/svc_infra/db/sql/utils.py†L85-L206】【F:src/svc_infra/db/nosql/mongo/settings.py†L9-L13】【F:src/svc_infra/db/nosql/utils.py†L56-L113】
126
- - **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:docs/jobs.md†L11-L48
154
+ - **Storage** – choose backend with `STORAGE_BACKEND` (local, s3, memory) and configure with `STORAGE_S3_BUCKET`, `STORAGE_S3_REGION`, `STORAGE_BASE_PATH`, or auto-detect from `RAILWAY_VOLUME_MOUNT_PATH` / AWS credentials. 【F:src/svc_infra/storage/settings.py】【F:src/svc_infra/docs/storage.md】
155
+ - **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:src/svc_infra/docs/jobs.md†L11-L48】
127
156
  - **Cache** – namespace keys and lifetimes through `CACHE_PREFIX`, `CACHE_VERSION`, and TTL overrides `CACHE_TTL_DEFAULT`, `CACHE_TTL_SHORT`, `CACHE_TTL_LONG`. 【F:src/svc_infra/cache/README.md†L20-L173】【F:src/svc_infra/cache/ttl.py†L26-L55】
128
157
  - **Observability** – turn metrics on/off or adjust scrape paths with `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and Prometheus/Grafana flags like `SVC_INFRA_DISABLE_PROMETHEUS`, `SVC_INFRA_RATE_WINDOW`, `SVC_INFRA_DASHBOARD_REFRESH`, `SVC_INFRA_DASHBOARD_RANGE`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/obs/metrics/asgi.py†L49-L206】【F:src/svc_infra/obs/cloud_dash.py†L85-L108】
129
- - **Webhooks** – reuse the jobs envs (`JOBS_DRIVER`, `REDIS_URL`) for the delivery worker and queue configuration. 【F:docs/webhooks.md†L32-L53】
130
- - **Security** – enforce password policy, MFA, and rotation with auth prefixes such as `AUTH_PASSWORD_MIN_LENGTH`, `AUTH_PASSWORD_REQUIRE_SYMBOL`, `AUTH_JWT__SECRET`, and `AUTH_JWT__OLD_SECRETS`. 【F:docs/security.md†L24-L70】
158
+ - **Webhooks** – reuse the jobs envs (`JOBS_DRIVER`, `REDIS_URL`) for the delivery worker and queue configuration. 【F:src/svc_infra/docs/webhooks.md†L32-L53】
159
+ - **Security** – enforce password policy, MFA, and rotation with auth prefixes such as `AUTH_PASSWORD_MIN_LENGTH`, `AUTH_PASSWORD_REQUIRE_SYMBOL`, `AUTH_JWT__SECRET`, and `AUTH_JWT__OLD_SECRETS`. 【F:src/svc_infra/docs/security.md†L24-L70】
131
160
 
@@ -0,0 +1,80 @@
1
+ # svc-infra
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/svc-infra.svg)](https://pypi.org/project/svc-infra/)
4
+ [![Docs](https://img.shields.io/badge/docs-reference-blue)](.)
5
+
6
+ svc-infra packages the shared building blocks we use to ship production FastAPI services fast—HTTP APIs with secure auth, durable persistence, background execution, cache, observability, and webhook plumbing that all share the same batteries-included defaults.
7
+
8
+ ## Helper index
9
+
10
+ | Area | What it covers | Guide |
11
+ | --- | --- | --- |
12
+ | Getting Started | Overview and entry points | [This page](src/svc_infra/docs/getting-started.md) |
13
+ | Environment | Feature switches and env vars | [Environment](src/svc_infra/docs/environment.md) |
14
+ | API | FastAPI bootstrap, middleware, docs wiring | [API guide](src/svc_infra/docs/api.md) |
15
+ | Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth](src/svc_infra/docs/auth.md) |
16
+ | Security | Password policy, lockout, signed cookies, headers | [Security](src/svc_infra/docs/security.md) |
17
+ | Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database](src/svc_infra/docs/database.md) |
18
+ | Storage | File storage with S3, local, memory backends | [Storage](src/svc_infra/docs/storage.md) |
19
+ | Documents | Generic document management with metadata | [Documents](src/svc_infra/docs/documents.md) |
20
+ | Tenancy | Multi-tenant boundaries and helpers | [Tenancy](src/svc_infra/docs/tenancy.md) |
21
+ | Idempotency | Idempotent endpoints and middleware | [Idempotency](src/svc_infra/docs/idempotency.md) |
22
+ | Rate Limiting | Middleware, dependency limiter, headers | [Rate limiting](src/svc_infra/docs/rate-limiting.md) |
23
+ | Cache | cashews decorators, namespace management, TTL helpers | [Cache](src/svc_infra/docs/cache.md) |
24
+ | Jobs | JobQueue, scheduler, CLI worker | [Jobs](src/svc_infra/docs/jobs.md) |
25
+ | Observability | Prometheus, Grafana, OpenTelemetry | [Observability](src/svc_infra/docs/observability.md) |
26
+ | Ops | Probes, breakers, SLOs & dashboards | [Ops](src/svc_infra/docs/ops.md) |
27
+ | Webhooks | Subscription store, signing, retry worker | [Webhooks](src/svc_infra/docs/webhooks.md) |
28
+ | CLI | Command groups for sql/mongo/obs/docs/dx/sdk/jobs | [CLI](src/svc_infra/docs/cli.md) |
29
+ | Docs & SDKs | Publishing docs, generating SDKs | [Docs & SDKs](src/svc_infra/docs/docs-and-sdks.md) |
30
+ | Acceptance | Acceptance harness and flows | [Acceptance](src/svc_infra/docs/acceptance.md), [Matrix](src/svc_infra/docs/acceptance-matrix.md) |
31
+ | Contributing | Dev setup and quality gates | [Contributing](src/svc_infra/docs/contributing.md) |
32
+ | Repo Review | Checklist for releasing/PRs | [Repo review](src/svc_infra/docs/repo-review.md) |
33
+ | Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](src/svc_infra/docs/data-lifecycle.md) |
34
+
35
+ ## Quick Start with Template Example
36
+
37
+ See **ALL** svc-infra features working together in a complete example:
38
+
39
+ ```bash
40
+ # One-time setup (from repo root)
41
+ make setup-template # Scaffolds models, runs migrations
42
+
43
+ # Run the example server
44
+ make run-template # Starts at http://localhost:8001
45
+ ```
46
+
47
+ See [`examples/README.md`](examples/README.md) for full documentation and manual setup options.
48
+
49
+ ## Minimal FastAPI bootstrap
50
+
51
+ ```python
52
+ from fastapi import Depends
53
+ from svc_infra.api.fastapi.ease import easy_service_app
54
+ from svc_infra.api.fastapi.db.sql.add import add_sql_db
55
+ from svc_infra.cache import init_cache
56
+ from svc_infra.jobs.easy import easy_jobs
57
+ from svc_infra.webhooks.fastapi import require_signature
58
+
59
+ app = easy_service_app(name="Billing", release="1.2.3")
60
+ add_sql_db(app) # reads SQL_URL / DB_* envs
61
+ init_cache() # honors CACHE_PREFIX / CACHE_VERSION
62
+ queue, scheduler = easy_jobs() # switches via JOBS_DRIVER / REDIS_URL
63
+
64
+ @app.post("/webhooks/billing")
65
+ async def handle_webhook(payload = Depends(require_signature(lambda: ["current", "next"]))):
66
+ queue.enqueue("process-billing-webhook", payload)
67
+ return {"status": "queued"}
68
+ ```
69
+
70
+ ## Environment switches
71
+
72
+ - **API** – toggle logging/observability and docs exposure with `ENABLE_LOGGING`, `LOG_LEVEL`, `LOG_FORMAT`, `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and `CORS_ALLOW_ORIGINS`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/api/fastapi/setup.py†L47-L88】
73
+ - **Auth** – configure JWT secrets, SMTP, cookies, and policy using the `AUTH_…` settings family (e.g., `AUTH_JWT__SECRET`, `AUTH_SMTP_HOST`, `AUTH_SESSION_COOKIE_SECURE`). 【F:src/svc_infra/api/fastapi/auth/settings.py†L23-L91】
74
+ - **Database** – set connection URLs or components via `SQL_URL`/`SQL_URL_FILE`, `DB_DIALECT`, `DB_HOST`, `DB_USER`, `DB_PASSWORD`, plus Mongo knobs like `MONGO_URL`, `MONGO_DB`, and `MONGO_URL_FILE`. 【F:src/svc_infra/api/fastapi/db/sql/add.py†L55-L114】【F:src/svc_infra/db/sql/utils.py†L85-L206】【F:src/svc_infra/db/nosql/mongo/settings.py†L9-L13】【F:src/svc_infra/db/nosql/utils.py†L56-L113】
75
+ - **Storage** – choose backend with `STORAGE_BACKEND` (local, s3, memory) and configure with `STORAGE_S3_BUCKET`, `STORAGE_S3_REGION`, `STORAGE_BASE_PATH`, or auto-detect from `RAILWAY_VOLUME_MOUNT_PATH` / AWS credentials. 【F:src/svc_infra/storage/settings.py】【F:src/svc_infra/docs/storage.md】
76
+ - **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:src/svc_infra/docs/jobs.md†L11-L48】
77
+ - **Cache** – namespace keys and lifetimes through `CACHE_PREFIX`, `CACHE_VERSION`, and TTL overrides `CACHE_TTL_DEFAULT`, `CACHE_TTL_SHORT`, `CACHE_TTL_LONG`. 【F:src/svc_infra/cache/README.md†L20-L173】【F:src/svc_infra/cache/ttl.py†L26-L55】
78
+ - **Observability** – turn metrics on/off or adjust scrape paths with `ENABLE_OBS`, `METRICS_PATH`, `OBS_SKIP_PATHS`, and Prometheus/Grafana flags like `SVC_INFRA_DISABLE_PROMETHEUS`, `SVC_INFRA_RATE_WINDOW`, `SVC_INFRA_DASHBOARD_REFRESH`, `SVC_INFRA_DASHBOARD_RANGE`. 【F:src/svc_infra/api/fastapi/ease.py†L67-L111】【F:src/svc_infra/obs/metrics/asgi.py†L49-L206】【F:src/svc_infra/obs/cloud_dash.py†L85-L108】
79
+ - **Webhooks** – reuse the jobs envs (`JOBS_DRIVER`, `REDIS_URL`) for the delivery worker and queue configuration. 【F:src/svc_infra/docs/webhooks.md†L32-L53】
80
+ - **Security** – enforce password policy, MFA, and rotation with auth prefixes such as `AUTH_PASSWORD_MIN_LENGTH`, `AUTH_PASSWORD_REQUIRE_SYMBOL`, `AUTH_JWT__SECRET`, and `AUTH_JWT__OLD_SECRETS`. 【F:src/svc_infra/docs/security.md†L24-L70】
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "svc-infra"
3
- version = "0.1.618"
3
+ version = "0.1.672"
4
4
  description = "Infrastructure for building and deploying prod-ready services"
5
5
  authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
6
6
  license = "MIT"
@@ -13,7 +13,7 @@ include = [
13
13
  "src/svc_infra/obs/templates/**/*",
14
14
  "src/svc_infra/obs/grafana/templates/**/*",
15
15
  "src/svc_infra/obs/grafana/dashboards/**/*",
16
- "docs/**/*"
16
+ "src/svc_infra/docs/**/*"
17
17
  ]
18
18
 
19
19
  classifiers = [
@@ -68,6 +68,10 @@ redshift-connector = { version = "^2.0.918", optional = true }
68
68
  duckdb = { version = "^1.1.3", optional = true }
69
69
  # (Removed hard psycopg2)
70
70
 
71
+ # Storage
72
+ aiofiles = "^24.1.0"
73
+ aioboto3 = { version = "^13.0.0", optional = true }
74
+
71
75
  # Other infra deps
72
76
  mcp = "^1.13.0"
73
77
  ai-infra = "^0.1.63"
@@ -102,6 +106,7 @@ snowflake = ["snowflake-connector-python"]
102
106
  redshift = ["redshift-connector"]
103
107
  duckdb = ["duckdb"]
104
108
  metrics = ["prometheus-client"]
109
+ s3 = ["aioboto3"]
105
110
 
106
111
  [tool.poetry.group.dev.dependencies]
107
112
  pytest = "^8.3.2"
@@ -112,6 +117,8 @@ types-requests = "^2.32.0.20241016"
112
117
  pytest-mock = "^3.15.1"
113
118
  httpx = "^0.28.1"
114
119
  fakeredis = "^2.27.0"
120
+ moto = {extras = ["s3"], version = "^5.0.0"}
121
+ aiosqlite = "^0.20.0" # Required for acceptance tests using SQLite
115
122
 
116
123
  [tool.poetry.scripts]
117
124
  svc-infra = "svc_infra.cli:main"
@@ -122,15 +129,20 @@ python_files = ["test_*.py", "*_test.py"]
122
129
  python_classes = ["Test*",]
123
130
  python_functions = ["test_*"]
124
131
  markers = [
132
+ "acceptance: End-to-end acceptance tests running against the acceptance app or BASE_URL",
133
+ "unit: Unit tests",
125
134
  "security: Security and auth hardening tests",
126
135
  "ratelimit: Rate limiting and abuse protection tests",
127
136
  "concurrency: Idempotency and concurrency control tests",
128
137
  "jobs: Background jobs and scheduling tests",
129
138
  "webhooks: Webhooks framework tests",
139
+ "billing: Billing primitives tests",
130
140
  "tenancy: Tenancy isolation and enforcement tests",
131
141
  "data_lifecycle: Data lifecycle (fixtures, retention, erasure, backups)",
132
142
  "ops: SLOs & Ops tests (probes, breaker, instrumentation)",
133
143
  "dx: Developer experience and quality gates tests",
144
+ "admin: Admin scope and impersonation tests",
145
+ "storage: File storage system tests",
134
146
  ]
135
147
  filterwarnings = [
136
148
  "ignore:The `route` decorator is deprecated:DeprecationWarning:starlette.*",
@@ -0,0 +1,3 @@
1
+ from .add import add_admin, admin_router
2
+
3
+ __all__ = ["add_admin", "admin_router"]
@@ -0,0 +1,231 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import hmac
5
+ import inspect
6
+ import json
7
+ import logging
8
+ import os
9
+ import time
10
+ from hashlib import sha256
11
+ from types import SimpleNamespace
12
+ from typing import Any, Callable, Optional
13
+
14
+ from fastapi import APIRouter, Depends, HTTPException, Request, Response
15
+
16
+ from ....app.env import get_current_environment
17
+ from ....security.permissions import RequirePermission
18
+ from ..auth.security import Identity, Principal, _current_principal
19
+ from ..auth.state import get_auth_state
20
+ from ..db.sql.session import SqlSessionDep
21
+ from ..dual.protected import roles_router
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def _b64u(data: bytes) -> str:
27
+ return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
28
+
29
+
30
+ def _b64u_decode(s: str) -> bytes:
31
+ pad = "=" * ((4 - len(s) % 4) % 4)
32
+ return base64.urlsafe_b64decode(s + pad)
33
+
34
+
35
+ def _sign(payload: dict, *, secret: str) -> str:
36
+ body = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("utf-8")
37
+ sig = hmac.new(secret.encode("utf-8"), body, sha256).digest()
38
+ return _b64u(body) + "." + _b64u(sig)
39
+
40
+
41
+ def _verify(token: str, *, secret: str) -> dict:
42
+ try:
43
+ b64_body, b64_sig = token.split(".", 1)
44
+ body = _b64u_decode(b64_body)
45
+ exp_sig = _b64u_decode(b64_sig)
46
+ got_sig = hmac.new(secret.encode("utf-8"), body, sha256).digest()
47
+ if not hmac.compare_digest(exp_sig, got_sig):
48
+ raise ValueError("bad_signature")
49
+ payload = json.loads(body)
50
+ if int(payload.get("exp", 0)) < int(time.time()):
51
+ raise ValueError("expired")
52
+ return payload
53
+ except Exception as e:
54
+ raise ValueError("invalid_token") from e
55
+
56
+
57
+ def admin_router(*, dependencies: Optional[list[Any]] = None, **kwargs) -> APIRouter:
58
+ """Role-gated admin router for coarse access control.
59
+
60
+ Use permission guards inside endpoints for fine-grained control.
61
+ """
62
+
63
+ return roles_router("admin", **kwargs)
64
+
65
+
66
+ def add_admin(
67
+ app,
68
+ *,
69
+ base_path: str = "/admin",
70
+ enable_impersonation: bool = True,
71
+ secret: Optional[str] = None,
72
+ ttl_seconds: int = 15 * 60,
73
+ cookie_name: str = "impersonation",
74
+ impersonation_user_getter: Optional[Callable[[Any, str], Any]] = None,
75
+ ) -> None:
76
+ """Wire admin surfaces with sensible defaults.
77
+
78
+ - Mounts an admin router under base_path.
79
+ - Optionally enables impersonation start/stop endpoints guarded by permissions.
80
+ - Registers a dependency override to honor impersonation cookie globally (idempotent).
81
+
82
+ impersonation_user_getter: optional callable (request, user_id) -> user object.
83
+ If omitted, defaults to loading from SQLAlchemy User model returned by get_auth_state().
84
+ """
85
+
86
+ # Idempotency: only mount once per app instance
87
+ if getattr(app.state, "_admin_added", False):
88
+ return
89
+
90
+ env = get_current_environment()
91
+ _secret = (
92
+ secret or os.getenv("ADMIN_IMPERSONATION_SECRET") or os.getenv("APP_SECRET") or "dev-secret"
93
+ )
94
+ _ttl = int(os.getenv("ADMIN_IMPERSONATION_TTL", str(ttl_seconds)))
95
+ _cookie = os.getenv("ADMIN_IMPERSONATION_COOKIE", cookie_name)
96
+
97
+ r = admin_router(prefix=base_path, tags=["admin"]) # role-gated
98
+
99
+ async def _default_user_getter(request: Request, user_id: str, session: SqlSessionDep):
100
+ try:
101
+ UserModel, _, _ = get_auth_state()
102
+ except Exception:
103
+ # Fallback: simple shim if auth state not configured
104
+ return SimpleNamespace(id=user_id)
105
+ obj = await session.get(UserModel, user_id)
106
+ if not obj:
107
+ raise HTTPException(404, "user_not_found")
108
+ return obj
109
+
110
+ user_getter = impersonation_user_getter
111
+
112
+ @r.post(
113
+ "/impersonate/start", status_code=204, dependencies=[RequirePermission("admin.impersonate")]
114
+ )
115
+ async def start_impersonation(
116
+ body: dict, request: Request, response: Response, session: SqlSessionDep, identity: Identity
117
+ ):
118
+ target_id = (body or {}).get("user_id")
119
+ reason = (body or {}).get("reason", "")
120
+ if not target_id:
121
+ raise HTTPException(422, "user_id_required")
122
+ # Load target for validation (custom getter or default)
123
+ _res = (
124
+ user_getter(request, target_id)
125
+ if user_getter
126
+ else _default_user_getter(request, target_id, session)
127
+ )
128
+ target = await _res if inspect.isawaitable(_res) else _res
129
+ actor: Principal = identity
130
+ payload = {
131
+ "actor_id": getattr(getattr(actor, "user", None), "id", None),
132
+ "target_id": str(getattr(target, "id", target_id)),
133
+ "iat": int(time.time()),
134
+ "exp": int(time.time()) + _ttl,
135
+ "nonce": _b64u(os.urandom(8)),
136
+ }
137
+ token = _sign(payload, secret=_secret)
138
+ response.set_cookie(
139
+ key=_cookie,
140
+ value=token,
141
+ httponly=True,
142
+ samesite="lax",
143
+ secure=(env in ("prod", "production")),
144
+ path="/",
145
+ max_age=_ttl,
146
+ )
147
+ logger.info(
148
+ "admin.impersonation.started",
149
+ extra={
150
+ "actor_id": payload["actor_id"],
151
+ "target_id": payload["target_id"],
152
+ "reason": reason,
153
+ "expires_in": _ttl,
154
+ },
155
+ )
156
+ # Re-compose override now to wrap any late overrides set by tests/harness
157
+ try:
158
+ _compose_override()
159
+ except Exception:
160
+ pass
161
+
162
+ @r.post("/impersonate/stop", status_code=204)
163
+ async def stop_impersonation(response: Response):
164
+ response.delete_cookie(_cookie, path="/")
165
+ logger.info("admin.impersonation.stopped")
166
+
167
+ app.include_router(r)
168
+
169
+ # Dependency override: wrap the base principal to honor impersonation cookie.
170
+ # Compose with any existing override (e.g., acceptance app/test harness) and
171
+ # re-compose at startup to capture late overrides.
172
+ def _compose_override():
173
+ existing = app.dependency_overrides.get(_current_principal)
174
+ if existing and getattr(existing, "_is_admin_impersonation_override", False):
175
+ dep_provider = getattr(existing, "_admin_impersonation_base", _current_principal)
176
+ else:
177
+ dep_provider = existing or _current_principal
178
+
179
+ async def _override_current_principal(
180
+ base: Principal = Depends(dep_provider),
181
+ request: Request = None,
182
+ session: SqlSessionDep = None,
183
+ ) -> Principal:
184
+ token = request.cookies.get(_cookie) if request else None
185
+ if not token:
186
+ return base
187
+ try:
188
+ payload = _verify(token, secret=_secret)
189
+ except Exception:
190
+ return base
191
+ # Load target user
192
+ target_id = payload.get("target_id")
193
+ if not target_id:
194
+ return base
195
+ # Preserve actor roles/claims so permissions remain that of the actor
196
+ actor_user = getattr(base, "user", None)
197
+ actor_roles = getattr(actor_user, "roles", []) or []
198
+ _res = (
199
+ user_getter(request, target_id)
200
+ if user_getter
201
+ else _default_user_getter(request, target_id, session)
202
+ )
203
+ target = await _res if inspect.isawaitable(_res) else _res
204
+ # Swap user but keep actor for audit if needed
205
+ setattr(base, "actor", getattr(base, "user", None))
206
+ # If target lacks roles, inherit actor roles to maintain permission checks
207
+ try:
208
+ if not getattr(target, "roles", None):
209
+ setattr(target, "roles", actor_roles)
210
+ except Exception:
211
+ # Best-effort; if target object is immutable, fallback by wrapping
212
+ target = SimpleNamespace(id=getattr(target, "id", target_id), roles=actor_roles)
213
+ base.user = target
214
+ base.via = "impersonated"
215
+ return base
216
+
217
+ app.dependency_overrides[_current_principal] = _override_current_principal
218
+ _override_current_principal._is_admin_impersonation_override = True # type: ignore[attr-defined]
219
+ _override_current_principal._admin_impersonation_base = dep_provider # type: ignore[attr-defined]
220
+
221
+ # Compose now (best-effort) and again on startup to wrap any later overrides
222
+ _compose_override()
223
+ try:
224
+ app.add_event_handler("startup", _compose_override)
225
+ except Exception:
226
+ # Best-effort; if app doesn't support event handlers, we already composed once
227
+ pass
228
+ app.state._admin_added = True
229
+
230
+
231
+ # no extra helpers
@@ -7,7 +7,6 @@ from fastapi import FastAPI
7
7
 
8
8
  from svc_infra.apf_payments.provider.registry import get_provider_registry
9
9
  from svc_infra.api.fastapi.apf_payments.router import build_payments_routers
10
- from svc_infra.api.fastapi.docs.scoped import add_prefixed_docs
11
10
 
12
11
  logger = logging.getLogger(__name__)
13
12
 
@@ -51,7 +50,6 @@ def add_payments(
51
50
  - Reuses your OpenAPI defaults (security + responses) via DualAPIRouter factories.
52
51
  """
53
52
  _maybe_register_default_providers(register_default_providers, adapters)
54
- add_prefixed_docs(app, prefix=prefix, title="Payments")
55
53
 
56
54
  for r in build_payments_routers(prefix=prefix):
57
55
  app.include_router(
@@ -17,7 +17,6 @@ from svc_infra.api.fastapi.paths.prefix import AUTH_PREFIX, USER_PREFIX
17
17
  from svc_infra.app.env import CURRENT_ENVIRONMENT, DEV_ENV, LOCAL_ENV
18
18
  from svc_infra.db.sql.apikey import bind_apikey_model
19
19
 
20
- from ..docs.scoped import add_prefixed_docs
21
20
  from .policy import AuthPolicy, DefaultAuthPolicy
22
21
  from .providers import providers_from_settings
23
22
  from .settings import get_auth_settings
@@ -293,9 +292,6 @@ def add_auth_users(
293
292
  https_only=bool(getattr(settings_obj, "session_cookie_secure", False)),
294
293
  )
295
294
 
296
- add_prefixed_docs(app, prefix=user_prefix, title="Users")
297
- add_prefixed_docs(app, prefix=auth_prefix, title="Auth")
298
-
299
295
  if enable_password:
300
296
  setup_password_authentication(
301
297
  app,
@@ -373,9 +373,8 @@ async def _update_provider_account(
373
373
  def _determine_final_redirect_url(request: Request, provider: str, post_login_redirect: str) -> str:
374
374
  """Determine the final redirect URL after successful authentication."""
375
375
  st = get_auth_settings()
376
- redirect_url = str(
377
- getattr(st, "post_login_redirect", post_login_redirect) or post_login_redirect
378
- )
376
+ # Prioritize the parameter passed to the router over settings
377
+ redirect_url = str(post_login_redirect or getattr(st, "post_login_redirect", "/"))
379
378
  allow_hosts = parse_redirect_allow_hosts(getattr(st, "redirect_allow_hosts_raw", None))
380
379
  require_https = bool(getattr(st, "session_cookie_secure", False))
381
380
 
@@ -669,7 +668,23 @@ def _create_oauth_router(
669
668
  ip_hash=None,
670
669
  )
671
670
 
672
- # Create response with auth + refresh cookies
671
+ # Generate JWT token for the response
672
+ strategy = auth_backend.get_strategy()
673
+ jwt_token = await strategy.write_token(user)
674
+
675
+ # If redirecting to a different origin, append token as URL fragment for frontend to extract
676
+ # This handles cross-port scenarios like localhost:8000 -> localhost:3000
677
+ parsed_redirect = urlparse(redirect_url)
678
+ request_origin = f"{request.url.scheme}://{request.url.netloc}"
679
+ redirect_origin = f"{parsed_redirect.scheme}://{parsed_redirect.netloc}"
680
+
681
+ if redirect_origin and redirect_origin != request_origin:
682
+ # Cross-origin redirect: append token as URL fragment
683
+ # Fragment is not sent to server, only accessible to client-side JS
684
+ separator = "#" if not parsed_redirect.fragment else "&"
685
+ redirect_url = f"{redirect_url}{separator}access_token={jwt_token}"
686
+
687
+ # Create response with auth + refresh cookies (for same-origin requests)
673
688
  resp = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND)
674
689
  await _set_cookie_on_response(resp, auth_backend, user, refresh_raw=raw_refresh)
675
690
 
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Annotated, Optional
5
+
6
+ from fastapi import APIRouter, Depends, Response, status
7
+
8
+ from svc_infra.api.fastapi.db.sql.session import SqlSessionDep
9
+ from svc_infra.api.fastapi.middleware.idempotency import require_idempotency_key
10
+ from svc_infra.api.fastapi.tenancy.context import TenantId
11
+ from svc_infra.billing.async_service import AsyncBillingService
12
+ from svc_infra.billing.schemas import UsageAckOut, UsageAggregateRow, UsageAggregatesOut, UsageIn
13
+
14
+ router = APIRouter(prefix="/_billing", tags=["Billing"])
15
+
16
+
17
+ def get_service(tenant_id: TenantId, session: SqlSessionDep) -> AsyncBillingService:
18
+ return AsyncBillingService(session=session, tenant_id=tenant_id)
19
+
20
+
21
+ @router.post(
22
+ "/usage",
23
+ name="billing_record_usage",
24
+ status_code=status.HTTP_202_ACCEPTED,
25
+ response_model=UsageAckOut,
26
+ dependencies=[Depends(require_idempotency_key)],
27
+ )
28
+ async def record_usage(
29
+ data: UsageIn, svc: Annotated[AsyncBillingService, Depends(get_service)], response: Response
30
+ ):
31
+ at = data.at or datetime.now(tz=timezone.utc)
32
+ evt_id = await svc.record_usage(
33
+ metric=data.metric,
34
+ amount=int(data.amount),
35
+ at=at,
36
+ idempotency_key=data.idempotency_key,
37
+ metadata=data.metadata,
38
+ )
39
+ # For 202, no Location header is required, but we can surface the id in the body
40
+ return UsageAckOut(id=evt_id, accepted=True)
41
+
42
+
43
+ @router.get(
44
+ "/usage",
45
+ name="billing_list_aggregates",
46
+ response_model=UsageAggregatesOut,
47
+ )
48
+ async def list_aggregates(
49
+ metric: str,
50
+ date_from: Optional[datetime] = None,
51
+ date_to: Optional[datetime] = None,
52
+ svc: Annotated[AsyncBillingService, Depends(get_service)] = None,
53
+ ):
54
+ rows = await svc.list_daily_aggregates(metric=metric, date_from=date_from, date_to=date_to)
55
+ items = [
56
+ UsageAggregateRow(
57
+ period_start=r.period_start,
58
+ granularity=r.granularity,
59
+ metric=r.metric,
60
+ total=int(r.total),
61
+ )
62
+ for r in rows
63
+ ]
64
+ return UsageAggregatesOut(items=items, next_cursor=None)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import FastAPI
4
+
5
+ from .router import router as billing_router
6
+
7
+
8
+ def add_billing(app: FastAPI, *, prefix: str = "/_billing") -> None:
9
+ # Mount under the chosen prefix; default is /_billing
10
+ if prefix and prefix != "/_billing":
11
+ # If a custom prefix is desired, clone router with new prefix
12
+ from fastapi import APIRouter
13
+
14
+ custom = APIRouter(prefix=prefix, tags=["Billing"])
15
+ for route in billing_router.routes:
16
+ custom.routes.append(route)
17
+ app.include_router(custom)
18
+ else:
19
+ app.include_router(billing_router)