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.
- {svc_infra-0.1.618 → svc_infra-0.1.672}/PKG-INFO +46 -17
- svc_infra-0.1.672/README.md +80 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/pyproject.toml +14 -2
- svc_infra-0.1.672/src/svc_infra/api/fastapi/admin/__init__.py +3 -0
- svc_infra-0.1.672/src/svc_infra/api/fastapi/admin/add.py +231 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -2
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/add.py +0 -4
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +19 -4
- svc_infra-0.1.672/src/svc_infra/api/fastapi/billing/router.py +64 -0
- svc_infra-0.1.672/src/svc_infra/api/fastapi/billing/setup.py +19 -0
- svc_infra-0.1.672/src/svc_infra/api/fastapi/cache/add.py +18 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/add.py +8 -5
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/crud_router.py +21 -21
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/session.py +16 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/landing.py +1 -1
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/scoped.py +41 -6
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +15 -0
- svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/graceful_shutdown.py +87 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/idempotency.py +7 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/ratelimit.py +20 -3
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +12 -6
- svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/request_id.py +37 -0
- svc_infra-0.1.672/src/svc_infra/api/fastapi/middleware/timeout.py +180 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/mutators.py +3 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/ops/add.py +9 -1
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/setup.py +38 -18
- svc_infra-0.1.672/src/svc_infra/api/fastapi/versioned.py +101 -0
- svc_infra-0.1.672/src/svc_infra/billing/async_service.py +147 -0
- svc_infra-0.1.672/src/svc_infra/billing/jobs.py +230 -0
- svc_infra-0.1.672/src/svc_infra/billing/quotas.py +101 -0
- svc_infra-0.1.672/src/svc_infra/billing/schemas.py +33 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/__init__.py +4 -0
- svc_infra-0.1.672/src/svc_infra/cache/add.py +158 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/__init__.py +17 -11
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +4 -3
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +4 -4
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +28 -12
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/sql_export_cmds.py +2 -4
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
- svc_infra-0.1.672/src/svc_infra/cli/cmds/docs/docs_cmds.py +140 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/help.py +4 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/obs/obs_cmds.py +4 -3
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/README.md +13 -13
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/repository.py +2 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +25 -11
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +20 -5
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/utils.py +18 -4
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/acceptance-matrix.md +17 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/acceptance.md +1 -1
- svc_infra-0.1.672/src/svc_infra/docs/admin.md +425 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0005-data-lifecycle.md +1 -1
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0008-billing-primitives.md +34 -0
- svc_infra-0.1.672/src/svc_infra/docs/adr/0010-timeouts-and-resource-limits.md +54 -0
- svc_infra-0.1.672/src/svc_infra/docs/adr/0011-admin-scope-and-impersonation.md +73 -0
- svc_infra-0.1.672/src/svc_infra/docs/adr/0012-generic-file-storage.md +498 -0
- svc_infra-0.1.672/src/svc_infra/docs/api.md +215 -0
- svc_infra-0.1.672/src/svc_infra/docs/billing.md +190 -0
- svc_infra-0.1.672/src/svc_infra/docs/cache.md +76 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/cli.md +5 -5
- svc_infra-0.1.672/src/svc_infra/docs/documents.md +697 -0
- svc_infra-0.1.618/README.md → svc_infra-0.1.672/src/svc_infra/docs/getting-started.md +25 -16
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/ops.md +4 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/rate-limiting.md +4 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/security.md +23 -2
- svc_infra-0.1.672/src/svc_infra/docs/storage.md +982 -0
- svc_infra-0.1.672/src/svc_infra/docs/timeouts-and-resource-limits.md +147 -0
- svc_infra-0.1.672/src/svc_infra/docs/versioned-integrations.md +146 -0
- svc_infra-0.1.672/src/svc_infra/documents/__init__.py +100 -0
- svc_infra-0.1.672/src/svc_infra/documents/add.py +258 -0
- svc_infra-0.1.672/src/svc_infra/documents/ease.py +233 -0
- svc_infra-0.1.672/src/svc_infra/documents/models.py +114 -0
- svc_infra-0.1.672/src/svc_infra/documents/storage.py +262 -0
- svc_infra-0.1.672/src/svc_infra/http/__init__.py +13 -0
- svc_infra-0.1.672/src/svc_infra/http/client.py +72 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/builtins/webhook_delivery.py +14 -2
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/queue.py +9 -1
- svc_infra-0.1.672/src/svc_infra/jobs/runner.py +75 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/worker.py +17 -1
- svc_infra-0.1.672/src/svc_infra/mcp/svc_infra_mcp.py +124 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/headers.py +15 -2
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/hibp.py +6 -2
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/models.py +27 -7
- svc_infra-0.1.672/src/svc_infra/security/oauth_models.py +59 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/permissions.py +1 -0
- svc_infra-0.1.672/src/svc_infra/storage/__init__.py +93 -0
- svc_infra-0.1.672/src/svc_infra/storage/add.py +250 -0
- svc_infra-0.1.672/src/svc_infra/storage/backends/__init__.py +11 -0
- svc_infra-0.1.672/src/svc_infra/storage/backends/local.py +331 -0
- svc_infra-0.1.672/src/svc_infra/storage/backends/memory.py +214 -0
- svc_infra-0.1.672/src/svc_infra/storage/backends/s3.py +329 -0
- svc_infra-0.1.672/src/svc_infra/storage/base.py +239 -0
- svc_infra-0.1.672/src/svc_infra/storage/easy.py +182 -0
- svc_infra-0.1.672/src/svc_infra/storage/settings.py +193 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/service.py +10 -2
- svc_infra-0.1.618/docs/api.md +0 -59
- svc_infra-0.1.618/docs/cache.md +0 -18
- svc_infra-0.1.618/src/svc_infra/api/fastapi/cache/add.py +0 -14
- svc_infra-0.1.618/src/svc_infra/api/fastapi/middleware/request_id.py +0 -23
- svc_infra-0.1.618/src/svc_infra/cli/cmds/docs/docs_cmds.py +0 -188
- svc_infra-0.1.618/src/svc_infra/mcp/svc_infra_mcp.py +0 -67
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/alembic.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/models.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/base.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/registry.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/schemas.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/service.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/apf_payments/settings.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/security.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/state.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/http.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/docs/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/public.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/router.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/dx.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/ease.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/pagination.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/paths/user.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/tenancy/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/tenancy/context.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/env.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/filter.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/logging/formats.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/app/root.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/models.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/billing/service.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/bundled_docs/getting-started.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/backend.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/decorators.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/demo.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/keys.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/recache.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/resources.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/tags.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/ttl.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cache/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/__main__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/dx/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/dx/dx_cmds.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/sdk/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/cmds/sdk/sdk_cmds.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/runner.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/backup.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/erasure.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/fixtures.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/data/retention.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/crud_schema.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/inbox.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/base.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/constants.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/core.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/indexes.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/management.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/client.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/repository.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/resource.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/scaffold.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/service.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/types.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/nosql/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/outbox.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/apikey.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/authref.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/base.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/constants.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/core.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/management.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/resource.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/scaffold.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/service.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/tenant.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/types.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/uniq.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/sql/versioning.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/db/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0002-background-jobs-and-scheduling.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0003-webhooks-framework.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0004-tenancy-model.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0006-ops-slos-and-metrics.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0007-docs-and-sdks.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/adr/0009-acceptance-harness.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/auth.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/contributing.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/data-lifecycle.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/database.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/docs-and-sdks.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/environment.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/idempotency.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/jobs.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/observability.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/repo-review.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/tenancy.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672/src/svc_infra}/docs/webhooks.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/changelog.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/dx/checks.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/easy.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/loader.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/redis_queue.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/jobs/scheduler.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/mcp/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/cloud_dash.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/grafana/dashboards/http-overview.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/asgi.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/base.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/http.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/metrics.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
- {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
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/settings.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/py.typed +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/audit.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/audit_service.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/jwt_rotation.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/lockout.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/org_invites.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/passwords.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/session.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/security/signed_cookies.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/utils.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/__init__.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/add.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/fastapi.py +0 -0
- {svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/webhooks/router.py +0 -0
- {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.
|
|
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
|
[](https://pypi.org/project/svc-infra/)
|
|
80
|
-
[](
|
|
83
|
+
[](.)
|
|
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
|
-
|
|
|
89
|
+
| Area | What it covers | Guide |
|
|
87
90
|
| --- | --- | --- |
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
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
|
-
- **
|
|
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
|
+
[](https://pypi.org/project/svc-infra/)
|
|
4
|
+
[](.)
|
|
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.
|
|
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,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,
|
{svc_infra-0.1.618 → svc_infra-0.1.672}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py
RENAMED
|
@@ -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
|
-
|
|
377
|
-
|
|
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
|
-
#
|
|
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)
|