svc-infra 0.1.506__py3-none-any.whl → 0.1.654__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. svc_infra/apf_payments/README.md +732 -0
  2. svc_infra/apf_payments/alembic.py +11 -0
  3. svc_infra/apf_payments/models.py +339 -0
  4. svc_infra/apf_payments/provider/__init__.py +4 -0
  5. svc_infra/apf_payments/provider/aiydan.py +797 -0
  6. svc_infra/apf_payments/provider/base.py +270 -0
  7. svc_infra/apf_payments/provider/registry.py +31 -0
  8. svc_infra/apf_payments/provider/stripe.py +873 -0
  9. svc_infra/apf_payments/schemas.py +333 -0
  10. svc_infra/apf_payments/service.py +892 -0
  11. svc_infra/apf_payments/settings.py +67 -0
  12. svc_infra/api/fastapi/__init__.py +6 -0
  13. svc_infra/api/fastapi/admin/__init__.py +3 -0
  14. svc_infra/api/fastapi/admin/add.py +231 -0
  15. svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
  16. svc_infra/api/fastapi/apf_payments/router.py +1082 -0
  17. svc_infra/api/fastapi/apf_payments/setup.py +73 -0
  18. svc_infra/api/fastapi/auth/add.py +15 -6
  19. svc_infra/api/fastapi/auth/gaurd.py +67 -5
  20. svc_infra/api/fastapi/auth/mfa/router.py +18 -9
  21. svc_infra/api/fastapi/auth/routers/account.py +3 -2
  22. svc_infra/api/fastapi/auth/routers/apikey_router.py +11 -5
  23. svc_infra/api/fastapi/auth/routers/oauth_router.py +82 -37
  24. svc_infra/api/fastapi/auth/routers/session_router.py +63 -0
  25. svc_infra/api/fastapi/auth/security.py +3 -1
  26. svc_infra/api/fastapi/auth/settings.py +2 -0
  27. svc_infra/api/fastapi/auth/state.py +1 -1
  28. svc_infra/api/fastapi/billing/router.py +64 -0
  29. svc_infra/api/fastapi/billing/setup.py +19 -0
  30. svc_infra/api/fastapi/cache/add.py +9 -5
  31. svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
  32. svc_infra/api/fastapi/db/sql/add.py +40 -18
  33. svc_infra/api/fastapi/db/sql/crud_router.py +176 -14
  34. svc_infra/api/fastapi/db/sql/session.py +16 -0
  35. svc_infra/api/fastapi/db/sql/users.py +14 -2
  36. svc_infra/api/fastapi/dependencies/ratelimit.py +116 -0
  37. svc_infra/api/fastapi/docs/add.py +160 -0
  38. svc_infra/api/fastapi/docs/landing.py +1 -1
  39. svc_infra/api/fastapi/docs/scoped.py +254 -0
  40. svc_infra/api/fastapi/dual/dualize.py +38 -33
  41. svc_infra/api/fastapi/dual/router.py +48 -1
  42. svc_infra/api/fastapi/dx.py +3 -3
  43. svc_infra/api/fastapi/http/__init__.py +0 -0
  44. svc_infra/api/fastapi/http/concurrency.py +14 -0
  45. svc_infra/api/fastapi/http/conditional.py +33 -0
  46. svc_infra/api/fastapi/http/deprecation.py +21 -0
  47. svc_infra/api/fastapi/middleware/errors/handlers.py +45 -7
  48. svc_infra/api/fastapi/middleware/graceful_shutdown.py +87 -0
  49. svc_infra/api/fastapi/middleware/idempotency.py +116 -0
  50. svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
  51. svc_infra/api/fastapi/middleware/optimistic_lock.py +37 -0
  52. svc_infra/api/fastapi/middleware/ratelimit.py +119 -0
  53. svc_infra/api/fastapi/middleware/ratelimit_store.py +84 -0
  54. svc_infra/api/fastapi/middleware/request_id.py +23 -0
  55. svc_infra/api/fastapi/middleware/request_size_limit.py +36 -0
  56. svc_infra/api/fastapi/middleware/timeout.py +148 -0
  57. svc_infra/api/fastapi/openapi/mutators.py +768 -55
  58. svc_infra/api/fastapi/ops/add.py +73 -0
  59. svc_infra/api/fastapi/pagination.py +363 -0
  60. svc_infra/api/fastapi/paths/auth.py +14 -14
  61. svc_infra/api/fastapi/paths/prefix.py +0 -1
  62. svc_infra/api/fastapi/paths/user.py +1 -1
  63. svc_infra/api/fastapi/routers/ping.py +1 -0
  64. svc_infra/api/fastapi/setup.py +48 -15
  65. svc_infra/api/fastapi/tenancy/add.py +19 -0
  66. svc_infra/api/fastapi/tenancy/context.py +112 -0
  67. svc_infra/api/fastapi/versioned.py +101 -0
  68. svc_infra/app/README.md +5 -5
  69. svc_infra/billing/__init__.py +23 -0
  70. svc_infra/billing/async_service.py +147 -0
  71. svc_infra/billing/jobs.py +230 -0
  72. svc_infra/billing/models.py +131 -0
  73. svc_infra/billing/quotas.py +101 -0
  74. svc_infra/billing/schemas.py +33 -0
  75. svc_infra/billing/service.py +115 -0
  76. svc_infra/bundled_docs/README.md +5 -0
  77. svc_infra/bundled_docs/__init__.py +1 -0
  78. svc_infra/bundled_docs/getting-started.md +6 -0
  79. svc_infra/cache/__init__.py +4 -0
  80. svc_infra/cache/add.py +158 -0
  81. svc_infra/cache/backend.py +5 -2
  82. svc_infra/cache/decorators.py +19 -1
  83. svc_infra/cache/keys.py +24 -4
  84. svc_infra/cli/__init__.py +32 -8
  85. svc_infra/cli/__main__.py +4 -0
  86. svc_infra/cli/cmds/__init__.py +10 -0
  87. svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +4 -3
  88. svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +4 -4
  89. svc_infra/cli/cmds/db/sql/alembic_cmds.py +120 -14
  90. svc_infra/cli/cmds/db/sql/sql_export_cmds.py +80 -0
  91. svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +5 -4
  92. svc_infra/cli/cmds/docs/docs_cmds.py +140 -0
  93. svc_infra/cli/cmds/dx/__init__.py +12 -0
  94. svc_infra/cli/cmds/dx/dx_cmds.py +99 -0
  95. svc_infra/cli/cmds/help.py +4 -0
  96. svc_infra/cli/cmds/jobs/__init__.py +1 -0
  97. svc_infra/cli/cmds/jobs/jobs_cmds.py +43 -0
  98. svc_infra/cli/cmds/obs/obs_cmds.py +4 -3
  99. svc_infra/cli/cmds/sdk/__init__.py +0 -0
  100. svc_infra/cli/cmds/sdk/sdk_cmds.py +102 -0
  101. svc_infra/data/add.py +61 -0
  102. svc_infra/data/backup.py +53 -0
  103. svc_infra/data/erasure.py +45 -0
  104. svc_infra/data/fixtures.py +40 -0
  105. svc_infra/data/retention.py +55 -0
  106. svc_infra/db/inbox.py +67 -0
  107. svc_infra/db/nosql/mongo/README.md +13 -13
  108. svc_infra/db/outbox.py +104 -0
  109. svc_infra/db/sql/apikey.py +1 -1
  110. svc_infra/db/sql/authref.py +61 -0
  111. svc_infra/db/sql/core.py +2 -2
  112. svc_infra/db/sql/repository.py +52 -12
  113. svc_infra/db/sql/resource.py +5 -0
  114. svc_infra/db/sql/scaffold.py +16 -4
  115. svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +1 -1
  116. svc_infra/db/sql/templates/setup/env_async.py.tmpl +199 -76
  117. svc_infra/db/sql/templates/setup/env_sync.py.tmpl +231 -79
  118. svc_infra/db/sql/tenant.py +79 -0
  119. svc_infra/db/sql/utils.py +18 -4
  120. svc_infra/db/sql/versioning.py +14 -0
  121. svc_infra/docs/acceptance-matrix.md +71 -0
  122. svc_infra/docs/acceptance.md +44 -0
  123. svc_infra/docs/admin.md +425 -0
  124. svc_infra/docs/adr/0002-background-jobs-and-scheduling.md +40 -0
  125. svc_infra/docs/adr/0003-webhooks-framework.md +24 -0
  126. svc_infra/docs/adr/0004-tenancy-model.md +42 -0
  127. svc_infra/docs/adr/0005-data-lifecycle.md +86 -0
  128. svc_infra/docs/adr/0006-ops-slos-and-metrics.md +47 -0
  129. svc_infra/docs/adr/0007-docs-and-sdks.md +83 -0
  130. svc_infra/docs/adr/0008-billing-primitives.md +143 -0
  131. svc_infra/docs/adr/0009-acceptance-harness.md +40 -0
  132. svc_infra/docs/adr/0010-timeouts-and-resource-limits.md +54 -0
  133. svc_infra/docs/adr/0011-admin-scope-and-impersonation.md +73 -0
  134. svc_infra/docs/api.md +59 -0
  135. svc_infra/docs/auth.md +11 -0
  136. svc_infra/docs/billing.md +190 -0
  137. svc_infra/docs/cache.md +76 -0
  138. svc_infra/docs/cli.md +74 -0
  139. svc_infra/docs/contributing.md +34 -0
  140. svc_infra/docs/data-lifecycle.md +52 -0
  141. svc_infra/docs/database.md +14 -0
  142. svc_infra/docs/docs-and-sdks.md +62 -0
  143. svc_infra/docs/environment.md +114 -0
  144. svc_infra/docs/getting-started.md +63 -0
  145. svc_infra/docs/idempotency.md +111 -0
  146. svc_infra/docs/jobs.md +67 -0
  147. svc_infra/docs/observability.md +16 -0
  148. svc_infra/docs/ops.md +37 -0
  149. svc_infra/docs/rate-limiting.md +125 -0
  150. svc_infra/docs/repo-review.md +48 -0
  151. svc_infra/docs/security.md +176 -0
  152. svc_infra/docs/tenancy.md +35 -0
  153. svc_infra/docs/timeouts-and-resource-limits.md +147 -0
  154. svc_infra/docs/versioned-integrations.md +146 -0
  155. svc_infra/docs/webhooks.md +112 -0
  156. svc_infra/dx/add.py +63 -0
  157. svc_infra/dx/changelog.py +74 -0
  158. svc_infra/dx/checks.py +67 -0
  159. svc_infra/http/__init__.py +13 -0
  160. svc_infra/http/client.py +72 -0
  161. svc_infra/jobs/builtins/outbox_processor.py +38 -0
  162. svc_infra/jobs/builtins/webhook_delivery.py +90 -0
  163. svc_infra/jobs/easy.py +32 -0
  164. svc_infra/jobs/loader.py +45 -0
  165. svc_infra/jobs/queue.py +81 -0
  166. svc_infra/jobs/redis_queue.py +191 -0
  167. svc_infra/jobs/runner.py +75 -0
  168. svc_infra/jobs/scheduler.py +41 -0
  169. svc_infra/jobs/worker.py +40 -0
  170. svc_infra/mcp/svc_infra_mcp.py +85 -28
  171. svc_infra/obs/README.md +2 -0
  172. svc_infra/obs/add.py +54 -7
  173. svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
  174. svc_infra/obs/metrics/__init__.py +53 -0
  175. svc_infra/obs/metrics.py +52 -0
  176. svc_infra/security/add.py +201 -0
  177. svc_infra/security/audit.py +130 -0
  178. svc_infra/security/audit_service.py +73 -0
  179. svc_infra/security/headers.py +52 -0
  180. svc_infra/security/hibp.py +95 -0
  181. svc_infra/security/jwt_rotation.py +53 -0
  182. svc_infra/security/lockout.py +96 -0
  183. svc_infra/security/models.py +255 -0
  184. svc_infra/security/org_invites.py +128 -0
  185. svc_infra/security/passwords.py +77 -0
  186. svc_infra/security/permissions.py +149 -0
  187. svc_infra/security/session.py +98 -0
  188. svc_infra/security/signed_cookies.py +80 -0
  189. svc_infra/webhooks/__init__.py +16 -0
  190. svc_infra/webhooks/add.py +322 -0
  191. svc_infra/webhooks/fastapi.py +37 -0
  192. svc_infra/webhooks/router.py +55 -0
  193. svc_infra/webhooks/service.py +67 -0
  194. svc_infra/webhooks/signing.py +30 -0
  195. svc_infra-0.1.654.dist-info/METADATA +154 -0
  196. svc_infra-0.1.654.dist-info/RECORD +352 -0
  197. svc_infra/api/fastapi/deps.py +0 -3
  198. svc_infra-0.1.506.dist-info/METADATA +0 -78
  199. svc_infra-0.1.506.dist-info/RECORD +0 -213
  200. /svc_infra/{api/fastapi/schemas → apf_payments}/__init__.py +0 -0
  201. {svc_infra-0.1.506.dist-info → svc_infra-0.1.654.dist-info}/WHEEL +0 -0
  202. {svc_infra-0.1.506.dist-info → svc_infra-0.1.654.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,190 @@
1
+ # Billing Primitives
2
+
3
+ This module provides internal-first billing building blocks for services that need usage-based and subscription billing without coupling to a specific provider. It complements APF Payments (provider-facing) with portable primitives you can use regardless of Stripe/Aiydan/etc.
4
+
5
+ ## What you get
6
+
7
+ - Usage ingestion with idempotency (UsageEvent)
8
+ - Windowed usage aggregation (UsageAggregate) — daily baseline
9
+ - Plan and entitlements registry (Plan, PlanEntitlement)
10
+ - Tenant subscriptions (Subscription)
11
+ - Price catalog for fixed/usage items (Price)
12
+ - Invoice and line items (Invoice, InvoiceLine)
13
+ - A small `BillingService` to record usage, aggregate, and generate monthly invoices
14
+ - Optional provider sync hook to mirror internal invoices/lines to your payment provider
15
+
16
+ ## Data model (SQL)
17
+
18
+ Tables (v1):
19
+ - usage_events(id, tenant_id, metric, amount, at_ts, idempotency_key, metadata_json, created_at)
20
+ - Unique (tenant_id, metric, idempotency_key)
21
+ - usage_aggregates(id, tenant_id, metric, period_start, granularity, total, updated_at)
22
+ - Unique (tenant_id, metric, period_start, granularity)
23
+ - plans(id, key, name, description, created_at)
24
+ - plan_entitlements(id, plan_id, key, limit_per_window, window, created_at)
25
+ - subscriptions(id, tenant_id, plan_id, effective_at, ended_at, created_at)
26
+ - prices(id, key, currency, unit_amount, metric, recurring_interval, created_at)
27
+ - invoices(id, tenant_id, period_start, period_end, status, total_amount, currency, provider_invoice_id, created_at)
28
+ - invoice_lines(id, invoice_id, price_id, metric, quantity, amount, created_at)
29
+
30
+ See `src/svc_infra/billing/models.py` for full definitions.
31
+
32
+ ## Quick start (Python)
33
+
34
+ ```python
35
+ from datetime import datetime, timezone
36
+ from sqlalchemy.orm import Session
37
+ from svc_infra.billing import BillingService
38
+
39
+ # session: SQLAlchemy Session (sync) targeting your DB
40
+ bs = BillingService(session=session, tenant_id="t_123")
41
+
42
+ # 1) Record usage (idempotent by (tenant, metric, idempotency_key))
43
+ evt_id = bs.record_usage(
44
+ metric="tokens", amount=42,
45
+ at=datetime.now(tz=timezone.utc),
46
+ idempotency_key="req-42",
47
+ metadata={"model": "gpt"},
48
+ )
49
+
50
+ # 2) Aggregate for a day (baseline v1 granularity)
51
+ bs.aggregate_daily(metric="tokens", day_start=datetime(2025,1,1,tzinfo=timezone.utc))
52
+
53
+ # 3) Generate a monthly invoice (fixed+usage lines TBD)
54
+ inv_id = bs.generate_monthly_invoice(
55
+ period_start=datetime(2025,1,1,tzinfo=timezone.utc),
56
+ period_end=datetime(2025,2,1,tzinfo=timezone.utc),
57
+ currency="usd",
58
+ )
59
+ ```
60
+
61
+ Optional: pass a provider sync hook if you want to mirror invoices/lines to Stripe/Aiydan:
62
+
63
+ ```python
64
+ from typing import Callable
65
+ from svc_infra.billing.models import Invoice, InvoiceLine
66
+
67
+ async def sync_to_provider(inv: Invoice, lines: list[InvoiceLine]):
68
+ # Map internal invoice/lines to provider calls here
69
+ ...
70
+
71
+ bs = BillingService(session=session, tenant_id="t_123", provider_sync=sync_to_provider)
72
+ ```
73
+
74
+ ### FastAPI router (usage ingestion & aggregates)
75
+
76
+ Mount the router and start recording usage with idempotency:
77
+
78
+ ```python
79
+ from fastapi import FastAPI
80
+ from svc_infra.api.fastapi.billing.setup import add_billing
81
+ from svc_infra.api.fastapi.middleware.idempotency import IdempotencyMiddleware
82
+ from svc_infra.api.fastapi.middleware.errors.handlers import register_error_handlers
83
+
84
+ app = FastAPI()
85
+ app.add_middleware(IdempotencyMiddleware, store={})
86
+ register_error_handlers(app)
87
+ add_billing(app) # mounts under /_billing
88
+
89
+ # POST /_billing/usage {metric, amount, at?, idempotency_key, metadata?} -> 202 {id}
90
+ # GET /_billing/usage?metric=tokens -> {items: [{period_start, granularity, metric, total}], next_cursor}
91
+ ```
92
+
93
+ ### Quotas (soft/hard limits)
94
+
95
+ Protect your feature endpoints with a quota dependency based on internal plan entitlements and daily aggregates:
96
+
97
+ ```python
98
+ from fastapi import Depends
99
+ from svc_infra.billing.quotas import require_quota
100
+
101
+ @app.get("/generate-report", dependencies=[Depends(require_quota("reports", window="day", soft=False))])
102
+ async def generate_report():
103
+ return {"ok": True}
104
+ ```
105
+
106
+ ## Relationship to APF Payments
107
+
108
+ - APF Payments is provider-facing: customers, intents, methods, products/prices, subscriptions, invoices, usage records via Stripe/Aiydan adapters and HTTP routers.
109
+ - Billing Primitives is provider-agnostic: an internal ledger of usage, plans/entitlements, and invoices that you can keep even if you change providers.
110
+ - You can use both: continue to use APF Payments for card/payments flows, and use Billing to meter custom features and create internal invoices; selectively sync them out later.
111
+
112
+ ## Jobs and webhooks
113
+
114
+ Billing includes helpers to enqueue and process jobs and emit webhooks:
115
+
116
+ - Job names:
117
+ - `billing.aggregate_daily` payload: `{tenant_id, metric, day_start: ISO8601}`
118
+ - `billing.generate_monthly_invoice` payload: `{tenant_id, period_start: ISO8601, period_end: ISO8601, currency}`
119
+ - Emitted webhook topics:
120
+ - `billing.usage_aggregated` payload: `{tenant_id, metric, day_start, total}`
121
+ - `billing.invoice.created` payload: `{tenant_id, invoice_id, period_start, period_end, currency}`
122
+
123
+ Usage with the built-in queue/scheduler and webhooks outbox:
124
+
125
+ ```python
126
+ from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
127
+ from svc_infra.jobs.easy import easy_jobs
128
+ from svc_infra.webhooks.add import add_webhooks
129
+ from svc_infra.webhooks.service import WebhookService
130
+ from svc_infra.db.outbox import InMemoryOutboxStore
131
+ from svc_infra.webhooks.service import InMemoryWebhookSubscriptions
132
+ from svc_infra.billing.jobs import (
133
+ enqueue_aggregate_daily,
134
+ enqueue_generate_monthly_invoice,
135
+ make_billing_job_handler,
136
+ )
137
+
138
+ # Create queue + scheduler
139
+ queue, scheduler = easy_jobs()
140
+
141
+ # Setup DB async session factory
142
+ engine = create_async_engine("sqlite+aiosqlite:///:memory:")
143
+ SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
144
+
145
+ # Setup webhooks (in-memory stores shown here)
146
+ outbox = InMemoryOutboxStore()
147
+ subs = InMemoryWebhookSubscriptions()
148
+ subs.add("billing.usage_aggregated", url="https://example.test/hook", secret="sekrit")
149
+ webhooks = WebhookService(outbox=outbox, subs=subs)
150
+
151
+ # Worker handler
152
+ handler = make_billing_job_handler(session_factory=SessionLocal, webhooks=webhooks)
153
+
154
+ # Enqueue example jobs
155
+ from datetime import datetime, timezone
156
+ enqueue_aggregate_daily(queue, tenant_id="t1", metric="tokens", day_start=datetime.now(timezone.utc))
157
+ enqueue_generate_monthly_invoice(
158
+ queue, tenant_id="t1", period_start=datetime(2025,1,1,tzinfo=timezone.utc), period_end=datetime(2025,2,1,tzinfo=timezone.utc), currency="usd"
159
+ )
160
+
161
+ # In your worker loop call process_one(queue, handler)
162
+ ```
163
+
164
+ ## Roadmap (v1 scope)
165
+
166
+ - Router: `/_billing` endpoints for usage ingestion (idempotent), aggregate listing, plans/subscriptions read.
167
+ - Quotas: decorator/dependency to enforce per-plan limits (soft/hard, day/month windows).
168
+ - Jobs: integrate aggregation and invoice-generation with the scheduler; emit `billing.*` webhooks. (helpers available in `svc_infra.billing.jobs`) — Implemented.
169
+ - Provider sync: optional mapper to Stripe invoices/payment intents; reuse idempotency.
170
+ - Migrations: author initial Alembic migration for billing tables.
171
+ - Docs: examples for quotas and jobs; admin flows for plans and prices.
172
+
173
+ ## Testing
174
+
175
+ - See `tests/unit/billing/test_billing_service.py` for usage, aggregation, invoice basics, and idempotency uniqueness.
176
+ - Additions planned: router tests (ingest/list), quotas, job executions, webhook events.
177
+
178
+ ## Security & Tenancy
179
+
180
+ - All records are tenant-scoped; ensure tenant_id is enforced in your service layer / router dependencies.
181
+ - Protect HTTP endpoints with RBAC permissions (e.g., billing.read, billing.write) if you expose them.
182
+
183
+ ## Observability
184
+
185
+ Planned metrics (names may evolve):
186
+ - billing_usage_ingest_total
187
+ - billing_aggregate_duration_ms
188
+ - billing_invoice_generated_total
189
+
190
+ See ADR 0008 for design details.
@@ -0,0 +1,76 @@
1
+ # Cache guide
2
+
3
+ The cache module wraps [cashews](https://github.com/Krukov/cashews) with decorators and namespace helpers so services can centralize key formats.
4
+
5
+ ```python
6
+ from svc_infra.cache import cache_read, cache_write, init_cache
7
+
8
+ init_cache() # uses CACHE_PREFIX / CACHE_VERSION
9
+
10
+ @cache_read(key="user:{user_id}", ttl=300)
11
+ async def get_user(user_id: int):
12
+ ...
13
+ ```
14
+
15
+ ### Environment
16
+
17
+ - `CACHE_PREFIX`, `CACHE_VERSION` – change the namespace alias used by the decorators. 【F:src/svc_infra/cache/README.md†L20-L173】
18
+ - `CACHE_TTL_DEFAULT`, `CACHE_TTL_SHORT`, `CACHE_TTL_LONG` – override canonical TTL buckets. 【F:src/svc_infra/cache/ttl.py†L26-L55】
19
+
20
+ ## Easy integration: add_cache
21
+
22
+ Use the one-liner helper to wire cache initialization into your ASGI app lifecycle with sensible defaults. This doesn’t replace the decorators; it standardizes init/readiness/shutdown and exposes a handle for convenience.
23
+
24
+ ```python
25
+ from fastapi import FastAPI
26
+ from svc_infra.cache import add_cache, cache_read, cache_write, resource
27
+
28
+ app = FastAPI()
29
+
30
+ # Wires startup (init + readiness) and shutdown (graceful close). Idempotent.
31
+ add_cache(app)
32
+
33
+ user = resource("user", "user_id")
34
+
35
+ @user.cache_read(suffix="profile", ttl=300)
36
+ async def get_user_profile(user_id: int):
37
+ ...
38
+
39
+ @user.cache_write()
40
+ async def update_user_profile(user_id: int, payload):
41
+ ...
42
+
43
+ # Optional: direct cache instance for advanced scenarios
44
+ # available after startup when using add_cache(app)
45
+ # app.state.cache -> cashews cache instance
46
+ ```
47
+
48
+ ### Env-driven defaults
49
+
50
+ - URL: `CACHE_URL` → `REDIS_URL` → `mem://`
51
+ - Prefix: `CACHE_PREFIX` (default `svc`)
52
+ - Version: `CACHE_VERSION` (default `v1`)
53
+
54
+ You can override explicitly:
55
+
56
+ ```python
57
+ add_cache(app, url="redis://localhost:6379/0", prefix="myapp", version="v2")
58
+ ```
59
+
60
+ ### Behavior
61
+
62
+ - Idempotent: multiple calls won’t duplicate handlers.
63
+ - Startup/shutdown hooks: registered when supported by the app; startup performs a readiness probe. Startup is optional for correctness, but recommended for production reliability.
64
+ - app.state exposure: by default, exposes `app.state.cache` to access the underlying cashews instance.
65
+
66
+ ### No-app usage
67
+
68
+ If you’re not wiring an app (e.g., a script), you can initialize without startup hooks:
69
+
70
+ ```python
71
+ from svc_infra.cache import add_cache
72
+
73
+ shutdown = add_cache(None) # immediate init (best-effort)
74
+ # ... do work ...
75
+ # call shutdown() is a no-op placeholder for symmetry
76
+ ```
svc_infra/docs/cli.md ADDED
@@ -0,0 +1,74 @@
1
+ # CLI Quick Reference
2
+
3
+ The `svc-infra` CLI wraps common database, observability, jobs, docs, and DX workflows using Typer.
4
+
5
+ - Entry points:
6
+ - Global: `svc-infra ...` (installed via Poetry scripts)
7
+ - Module: `python -m svc_infra.cli ...` (works in editable installs and containers)
8
+
9
+ ## Top-level help
10
+
11
+ Run:
12
+
13
+ ```
14
+ svc-infra --help
15
+ ```
16
+
17
+ You should see groups for SQL, Mongo, Observability, DX, Jobs, and SDK.
18
+
19
+ ## Database (Alembic) commands
20
+
21
+ - End-to-end setup and migrate (detects async from URL):
22
+ - Environment variables (commonly): `SQL_URL` or compose parts `DB_*`.
23
+
24
+ Example with SQLite for quick smoke tests:
25
+
26
+ ```
27
+ python -m svc_infra.cli sql setup-and-migrate --database-url sqlite+aiosqlite:///./accept.db \
28
+ --discover-packages "app.models" --with-payments false
29
+ ```
30
+
31
+ - Current revision, history, upgrade/downgrade:
32
+
33
+ ```
34
+ python -m svc_infra.cli sql current
35
+ python -m svc_infra.cli sql-history
36
+ python -m svc_infra.cli sql upgrade head
37
+ python -m svc_infra.cli sql downgrade -1
38
+ ```
39
+
40
+ - Seed fixtures/reference data with your callable:
41
+
42
+ ```
43
+ python -m svc_infra.cli sql seed path.to.module:seed_func
44
+ ```
45
+
46
+ Notes:
47
+ - The target must be in the format `module.path:callable`.
48
+ - If you previously referenced legacy test modules under `tests.db.*`, the CLI shims import to `tests.unit.db.*` when possible.
49
+
50
+ ## Jobs
51
+
52
+ Start the local jobs runner loop:
53
+
54
+ ```
55
+ svc-infra jobs run
56
+ ```
57
+
58
+ ## DX helpers
59
+
60
+ - Generate CI workflow and checks template:
61
+
62
+ ```
63
+ python -m svc_infra.cli dx ci --openapi openapi.json
64
+ ```
65
+
66
+ - Lint OpenAPI and Problem+JSON samples:
67
+
68
+ ```
69
+ python -m svc_infra.cli dx openapi openapi.json
70
+ ```
71
+
72
+ ## SDKs
73
+
74
+ Generate SDKs from OpenAPI (dry-run by default): see `docs/docs-and-sdks.md` for full examples.
@@ -0,0 +1,34 @@
1
+ # Contributing
2
+
3
+ Thanks for considering a contribution! This repo aims to provide production-ready primitives with clear gates.
4
+
5
+ ## Local setup
6
+
7
+ - Python 3.11–3.13
8
+ - Install via Poetry:
9
+ - `poetry install`
10
+ - `poetry run pre-commit install`
11
+
12
+ ## Quality gates (run before PR)
13
+
14
+ - Lint: `poetry run flake8 --select=E,F`
15
+ - Typecheck: `poetry run mypy src`
16
+ - Tests: `poetry run pytest -q -W error`
17
+ - OpenAPI lint (optional): `poetry run python -m svc_infra.cli dx openapi openapi.json`
18
+ - Migrations present (optional): `poetry run python -m svc_infra.cli dx migrations --project-root .`
19
+ - CI dry-run (optional): `poetry run python -m svc_infra.cli dx ci --openapi openapi.json`
20
+
21
+ ## Commit style
22
+
23
+ - Prefer Conventional Commits: `feat:`, `fix:`, `refactor:`, etc.
24
+ - Use changelog generator for releases:
25
+ - `poetry run python -m svc_infra.cli dx changelog 0.1.604 --commits-file commits.jsonl`
26
+
27
+ ## Release process
28
+
29
+ 1. Ensure all gates are green locally (see above).
30
+ 2. Update version in `pyproject.toml`.
31
+ 3. Export OpenAPI (if applicable) via docs helper.
32
+ 4. Generate changelog section and review.
33
+ 5. Merge to `main`. CI will run tests, lint, and typecheck.
34
+ 6. Tag and publish.
@@ -0,0 +1,52 @@
1
+ # Data Lifecycle
2
+
3
+ This guide covers fixtures (reference data), retention policies (soft/hard delete), GDPR erasure, and backup verification.
4
+
5
+ ## Quickstart
6
+
7
+ - Fixtures:
8
+ - Use `run_fixtures([...])` for ad-hoc loads.
9
+ - Or wire a one-time loader with `make_on_load_fixtures(fn, run_once_file)`, then `add_data_lifecycle(app, on_startup=[on_load])`.
10
+ - Retention:
11
+ - Define `RetentionPolicy(name, model, older_than_days, soft_delete_field|None, hard_delete=False)`.
12
+ - Execute manually with `await run_retention_purge(session, [policy,...])` or schedule via your jobs runner.
13
+ - Erasure:
14
+ - Compose an `ErasurePlan([ErasureStep(name, func), ...])` where functions accept `(session, principal_id)` and may be async.
15
+ - Run with `await run_erasure(session, principal_id, plan, on_audit=callable)`; `on_audit` receives `(event, context)`.
16
+ - Backups:
17
+ - `verify_backups(last_success: datetime|None, retention_days: int)` returns a `BackupHealthReport`.
18
+ - Wrap as a job: `make_backup_verification_job(checker, on_report=callback)`.
19
+
20
+ ## APIs
21
+
22
+ - `fixtures.py`:
23
+ - `run_fixtures(callables: Iterable[Callable]) -> Awaitable[None]`
24
+ - `make_on_load_fixtures(*fns, run_once_file: str | None = None) -> Callable[[], Awaitable[None]]`
25
+ - `retention.py`:
26
+ - `RetentionPolicy(name, model, older_than_days, soft_delete_field: str | None, hard_delete: bool = False)`
27
+ - `run_retention_purge(session, policies: Sequence[RetentionPolicy]) -> Awaitable[int]`
28
+ - `erasure.py`:
29
+ - `ErasureStep(name: str, func: Callable)`
30
+ - `ErasurePlan(steps: Sequence[ErasureStep])`
31
+ - `run_erasure(session, principal_id: str, plan: ErasurePlan, on_audit: Callable | None = None) -> Awaitable[int]`
32
+ - `backup.py`:
33
+ - `BackupHealthReport(ok: bool, last_success: datetime | None, reason: str | None)`
34
+ - `verify_backups(last_success: datetime | None = None, retention_days: int = 1) -> BackupHealthReport`
35
+ - `make_backup_verification_job(checker: Callable[[], BackupHealthReport], on_report: Callable[[BackupHealthReport], None] | None = None) -> Callable[[], BackupHealthReport]`
36
+
37
+ ## Scheduling
38
+
39
+ Use the jobs helpers to run retention and backup checks periodically. Example schedule JSON (via JOBS_SCHEDULE_JSON):
40
+
41
+ ```json
42
+ [
43
+ {"name": "retention-purge", "interval": "6h", "handler": "your.module:run_retention"},
44
+ {"name": "backup-verify", "interval": "12h", "handler": "your.module:verify_backups_job"}
45
+ ]
46
+ ```
47
+
48
+ ## Notes
49
+
50
+ - Soft delete expects a `deleted_at` column and optionally an `is_active` flag in repositories.
51
+ - `run_fixtures` and erasure steps support async functions seamlessly.
52
+ - `add_data_lifecycle` already awaits async fixture loaders and uses lifespan instead of deprecated startup events.
@@ -0,0 +1,14 @@
1
+ # Database guide
2
+
3
+ svc-infra exposes helpers for SQLAlchemy and Mongo so APIs get lifecycle management, migrations, and connection URLs from environment variables.
4
+
5
+ ## SQL
6
+
7
+ - `add_sql_db(app, url=None, dsn_env="SQL_URL")` wires the session and raises if the URL env is missing. 【F:src/svc_infra/api/fastapi/db/sql/add.py†L55-L114】
8
+ - Build URLs piecemeal with `DB_DIALECT`, `DB_DRIVER`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_PARAMS`, or point at `SQL_URL_FILE`/`DB_PASSWORD_FILE`. 【F:src/svc_infra/db/sql/utils.py†L85-L206】
9
+ - Alembic templates respect overrides such as `ALEMBIC_DISCOVER_PACKAGES`, `ALEMBIC_INCLUDE_SCHEMAS`, and `ALEMBIC_SKIP_DROPS`. 【F:src/svc_infra/db/sql/utils.py†L274-L347】
10
+
11
+ ## Mongo
12
+
13
+ - `add_mongo_db(app, dsn_env="MONGO_URL")` validates the URL and optional db name. 【F:src/svc_infra/api/fastapi/db/nosql/mongo/add.py†L28-L53】
14
+ - Configure via `MONGO_URL`, `MONGO_DB`, `MONGO_APPNAME`, `MONGO_MIN_POOL`, `MONGO_MAX_POOL`, or point at `MONGO_URL_FILE`. 【F:src/svc_infra/db/nosql/mongo/settings.py†L9-L13】【F:src/svc_infra/db/nosql/utils.py†L56-L113】
@@ -0,0 +1,62 @@
1
+ # Docs & SDKs
2
+
3
+ This guide shows how to enable API docs, enrich your OpenAPI, and generate SDKs.
4
+
5
+ ## Enabling docs
6
+
7
+ - Use `setup_service_api(...)` for versioned apps; root docs are auto-mounted in local/dev.
8
+ - For standalone FastAPI apps, call `add_docs(app)` to mount:
9
+ - `/docs` (Swagger UI)
10
+ - `/redoc` (ReDoc)
11
+ - `/openapi.json` (OpenAPI schema)
12
+ - A landing page at `/` listing root and scoped docs (falls back to `/_docs` if `/` is taken).
13
+
14
+ Tip: Append `?theme=dark` to `/docs` or `/redoc` for a minimal dark mode.
15
+
16
+ ## OpenAPI enrichment
17
+
18
+ The OpenAPI pipeline adds helpful metadata automatically:
19
+ - `x-codeSamples` per operation (curl and httpie) using your server base URL.
20
+ - Problem+JSON examples on error responses (4xx/5xx) referencing the `Problem` schema.
21
+ - Existing success/media examples are preserved and normalized.
22
+
23
+ These mutators are applied for both root and versioned apps via `setup_mutators(...)`.
24
+
25
+ ## Exporting OpenAPI
26
+
27
+ `add_docs(app, export_openapi_to="openapi.json")` writes the schema to disk on startup.
28
+
29
+ ## Generate SDKs (CLI)
30
+
31
+ Use the CLI to generate SDKs from OpenAPI (defaults to dry-run, uses npx tools):
32
+
33
+ - TypeScript (openapi-typescript-codegen):
34
+ svc-infra sdk ts openapi.json --outdir sdk-ts --dry-run=false
35
+
36
+ - Python (openapi-generator):
37
+ svc-infra sdk py openapi.json --outdir sdk-py --package-name client_sdk --dry-run=false
38
+
39
+ - Postman collection:
40
+ svc-infra sdk postman openapi.json --out postman.json --dry-run=false
41
+
42
+ ## Quick curl examples
43
+
44
+ Replace URL and payload as needed; these align with x-codeSamples included in the schema.
45
+
46
+ - GET
47
+ curl -X GET 'http://localhost:8000/v1/projects'
48
+
49
+ - POST with JSON
50
+ curl -X POST 'http://localhost:8000/v1/projects' \
51
+ -H 'Content-Type: application/json' \
52
+ -d '{"name":"Example"}'
53
+
54
+ Notes:
55
+ - You need Node.js; the CLI calls `npx` for generator tools. Add them to your devDependencies for reproducibility.
56
+ - For CI, export OpenAPI to a path and run the CLI with `--dry-run=false`.
57
+
58
+ ## Troubleshooting
59
+
60
+ - Docs not visible at `/`? If your app already handles `/`, the landing page is mounted at `/_docs`.
61
+ - Dark mode not applying? Use `/docs?theme=dark` or `/redoc?theme=dark`.
62
+ - Missing Problem examples? Ensure your error handlers reference the `Problem` schema and that mutators run (they are wired by default in `setup_service_api`).
@@ -0,0 +1,114 @@
1
+ # Environment Reference
2
+
3
+ This guide consolidates every environment variable consumed by the svc-infra helpers in FastAPI, jobs, observability, security, and webhooks. Defaults shown below reflect the library's fallbacks when a variable is absent. Where a helper relies on `svc_infra.app.pick`, the note column calls out the environment-specific behavior.
4
+
5
+ ## FastAPI helpers
6
+
7
+ ### App bootstrap (`easy_service_app` / `setup_service_api`)
8
+
9
+ | Variable | Default | Consumed by | Notes |
10
+ | --- | --- | --- | --- |
11
+ | `ENABLE_LOGGING` | `true` | `EasyAppOptions.from_env()` | Disables `setup_logging` when set to false. |
12
+ | `LOG_LEVEL` | Auto (`INFO` in prod/test, `DEBUG` in dev/local via `pick()`) | `easy_service_app()` | Overrides the log level chosen by `svc_infra.app.pick`. |
13
+ | `LOG_FORMAT` | Auto (JSON in prod, plain elsewhere) | `easy_service_app()` | Explicit `json` or `plain` format overrides auto-detection. |
14
+ | `ENABLE_OBS` | `true` | `EasyAppOptions.from_env()` / `easy_service_app()` | Turns observability instrumentation on/off. |
15
+ | `METRICS_PATH` | `None` → falls back to Observability settings | `EasyAppOptions.from_env()` | Use to expose metrics at a non-default path. |
16
+ | `OBS_SKIP_PATHS` | `None` → defaults to metrics + health endpoints | `EasyAppOptions.from_env()` | Comma/space-separated list of paths skipped by Prometheus middleware. |
17
+ | `CORS_ALLOW_ORIGINS` | `""` (no origins) | `_setup_cors()` | Adds `CORSMiddleware` allow-list when non-empty. |
18
+
19
+ ### SQL helpers (`add_sql_db`, `setup_sql`)
20
+
21
+ | Variable | Default | Consumed by | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `SQL_URL` (overridable via `dsn_env`) | _required_ | `add_sql_db()` / `setup_sql()` | Missing value raises `RuntimeError`; point at your primary database URL. |
24
+
25
+ ### Mongo helpers (`add_mongo_db`, `init_mongo`)
26
+
27
+ | Variable | Default | Consumed by | Notes |
28
+ | --- | --- | --- | --- |
29
+ | `MONGO_URL` / `MONGODB_URL` | `mongodb://localhost:27017` | `MongoSettings`, `add_mongo_db()` | Primary Mongo connection string; `_FILE` suffix or `MONGO_URL_FILE` allow secret mounts. |
30
+ | `MONGO_DB` / `MONGODB_DB` / `MONGO_DATABASE` | unset (optional) | `get_mongo_dbname_from_env()` | When set, verified against the connected database name. |
31
+ | `MONGO_APPNAME` | `svc-infra` | `MongoSettings` | Sets the Mongo client `appname`. |
32
+ | `MONGO_MIN_POOL` | `0` | `MongoSettings` | Minimum Motor/Mongo client pool size. |
33
+ | `MONGO_MAX_POOL` | `100` | `MongoSettings` | Maximum Motor/Mongo client pool size. |
34
+ | `MONGO_URL_FILE` | unset | `get_mongo_url_from_env()` | Alternate secret file path when not using `_FILE` suffix envs. |
35
+ | `/run/secrets/mongo_url` | unset | `get_mongo_url_from_env()` | Auto-mounted Docker/K8s secret fallback for the URL. |
36
+
37
+ ### Auth settings (`get_auth_settings` → `AuthSettings`)
38
+
39
+ Pydantic loads these with the `AUTH_` prefix and `__` as the nested delimiter.
40
+
41
+ | Variable | Default | Consumed by | Notes |
42
+ | --- | --- | --- | --- |
43
+ | `AUTH_JWT__SECRET` | _required when JWT auth enabled_ | `AuthSettings.jwt.secret` | Primary HS256 signing secret. |
44
+ | `AUTH_JWT__LIFETIME_SECONDS` | `604800` (7 days) | `AuthSettings.jwt.lifetime_seconds` | Adjusts refresh token lifetime. |
45
+ | `AUTH_JWT__OLD_SECRETS__*` | `[]` | `AuthSettings.jwt.old_secrets` | Accepted legacy secrets during rotation. |
46
+ | `AUTH_PASSWORD_CLIENTS__{n}__CLIENT_ID` | `[]` | `AuthSettings.password_clients[*].client_id` | Register password clients (list entries indexed by `{n}`). |
47
+ | `AUTH_PASSWORD_CLIENTS__{n}__CLIENT_SECRET` | `[]` | `AuthSettings.password_clients[*].client_secret` | Secret per password client. |
48
+ | `AUTH_REQUIRE_CLIENT_SECRET_ON_PASSWORD_LOGIN` | `false` | `AuthSettings.require_client_secret_on_password_login` | Enforces client secret on password grant. |
49
+ | `AUTH_MFA_DEFAULT_ENABLED_FOR_NEW_USERS` | `false` | `AuthSettings.mfa_default_enabled_for_new_users` | Enable TOTP by default on signup. |
50
+ | `AUTH_MFA_ENFORCE_FOR_ALL_USERS` | `false` | `AuthSettings.mfa_enforce_for_all_users` | Force MFA globally. |
51
+ | `AUTH_MFA_ENFORCE_FOR_TENANTS` | `[]` | `AuthSettings.mfa_enforce_for_tenants` | Tenant allow-list requiring MFA. |
52
+ | `AUTH_MFA_ISSUER` | `"svc-infra"` | `AuthSettings.mfa_issuer` | Label for TOTP apps. |
53
+ | `AUTH_MFA_PRE_TOKEN_LIFETIME_SECONDS` | `300` | `AuthSettings.mfa_pre_token_lifetime_seconds` | Lifespan of MFA pre-token. |
54
+ | `AUTH_MFA_RECOVERY_CODES` | `8` | `AuthSettings.mfa_recovery_codes` | Number of recovery codes issued. |
55
+ | `AUTH_MFA_RECOVERY_CODE_LENGTH` | `10` | `AuthSettings.mfa_recovery_code_length` | Digits per recovery code. |
56
+ | `AUTH_EMAIL_OTP_TTL_SECONDS` | `300` | `AuthSettings.email_otp_ttl_seconds` | Email OTP validity window. |
57
+ | `AUTH_EMAIL_OTP_COOLDOWN_SECONDS` | `60` | `AuthSettings.email_otp_cooldown_seconds` | Cooldown between OTP sends. |
58
+ | `AUTH_EMAIL_OTP_ATTEMPTS` | `5` | `AuthSettings.email_otp_attempts` | Maximum OTP attempts before lock. |
59
+ | `AUTH_SMTP_HOST` | `None` | `AuthSettings.smtp_host` | SMTP hostname (required for prod email). |
60
+ | `AUTH_SMTP_PORT` | `587` | `AuthSettings.smtp_port` | SMTP port. |
61
+ | `AUTH_SMTP_USERNAME` | `None` | `AuthSettings.smtp_username` | SMTP username. |
62
+ | `AUTH_SMTP_PASSWORD` | `None` | `AuthSettings.smtp_password` | SMTP password/secret. |
63
+ | `AUTH_SMTP_FROM` | `None` | `AuthSettings.smtp_from` | Default From address. |
64
+ | `AUTH_AUTO_VERIFY_IN_DEV` | `true` | `AuthSettings.auto_verify_in_dev` | Auto-confirms accounts outside prod. |
65
+ | `AUTH_GOOGLE_CLIENT_ID` | `None` | `AuthSettings.google_client_id` | Built-in Google OAuth client ID. |
66
+ | `AUTH_GOOGLE_CLIENT_SECRET` | `None` | `AuthSettings.google_client_secret` | Built-in Google OAuth secret. |
67
+ | `AUTH_GITHUB_CLIENT_ID` | `None` | `AuthSettings.github_client_id` | GitHub OAuth client ID. |
68
+ | `AUTH_GITHUB_CLIENT_SECRET` | `None` | `AuthSettings.github_client_secret` | GitHub OAuth secret. |
69
+ | `AUTH_MS_CLIENT_ID` | `None` | `AuthSettings.ms_client_id` | Microsoft OAuth client ID. |
70
+ | `AUTH_MS_CLIENT_SECRET` | `None` | `AuthSettings.ms_client_secret` | Microsoft OAuth secret. |
71
+ | `AUTH_MS_TENANT` | `None` | `AuthSettings.ms_tenant` | Microsoft tenant ID. |
72
+ | `AUTH_LI_CLIENT_ID` | `None` | `AuthSettings.li_client_id` | LinkedIn OAuth client ID. |
73
+ | `AUTH_LI_CLIENT_SECRET` | `None` | `AuthSettings.li_client_secret` | LinkedIn OAuth secret. |
74
+ | `AUTH_OIDC_PROVIDERS__{n}__NAME` | `[]` | `AuthSettings.oidc_providers[*].name` | Custom OIDC providers (list entries indexed by `{n}`). |
75
+ | `AUTH_OIDC_PROVIDERS__{n}__ISSUER` | `[]` | `AuthSettings.oidc_providers[*].issuer` | OIDC issuer URL. |
76
+ | `AUTH_OIDC_PROVIDERS__{n}__CLIENT_ID` | `[]` | `AuthSettings.oidc_providers[*].client_id` | OIDC client ID. |
77
+ | `AUTH_OIDC_PROVIDERS__{n}__CLIENT_SECRET` | `[]` | `AuthSettings.oidc_providers[*].client_secret` | OIDC client secret. |
78
+ | `AUTH_OIDC_PROVIDERS__{n}__SCOPE` | `"openid email profile"` | `AuthSettings.oidc_providers[*].scope` | Additional OIDC scopes. |
79
+ | `AUTH_POST_LOGIN_REDIRECT` | `http://localhost:3000/app` | `AuthSettings.post_login_redirect` | Default redirect after login. |
80
+ | `AUTH_REDIRECT_ALLOW_HOSTS_RAW` | `"localhost,127.0.0.1"` | `AuthSettings.redirect_allow_hosts_raw` | CSV/JSON allow-list for redirects. |
81
+ | `AUTH_SESSION_COOKIE_NAME` | `"svc_session"` | `AuthSettings.session_cookie_name` | Session cookie key. |
82
+ | `AUTH_AUTH_COOKIE_NAME` | `"svc_auth"` | `AuthSettings.auth_cookie_name` | Auth cookie key. |
83
+ | `AUTH_SESSION_COOKIE_SECURE` | `false` | `AuthSettings.session_cookie_secure` | Marks session cookie `Secure`. |
84
+ | `AUTH_SESSION_COOKIE_SAMESITE` | `"lax"` | `AuthSettings.session_cookie_samesite` | SameSite policy. |
85
+ | `AUTH_SESSION_COOKIE_DOMAIN` | `None` | `AuthSettings.session_cookie_domain` | Explicit cookie domain. |
86
+ | `AUTH_SESSION_COOKIE_MAX_AGE_SECONDS` | `14400` (4 hours) | `AuthSettings.session_cookie_max_age_seconds` | Session cookie lifetime. |
87
+
88
+ ## Jobs helpers
89
+
90
+ | Variable | Default | Consumed by | Notes |
91
+ | --- | --- | --- | --- |
92
+ | `JOBS_DRIVER` | `memory` | `JobsConfig`, `easy_jobs()` | Choose `redis` to activate Redis-backed queue. |
93
+ | `REDIS_URL` | `redis://localhost:6379/0` | `easy_jobs()` (Redis driver) | Redis connection string when `JOBS_DRIVER=redis`. |
94
+ | `JOBS_SCHEDULE_JSON` | unset | `schedule_from_env()` | JSON array of scheduler tasks (name, interval_seconds, target). |
95
+
96
+ ## Observability helpers
97
+
98
+ | Variable | Default | Consumed by | Notes |
99
+ | --- | --- | --- | --- |
100
+ | `METRICS_ENABLED` | `true` | `ObservabilitySettings` | Gate for Prometheus middleware registration. |
101
+ | `METRICS_PATH` | `/metrics` | `ObservabilitySettings`, `add_observability()` | Metrics endpoint path. |
102
+ | `METRICS_DEFAULT_BUCKETS` | `0.005,0.01,0.025,0.05,0.1,0.25,0.5,1.0,2.0,5.0,10.0` | `ObservabilitySettings` | Histogram buckets for request latency. |
103
+ | `SVC_INFRA_DISABLE_PROMETHEUS` | unset (`"1"` disables) | `metrics.asgi` | Skip Prometheus setup when toggled. |
104
+ | `SVC_INFRA_RATE_WINDOW` | unset | `cloud_dash.push_dashboards_from_pkg()` | Overrides `$__rate_interval` in dashboards. |
105
+ | `SVC_INFRA_DASHBOARD_REFRESH` | `5s` | `cloud_dash.push_dashboards_from_pkg()` | Grafana dashboard auto-refresh interval. |
106
+ | `SVC_INFRA_DASHBOARD_RANGE` | `now-6h` | `cloud_dash.push_dashboards_from_pkg()` | Default Grafana time range start. |
107
+
108
+ ## Security helpers
109
+
110
+ The primitives under `svc_infra.security` rely on configuration objects passed from application code; they do not read environment variables directly beyond the shared `AuthSettings` listed above.
111
+
112
+ ## Webhook helpers
113
+
114
+ Current webhook helpers (`fastapi.require_signature`, `InMemoryWebhookSubscriptions`, `WebhookService`) rely on dependency injection for secrets and stores and do not read environment variables directly.