kstlib 2.3.0__tar.gz → 2.4.0__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.
- {kstlib-2.3.0/src/kstlib.egg-info → kstlib-2.4.0}/PKG-INFO +1 -1
- {kstlib-2.3.0 → kstlib-2.4.0}/pyproject.toml +4 -1
- kstlib-2.4.0/src/kstlib/_shared/__init__.py +6 -0
- kstlib-2.4.0/src/kstlib/_shared/jinja.py +91 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/config/loader.py +71 -3
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/limits.py +63 -45
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/__init__.py +2 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/builder.py +235 -70
- kstlib-2.4.0/src/kstlib/mail/collector.py +304 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/meta.py +1 -1
- {kstlib-2.3.0 → kstlib-2.4.0/src/kstlib.egg-info}/PKG-INFO +1 -1
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib.egg-info/SOURCES.txt +3 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/LICENSE.md +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/MANIFEST.in +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/README.md +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/setup.cfg +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/__main__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/channels/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/channels/base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/channels/email.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/channels/slack.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/manager.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/alerts/throttle.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/callback.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/check.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/config.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/errors.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/providers/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/providers/base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/providers/oauth2.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/providers/oidc.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/session.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/auth/token.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cache/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cache/decorator.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cache/strategies.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/app.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/check.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/common.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/login.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/logout.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/providers.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/status.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/token.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/auth/whoami.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/config.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/attach.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/common.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/list_sessions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/logs.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/start.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/status.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/ops/stop.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/rapi/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/rapi/call.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/rapi/list.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/rapi/show.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/common.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/decrypt.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/doctor.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/encrypt.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/commands/secrets/shred.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/cli/common.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/config/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/config/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/config/export.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/config/sops.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/aiosqlcipher.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/cipher.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/database.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/db/pool.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/helpers/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/helpers/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/helpers/time_trigger.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/kstlib.conf.yml +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/logging/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/logging/manager.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/filesystem.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transport.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transports/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transports/gmail.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transports/resend.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transports/ses.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/mail/transports/smtp.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/metrics/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/metrics/decorators.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/metrics/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/_styles.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/cell.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/config.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/delivery.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/image.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/kv.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/list.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/metric.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/monitoring.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/renderer.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/service.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/table.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/monitoring/types.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/container.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/manager.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/tmux.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ops/validators.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/runner.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/steps/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/steps/_base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/steps/callable.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/steps/python.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/steps/shell.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/pipeline/validators.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/py.typed +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/rapi/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/rapi/client.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/rapi/config.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/rapi/credentials.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/rapi/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/circuit_breaker.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/heartbeat.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/rate_limiter.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/shutdown.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/resilience/watchdog.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/base.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/environment.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/keyring.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/kms.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/kwargs.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/providers/sops.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/resolver.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secrets/sensitive.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secure/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secure/fs.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/secure/permissions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ssl.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/chain.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/config.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/primitives.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/transform/validators.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ui/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ui/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ui/panels.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ui/spinner.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/ui/tables.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/dict.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/formatting.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/http_trace.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/lazy.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/secure_delete.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/serialization.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/text.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/utils/validators.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/websocket/__init__.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/websocket/exceptions.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/websocket/manager.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib/websocket/models.py +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib.egg-info/dependency_links.txt +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib.egg-info/entry_points.txt +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib.egg-info/requires.txt +0 -0
- {kstlib-2.3.0 → kstlib-2.4.0}/src/kstlib.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kstlib
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Config-driven helpers for Python projects (dynamic config, secure secrets, preset logging, and more…)
|
|
5
5
|
Author-email: Michel TRUONG <michel.truong@gmail.com>
|
|
6
6
|
Maintainer-email: Michel TRUONG <michel.truong@gmail.com>
|
|
@@ -368,7 +368,10 @@ ignore = [
|
|
|
368
368
|
"src/kstlib/logging/__init__.py" = ["S110"]
|
|
369
369
|
"src/kstlib/logging/manager.py" = ["S110"]
|
|
370
370
|
# Mail builder - snapshot/notify path legitimately reassigns same-class private state
|
|
371
|
-
|
|
371
|
+
# PLR0913: Many kwargs - notify decorator API exposes mode/collector/include_* options (parametrised decorator pattern, mirrors metrics/decorators.py)
|
|
372
|
+
"src/kstlib/mail/builder.py" = ["SLF001", "PLR0913"]
|
|
373
|
+
# Shared Jinja2 helper - autoescape default is intentionally False: kstlib mail templates are author-controlled (dev), not user input. Callers opt in to autoescape=True for HTML user-facing content.
|
|
374
|
+
"src/kstlib/_shared/jinja.py" = ["S701"]
|
|
372
375
|
# Monitoring image - RenderError chain already raised via "from err"; no additional ignores needed here
|
|
373
376
|
# Monitoring renderer - Markup values are escaped before wrapping; jinja2 Environment defaults to autoescape=True
|
|
374
377
|
"src/kstlib/monitoring/renderer.py" = ["S701", "S704"]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Shared cross-module helpers (private to kstlib internals).
|
|
2
|
+
|
|
3
|
+
Modules under ``kstlib._shared`` are imported via their full path
|
|
4
|
+
(e.g. ``from kstlib._shared.jinja import render_jinja``) rather than
|
|
5
|
+
re-exported here. They are not part of the public ``kstlib`` API.
|
|
6
|
+
"""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Shared Jinja2 rendering helpers for kstlib internal modules.
|
|
2
|
+
|
|
3
|
+
Provides a thin wrapper around :class:`jinja2.Environment` with kstlib's
|
|
4
|
+
default policy:
|
|
5
|
+
|
|
6
|
+
- :class:`jinja2.ChainableUndefined`: missing variables resolve to empty
|
|
7
|
+
string (and dotted access on them keeps returning empty), preserving
|
|
8
|
+
graceful behavior for templates with optional fields.
|
|
9
|
+
- ``keep_trailing_newline=True``: file templates retain their trailing
|
|
10
|
+
newline if any.
|
|
11
|
+
- ``autoescape=False`` by default: kstlib mail templates are authored by
|
|
12
|
+
the application developer, not by end users, so the caller controls
|
|
13
|
+
escaping. Pass ``autoescape=True`` for HTML/XML user-facing content.
|
|
14
|
+
|
|
15
|
+
These helpers live under ``kstlib._shared`` because they are reused by
|
|
16
|
+
several internal subpackages (``mail``, future migrations of
|
|
17
|
+
``monitoring``). Import via the full path:
|
|
18
|
+
|
|
19
|
+
from kstlib._shared.jinja import render_jinja, render_jinja_file
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
import jinja2
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Mapping
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_jinja(
|
|
34
|
+
source: str,
|
|
35
|
+
context: Mapping[str, Any],
|
|
36
|
+
*,
|
|
37
|
+
autoescape: bool = False,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Render a Jinja2 template string with the provided context.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
source: Jinja2 template source as a string.
|
|
43
|
+
context: Mapping of variables exposed to the template.
|
|
44
|
+
autoescape: When True, autoescape HTML/XML special characters.
|
|
45
|
+
Default False; kstlib mail templates are author-controlled.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The rendered template as a string.
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
>>> from kstlib._shared.jinja import render_jinja
|
|
52
|
+
>>> render_jinja("Hello {{ name }}", {"name": "Ada"})
|
|
53
|
+
'Hello Ada'
|
|
54
|
+
>>> render_jinja("{{ missing }}", {})
|
|
55
|
+
''
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
env = jinja2.Environment(
|
|
59
|
+
autoescape=autoescape,
|
|
60
|
+
undefined=jinja2.ChainableUndefined,
|
|
61
|
+
keep_trailing_newline=True,
|
|
62
|
+
)
|
|
63
|
+
return env.from_string(source).render(**dict(context))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def render_jinja_file(
|
|
67
|
+
path: Path,
|
|
68
|
+
context: Mapping[str, Any],
|
|
69
|
+
*,
|
|
70
|
+
autoescape: bool = False,
|
|
71
|
+
encoding: str = "utf-8",
|
|
72
|
+
) -> str:
|
|
73
|
+
"""Render a Jinja2 template file with the provided context.
|
|
74
|
+
|
|
75
|
+
Reads the file at ``path`` with the requested encoding and renders
|
|
76
|
+
it via :func:`render_jinja`.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
path: Path to the Jinja2 template file.
|
|
80
|
+
context: Mapping of variables exposed to the template.
|
|
81
|
+
autoescape: When True, autoescape HTML/XML special characters.
|
|
82
|
+
encoding: Text encoding used to read the template file.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The rendered template as a string.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
return render_jinja(path.read_text(encoding=encoding), context, autoescape=autoescape)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = ["render_jinja", "render_jinja_file"]
|
|
@@ -318,6 +318,76 @@ def _load_any_config_file(
|
|
|
318
318
|
return data
|
|
319
319
|
|
|
320
320
|
|
|
321
|
+
def _normalize_includes(raw: Any, source_path: pathlib.Path) -> list[str]:
|
|
322
|
+
"""Normalize the raw ``include`` YAML value to a clean list of paths.
|
|
323
|
+
|
|
324
|
+
Handles the common footgun where an author leaves ``include:`` with an
|
|
325
|
+
empty body (or comments out every entry) and gets a cryptic
|
|
326
|
+
``TypeError: 'NoneType' object is not iterable`` at iteration time.
|
|
327
|
+
Normalization rules:
|
|
328
|
+
|
|
329
|
+
1. Missing key, ``None``, ``[]``, empty string, or whitespace-only
|
|
330
|
+
string produces an empty list silently. Commenting out a key is a
|
|
331
|
+
legitimate editing pattern, not an error worth logging.
|
|
332
|
+
2. A non-empty string is wrapped as a single-element list after
|
|
333
|
+
``strip()``.
|
|
334
|
+
3. A list whose individual items are ``None``, empty, or whitespace-only
|
|
335
|
+
has those entries dropped with a single ``WARNING`` log message
|
|
336
|
+
naming the source file, the drop count, and the original indices.
|
|
337
|
+
4. Any other top-level type (``int``, ``dict``, ...) or any non-string
|
|
338
|
+
list item raises :exc:`ConfigFormatError`, consistent with the rest
|
|
339
|
+
of this loader.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
raw: Raw value of the ``include`` key popped from the YAML mapping.
|
|
343
|
+
source_path: Path of the YAML file that declared the include, used
|
|
344
|
+
in log and error messages for actionable feedback.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Cleaned list of include path strings. Never ``None`` and never
|
|
348
|
+
containing ``None`` or empty entries.
|
|
349
|
+
|
|
350
|
+
Raises:
|
|
351
|
+
ConfigFormatError: If ``raw`` is not ``None``, ``str``, or ``list``,
|
|
352
|
+
or if any list element is not a ``str``.
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
if raw is None:
|
|
356
|
+
return []
|
|
357
|
+
if isinstance(raw, str):
|
|
358
|
+
stripped = raw.strip()
|
|
359
|
+
return [stripped] if stripped else []
|
|
360
|
+
if not isinstance(raw, list):
|
|
361
|
+
raise ConfigFormatError(f"include must be string or list, got {type(raw).__name__} (file: {source_path})")
|
|
362
|
+
|
|
363
|
+
cleaned: list[str] = []
|
|
364
|
+
dropped: list[int] = []
|
|
365
|
+
for idx, item in enumerate(raw):
|
|
366
|
+
if item is None:
|
|
367
|
+
dropped.append(idx)
|
|
368
|
+
continue
|
|
369
|
+
if not isinstance(item, str):
|
|
370
|
+
raise ConfigFormatError(f"include[{idx}] must be string, got {type(item).__name__} (file: {source_path})")
|
|
371
|
+
stripped = item.strip()
|
|
372
|
+
if not stripped:
|
|
373
|
+
dropped.append(idx)
|
|
374
|
+
continue
|
|
375
|
+
cleaned.append(stripped)
|
|
376
|
+
|
|
377
|
+
if dropped:
|
|
378
|
+
# Lazy import: keep loader import graph shallow (see kstlib.limits
|
|
379
|
+
# circular import fix).
|
|
380
|
+
import logging
|
|
381
|
+
|
|
382
|
+
logging.getLogger(__name__).warning(
|
|
383
|
+
"include in %s: dropped %d empty entries at indices %s",
|
|
384
|
+
source_path,
|
|
385
|
+
len(dropped),
|
|
386
|
+
dropped,
|
|
387
|
+
)
|
|
388
|
+
return cleaned
|
|
389
|
+
|
|
390
|
+
|
|
321
391
|
def _load_with_includes(
|
|
322
392
|
path: pathlib.Path,
|
|
323
393
|
loaded_paths: set[pathlib.Path] | None = None,
|
|
@@ -365,9 +435,7 @@ def _load_with_includes(
|
|
|
365
435
|
loaded_paths.add(path)
|
|
366
436
|
|
|
367
437
|
data = _load_any_config_file(path, encoding, sops_decrypt=sops_decrypt)
|
|
368
|
-
includes = data.pop("include", [])
|
|
369
|
-
if isinstance(includes, str):
|
|
370
|
-
includes = [includes]
|
|
438
|
+
includes = _normalize_includes(data.pop("include", []), path)
|
|
371
439
|
|
|
372
440
|
# Get real extension (handles .sops.yml -> .yml)
|
|
373
441
|
parent_ext = get_real_extension(path) if is_sops_file(path) else path.suffix.lower()
|
|
@@ -16,52 +16,26 @@ Example:
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
from collections.abc import Mapping
|
|
20
|
-
from dataclasses import dataclass
|
|
21
|
-
from typing import Any
|
|
22
|
-
|
|
23
|
-
from kstlib.utils.formatting import format_bytes, parse_size_string
|
|
24
|
-
|
|
25
|
-
__all__ = [
|
|
26
|
-
"HARD_MAX_CONFIG_FILE_SIZE",
|
|
27
|
-
"HARD_MAX_DATETIME_FORMAT_LENGTH",
|
|
28
|
-
"HARD_MAX_DISPLAY_VALUE_LENGTH",
|
|
29
|
-
"HARD_MAX_ENDPOINT_REF_LENGTH",
|
|
30
|
-
"HARD_MAX_EPOCH_TIMESTAMP",
|
|
31
|
-
"HARD_MAX_PIPELINE_STEPS",
|
|
32
|
-
"HARD_MAX_PIPELINE_TIMEOUT",
|
|
33
|
-
"HARD_MAX_RAPI_UPLOAD_SIZE",
|
|
34
|
-
"HARD_MAX_STEP_ARGS",
|
|
35
|
-
"HARD_MAX_TIMEZONE_LENGTH",
|
|
36
|
-
"HARD_MIN_EPOCH_TIMESTAMP",
|
|
37
|
-
"HARD_MIN_PIPELINE_TIMEOUT",
|
|
38
|
-
"AlertsLimits",
|
|
39
|
-
"CacheLimits",
|
|
40
|
-
"DatabaseLimits",
|
|
41
|
-
"MailLimits",
|
|
42
|
-
"PipelineLimits",
|
|
43
|
-
"RapiLimits",
|
|
44
|
-
"RapiRenderConfig",
|
|
45
|
-
"ResilienceLimits",
|
|
46
|
-
"SopsLimits",
|
|
47
|
-
"WebSocketLimits",
|
|
48
|
-
"clamp_with_limits",
|
|
49
|
-
"get_alerts_limits",
|
|
50
|
-
"get_cache_limits",
|
|
51
|
-
"get_db_limits",
|
|
52
|
-
"get_mail_limits",
|
|
53
|
-
"get_pipeline_limits",
|
|
54
|
-
"get_rapi_limits",
|
|
55
|
-
"get_rapi_render_config",
|
|
56
|
-
"get_resilience_limits",
|
|
57
|
-
"get_sops_limits",
|
|
58
|
-
"get_websocket_limits",
|
|
59
|
-
]
|
|
60
|
-
|
|
61
19
|
# =============================================================================
|
|
62
|
-
#
|
|
20
|
+
# Pre-import constants (hard limits + defaults)
|
|
21
|
+
#
|
|
22
|
+
# Defined BEFORE any cross-module import to break a latent circular import.
|
|
23
|
+
# During `kstlib.limits` initialisation, importing `kstlib.utils.formatting`
|
|
24
|
+
# transitively triggers `kstlib.config.__init__`, which re-enters
|
|
25
|
+
# `kstlib.limits` from two points:
|
|
26
|
+
# - `kstlib.config.loader` -> reads HARD_MAX_CONFIG_FILE_SIZE
|
|
27
|
+
# - `kstlib.config.sops` -> reads DEFAULT_MAX_SOPS_CACHE_ENTRIES and
|
|
28
|
+
# HARD_MAX_SOPS_CACHE_ENTRIES
|
|
29
|
+
# Both the HARD_* block below and the DEFAULT_* block that follows must
|
|
30
|
+
# therefore be defined before the cross-module import. If these constants
|
|
31
|
+
# sat at their usual position (after imports), Python would raise
|
|
32
|
+
# ImportError on the cycle re-entry into a still-initialising kstlib.limits.
|
|
33
|
+
#
|
|
34
|
+
# See: fix-circular-import-mail request (2026-04-24).
|
|
63
35
|
# =============================================================================
|
|
64
36
|
|
|
37
|
+
# Hard limits (deep defense - cannot be exceeded via config)
|
|
38
|
+
|
|
65
39
|
#: Absolute maximum attachment size (25 MiB) - protects against memory exhaustion.
|
|
66
40
|
HARD_MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024
|
|
67
41
|
|
|
@@ -240,9 +214,7 @@ HARD_MIN_EPOCH_TIMESTAMP = 0
|
|
|
240
214
|
#: Maximum valid epoch timestamp (year 2100) - protects against overflow.
|
|
241
215
|
HARD_MAX_EPOCH_TIMESTAMP = 4102444800
|
|
242
216
|
|
|
243
|
-
# =============================================================================
|
|
244
217
|
# Default limits (used when config is not available)
|
|
245
|
-
# =============================================================================
|
|
246
218
|
|
|
247
219
|
DEFAULT_MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024 # 25 MiB
|
|
248
220
|
DEFAULT_MAX_ATTACHMENTS = 20
|
|
@@ -291,6 +263,52 @@ DEFAULT_WS_DISCONNECT_ALERT_INTERVAL = 300.0 # seconds between throttled alerts
|
|
|
291
263
|
DEFAULT_PIPELINE_TIMEOUT = 300.0 # seconds (5 minutes)
|
|
292
264
|
DEFAULT_PIPELINE_ON_ERROR = "fail_fast"
|
|
293
265
|
|
|
266
|
+
# =============================================================================
|
|
267
|
+
# Cross-module imports (intentionally after pre-import constants, see note above)
|
|
268
|
+
# =============================================================================
|
|
269
|
+
|
|
270
|
+
from collections.abc import Mapping # noqa: E402
|
|
271
|
+
from dataclasses import dataclass # noqa: E402
|
|
272
|
+
from typing import Any # noqa: E402
|
|
273
|
+
|
|
274
|
+
from kstlib.utils.formatting import format_bytes, parse_size_string # noqa: E402
|
|
275
|
+
|
|
276
|
+
__all__ = [
|
|
277
|
+
"HARD_MAX_CONFIG_FILE_SIZE",
|
|
278
|
+
"HARD_MAX_DATETIME_FORMAT_LENGTH",
|
|
279
|
+
"HARD_MAX_DISPLAY_VALUE_LENGTH",
|
|
280
|
+
"HARD_MAX_ENDPOINT_REF_LENGTH",
|
|
281
|
+
"HARD_MAX_EPOCH_TIMESTAMP",
|
|
282
|
+
"HARD_MAX_PIPELINE_STEPS",
|
|
283
|
+
"HARD_MAX_PIPELINE_TIMEOUT",
|
|
284
|
+
"HARD_MAX_RAPI_UPLOAD_SIZE",
|
|
285
|
+
"HARD_MAX_STEP_ARGS",
|
|
286
|
+
"HARD_MAX_TIMEZONE_LENGTH",
|
|
287
|
+
"HARD_MIN_EPOCH_TIMESTAMP",
|
|
288
|
+
"HARD_MIN_PIPELINE_TIMEOUT",
|
|
289
|
+
"AlertsLimits",
|
|
290
|
+
"CacheLimits",
|
|
291
|
+
"DatabaseLimits",
|
|
292
|
+
"MailLimits",
|
|
293
|
+
"PipelineLimits",
|
|
294
|
+
"RapiLimits",
|
|
295
|
+
"RapiRenderConfig",
|
|
296
|
+
"ResilienceLimits",
|
|
297
|
+
"SopsLimits",
|
|
298
|
+
"WebSocketLimits",
|
|
299
|
+
"clamp_with_limits",
|
|
300
|
+
"get_alerts_limits",
|
|
301
|
+
"get_cache_limits",
|
|
302
|
+
"get_db_limits",
|
|
303
|
+
"get_mail_limits",
|
|
304
|
+
"get_pipeline_limits",
|
|
305
|
+
"get_rapi_limits",
|
|
306
|
+
"get_rapi_render_config",
|
|
307
|
+
"get_resilience_limits",
|
|
308
|
+
"get_sops_limits",
|
|
309
|
+
"get_websocket_limits",
|
|
310
|
+
]
|
|
311
|
+
|
|
294
312
|
|
|
295
313
|
@dataclass(frozen=True, slots=True)
|
|
296
314
|
class MailLimits:
|
|
@@ -25,6 +25,7 @@ Examples:
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from kstlib.mail.builder import MailBuilder, NotifyResult
|
|
28
|
+
from kstlib.mail.collector import NotifyCollector
|
|
28
29
|
from kstlib.mail.exceptions import MailConfigurationError, MailError, MailTransportError, MailValidationError
|
|
29
30
|
from kstlib.mail.filesystem import MailFilesystemGuards
|
|
30
31
|
from kstlib.mail.transport import AsyncMailTransport, AsyncTransportWrapper, MailTransport
|
|
@@ -39,5 +40,6 @@ __all__ = [
|
|
|
39
40
|
"MailTransport",
|
|
40
41
|
"MailTransportError",
|
|
41
42
|
"MailValidationError",
|
|
43
|
+
"NotifyCollector",
|
|
42
44
|
"NotifyResult",
|
|
43
45
|
]
|