svc-infra 0.1.602__tar.gz → 0.1.604__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.602 → svc_infra-0.1.604}/PKG-INFO +3 -1
- {svc_infra-0.1.602 → svc_infra-0.1.604}/README.md +2 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/pyproject.toml +3 -1
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/crud_router.py +41 -23
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/docs/add.py +29 -7
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +26 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/data/add.py +8 -6
- svc_infra-0.1.604/src/svc_infra/data/backup.py +53 -0
- svc_infra-0.1.604/src/svc_infra/data/erasure.py +45 -0
- svc_infra-0.1.604/src/svc_infra/data/fixtures.py +40 -0
- svc_infra-0.1.604/src/svc_infra/data/retention.py +55 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/repository.py +3 -2
- svc_infra-0.1.604/src/svc_infra/obs/add.py +115 -0
- svc_infra-0.1.604/src/svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
- svc_infra-0.1.602/src/svc_infra/obs/add.py +0 -68
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/alembic.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/models.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/provider/base.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/provider/registry.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/schemas.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/service.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/apf_payments/settings.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/security.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/auth/state.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/cache/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/http.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/session.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/docs/landing.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/docs/scoped.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/public.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/dx.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/ease.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/idempotency.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/ratelimit.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/request_id.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/mutators.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/ops/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/pagination.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/paths/user.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/setup.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/tenancy/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/api/fastapi/tenancy/context.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/env.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/logging/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/logging/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/logging/filter.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/logging/formats.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/app/root.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/backend.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/decorators.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/demo.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/keys.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/recache.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/resources.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/tags.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/ttl.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cache/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/__main__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/sql/sql_export_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/help.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/cmds/obs/obs_cmds.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/foundation/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/foundation/runner.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/crud_schema.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/inbox.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/base.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/constants.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/core.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/indexes.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/management.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/client.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/repository.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/resource.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/scaffold.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/service.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/types.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/nosql/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/outbox.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/apikey.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/authref.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/base.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/constants.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/core.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/management.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/resource.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/scaffold.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/service.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/tenant.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/types.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/uniq.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/sql/versioning.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/db/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/dx/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/builtins/webhook_delivery.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/easy.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/loader.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/queue.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/redis_queue.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/scheduler.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/jobs/worker.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/mcp/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/cloud_dash.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics/asgi.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics/base.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics/http.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/metrics.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/compose_cloud/templates/docker-compose.cloud.yml.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/settings.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/py.typed +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/audit.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/audit_service.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/headers.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/hibp.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/jwt_rotation.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/lockout.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/models.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/org_invites.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/passwords.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/permissions.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/session.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/security/signed_cookies.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/utils.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/webhooks/__init__.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/webhooks/add.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/webhooks/fastapi.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/webhooks/router.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/src/svc_infra/webhooks/service.py +0 -0
- {svc_infra-0.1.602 → svc_infra-0.1.604}/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.604
|
|
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
|
|
@@ -91,8 +91,10 @@ svc-infra packages the shared building blocks we use to ship production FastAPI
|
|
|
91
91
|
| Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
|
|
92
92
|
| Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
|
|
93
93
|
| Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
|
|
94
|
+
| Ops | Probes, breaker, SLOs & dashboards | [SLOs & Ops](docs/ops.md) |
|
|
94
95
|
| Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
|
|
95
96
|
| Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
|
|
97
|
+
| Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](docs/data-lifecycle.md) |
|
|
96
98
|
|
|
97
99
|
## Minimal FastAPI bootstrap
|
|
98
100
|
|
|
@@ -15,8 +15,10 @@ svc-infra packages the shared building blocks we use to ship production FastAPI
|
|
|
15
15
|
| Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
|
|
16
16
|
| Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
|
|
17
17
|
| Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
|
|
18
|
+
| Ops | Probes, breaker, SLOs & dashboards | [SLOs & Ops](docs/ops.md) |
|
|
18
19
|
| Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
|
|
19
20
|
| Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
|
|
21
|
+
| Data Lifecycle | Fixtures, retention, erasure, backups | [Data lifecycle](docs/data-lifecycle.md) |
|
|
20
22
|
|
|
21
23
|
## Minimal FastAPI bootstrap
|
|
22
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "svc-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.604"
|
|
4
4
|
description = "Infrastructure for building and deploying prod-ready services"
|
|
5
5
|
authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -127,6 +127,8 @@ markers = [
|
|
|
127
127
|
"jobs: Background jobs and scheduling tests",
|
|
128
128
|
"webhooks: Webhooks framework tests",
|
|
129
129
|
"tenancy: Tenancy isolation and enforcement tests",
|
|
130
|
+
"data_lifecycle: Data lifecycle (fixtures, retention, erasure, backups)",
|
|
131
|
+
"ops: SLOs & Ops tests (probes, breaker, instrumentation)",
|
|
130
132
|
]
|
|
131
133
|
filterwarnings = [
|
|
132
134
|
"ignore:The `route` decorator is deprecated:DeprecationWarning:starlette.*",
|
|
@@ -46,6 +46,18 @@ def make_crud_router_plus_sql(
|
|
|
46
46
|
redirect_slashes=False,
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
+
def _coerce_id(v: Any) -> Any:
|
|
50
|
+
"""Best-effort coercion of path ids: cast digit-only strings to int.
|
|
51
|
+
|
|
52
|
+
Keeps original type otherwise to avoid breaking non-integer IDs.
|
|
53
|
+
"""
|
|
54
|
+
if isinstance(v, str) and v.isdigit():
|
|
55
|
+
try:
|
|
56
|
+
return int(v)
|
|
57
|
+
except Exception:
|
|
58
|
+
return v
|
|
59
|
+
return v
|
|
60
|
+
|
|
49
61
|
def _parse_ordering_to_fields(order_spec: Optional[str]) -> list[str]:
|
|
50
62
|
if not order_spec:
|
|
51
63
|
return []
|
|
@@ -87,9 +99,7 @@ def make_crud_router_plus_sql(
|
|
|
87
99
|
else:
|
|
88
100
|
items = await service.list(session, limit=lp.limit, offset=lp.offset, order_by=order_by)
|
|
89
101
|
total = await service.count(session)
|
|
90
|
-
return Page[
|
|
91
|
-
total=total, items=items, limit=lp.limit, offset=lp.offset
|
|
92
|
-
)
|
|
102
|
+
return Page[Any].from_items(total=total, items=items, limit=lp.limit, offset=lp.offset)
|
|
93
103
|
|
|
94
104
|
# -------- GET by id --------
|
|
95
105
|
@router.get(
|
|
@@ -98,7 +108,7 @@ def make_crud_router_plus_sql(
|
|
|
98
108
|
description=f"Get item of type {model.__name__}",
|
|
99
109
|
)
|
|
100
110
|
async def get_item(item_id: Any, session: SqlSessionDep): # type: ignore[name-defined]
|
|
101
|
-
row = await service.get(session, item_id)
|
|
111
|
+
row = await service.get(session, _coerce_id(item_id))
|
|
102
112
|
if not row:
|
|
103
113
|
raise HTTPException(404, "Not found")
|
|
104
114
|
return row
|
|
@@ -139,7 +149,7 @@ def make_crud_router_plus_sql(
|
|
|
139
149
|
data = payload
|
|
140
150
|
else:
|
|
141
151
|
raise HTTPException(422, "invalid_payload")
|
|
142
|
-
row = await service.update(session, item_id, data)
|
|
152
|
+
row = await service.update(session, _coerce_id(item_id), data)
|
|
143
153
|
if not row:
|
|
144
154
|
raise HTTPException(404, "Not found")
|
|
145
155
|
return row
|
|
@@ -149,7 +159,7 @@ def make_crud_router_plus_sql(
|
|
|
149
159
|
"/{item_id}", status_code=204, description=f"Delete item of type {model.__name__}"
|
|
150
160
|
)
|
|
151
161
|
async def delete_item(item_id: Any, session: SqlSessionDep): # type: ignore[name-defined]
|
|
152
|
-
ok = await service.delete(session, item_id)
|
|
162
|
+
ok = await service.delete(session, _coerce_id(item_id))
|
|
153
163
|
if not ok:
|
|
154
164
|
raise HTTPException(404, "Not found")
|
|
155
165
|
return
|
|
@@ -180,6 +190,25 @@ def make_tenant_crud_router_plus_sql(
|
|
|
180
190
|
redirect_slashes=False,
|
|
181
191
|
)
|
|
182
192
|
|
|
193
|
+
# Evaluate the base service once to preserve in-memory state across requests in tests/local.
|
|
194
|
+
# Consumers may pass either an instance or a zero-arg factory function.
|
|
195
|
+
try:
|
|
196
|
+
_base_instance = service_factory() if callable(service_factory) else service_factory # type: ignore[misc]
|
|
197
|
+
except TypeError:
|
|
198
|
+
# If the callable requires args, assume it's already an instance
|
|
199
|
+
_base_instance = service_factory # type: ignore[assignment]
|
|
200
|
+
|
|
201
|
+
def _coerce_id(v: Any) -> Any:
|
|
202
|
+
"""Best-effort coercion of path ids: cast digit-only strings to int.
|
|
203
|
+
Keeps original type otherwise.
|
|
204
|
+
"""
|
|
205
|
+
if isinstance(v, str) and v.isdigit():
|
|
206
|
+
try:
|
|
207
|
+
return int(v)
|
|
208
|
+
except Exception:
|
|
209
|
+
return v
|
|
210
|
+
return v
|
|
211
|
+
|
|
183
212
|
def _parse_ordering_to_fields(order_spec: Optional[str]) -> list[str]:
|
|
184
213
|
if not order_spec:
|
|
185
214
|
return []
|
|
@@ -194,17 +223,8 @@ def make_tenant_crud_router_plus_sql(
|
|
|
194
223
|
|
|
195
224
|
# create per-request service with tenant scoping
|
|
196
225
|
async def _svc(session: SqlSessionDep, tenant_id: TenantId): # type: ignore[name-defined]
|
|
197
|
-
|
|
198
|
-
svc =
|
|
199
|
-
if callable(base):
|
|
200
|
-
svc = base # the consumer likely closed over repo
|
|
201
|
-
# if callable returns a service, call it now
|
|
202
|
-
try:
|
|
203
|
-
svc = base() # type: ignore[misc]
|
|
204
|
-
except TypeError:
|
|
205
|
-
svc = base # already instance
|
|
206
|
-
if not isinstance(svc, TenantSqlService):
|
|
207
|
-
svc = TenantSqlService(getattr(svc, "repo", svc), tenant_id=tenant_id, tenant_field=tenant_field) # type: ignore[arg-type]
|
|
226
|
+
repo_or_service = getattr(_base_instance, "repo", _base_instance)
|
|
227
|
+
svc: Any = TenantSqlService(repo_or_service, tenant_id=tenant_id, tenant_field=tenant_field) # type: ignore[arg-type]
|
|
208
228
|
return svc # type: ignore[return-value]
|
|
209
229
|
|
|
210
230
|
@router.get("", response_model=cast(Any, Page[Any]))
|
|
@@ -232,14 +252,12 @@ def make_tenant_crud_router_plus_sql(
|
|
|
232
252
|
else:
|
|
233
253
|
items = await svc.list(session, limit=lp.limit, offset=lp.offset, order_by=order_by)
|
|
234
254
|
total = await svc.count(session)
|
|
235
|
-
return Page[
|
|
236
|
-
total=total, items=items, limit=lp.limit, offset=lp.offset
|
|
237
|
-
)
|
|
255
|
+
return Page[Any].from_items(total=total, items=items, limit=lp.limit, offset=lp.offset)
|
|
238
256
|
|
|
239
257
|
@router.get("/{item_id}", response_model=cast(Any, Any))
|
|
240
258
|
async def get_item(item_id: Any, session: SqlSessionDep, tenant_id: TenantId): # type: ignore[name-defined]
|
|
241
259
|
svc = await _svc(session, tenant_id)
|
|
242
|
-
row = await svc.get(session, item_id)
|
|
260
|
+
row = await svc.get(session, _coerce_id(item_id))
|
|
243
261
|
if not row:
|
|
244
262
|
raise HTTPException(404, "Not found")
|
|
245
263
|
return row
|
|
@@ -273,7 +291,7 @@ def make_tenant_crud_router_plus_sql(
|
|
|
273
291
|
data = payload
|
|
274
292
|
else:
|
|
275
293
|
raise HTTPException(422, "invalid_payload")
|
|
276
|
-
row = await svc.update(session, item_id, data)
|
|
294
|
+
row = await svc.update(session, _coerce_id(item_id), data)
|
|
277
295
|
if not row:
|
|
278
296
|
raise HTTPException(404, "Not found")
|
|
279
297
|
return row
|
|
@@ -281,7 +299,7 @@ def make_tenant_crud_router_plus_sql(
|
|
|
281
299
|
@router.delete("/{item_id}", status_code=204)
|
|
282
300
|
async def delete_item(item_id: Any, session: SqlSessionDep, tenant_id: TenantId): # type: ignore[name-defined]
|
|
283
301
|
svc = await _svc(session, tenant_id)
|
|
284
|
-
ok = await svc.delete(session, item_id)
|
|
302
|
+
ok = await svc.delete(session, _coerce_id(item_id))
|
|
285
303
|
if not ok:
|
|
286
304
|
raise HTTPException(404, "Not found")
|
|
287
305
|
return
|
|
@@ -5,6 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from fastapi import FastAPI
|
|
8
|
+
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
|
9
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def add_docs(
|
|
@@ -15,21 +17,41 @@ def add_docs(
|
|
|
15
17
|
openapi_url: str = "/openapi.json",
|
|
16
18
|
export_openapi_to: Optional[str] = None,
|
|
17
19
|
) -> None:
|
|
18
|
-
"""Enable docs endpoints and optionally export OpenAPI schema to disk on startup.
|
|
19
|
-
# Configure FastAPI docs URLs
|
|
20
|
-
app.docs_url = swagger_url
|
|
21
|
-
app.redoc_url = redoc_url
|
|
22
|
-
app.openapi_url = openapi_url
|
|
20
|
+
"""Enable docs endpoints and optionally export OpenAPI schema to disk on startup.
|
|
23
21
|
|
|
22
|
+
We mount docs and OpenAPI routes explicitly so this works even when configured post-init.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# OpenAPI JSON route
|
|
26
|
+
async def openapi_handler() -> JSONResponse: # noqa: ANN201
|
|
27
|
+
return JSONResponse(app.openapi())
|
|
28
|
+
|
|
29
|
+
app.add_api_route(openapi_url, openapi_handler, methods=["GET"], include_in_schema=False)
|
|
30
|
+
|
|
31
|
+
# Swagger UI route
|
|
32
|
+
async def swagger_ui() -> HTMLResponse: # noqa: ANN201
|
|
33
|
+
return get_swagger_ui_html(openapi_url=openapi_url, title="API Docs")
|
|
34
|
+
|
|
35
|
+
app.add_api_route(swagger_url, swagger_ui, methods=["GET"], include_in_schema=False)
|
|
36
|
+
|
|
37
|
+
# Redoc route
|
|
38
|
+
async def redoc_ui() -> HTMLResponse: # noqa: ANN201
|
|
39
|
+
return get_redoc_html(openapi_url=openapi_url, title="API ReDoc")
|
|
40
|
+
|
|
41
|
+
app.add_api_route(redoc_url, redoc_ui, methods=["GET"], include_in_schema=False)
|
|
42
|
+
|
|
43
|
+
# Optional export to disk on startup
|
|
24
44
|
if export_openapi_to:
|
|
25
45
|
export_path = Path(export_openapi_to)
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
async def _export_docs() -> None:
|
|
48
|
+
# Startup export
|
|
29
49
|
spec = app.openapi()
|
|
30
50
|
export_path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
51
|
export_path.write_text(json.dumps(spec, indent=2))
|
|
32
52
|
|
|
53
|
+
app.add_event_handler("startup", _export_docs)
|
|
54
|
+
|
|
33
55
|
|
|
34
56
|
def add_sdk_generation_stub(
|
|
35
57
|
app: FastAPI,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from importlib import import_module
|
|
4
5
|
from typing import List, Optional
|
|
5
6
|
|
|
6
7
|
import typer
|
|
@@ -209,3 +210,28 @@ def register(app: typer.Typer) -> None:
|
|
|
209
210
|
app.command("sql-stamp")(cmd_stamp)
|
|
210
211
|
app.command("sql-merge-heads")(cmd_merge_heads)
|
|
211
212
|
app.command("sql-setup-and-migrate")(cmd_setup_and_migrate)
|
|
213
|
+
app.command("sql-seed")(cmd_seed)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _import_callable(path: str):
|
|
217
|
+
mod_name, _, fn_name = path.partition(":")
|
|
218
|
+
if not mod_name or not fn_name:
|
|
219
|
+
raise typer.BadParameter("Expected format 'module.path:callable'")
|
|
220
|
+
mod = import_module(mod_name)
|
|
221
|
+
fn = getattr(mod, fn_name, None)
|
|
222
|
+
if not callable(fn):
|
|
223
|
+
raise typer.BadParameter(f"Callable '{fn_name}' not found in module '{mod_name}'")
|
|
224
|
+
return fn
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def cmd_seed(
|
|
228
|
+
target: str = typer.Argument(..., help="Seed callable path 'module:func'"),
|
|
229
|
+
database_url: Optional[str] = typer.Option(
|
|
230
|
+
None,
|
|
231
|
+
help="Database URL; overrides env for this command.",
|
|
232
|
+
),
|
|
233
|
+
):
|
|
234
|
+
"""Run a user-provided seed function to load fixtures/reference data."""
|
|
235
|
+
apply_database_url(database_url)
|
|
236
|
+
fn = _import_callable(target)
|
|
237
|
+
fn()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
from typing import Callable, Iterable, Optional
|
|
4
5
|
|
|
5
6
|
from fastapi import FastAPI
|
|
@@ -30,10 +31,9 @@ def add_data_lifecycle(
|
|
|
30
31
|
and offers extension points. Jobs should be scheduled using svc_infra.jobs helpers.
|
|
31
32
|
"""
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
async def _run_lifecycle() -> None:
|
|
35
|
+
# Startup
|
|
35
36
|
if auto_migrate:
|
|
36
|
-
# Use existing CLI function to perform end-to-end setup and migrate.
|
|
37
37
|
cmd_setup_and_migrate(
|
|
38
38
|
database_url=database_url,
|
|
39
39
|
overwrite_scaffold=False,
|
|
@@ -44,10 +44,12 @@ def add_data_lifecycle(
|
|
|
44
44
|
discover_packages=discover_packages,
|
|
45
45
|
with_payments=with_payments,
|
|
46
46
|
)
|
|
47
|
-
|
|
48
47
|
if on_load_fixtures:
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
res = on_load_fixtures()
|
|
49
|
+
if inspect.isawaitable(res):
|
|
50
|
+
await res # type: ignore[misc]
|
|
51
|
+
|
|
52
|
+
app.add_event_handler("startup", _run_lifecycle)
|
|
51
53
|
|
|
52
54
|
# Store optional jobs on app.state for external schedulers to discover/register.
|
|
53
55
|
if retention_jobs is not None:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class BackupHealthReport:
|
|
10
|
+
ok: bool
|
|
11
|
+
last_success: Optional[datetime]
|
|
12
|
+
retention_days: Optional[int]
|
|
13
|
+
message: str = ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def verify_backups(
|
|
17
|
+
*, last_success: Optional[datetime] = None, retention_days: Optional[int] = None
|
|
18
|
+
) -> BackupHealthReport:
|
|
19
|
+
"""Return a basic backup health report.
|
|
20
|
+
|
|
21
|
+
In production, callers should plug a provider-specific checker and translate into this report.
|
|
22
|
+
"""
|
|
23
|
+
if last_success is None:
|
|
24
|
+
return BackupHealthReport(
|
|
25
|
+
ok=False, last_success=None, retention_days=retention_days, message="no_backup_seen"
|
|
26
|
+
)
|
|
27
|
+
now = datetime.now(timezone.utc)
|
|
28
|
+
age_days = (now - last_success).total_seconds() / 86400.0
|
|
29
|
+
ok = retention_days is None or age_days <= max(1, retention_days)
|
|
30
|
+
return BackupHealthReport(ok=ok, last_success=last_success, retention_days=retention_days)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["BackupHealthReport", "verify_backups"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def make_backup_verification_job(
|
|
37
|
+
checker: Callable[[], BackupHealthReport],
|
|
38
|
+
*,
|
|
39
|
+
on_report: Optional[Callable[[BackupHealthReport], None]] = None,
|
|
40
|
+
):
|
|
41
|
+
"""Return a callable suitable for scheduling in a job runner.
|
|
42
|
+
|
|
43
|
+
The checker should perform provider-specific checks and return a BackupHealthReport.
|
|
44
|
+
If on_report is provided, it will be invoked with the report.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def _job() -> BackupHealthReport:
|
|
48
|
+
rep = checker()
|
|
49
|
+
if on_report:
|
|
50
|
+
on_report(rep)
|
|
51
|
+
return rep
|
|
52
|
+
|
|
53
|
+
return _job
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Awaitable, Callable, Iterable, Optional, Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SqlSession(Protocol): # minimal protocol for tests/integration
|
|
8
|
+
async def execute(self, stmt: Any) -> Any:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ErasureStep:
|
|
14
|
+
name: str
|
|
15
|
+
run: Callable[[SqlSession, str], Awaitable[int] | int]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class ErasurePlan:
|
|
20
|
+
steps: Iterable[ErasureStep]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def run_erasure(
|
|
24
|
+
session: SqlSession,
|
|
25
|
+
principal_id: str,
|
|
26
|
+
plan: ErasurePlan,
|
|
27
|
+
*,
|
|
28
|
+
on_audit: Optional[Callable[[str, dict[str, Any]], None]] = None,
|
|
29
|
+
) -> int:
|
|
30
|
+
"""Run an erasure plan and optionally emit an audit event.
|
|
31
|
+
|
|
32
|
+
Returns total affected rows across steps.
|
|
33
|
+
"""
|
|
34
|
+
total = 0
|
|
35
|
+
for s in plan.steps:
|
|
36
|
+
res = s.run(session, principal_id)
|
|
37
|
+
if hasattr(res, "__await__"):
|
|
38
|
+
res = await res # type: ignore[misc]
|
|
39
|
+
total += int(res or 0)
|
|
40
|
+
if on_audit:
|
|
41
|
+
on_audit("erasure.completed", {"principal_id": principal_id, "affected": total})
|
|
42
|
+
return total
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["ErasureStep", "ErasurePlan", "run_erasure"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Awaitable, Callable, Iterable, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def run_fixtures(
|
|
9
|
+
loaders: Iterable[Callable[[], None | Awaitable[None]]], *, run_once_file: Optional[str] = None
|
|
10
|
+
) -> None:
|
|
11
|
+
"""Run a sequence of fixture loaders (sync or async).
|
|
12
|
+
|
|
13
|
+
- If run_once_file is provided and exists, does nothing.
|
|
14
|
+
- On success, creates the run_once_file sentinel (parent dirs included).
|
|
15
|
+
"""
|
|
16
|
+
if run_once_file:
|
|
17
|
+
sentinel = Path(run_once_file)
|
|
18
|
+
if sentinel.exists():
|
|
19
|
+
return
|
|
20
|
+
for fn in loaders:
|
|
21
|
+
res = fn()
|
|
22
|
+
if inspect.isawaitable(res): # type: ignore[arg-type]
|
|
23
|
+
await res # type: ignore[misc]
|
|
24
|
+
if run_once_file:
|
|
25
|
+
sentinel.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
Path(run_once_file).write_text("ok")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def make_on_load_fixtures(
|
|
30
|
+
*loaders: Callable[[], None | Awaitable[None]], run_once_file: Optional[str] = None
|
|
31
|
+
) -> Callable[[], Awaitable[None]]:
|
|
32
|
+
"""Return an async callable suitable for add_data_lifecycle(on_load_fixtures=...)."""
|
|
33
|
+
|
|
34
|
+
async def _runner() -> None:
|
|
35
|
+
await run_fixtures(loaders, run_once_file=run_once_file)
|
|
36
|
+
|
|
37
|
+
return _runner
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = ["run_fixtures", "make_on_load_fixtures"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from typing import Any, Iterable, Optional, Protocol, Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SqlSession(Protocol): # minimal protocol for tests/integration
|
|
9
|
+
async def execute(self, stmt: Any) -> Any:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class RetentionPolicy:
|
|
15
|
+
name: str
|
|
16
|
+
model: Any # SQLAlchemy model or test double exposing columns
|
|
17
|
+
older_than_days: int
|
|
18
|
+
soft_delete_field: Optional[str] = "deleted_at"
|
|
19
|
+
extra_where: Optional[Sequence[Any]] = None
|
|
20
|
+
hard_delete: bool = False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def purge_policy(session: SqlSession, policy: RetentionPolicy) -> int:
|
|
24
|
+
"""Execute a single retention purge according to policy.
|
|
25
|
+
|
|
26
|
+
If hard_delete is False and soft_delete_field exists on model, set timestamp; else DELETE.
|
|
27
|
+
Returns number of affected rows (best-effort; test doubles may return an int directly).
|
|
28
|
+
"""
|
|
29
|
+
cutoff = datetime.now(timezone.utc) - timedelta(days=policy.older_than_days)
|
|
30
|
+
m = policy.model
|
|
31
|
+
where = list(policy.extra_where or [])
|
|
32
|
+
created_col = getattr(m, "created_at", None)
|
|
33
|
+
if created_col is not None and hasattr(created_col, "__le__"):
|
|
34
|
+
where.append(created_col <= cutoff) # type: ignore[operator]
|
|
35
|
+
|
|
36
|
+
# Soft-delete path when available and requested
|
|
37
|
+
if not policy.hard_delete and policy.soft_delete_field and hasattr(m, policy.soft_delete_field):
|
|
38
|
+
stmt = m.update().where(*where).values({policy.soft_delete_field: cutoff}) # type: ignore[attr-defined]
|
|
39
|
+
res = await session.execute(stmt)
|
|
40
|
+
return getattr(res, "rowcount", 0)
|
|
41
|
+
|
|
42
|
+
# Hard delete fallback
|
|
43
|
+
stmt = m.delete().where(*where) # type: ignore[attr-defined]
|
|
44
|
+
res = await session.execute(stmt)
|
|
45
|
+
return getattr(res, "rowcount", 0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def run_retention_purge(session: SqlSession, policies: Iterable[RetentionPolicy]) -> int:
|
|
49
|
+
total = 0
|
|
50
|
+
for p in policies:
|
|
51
|
+
total += await purge_policy(session, p)
|
|
52
|
+
return total
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = ["RetentionPolicy", "purge_policy", "run_retention_purge"]
|
|
@@ -124,9 +124,10 @@ class SqlRepository:
|
|
|
124
124
|
return False
|
|
125
125
|
if self.soft_delete:
|
|
126
126
|
# Prefer timestamp, also optionally set flag to False
|
|
127
|
-
|
|
127
|
+
# Check attributes on the instance to support test doubles without class-level fields
|
|
128
|
+
if hasattr(obj, self.soft_delete_field):
|
|
128
129
|
setattr(obj, self.soft_delete_field, func.now())
|
|
129
|
-
if self.soft_delete_flag_field and hasattr(
|
|
130
|
+
if self.soft_delete_flag_field and hasattr(obj, self.soft_delete_flag_field):
|
|
130
131
|
setattr(obj, self.soft_delete_flag_field, False)
|
|
131
132
|
await session.flush()
|
|
132
133
|
return True
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Iterable, Optional, Protocol
|
|
4
|
+
|
|
5
|
+
from svc_infra.obs.settings import ObservabilitySettings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _want_metrics(cfg: ObservabilitySettings) -> bool:
|
|
9
|
+
return bool(cfg.METRICS_ENABLED)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RouteClassifier(Protocol):
|
|
13
|
+
def __call__(
|
|
14
|
+
self, route_path: str, method: str
|
|
15
|
+
) -> str: # e.g., returns "public|internal|admin"
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_observability(
|
|
20
|
+
app: Any | None = None,
|
|
21
|
+
*,
|
|
22
|
+
db_engines: Optional[Iterable[Any]] = None,
|
|
23
|
+
metrics_path: str | None = None,
|
|
24
|
+
skip_metric_paths: Optional[Iterable[str]] = None,
|
|
25
|
+
route_classifier: RouteClassifier | None = None,
|
|
26
|
+
) -> Callable[[], None]:
|
|
27
|
+
"""
|
|
28
|
+
Enable Prometheus metrics for the ASGI app and optional SQLAlchemy pool metrics.
|
|
29
|
+
Returns a no-op shutdown callable for API compatibility.
|
|
30
|
+
"""
|
|
31
|
+
cfg = ObservabilitySettings()
|
|
32
|
+
|
|
33
|
+
# --- Metrics (Prometheus) — import lazily so CLIs/tests don’t require prometheus_client
|
|
34
|
+
if app is not None and _want_metrics(cfg):
|
|
35
|
+
try:
|
|
36
|
+
from svc_infra.obs.metrics.asgi import ( # lazy
|
|
37
|
+
PrometheusMiddleware,
|
|
38
|
+
add_prometheus,
|
|
39
|
+
metrics_endpoint,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
path = metrics_path or cfg.METRICS_PATH
|
|
43
|
+
skip_paths = tuple(skip_metric_paths or (path, "/health", "/healthz"))
|
|
44
|
+
# If a route_classifier is provided, use a custom route_resolver to append class label
|
|
45
|
+
if route_classifier is None:
|
|
46
|
+
add_prometheus(
|
|
47
|
+
app,
|
|
48
|
+
path=path,
|
|
49
|
+
skip_paths=skip_paths,
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
# Install middleware manually to pass route_resolver
|
|
53
|
+
def _resolver(req):
|
|
54
|
+
# Base template
|
|
55
|
+
from svc_infra.obs.metrics.asgi import _route_template # type: ignore
|
|
56
|
+
|
|
57
|
+
base = _route_template(req)
|
|
58
|
+
method = getattr(req, "method", "GET")
|
|
59
|
+
cls = route_classifier(base, method)
|
|
60
|
+
# Encode as base|class for downstream label splitting in dashboards
|
|
61
|
+
return f"{base}|{cls}"
|
|
62
|
+
|
|
63
|
+
app.add_middleware(
|
|
64
|
+
PrometheusMiddleware,
|
|
65
|
+
skip_paths=skip_paths,
|
|
66
|
+
route_resolver=_resolver,
|
|
67
|
+
)
|
|
68
|
+
# Mount /metrics endpoint without re-adding middleware
|
|
69
|
+
try:
|
|
70
|
+
from svc_infra.api.fastapi.dual.public import public_router
|
|
71
|
+
from svc_infra.app.env import CURRENT_ENVIRONMENT, DEV_ENV, LOCAL_ENV
|
|
72
|
+
|
|
73
|
+
router = public_router()
|
|
74
|
+
router.add_api_route(
|
|
75
|
+
path,
|
|
76
|
+
endpoint=metrics_endpoint(),
|
|
77
|
+
include_in_schema=CURRENT_ENVIRONMENT in (LOCAL_ENV, DEV_ENV),
|
|
78
|
+
tags=["observability"],
|
|
79
|
+
)
|
|
80
|
+
app.include_router(router)
|
|
81
|
+
except Exception:
|
|
82
|
+
app.add_route(path, metrics_endpoint())
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# --- DB pool metrics (best effort) — also lazy
|
|
87
|
+
if db_engines:
|
|
88
|
+
try:
|
|
89
|
+
from svc_infra.obs.metrics.sqlalchemy import bind_sqlalchemy_pool_metrics # lazy
|
|
90
|
+
|
|
91
|
+
for eng in db_engines:
|
|
92
|
+
try:
|
|
93
|
+
bind_sqlalchemy_pool_metrics(eng)
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
# --- HTTP client metrics (best effort) — import lazily
|
|
100
|
+
try:
|
|
101
|
+
from svc_infra.obs.metrics.http import instrument_httpx, instrument_requests # lazy
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
instrument_requests()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
try:
|
|
108
|
+
instrument_httpx()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Tracing removed; return no-op for backward compatibility
|
|
115
|
+
return lambda: None
|