svc-infra 0.1.597__tar.gz → 0.1.599__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.599/PKG-INFO +128 -0
- svc_infra-0.1.599/README.md +51 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/pyproject.toml +2 -1
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/inbox.py +12 -0
- svc_infra-0.1.599/src/svc_infra/jobs/builtins/webhook_delivery.py +78 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/README.md +2 -0
- svc_infra-0.1.599/src/svc_infra/security/add.py +201 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/__init__.py +16 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/add.py +322 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/fastapi.py +37 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/router.py +55 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/service.py +59 -0
- svc_infra-0.1.599/src/svc_infra/webhooks/signing.py +30 -0
- svc_infra-0.1.597/PKG-INFO +0 -80
- svc_infra-0.1.597/README.md +0 -3
- svc_infra-0.1.597/src/svc_infra/jobs/builtins/webhook_delivery.py +0 -59
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/alembic.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/models.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/provider/base.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/provider/registry.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/schemas.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/service.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/apf_payments/settings.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/security.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/auth/state.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/cache/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/http.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/crud_router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/session.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/docs/landing.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/docs/scoped.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/public.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/router.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/dx.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/ease.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/idempotency.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/ratelimit.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/request_id.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/mutators.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/pagination.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/paths/user.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/api/fastapi/setup.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/env.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/logging/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/logging/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/logging/filter.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/logging/formats.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/app/root.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/backend.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/decorators.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/demo.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/keys.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/recache.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/resources.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/tags.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/ttl.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cache/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/__main__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/help.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/cmds/obs/obs_cmds.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/foundation/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/foundation/runner.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/crud_schema.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/base.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/constants.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/core.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/indexes.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/management.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/client.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/repository.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/resource.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/scaffold.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/service.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/types.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/nosql/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/outbox.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/apikey.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/authref.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/base.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/constants.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/core.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/management.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/repository.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/resource.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/scaffold.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/service.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/types.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/uniq.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/sql/versioning.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/db/utils.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/easy.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/loader.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/queue.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/redis_queue.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/scheduler.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/jobs/worker.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/mcp/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/add.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/cloud_dash.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics/asgi.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics/base.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics/http.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/metrics.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/compose_cloud/templates/docker-compose.cloud.yml.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/settings.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/py.typed +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/audit.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/audit_service.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/headers.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/hibp.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/jwt_rotation.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/lockout.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/models.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/org_invites.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/passwords.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/permissions.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/session.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/security/signed_cookies.py +0 -0
- {svc_infra-0.1.597 → svc_infra-0.1.599}/src/svc_infra/utils.py +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: svc-infra
|
|
3
|
+
Version: 0.1.599
|
|
4
|
+
Summary: Infrastructure for building and deploying prod-ready services
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: fastapi,sqlalchemy,alembic,auth,infra,async,pydantic
|
|
7
|
+
Author: Ali Khatami
|
|
8
|
+
Author-email: aliikhatami94@gmail.com
|
|
9
|
+
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Provides-Extra: duckdb
|
|
21
|
+
Provides-Extra: metrics
|
|
22
|
+
Provides-Extra: mssql
|
|
23
|
+
Provides-Extra: mysql
|
|
24
|
+
Provides-Extra: pg
|
|
25
|
+
Provides-Extra: pg2
|
|
26
|
+
Provides-Extra: redshift
|
|
27
|
+
Provides-Extra: snowflake
|
|
28
|
+
Provides-Extra: sqlite
|
|
29
|
+
Requires-Dist: adyen (>=13.4.0,<14.0.0)
|
|
30
|
+
Requires-Dist: ai-infra (>=0.1.63,<0.2.0)
|
|
31
|
+
Requires-Dist: aiosqlite (>=0.20.0,<0.21.0) ; extra == "sqlite"
|
|
32
|
+
Requires-Dist: alembic (>=1.13.2,<2.0.0)
|
|
33
|
+
Requires-Dist: asyncpg (>=0.30.0,<0.31.0) ; extra == "pg"
|
|
34
|
+
Requires-Dist: authlib (>=1.6.2,<2.0.0)
|
|
35
|
+
Requires-Dist: cashews[redis] (>=7.4.1,<8.0.0)
|
|
36
|
+
Requires-Dist: duckdb (>=1.1.3,<2.0.0) ; extra == "duckdb"
|
|
37
|
+
Requires-Dist: email-validator (>=2.2.0,<3.0.0)
|
|
38
|
+
Requires-Dist: fastapi (>=0.116.1,<0.117.0)
|
|
39
|
+
Requires-Dist: fastapi-users-db-sqlalchemy (>=7.0.0,<8.0.0)
|
|
40
|
+
Requires-Dist: fastapi-users[oauth] (>=14.0.1,<15.0.0)
|
|
41
|
+
Requires-Dist: greenlet (>=3,<4)
|
|
42
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
43
|
+
Requires-Dist: httpx-oauth (>=0.16.1,<0.17.0)
|
|
44
|
+
Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
|
|
45
|
+
Requires-Dist: mcp (>=1.13.0,<2.0.0)
|
|
46
|
+
Requires-Dist: motor (>=3.7.1,<4.0.0)
|
|
47
|
+
Requires-Dist: mysqlclient (>=2.2.4,<3.0.0) ; extra == "mysql"
|
|
48
|
+
Requires-Dist: opentelemetry-exporter-otlp (>=1.36.0,<2.0.0)
|
|
49
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi (>=0.57b0,<0.58)
|
|
50
|
+
Requires-Dist: opentelemetry-instrumentation-httpx (>=0.57b0,<0.58)
|
|
51
|
+
Requires-Dist: opentelemetry-instrumentation-requests (>=0.57b0,<0.58)
|
|
52
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy (>=0.57b0,<0.58)
|
|
53
|
+
Requires-Dist: opentelemetry-propagator-b3 (>=1.36.0,<2.0.0)
|
|
54
|
+
Requires-Dist: opentelemetry-sdk (>=1.36.0,<2.0.0)
|
|
55
|
+
Requires-Dist: passlib[bcrypt] (>=1.7.4,<2.0.0)
|
|
56
|
+
Requires-Dist: pre-commit (>=4.3.0,<5.0.0)
|
|
57
|
+
Requires-Dist: prometheus-client (>=0.22.1,<0.23.0) ; extra == "metrics"
|
|
58
|
+
Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0) ; extra == "pg2"
|
|
59
|
+
Requires-Dist: psycopg[binary] (>=3.2.10,<4.0.0) ; extra == "pg"
|
|
60
|
+
Requires-Dist: pydantic-settings (>=2.10.1,<3.0.0)
|
|
61
|
+
Requires-Dist: pymysql (>=1.1.1,<2.0.0) ; extra == "mysql"
|
|
62
|
+
Requires-Dist: pyodbc (>=5.1.0,<6.0.0) ; extra == "mssql"
|
|
63
|
+
Requires-Dist: pyotp (>=2.9.0,<3.0.0)
|
|
64
|
+
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
65
|
+
Requires-Dist: redis (>=6.4.0,<7.0.0)
|
|
66
|
+
Requires-Dist: redshift-connector (>=2.0.918,<3.0.0) ; extra == "redshift"
|
|
67
|
+
Requires-Dist: snowflake-connector-python (>=3.12.0,<4.0.0) ; extra == "snowflake"
|
|
68
|
+
Requires-Dist: sqlalchemy[asyncio] (>=2.0.43,<3.0.0)
|
|
69
|
+
Requires-Dist: stripe (>=13.0.1,<14.0.0)
|
|
70
|
+
Requires-Dist: typer (>=0.16.1,<0.17.0)
|
|
71
|
+
Project-URL: Documentation, https://github.com/your-org/svc-infra#readme
|
|
72
|
+
Project-URL: Homepage, https://github.com/your-org/svc-infra
|
|
73
|
+
Project-URL: Issues, https://github.com/your-org/svc-infra/issues
|
|
74
|
+
Project-URL: Repository, https://github.com/your-org/svc-infra
|
|
75
|
+
Description-Content-Type: text/markdown
|
|
76
|
+
|
|
77
|
+
# svc-infra
|
|
78
|
+
|
|
79
|
+
[](https://pypi.org/project/svc-infra/)
|
|
80
|
+
[](docs/)
|
|
81
|
+
|
|
82
|
+
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
|
+
|
|
84
|
+
## Helper index
|
|
85
|
+
|
|
86
|
+
| Helper | What it covers | Guide |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| API | FastAPI bootstrap, envelopes, middleware, docs wiring | [FastAPI guide](docs/api.md) |
|
|
89
|
+
| Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth settings](docs/auth.md) |
|
|
90
|
+
| Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database guide](docs/database.md) |
|
|
91
|
+
| Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
|
|
92
|
+
| Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
|
|
93
|
+
| Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
|
|
94
|
+
| Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
|
|
95
|
+
| Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
|
|
96
|
+
|
|
97
|
+
## Minimal FastAPI bootstrap
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from fastapi import Depends
|
|
101
|
+
from svc_infra.api.fastapi.ease import easy_service_app
|
|
102
|
+
from svc_infra.api.fastapi.db.sql.add import add_sql_db
|
|
103
|
+
from svc_infra.cache import init_cache
|
|
104
|
+
from svc_infra.jobs.easy import easy_jobs
|
|
105
|
+
from svc_infra.webhooks.fastapi import require_signature
|
|
106
|
+
|
|
107
|
+
app = easy_service_app(name="Billing", release="1.2.3")
|
|
108
|
+
add_sql_db(app) # reads SQL_URL / DB_* envs
|
|
109
|
+
init_cache() # honors CACHE_PREFIX / CACHE_VERSION
|
|
110
|
+
queue, scheduler = easy_jobs() # switches via JOBS_DRIVER / REDIS_URL
|
|
111
|
+
|
|
112
|
+
@app.post("/webhooks/billing")
|
|
113
|
+
async def handle_webhook(payload = Depends(require_signature(lambda: ["current", "next"]))):
|
|
114
|
+
queue.enqueue("process-billing-webhook", payload)
|
|
115
|
+
return {"status": "queued"}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Environment switches
|
|
119
|
+
|
|
120
|
+
- **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】
|
|
121
|
+
- **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】
|
|
122
|
+
- **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】
|
|
123
|
+
- **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:docs/jobs.md†L11-L48】
|
|
124
|
+
- **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】
|
|
125
|
+
- **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】
|
|
126
|
+
- **Webhooks** – reuse the jobs envs (`JOBS_DRIVER`, `REDIS_URL`) for the delivery worker and queue configuration. 【F:docs/webhooks.md†L32-L53】
|
|
127
|
+
- **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】
|
|
128
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# svc-infra
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/svc-infra/)
|
|
4
|
+
[](docs/)
|
|
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
|
+
| Helper | What it covers | Guide |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| API | FastAPI bootstrap, envelopes, middleware, docs wiring | [FastAPI guide](docs/api.md) |
|
|
13
|
+
| Auth | Sessions, OAuth/OIDC, MFA, SMTP delivery | [Auth settings](docs/auth.md) |
|
|
14
|
+
| Database | SQL + Mongo wiring, Alembic helpers, inbox/outbox patterns | [Database guide](docs/database.md) |
|
|
15
|
+
| Jobs | JobQueue, scheduler, CLI worker | [Jobs quickstart](docs/jobs.md) |
|
|
16
|
+
| Cache | cashews decorators, namespace management, TTL helpers | [Cache guide](docs/cache.md) |
|
|
17
|
+
| Observability | Prometheus middleware, Grafana automation, OTEL hooks | [Observability guide](docs/observability.md) |
|
|
18
|
+
| Webhooks | Subscription store, signing, retry worker | [Webhooks framework](docs/webhooks.md) |
|
|
19
|
+
| Security | Password policy, lockout, signed cookies, headers | [Security hardening](docs/security.md) |
|
|
20
|
+
|
|
21
|
+
## Minimal FastAPI bootstrap
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from fastapi import Depends
|
|
25
|
+
from svc_infra.api.fastapi.ease import easy_service_app
|
|
26
|
+
from svc_infra.api.fastapi.db.sql.add import add_sql_db
|
|
27
|
+
from svc_infra.cache import init_cache
|
|
28
|
+
from svc_infra.jobs.easy import easy_jobs
|
|
29
|
+
from svc_infra.webhooks.fastapi import require_signature
|
|
30
|
+
|
|
31
|
+
app = easy_service_app(name="Billing", release="1.2.3")
|
|
32
|
+
add_sql_db(app) # reads SQL_URL / DB_* envs
|
|
33
|
+
init_cache() # honors CACHE_PREFIX / CACHE_VERSION
|
|
34
|
+
queue, scheduler = easy_jobs() # switches via JOBS_DRIVER / REDIS_URL
|
|
35
|
+
|
|
36
|
+
@app.post("/webhooks/billing")
|
|
37
|
+
async def handle_webhook(payload = Depends(require_signature(lambda: ["current", "next"]))):
|
|
38
|
+
queue.enqueue("process-billing-webhook", payload)
|
|
39
|
+
return {"status": "queued"}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Environment switches
|
|
43
|
+
|
|
44
|
+
- **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】
|
|
45
|
+
- **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】
|
|
46
|
+
- **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】
|
|
47
|
+
- **Jobs** – choose the queue backend with `JOBS_DRIVER` and provide Redis via `REDIS_URL`; interval schedules can be declared with `JOBS_SCHEDULE_JSON`. 【F:src/svc_infra/jobs/easy.py†L11-L27】【F:docs/jobs.md†L11-L48】
|
|
48
|
+
- **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】
|
|
49
|
+
- **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】
|
|
50
|
+
- **Webhooks** – reuse the jobs envs (`JOBS_DRIVER`, `REDIS_URL`) for the delivery worker and queue configuration. 【F:docs/webhooks.md†L32-L53】
|
|
51
|
+
- **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】
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "svc-infra"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.599"
|
|
4
4
|
description = "Infrastructure for building and deploying prod-ready services"
|
|
5
5
|
authors = ["Ali Khatami <aliikhatami94@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -125,6 +125,7 @@ markers = [
|
|
|
125
125
|
"ratelimit: Rate limiting and abuse protection tests",
|
|
126
126
|
"concurrency: Idempotency and concurrency control tests",
|
|
127
127
|
"jobs: Background jobs and scheduling tests",
|
|
128
|
+
"webhooks: Webhooks framework tests",
|
|
128
129
|
]
|
|
129
130
|
filterwarnings = [
|
|
130
131
|
"ignore:The `route` decorator is deprecated:DeprecationWarning:starlette.*",
|
|
@@ -13,6 +13,10 @@ class InboxStore(Protocol):
|
|
|
13
13
|
"""Optional: remove expired keys, return number purged."""
|
|
14
14
|
...
|
|
15
15
|
|
|
16
|
+
def is_marked(self, key: str) -> bool:
|
|
17
|
+
"""Return True if key is already marked (not expired), without modifying it."""
|
|
18
|
+
...
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
class InMemoryInboxStore:
|
|
18
22
|
def __init__(self) -> None:
|
|
@@ -33,6 +37,11 @@ class InMemoryInboxStore:
|
|
|
33
37
|
self._keys.pop(k, None)
|
|
34
38
|
return len(to_del)
|
|
35
39
|
|
|
40
|
+
def is_marked(self, key: str) -> bool:
|
|
41
|
+
now = time.time()
|
|
42
|
+
exp = self._keys.get(key)
|
|
43
|
+
return bool(exp and exp > now)
|
|
44
|
+
|
|
36
45
|
|
|
37
46
|
class SqlInboxStore:
|
|
38
47
|
"""Skeleton for a SQL-backed inbox store (dedupe table).
|
|
@@ -53,3 +62,6 @@ class SqlInboxStore:
|
|
|
53
62
|
|
|
54
63
|
def purge_expired(self) -> int: # pragma: no cover - skeleton
|
|
55
64
|
raise NotImplementedError
|
|
65
|
+
|
|
66
|
+
def is_marked(self, key: str) -> bool: # pragma: no cover - skeleton
|
|
67
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from svc_infra.db.inbox import InboxStore
|
|
6
|
+
from svc_infra.db.outbox import OutboxStore
|
|
7
|
+
from svc_infra.jobs.queue import Job
|
|
8
|
+
from svc_infra.webhooks.signing import sign
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_webhook_handler(
|
|
12
|
+
*,
|
|
13
|
+
outbox: OutboxStore,
|
|
14
|
+
inbox: InboxStore,
|
|
15
|
+
get_webhook_url_for_topic,
|
|
16
|
+
get_secret_for_topic,
|
|
17
|
+
header_name: str = "X-Signature",
|
|
18
|
+
):
|
|
19
|
+
"""Return an async job handler to deliver webhooks.
|
|
20
|
+
|
|
21
|
+
Expected job payload shape:
|
|
22
|
+
{"outbox_id": int, "topic": str, "payload": {...}}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
async def _handler(job: Job) -> None:
|
|
26
|
+
data = job.payload or {}
|
|
27
|
+
outbox_id = data.get("outbox_id")
|
|
28
|
+
topic = data.get("topic")
|
|
29
|
+
payload = data.get("payload") or {}
|
|
30
|
+
if not outbox_id or not topic:
|
|
31
|
+
# Nothing we can do; ack to avoid poison loop
|
|
32
|
+
return
|
|
33
|
+
# dedupe marker key (marked after successful delivery)
|
|
34
|
+
key = f"webhook:{outbox_id}"
|
|
35
|
+
if inbox.is_marked(key):
|
|
36
|
+
# already delivered
|
|
37
|
+
outbox.mark_processed(int(outbox_id))
|
|
38
|
+
return
|
|
39
|
+
event = payload.get("event") if isinstance(payload, dict) else None
|
|
40
|
+
subscription = payload.get("subscription") if isinstance(payload, dict) else None
|
|
41
|
+
if event is not None and subscription is not None:
|
|
42
|
+
delivery_payload = event
|
|
43
|
+
url = subscription.get("url") or get_webhook_url_for_topic(topic)
|
|
44
|
+
secret = subscription.get("secret") or get_secret_for_topic(topic)
|
|
45
|
+
subscription_id = subscription.get("id")
|
|
46
|
+
else:
|
|
47
|
+
delivery_payload = payload
|
|
48
|
+
url = get_webhook_url_for_topic(topic)
|
|
49
|
+
secret = get_secret_for_topic(topic)
|
|
50
|
+
subscription_id = None
|
|
51
|
+
sig = sign(secret, delivery_payload)
|
|
52
|
+
headers = {
|
|
53
|
+
header_name: sig,
|
|
54
|
+
"X-Event-Id": str(outbox_id),
|
|
55
|
+
"X-Topic": str(topic),
|
|
56
|
+
"X-Attempt": str(job.attempts or 1),
|
|
57
|
+
"X-Signature-Alg": "hmac-sha256",
|
|
58
|
+
"X-Signature-Version": "v1",
|
|
59
|
+
}
|
|
60
|
+
if subscription_id:
|
|
61
|
+
headers["X-Webhook-Subscription"] = str(subscription_id)
|
|
62
|
+
# include event payload version if present
|
|
63
|
+
version = None
|
|
64
|
+
if isinstance(delivery_payload, dict):
|
|
65
|
+
version = delivery_payload.get("version")
|
|
66
|
+
if version is not None:
|
|
67
|
+
headers["X-Payload-Version"] = str(version)
|
|
68
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
69
|
+
resp = await client.post(url, json=delivery_payload, headers=headers)
|
|
70
|
+
if 200 <= resp.status_code < 300:
|
|
71
|
+
# record delivery and mark processed
|
|
72
|
+
inbox.mark_if_new(key, ttl_seconds=24 * 3600)
|
|
73
|
+
outbox.mark_processed(int(outbox_id))
|
|
74
|
+
return
|
|
75
|
+
# allow retry on non-2xx: raise to trigger fail/backoff
|
|
76
|
+
raise RuntimeError(f"webhook delivery failed: {resp.status_code}")
|
|
77
|
+
|
|
78
|
+
return _handler
|
|
@@ -8,6 +8,8 @@ This guide shows you how to turn on metrics + dashboards in three easy modes:
|
|
|
8
8
|
|
|
9
9
|
It's "one button": run `svc-infra obs-up` and you're good. The CLI will read your `.env` automatically and do the right thing.
|
|
10
10
|
|
|
11
|
+
> ℹ️ A complete list of observability-related environment variables lives in [Environment Reference](../../../docs/environment.md).
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## 0) Install & instrument your app (once)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
9
|
+
from starlette.middleware.sessions import SessionMiddleware
|
|
10
|
+
|
|
11
|
+
from svc_infra.security.headers import SECURE_DEFAULTS, SecurityHeadersMiddleware
|
|
12
|
+
|
|
13
|
+
DEFAULT_SESSION_SECRET = "svc-dev-secret-change-me"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _parse_bool(value: str | None) -> bool | None:
|
|
17
|
+
if value is None:
|
|
18
|
+
return None
|
|
19
|
+
lowered = value.strip().lower()
|
|
20
|
+
if lowered in {"1", "true", "yes", "on"}:
|
|
21
|
+
return True
|
|
22
|
+
if lowered in {"0", "false", "no", "off"}:
|
|
23
|
+
return False
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_origins(value: Iterable[str] | str | None) -> list[str]:
|
|
28
|
+
if value is None:
|
|
29
|
+
return []
|
|
30
|
+
if isinstance(value, str):
|
|
31
|
+
parts = [p.strip() for p in value.split(",")]
|
|
32
|
+
else:
|
|
33
|
+
parts = [str(v).strip() for v in value]
|
|
34
|
+
return [p for p in parts if p]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_cors_origins(
|
|
38
|
+
provided: Iterable[str] | str | None,
|
|
39
|
+
env: Mapping[str, str],
|
|
40
|
+
) -> list[str]:
|
|
41
|
+
if provided is not None:
|
|
42
|
+
return _normalize_origins(provided)
|
|
43
|
+
return _normalize_origins(env.get("CORS_ALLOW_ORIGINS"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _resolve_allow_credentials(
|
|
47
|
+
allow_credentials: bool,
|
|
48
|
+
env: Mapping[str, str],
|
|
49
|
+
) -> bool:
|
|
50
|
+
env_value = _parse_bool(env.get("CORS_ALLOW_CREDENTIALS"))
|
|
51
|
+
if env_value is None:
|
|
52
|
+
return allow_credentials
|
|
53
|
+
# Allow explicit overrides via function arguments.
|
|
54
|
+
if allow_credentials is not True:
|
|
55
|
+
return allow_credentials
|
|
56
|
+
return env_value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _configure_cors(
|
|
60
|
+
app: FastAPI,
|
|
61
|
+
*,
|
|
62
|
+
cors_origins: Iterable[str] | str | None,
|
|
63
|
+
allow_credentials: bool,
|
|
64
|
+
env: Mapping[str, str],
|
|
65
|
+
) -> None:
|
|
66
|
+
origins = _resolve_cors_origins(cors_origins, env)
|
|
67
|
+
if not origins:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
allow_methods = _normalize_origins(env.get("CORS_ALLOW_METHODS")) or ["*"]
|
|
71
|
+
allow_headers = _normalize_origins(env.get("CORS_ALLOW_HEADERS")) or ["*"]
|
|
72
|
+
|
|
73
|
+
credentials = _resolve_allow_credentials(allow_credentials, env)
|
|
74
|
+
|
|
75
|
+
wildcard_origins = "*" in origins
|
|
76
|
+
|
|
77
|
+
cors_kwargs: dict[str, object] = {
|
|
78
|
+
"allow_credentials": credentials,
|
|
79
|
+
"allow_methods": allow_methods,
|
|
80
|
+
"allow_headers": allow_headers,
|
|
81
|
+
"allow_origins": ["*"] if wildcard_origins else origins,
|
|
82
|
+
}
|
|
83
|
+
origin_regex = env.get("CORS_ALLOW_ORIGIN_REGEX")
|
|
84
|
+
if wildcard_origins:
|
|
85
|
+
cors_kwargs["allow_origin_regex"] = origin_regex or ".*"
|
|
86
|
+
else:
|
|
87
|
+
if origin_regex:
|
|
88
|
+
cors_kwargs["allow_origin_regex"] = origin_regex
|
|
89
|
+
|
|
90
|
+
app.add_middleware(CORSMiddleware, **cors_kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _configure_security_headers(
|
|
94
|
+
app: FastAPI,
|
|
95
|
+
*,
|
|
96
|
+
overrides: dict[str, str] | None,
|
|
97
|
+
enable_hsts_preload: bool | None,
|
|
98
|
+
) -> None:
|
|
99
|
+
merged_overrides = dict(overrides or {})
|
|
100
|
+
if enable_hsts_preload is not None:
|
|
101
|
+
current = merged_overrides.get(
|
|
102
|
+
"Strict-Transport-Security",
|
|
103
|
+
SECURE_DEFAULTS["Strict-Transport-Security"],
|
|
104
|
+
)
|
|
105
|
+
directives = [p.strip() for p in current.split(";") if p.strip()]
|
|
106
|
+
directives = [d for d in directives if d.lower() != "preload"]
|
|
107
|
+
if enable_hsts_preload:
|
|
108
|
+
directives.append("preload")
|
|
109
|
+
merged_overrides["Strict-Transport-Security"] = "; ".join(directives)
|
|
110
|
+
|
|
111
|
+
app.add_middleware(SecurityHeadersMiddleware, overrides=merged_overrides)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _should_add_session_middleware(app: FastAPI) -> bool:
|
|
115
|
+
return not any(m.cls is SessionMiddleware for m in app.user_middleware)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _configure_session_middleware(
|
|
119
|
+
app: FastAPI,
|
|
120
|
+
*,
|
|
121
|
+
env: Mapping[str, str],
|
|
122
|
+
install: bool,
|
|
123
|
+
secret_key: str | None,
|
|
124
|
+
session_cookie: str,
|
|
125
|
+
max_age: int,
|
|
126
|
+
same_site: str,
|
|
127
|
+
https_only: bool | None,
|
|
128
|
+
) -> None:
|
|
129
|
+
if not install or not _should_add_session_middleware(app):
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
secret = secret_key or env.get("SESSION_SECRET") or DEFAULT_SESSION_SECRET
|
|
133
|
+
https_env = _parse_bool(env.get("SESSION_COOKIE_SECURE"))
|
|
134
|
+
effective_https_only = (
|
|
135
|
+
https_only if https_only is not None else (https_env if https_env is not None else False)
|
|
136
|
+
)
|
|
137
|
+
same_site_env = env.get("SESSION_COOKIE_SAMESITE")
|
|
138
|
+
same_site_value = same_site_env.strip() if same_site_env else same_site
|
|
139
|
+
|
|
140
|
+
max_age_env = env.get("SESSION_COOKIE_MAX_AGE_SECONDS")
|
|
141
|
+
try:
|
|
142
|
+
max_age_value = int(max_age_env) if max_age_env is not None else max_age
|
|
143
|
+
except ValueError:
|
|
144
|
+
max_age_value = max_age
|
|
145
|
+
|
|
146
|
+
session_cookie_env = env.get("SESSION_COOKIE_NAME")
|
|
147
|
+
session_cookie_value = session_cookie_env.strip() if session_cookie_env else session_cookie
|
|
148
|
+
|
|
149
|
+
app.add_middleware(
|
|
150
|
+
SessionMiddleware,
|
|
151
|
+
secret_key=secret,
|
|
152
|
+
session_cookie=session_cookie_value,
|
|
153
|
+
max_age=max_age_value,
|
|
154
|
+
same_site=same_site_value,
|
|
155
|
+
https_only=effective_https_only,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def add_security(
|
|
160
|
+
app: FastAPI,
|
|
161
|
+
*,
|
|
162
|
+
cors_origins: Iterable[str] | str | None = None,
|
|
163
|
+
headers_overrides: dict[str, str] | None = None,
|
|
164
|
+
allow_credentials: bool = True,
|
|
165
|
+
env: Mapping[str, str] = os.environ,
|
|
166
|
+
enable_hsts_preload: bool | None = None,
|
|
167
|
+
install_session_middleware: bool = False,
|
|
168
|
+
session_secret_key: str | None = None,
|
|
169
|
+
session_cookie_name: str = "svc_session",
|
|
170
|
+
session_cookie_max_age_seconds: int = 4 * 3600,
|
|
171
|
+
session_cookie_samesite: str = "lax",
|
|
172
|
+
session_cookie_https_only: bool | None = None,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Install security middlewares with svc-infra defaults."""
|
|
175
|
+
|
|
176
|
+
_configure_security_headers(
|
|
177
|
+
app,
|
|
178
|
+
overrides=headers_overrides,
|
|
179
|
+
enable_hsts_preload=enable_hsts_preload,
|
|
180
|
+
)
|
|
181
|
+
_configure_cors(
|
|
182
|
+
app,
|
|
183
|
+
cors_origins=cors_origins,
|
|
184
|
+
allow_credentials=allow_credentials,
|
|
185
|
+
env=env,
|
|
186
|
+
)
|
|
187
|
+
_configure_session_middleware(
|
|
188
|
+
app,
|
|
189
|
+
env=env,
|
|
190
|
+
install=install_session_middleware,
|
|
191
|
+
secret_key=session_secret_key,
|
|
192
|
+
session_cookie=session_cookie_name,
|
|
193
|
+
max_age=session_cookie_max_age_seconds,
|
|
194
|
+
same_site=session_cookie_samesite,
|
|
195
|
+
https_only=session_cookie_https_only,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
__all__ = [
|
|
200
|
+
"add_security",
|
|
201
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING: # pragma: no cover - for type checkers only
|
|
6
|
+
from .add import add_webhooks
|
|
7
|
+
|
|
8
|
+
__all__ = ["add_webhooks"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def __getattr__(name: str):
|
|
12
|
+
if name == "add_webhooks":
|
|
13
|
+
from .add import add_webhooks
|
|
14
|
+
|
|
15
|
+
return add_webhooks
|
|
16
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|