svc-infra 0.1.620__tar.gz → 0.1.621__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 (330) hide show
  1. {svc_infra-0.1.620 → svc_infra-0.1.621}/PKG-INFO +1 -1
  2. {svc_infra-0.1.620 → svc_infra-0.1.621}/pyproject.toml +1 -1
  3. svc_infra-0.1.621/src/svc_infra/cli/cmds/docs/docs_cmds.py +266 -0
  4. svc_infra-0.1.620/src/svc_infra/cli/cmds/docs/docs_cmds.py +0 -213
  5. {svc_infra-0.1.620 → svc_infra-0.1.621}/README.md +0 -0
  6. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/acceptance-matrix.md +0 -0
  7. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/acceptance.md +0 -0
  8. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0002-background-jobs-and-scheduling.md +0 -0
  9. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0003-webhooks-framework.md +0 -0
  10. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0004-tenancy-model.md +0 -0
  11. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0005-data-lifecycle.md +0 -0
  12. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0006-ops-slos-and-metrics.md +0 -0
  13. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0007-docs-and-sdks.md +0 -0
  14. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0008-billing-primitives.md +0 -0
  15. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/adr/0009-acceptance-harness.md +0 -0
  16. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/api.md +0 -0
  17. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/auth.md +0 -0
  18. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/cache.md +0 -0
  19. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/cli.md +0 -0
  20. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/contributing.md +0 -0
  21. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/data-lifecycle.md +0 -0
  22. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/database.md +0 -0
  23. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/docs-and-sdks.md +0 -0
  24. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/environment.md +0 -0
  25. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/idempotency.md +0 -0
  26. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/jobs.md +0 -0
  27. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/observability.md +0 -0
  28. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/ops.md +0 -0
  29. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/rate-limiting.md +0 -0
  30. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/repo-review.md +0 -0
  31. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/security.md +0 -0
  32. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/tenancy.md +0 -0
  33. {svc_infra-0.1.620 → svc_infra-0.1.621}/docs/webhooks.md +0 -0
  34. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/__init__.py +0 -0
  35. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/README.md +0 -0
  36. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/__init__.py +0 -0
  37. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/alembic.py +0 -0
  38. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/models.py +0 -0
  39. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
  40. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
  41. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/provider/base.py +0 -0
  42. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/provider/registry.py +0 -0
  43. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
  44. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/schemas.py +0 -0
  45. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/service.py +0 -0
  46. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/apf_payments/settings.py +0 -0
  47. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/__init__.py +0 -0
  48. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/__init__.py +0 -0
  49. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
  50. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
  51. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -0
  52. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
  53. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
  54. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/add.py +0 -0
  55. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
  56. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
  57. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
  58. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
  59. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
  60. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
  61. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
  62. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
  63. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
  64. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
  65. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
  66. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
  67. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
  68. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +0 -0
  69. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
  70. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/security.py +0 -0
  71. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
  72. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
  73. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/auth/state.py +0 -0
  74. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
  75. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/cache/add.py +0 -0
  76. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
  77. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/http.py +0 -0
  78. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
  79. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
  80. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +0 -0
  81. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
  82. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
  83. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
  84. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
  85. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/add.py +0 -0
  86. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/crud_router.py +0 -0
  87. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
  88. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/session.py +0 -0
  89. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
  90. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
  91. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
  92. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/docs/add.py +0 -0
  93. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/docs/landing.py +0 -0
  94. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/docs/scoped.py +0 -0
  95. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
  96. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
  97. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
  98. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/public.py +0 -0
  99. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/router.py +0 -0
  100. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
  101. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/dx.py +0 -0
  102. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/ease.py +0 -0
  103. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
  104. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
  105. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
  106. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
  107. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
  108. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
  109. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
  110. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
  111. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
  112. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +0 -0
  113. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/idempotency.py +0 -0
  114. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
  115. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
  116. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/ratelimit.py +0 -0
  117. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +0 -0
  118. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/request_id.py +0 -0
  119. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
  120. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
  121. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
  122. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
  123. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
  124. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/mutators.py +0 -0
  125. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
  126. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
  127. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
  128. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/ops/add.py +0 -0
  129. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/pagination.py +0 -0
  130. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
  131. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
  132. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
  133. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
  134. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/paths/user.py +0 -0
  135. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
  136. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
  137. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/setup.py +0 -0
  138. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/tenancy/add.py +0 -0
  139. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/api/fastapi/tenancy/context.py +0 -0
  140. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/README.md +0 -0
  141. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/__init__.py +0 -0
  142. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/env.py +0 -0
  143. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/logging/__init__.py +0 -0
  144. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/logging/add.py +0 -0
  145. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/logging/filter.py +0 -0
  146. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/logging/formats.py +0 -0
  147. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/app/root.py +0 -0
  148. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/billing/__init__.py +0 -0
  149. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/billing/models.py +0 -0
  150. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/billing/service.py +0 -0
  151. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/bundled_docs/README.md +0 -0
  152. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/bundled_docs/__init__.py +0 -0
  153. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/bundled_docs/getting-started.md +0 -0
  154. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/README.md +0 -0
  155. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/__init__.py +0 -0
  156. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/backend.py +0 -0
  157. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/decorators.py +0 -0
  158. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/demo.py +0 -0
  159. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/keys.py +0 -0
  160. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/recache.py +0 -0
  161. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/resources.py +0 -0
  162. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/tags.py +0 -0
  163. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/ttl.py +0 -0
  164. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cache/utils.py +0 -0
  165. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/__init__.py +0 -0
  166. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/__main__.py +0 -0
  167. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/__init__.py +0 -0
  168. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
  169. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
  170. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
  171. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
  172. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +0 -0
  173. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +0 -0
  174. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
  175. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +0 -0
  176. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/sql/sql_export_cmds.py +0 -0
  177. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +0 -0
  178. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/dx/__init__.py +0 -0
  179. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/dx/dx_cmds.py +0 -0
  180. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/help.py +0 -0
  181. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
  182. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
  183. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
  184. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/obs/obs_cmds.py +0 -0
  185. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/sdk/__init__.py +0 -0
  186. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/cmds/sdk/sdk_cmds.py +0 -0
  187. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/foundation/__init__.py +0 -0
  188. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/foundation/runner.py +0 -0
  189. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
  190. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/data/add.py +0 -0
  191. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/data/backup.py +0 -0
  192. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/data/erasure.py +0 -0
  193. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/data/fixtures.py +0 -0
  194. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/data/retention.py +0 -0
  195. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/__init__.py +0 -0
  196. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/crud_schema.py +0 -0
  197. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/inbox.py +0 -0
  198. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/__init__.py +0 -0
  199. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/base.py +0 -0
  200. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/constants.py +0 -0
  201. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/core.py +0 -0
  202. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/indexes.py +0 -0
  203. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/management.py +0 -0
  204. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/README.md +0 -0
  205. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
  206. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/client.py +0 -0
  207. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
  208. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
  209. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
  210. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
  211. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
  212. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/repository.py +0 -0
  213. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/resource.py +0 -0
  214. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/scaffold.py +0 -0
  215. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/service.py +0 -0
  216. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
  217. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/types.py +0 -0
  218. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/nosql/utils.py +0 -0
  219. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/outbox.py +0 -0
  220. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/README.md +0 -0
  221. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/__init__.py +0 -0
  222. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/apikey.py +0 -0
  223. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/authref.py +0 -0
  224. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/base.py +0 -0
  225. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/constants.py +0 -0
  226. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/core.py +0 -0
  227. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/management.py +0 -0
  228. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/repository.py +0 -0
  229. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/resource.py +0 -0
  230. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/scaffold.py +0 -0
  231. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/service.py +0 -0
  232. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
  233. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/__init__.py +0 -0
  234. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
  235. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +0 -0
  236. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
  237. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
  238. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
  239. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
  240. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
  241. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +0 -0
  242. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +0 -0
  243. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
  244. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/tenant.py +0 -0
  245. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/types.py +0 -0
  246. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/uniq.py +0 -0
  247. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
  248. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/utils.py +0 -0
  249. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/sql/versioning.py +0 -0
  250. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/db/utils.py +0 -0
  251. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/dx/add.py +0 -0
  252. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/dx/changelog.py +0 -0
  253. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/dx/checks.py +0 -0
  254. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
  255. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/builtins/webhook_delivery.py +0 -0
  256. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/easy.py +0 -0
  257. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/loader.py +0 -0
  258. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/queue.py +0 -0
  259. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/redis_queue.py +0 -0
  260. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/scheduler.py +0 -0
  261. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/jobs/worker.py +0 -0
  262. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/mcp/__init__.py +0 -0
  263. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
  264. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/README.md +0 -0
  265. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/__init__.py +0 -0
  266. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/add.py +0 -0
  267. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/cloud_dash.py +0 -0
  268. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/grafana/dashboards/http-overview.json +0 -0
  269. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics/__init__.py +0 -0
  270. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics/asgi.py +0 -0
  271. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics/base.py +0 -0
  272. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics/http.py +0 -0
  273. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
  274. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/metrics.py +0 -0
  275. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/__init__.py +0 -0
  276. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
  277. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
  278. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
  279. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/compose_cloud/templates/docker-compose.cloud.yml.tmpl +0 -0
  280. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
  281. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
  282. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
  283. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
  284. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
  285. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
  286. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
  287. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
  288. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
  289. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
  290. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
  291. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
  292. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
  293. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/settings.py +0 -0
  294. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
  295. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
  296. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
  297. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
  298. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
  299. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
  300. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
  301. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
  302. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
  303. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
  304. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
  305. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
  306. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
  307. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
  308. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
  309. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
  310. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/py.typed +0 -0
  311. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/add.py +0 -0
  312. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/audit.py +0 -0
  313. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/audit_service.py +0 -0
  314. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/headers.py +0 -0
  315. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/hibp.py +0 -0
  316. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/jwt_rotation.py +0 -0
  317. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/lockout.py +0 -0
  318. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/models.py +0 -0
  319. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/org_invites.py +0 -0
  320. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/passwords.py +0 -0
  321. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/permissions.py +0 -0
  322. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/session.py +0 -0
  323. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/security/signed_cookies.py +0 -0
  324. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/utils.py +0 -0
  325. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/webhooks/__init__.py +0 -0
  326. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/webhooks/add.py +0 -0
  327. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/webhooks/fastapi.py +0 -0
  328. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/webhooks/router.py +0 -0
  329. {svc_infra-0.1.620 → svc_infra-0.1.621}/src/svc_infra/webhooks/service.py +0 -0
  330. {svc_infra-0.1.620 → svc_infra-0.1.621}/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.620
3
+ Version: 0.1.621
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "svc-infra"
3
- version = "0.1.620"
3
+ version = "0.1.621"
4
4
  description = "Infrastructure for building and deploying prod-ready services"
5
5
  authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
6
6
  license = "MIT"
@@ -0,0 +1,266 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+ import os
5
+ import sys
6
+ from importlib.metadata import PackageNotFoundError, distribution
7
+ from pathlib import Path
8
+ from typing import Dict, List
9
+
10
+ import click
11
+ import typer
12
+ from typer.core import TyperGroup
13
+
14
+
15
+ def _norm(name: str) -> str:
16
+ """Normalize a topic name for stable CLI commands.
17
+
18
+ - Lowercase
19
+ - Replace spaces and underscores with hyphens
20
+ - Strip leading/trailing whitespace
21
+ """
22
+ return name.strip().lower().replace(" ", "-").replace("_", "-")
23
+
24
+
25
+ def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
26
+ topics: Dict[str, Path] = {}
27
+ if docs_dir.exists() and docs_dir.is_dir():
28
+ for p in sorted(docs_dir.glob("*.md")):
29
+ if p.is_file():
30
+ topics[_norm(p.stem)] = p
31
+ return topics
32
+
33
+
34
+ def _discover_pkg_topics() -> Dict[str, Path]:
35
+ """Discover docs packaged under 'docs/' in the installed distribution.
36
+
37
+ Works in external projects without a local docs/ by inspecting the wheel
38
+ metadata and, as a fallback, searching for a top-level docs/ next to the
39
+ installed package directory in site-packages.
40
+ """
41
+ topics: Dict[str, Path] = {}
42
+
43
+ # 1) Prefer distribution metadata (RECORD) for both hyphen/underscore names
44
+ dist = None
45
+ for name in ("svc-infra", "svc_infra"):
46
+ try:
47
+ dist = distribution(name)
48
+ break
49
+ except PackageNotFoundError:
50
+ dist = None
51
+
52
+ if dist is not None:
53
+ files = getattr(dist, "files", None) or []
54
+ for f in files:
55
+ s = str(f)
56
+ if not s.startswith("docs/") or not s.endswith(".md"):
57
+ continue
58
+ topic_name = _norm(Path(s).stem)
59
+ try:
60
+ abs_path = Path(dist.locate_file(f))
61
+ if abs_path.exists() and abs_path.is_file():
62
+ topics[topic_name] = abs_path
63
+ except Exception:
64
+ # Best effort; continue to next
65
+ continue
66
+
67
+ # 2) Fallback: site-packages sibling 'docs/' directory (and repo-root docs in editable installs)
68
+ try:
69
+ spec = importlib.util.find_spec("svc_infra")
70
+ if spec and spec.submodule_search_locations:
71
+ pkg_dir = Path(next(iter(spec.submodule_search_locations)))
72
+ candidates = [
73
+ pkg_dir.parent / "docs", # site-packages/docs OR src/docs
74
+ pkg_dir / "docs", # site-packages/svc_infra/docs OR src/svc_infra/docs
75
+ pkg_dir.parent.parent
76
+ / "docs", # repo-root/docs when running editable from repo (src/svc_infra → ../../docs)
77
+ ]
78
+ for candidate in candidates:
79
+ if candidate.exists() and candidate.is_dir():
80
+ for p in sorted(candidate.glob("*.md")):
81
+ if p.is_file():
82
+ topics.setdefault(_norm(p.stem), p)
83
+ # If one candidate had docs, that's sufficient
84
+ if any(k for k in topics):
85
+ break
86
+ except Exception:
87
+ # Optional fallback only
88
+ pass
89
+
90
+ # 3) Last-resort: scan sys.path entries that look like site-/dist-packages for a top-level docs/
91
+ # directory containing markdown files. This covers non-standard installs/editable modes.
92
+ try:
93
+ if not topics:
94
+ for entry in sys.path:
95
+ try:
96
+ if not entry or ("site-packages" not in entry and "dist-packages" not in entry):
97
+ continue
98
+ docs_dir = Path(entry) / "docs"
99
+ if docs_dir.exists() and docs_dir.is_dir():
100
+ found = _discover_fs_topics(docs_dir)
101
+ if found:
102
+ # Merge but do not override anything already found
103
+ for k, v in found.items():
104
+ topics.setdefault(k, v)
105
+ # If we found one valid docs dir, it's enough
106
+ break
107
+ except Exception:
108
+ continue
109
+ except Exception:
110
+ pass
111
+
112
+ # 4) Parse dist-info/RECORD or egg-info/SOURCES.txt to enumerate docs if available
113
+ try:
114
+ if not topics:
115
+ spec = importlib.util.find_spec("svc_infra")
116
+ base_dir: Path | None = None
117
+ if spec and spec.submodule_search_locations:
118
+ base_dir = Path(next(iter(spec.submodule_search_locations))).parent
119
+ # Fallback to first site-packages on sys.path
120
+ if base_dir is None:
121
+ for entry in sys.path:
122
+ if entry and "site-packages" in entry:
123
+ base_dir = Path(entry)
124
+ break
125
+ if base_dir and base_dir.exists():
126
+ # Check for both hyphen and underscore dist-info names
127
+ candidates = list(base_dir.glob("svc_infra-*.dist-info")) + list(
128
+ base_dir.glob("svc-infra-*.dist-info")
129
+ )
130
+ for di in candidates:
131
+ record = di / "RECORD"
132
+ if record.exists():
133
+ try:
134
+ for line in record.read_text(
135
+ encoding="utf-8", errors="ignore"
136
+ ).splitlines():
137
+ rel = line.split(",", 1)[0]
138
+ if rel.startswith("docs/") and rel.endswith(".md"):
139
+ abs_p = base_dir / rel
140
+ if abs_p.exists() and abs_p.is_file():
141
+ topics.setdefault(_norm(Path(rel).stem), abs_p)
142
+ except Exception:
143
+ continue
144
+ # egg-info fallback
145
+ if not topics:
146
+ egg_candidates = list(base_dir.glob("svc_infra-*.egg-info")) + list(
147
+ base_dir.glob("svc-infra-*.egg-info")
148
+ )
149
+ for ei in egg_candidates:
150
+ sources = ei / "SOURCES.txt"
151
+ if sources.exists():
152
+ try:
153
+ for rel in sources.read_text(
154
+ encoding="utf-8", errors="ignore"
155
+ ).splitlines():
156
+ rel = rel.strip()
157
+ if rel.startswith("docs/") and rel.endswith(".md"):
158
+ abs_p = base_dir / rel
159
+ if abs_p.exists() and abs_p.is_file():
160
+ topics.setdefault(_norm(Path(rel).stem), abs_p)
161
+ except Exception:
162
+ continue
163
+ except Exception:
164
+ pass
165
+
166
+ # 5) Deep fallback: recursively search site-packages/dist-packages for any 'docs' folder
167
+ # containing markdown files (limited depth to keep overhead reasonable).
168
+ try:
169
+ if not topics:
170
+ for entry in sys.path:
171
+ if not entry or ("site-packages" not in entry and "dist-packages" not in entry):
172
+ continue
173
+ base = Path(entry)
174
+ if not base.exists() or not base.is_dir():
175
+ continue
176
+ base_parts = len(base.parts)
177
+ for root, dirs, files in os.walk(base):
178
+ root_path = Path(root)
179
+ # Limit search depth to avoid expensive scans
180
+ if len(root_path.parts) - base_parts > 4:
181
+ # prune
182
+ dirs[:] = []
183
+ continue
184
+ if root_path.name == "docs":
185
+ for p in sorted(root_path.glob("*.md")):
186
+ if p.is_file():
187
+ topics.setdefault(_norm(p.stem), p)
188
+ # do not break; there might be multiple doc dirs
189
+ except Exception:
190
+ pass
191
+
192
+ return topics
193
+
194
+
195
+ def _resolve_docs_dir(ctx: click.Context) -> Path | None:
196
+ # Deprecated: we no longer read docs from arbitrary paths or env.
197
+ # All docs are sourced from the packaged svc-infra distribution only.
198
+ return None
199
+
200
+
201
+ class DocsGroup(TyperGroup):
202
+ def list_commands(self, ctx: click.Context) -> List[str]:
203
+ names: List[str] = list(super().list_commands(ctx) or [])
204
+ pkg = _discover_pkg_topics()
205
+ names.extend([k for k in pkg.keys()])
206
+ # Deduplicate and sort
207
+ return sorted({*names})
208
+
209
+ def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
210
+ # Built-ins first (e.g., list, show)
211
+ cmd = super().get_command(ctx, name)
212
+ if cmd is not None:
213
+ return cmd
214
+
215
+ # Packaged topics only
216
+ pkg = _discover_pkg_topics()
217
+ if name in pkg:
218
+ file_path = pkg[name]
219
+
220
+ @click.command(name=name)
221
+ def _show_pkg() -> None:
222
+ click.echo(file_path.read_text(encoding="utf-8", errors="replace"))
223
+
224
+ return _show_pkg
225
+
226
+ return None
227
+
228
+
229
+ def register(app: typer.Typer) -> None:
230
+ """Register the `docs` command group with dynamic topic subcommands."""
231
+
232
+ docs_app = typer.Typer(no_args_is_help=True, add_completion=False, cls=DocsGroup)
233
+
234
+ @docs_app.callback(invoke_without_command=True)
235
+ def _docs_options(
236
+ topic: str | None = typer.Option(None, "--topic", help="Topic to show directly"),
237
+ ) -> None:
238
+ """Support --topic at group level (packaged docs only)."""
239
+ if topic:
240
+ pkg = _discover_pkg_topics()
241
+ if topic in pkg:
242
+ typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
243
+ raise typer.Exit(code=0)
244
+ raise typer.BadParameter(f"Unknown topic: {topic}")
245
+
246
+ @docs_app.command("list", help="List available documentation topics")
247
+ def list_topics() -> None:
248
+ pkg = _discover_pkg_topics()
249
+
250
+ # Print packaged topics only
251
+ def _print(name: str, path: Path) -> None:
252
+ typer.echo(f"{name}\t{path}")
253
+
254
+ for name, path in pkg.items():
255
+ _print(name, path)
256
+
257
+ # Also support a generic "show" command
258
+ @docs_app.command("show", help="Show docs for a topic (alternative to dynamic subcommand)")
259
+ def show(topic: str) -> None:
260
+ pkg = _discover_pkg_topics()
261
+ if topic in pkg:
262
+ typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
263
+ return
264
+ raise typer.BadParameter(f"Unknown topic: {topic}")
265
+
266
+ app.add_typer(docs_app, name="docs")
@@ -1,213 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import importlib.util
4
- import os
5
- from importlib.metadata import PackageNotFoundError, distribution
6
- from pathlib import Path
7
- from typing import Dict, List
8
-
9
- import click
10
- import typer
11
- from typer.core import TyperGroup
12
-
13
- from svc_infra.app.root import resolve_project_root
14
-
15
-
16
- def _discover_fs_topics(docs_dir: Path) -> Dict[str, Path]:
17
- topics: Dict[str, Path] = {}
18
- if docs_dir.exists() and docs_dir.is_dir():
19
- for p in sorted(docs_dir.glob("*.md")):
20
- if p.is_file():
21
- topics[p.stem.replace(" ", "-")] = p
22
- return topics
23
-
24
-
25
- def _discover_pkg_topics() -> Dict[str, Path]:
26
- """Discover docs packaged under 'docs/' in the installed distribution.
27
-
28
- Works in external projects without a local docs/ by inspecting the wheel
29
- metadata and, as a fallback, searching for a top-level docs/ next to the
30
- installed package directory in site-packages.
31
- """
32
- topics: Dict[str, Path] = {}
33
-
34
- # 1) Prefer distribution metadata (RECORD) for both hyphen/underscore names
35
- dist = None
36
- for name in ("svc-infra", "svc_infra"):
37
- try:
38
- dist = distribution(name)
39
- break
40
- except PackageNotFoundError:
41
- dist = None
42
-
43
- if dist is not None:
44
- files = getattr(dist, "files", None) or []
45
- for f in files:
46
- s = str(f)
47
- if not s.startswith("docs/") or not s.endswith(".md"):
48
- continue
49
- topic_name = Path(s).stem.replace(" ", "-")
50
- try:
51
- abs_path = Path(dist.locate_file(f))
52
- if abs_path.exists() and abs_path.is_file():
53
- topics[topic_name] = abs_path
54
- except Exception:
55
- # Best effort; continue to next
56
- continue
57
-
58
- # 2) Fallback: site-packages sibling 'docs/' directory
59
- try:
60
- spec = importlib.util.find_spec("svc_infra")
61
- if spec and spec.submodule_search_locations:
62
- pkg_dir = Path(next(iter(spec.submodule_search_locations)))
63
- candidate = pkg_dir.parent / "docs"
64
- if candidate.exists() and candidate.is_dir():
65
- for p in sorted(candidate.glob("*.md")):
66
- if p.is_file():
67
- topics.setdefault(p.stem.replace(" ", "-"), p)
68
- except Exception:
69
- # Optional fallback only
70
- pass
71
-
72
- return topics
73
-
74
-
75
- def _resolve_docs_dir(ctx: click.Context) -> Path | None:
76
- # CLI option takes precedence; walk up parent contexts because Typer
77
- # executes subcommands in child contexts that do not inherit params.
78
- current: click.Context | None = ctx
79
- while current is not None:
80
- docs_dir_opt = (current.params or {}).get("docs_dir")
81
- if docs_dir_opt:
82
- path = docs_dir_opt if isinstance(docs_dir_opt, Path) else Path(docs_dir_opt)
83
- path = path.expanduser()
84
- if path.exists():
85
- return path
86
- current = current.parent
87
-
88
- # Env var next
89
- env_dir = os.getenv("SVC_INFRA_DOCS_DIR")
90
- if env_dir:
91
- p = Path(env_dir).expanduser()
92
- if p.exists():
93
- return p
94
-
95
- # Project docs
96
- root = resolve_project_root()
97
- proj_docs = root / "docs"
98
- if proj_docs.exists():
99
- return proj_docs
100
- return None
101
-
102
-
103
- class DocsGroup(TyperGroup):
104
- def list_commands(self, ctx: click.Context) -> List[str]:
105
- names: List[str] = list(super().list_commands(ctx) or [])
106
- dir_to_use = _resolve_docs_dir(ctx)
107
- fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
108
- pkg = _discover_pkg_topics()
109
- # FS topics win on conflicts; add both for visibility
110
- names.extend([k for k in fs.keys()])
111
- names.extend([k for k in pkg.keys() if k not in fs])
112
- # Deduplicate and sort
113
- return sorted({*names})
114
-
115
- def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
116
- # Built-ins first (e.g., list, show)
117
- cmd = super().get_command(ctx, name)
118
- if cmd is not None:
119
- return cmd
120
-
121
- # Dynamic topic resolution from FS
122
- dir_to_use = _resolve_docs_dir(ctx)
123
- fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
124
- if name in fs:
125
- file_path = fs[name]
126
-
127
- @click.command(name=name)
128
- def _show_fs() -> None:
129
- click.echo(file_path.read_text(encoding="utf-8", errors="replace"))
130
-
131
- return _show_fs
132
-
133
- # Packaged fallback
134
- pkg = _discover_pkg_topics()
135
- if name in pkg:
136
- file_path = pkg[name]
137
-
138
- @click.command(name=name)
139
- def _show_pkg() -> None:
140
- click.echo(file_path.read_text(encoding="utf-8", errors="replace"))
141
-
142
- return _show_pkg
143
-
144
- return None
145
-
146
-
147
- def register(app: typer.Typer) -> None:
148
- """Register the `docs` command group with dynamic topic subcommands."""
149
-
150
- docs_app = typer.Typer(no_args_is_help=True, add_completion=False, cls=DocsGroup)
151
-
152
- @docs_app.callback(invoke_without_command=True)
153
- def _docs_options(
154
- docs_dir: Path | None = typer.Option(
155
- None,
156
- "--docs-dir",
157
- help="Path to a docs directory to read from (overrides env/project root)",
158
- ),
159
- topic: str | None = typer.Option(None, "--topic", help="Topic to show directly"),
160
- ) -> None:
161
- """Support --docs-dir and --topic at group level."""
162
- if topic:
163
- ctx = click.get_current_context()
164
- dir_to_use = _resolve_docs_dir(ctx)
165
- fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
166
- if topic in fs:
167
- typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
168
- raise typer.Exit(code=0)
169
- pkg = _discover_pkg_topics()
170
- if topic in pkg:
171
- typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
172
- raise typer.Exit(code=0)
173
- raise typer.BadParameter(f"Unknown topic: {topic}")
174
-
175
- @docs_app.command("list", help="List available documentation topics")
176
- def list_topics() -> None:
177
- ctx = click.get_current_context()
178
- root = resolve_project_root()
179
- dir_to_use = _resolve_docs_dir(ctx)
180
- fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
181
- pkg = _discover_pkg_topics()
182
-
183
- # Print FS topics first (project/env/option), then packaged topics not shadowed by FS
184
- def _print(name: str, path: Path) -> None:
185
- try:
186
- rel = path.relative_to(root)
187
- typer.echo(f"{name}\t{rel}")
188
- except Exception:
189
- # For packaged topics, path will be site-packages absolute path
190
- typer.echo(f"{name}\t{path}")
191
-
192
- for name, path in fs.items():
193
- _print(name, path)
194
- for name, path in pkg.items():
195
- if name not in fs:
196
- _print(name, path)
197
-
198
- # Also support a generic "show" command
199
- @docs_app.command("show", help="Show docs for a topic (alternative to dynamic subcommand)")
200
- def show(topic: str) -> None:
201
- ctx = click.get_current_context()
202
- dir_to_use = _resolve_docs_dir(ctx)
203
- fs = _discover_fs_topics(dir_to_use) if dir_to_use else {}
204
- if topic in fs:
205
- typer.echo(fs[topic].read_text(encoding="utf-8", errors="replace"))
206
- return
207
- pkg = _discover_pkg_topics()
208
- if topic in pkg:
209
- typer.echo(pkg[topic].read_text(encoding="utf-8", errors="replace"))
210
- return
211
- raise typer.BadParameter(f"Unknown topic: {topic}")
212
-
213
- app.add_typer(docs_app, name="docs")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes