kstlib 3.3.0__tar.gz → 3.3.1__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-3.3.0/src/kstlib.egg-info → kstlib-3.3.1}/PKG-INFO +1 -1
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/app.py +3 -3
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/config.py +4 -4
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/rapi/__init__.py +2 -2
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/rapi/call.py +52 -70
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/rapi/list.py +3 -3
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/rapi/show.py +6 -6
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/common.py +12 -4
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/meta.py +1 -1
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/rapi/__init__.py +2 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/rapi/config.py +10 -6
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/rapi/exceptions.py +27 -0
- {kstlib-3.3.0 → kstlib-3.3.1/src/kstlib.egg-info}/PKG-INFO +1 -1
- {kstlib-3.3.0 → kstlib-3.3.1}/LICENSE.md +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/MANIFEST.in +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/README.md +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/pyproject.toml +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/setup.cfg +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/__main__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/_shared/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/_shared/jinja.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/_shared/logging_helpers.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/_shared/redaction.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/channels/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/channels/base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/channels/email.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/channels/slack.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/manager.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/alerts/throttle.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/callback.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/check.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/config.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/errors.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/providers/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/providers/base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/providers/oauth2.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/providers/oidc.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/session.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/auth/token.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cache/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cache/decorator.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cache/strategies.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/check.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/common.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/login.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/logout.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/providers.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/status.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/token.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/auth/whoami.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/attach.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/common.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/list_sessions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/logs.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/start.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/status.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/ops/stop.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/common.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/decrypt.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/doctor.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/encrypt.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/cli/commands/secrets/shred.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/config/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/config/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/config/export.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/config/loader.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/config/sops.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/aiosqlcipher.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/cipher.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/database.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/db/pool.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/helpers/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/helpers/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/helpers/time_trigger.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/kstlib.conf.yml +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/limits.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/logging/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/logging/manager.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/_helpers.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/builder.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/collector.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/filesystem.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/throttle.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transport.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transports/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transports/gmail.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transports/resend.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transports/ses.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/mail/transports/smtp.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/metrics/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/metrics/decorators.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/metrics/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/_styles.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/cell.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/config.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/delivery.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/image.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/kv.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/list.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/metric.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/monitoring.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/renderer.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/service.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/table.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/monitoring/types.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/container.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/manager.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/tmux.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ops/validators.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/runner.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/_base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/_helpers.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/callable.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/python.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/steps/shell.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/pipeline/validators.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/py.typed +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/rapi/client.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/rapi/credentials.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/circuit_breaker.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/heartbeat.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/rate_limiter.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/shutdown.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/resilience/watchdog.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/base.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/environment.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/keyring.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/kms.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/kwargs.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/providers/sops.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/resolver.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secrets/sensitive.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secure/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secure/fs.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secure/passwords.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/secure/permissions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ssl.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/chain.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/config.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/primitives.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/transform/validators.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ui/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ui/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ui/panels.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ui/spinner.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/ui/tables.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/dict.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/formatting.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/http_trace.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/lazy.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/secure_delete.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/serialization.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/text.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/utils/validators.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/websocket/__init__.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/websocket/exceptions.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/websocket/manager.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib/websocket/models.py +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib.egg-info/SOURCES.txt +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib.egg-info/dependency_links.txt +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib.egg-info/entry_points.txt +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/src/kstlib.egg-info/requires.txt +0 -0
- {kstlib-3.3.0 → kstlib-3.3.1}/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: 3.3.
|
|
3
|
+
Version: 3.3.1
|
|
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>
|
|
@@ -24,7 +24,7 @@ from kstlib.cli.commands.ops import register_cli as register_ops_cli
|
|
|
24
24
|
from kstlib.cli.commands.rapi import register_cli as register_rapi_cli
|
|
25
25
|
from kstlib.cli.commands.secrets import register_cli as register_secrets_cli
|
|
26
26
|
from kstlib.cli.commands.secrets import shred as secrets_shred
|
|
27
|
-
from kstlib.cli.common import console
|
|
27
|
+
from kstlib.cli.common import console, err_console
|
|
28
28
|
from kstlib.logging import LogManager, get_logger, init_logging
|
|
29
29
|
|
|
30
30
|
app = typer.Typer(add_completion=False, name=meta.__app_name__)
|
|
@@ -223,8 +223,8 @@ def main( # pylint: disable=unused-argument
|
|
|
223
223
|
# Explicit --log-level takes precedence
|
|
224
224
|
level = log_level.upper()
|
|
225
225
|
if level not in LOG_LEVELS:
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
err_console.print(f"[red]Invalid log level: {log_level}[/]")
|
|
227
|
+
err_console.print(f"[dim]Valid levels: {', '.join(LOG_LEVELS)}[/]")
|
|
228
228
|
raise typer.Exit(1)
|
|
229
229
|
elif verbose > 0:
|
|
230
230
|
# -v/-vv/-vvv flags
|
|
@@ -8,7 +8,7 @@ from typing import Annotated
|
|
|
8
8
|
import typer
|
|
9
9
|
from rich.panel import Panel
|
|
10
10
|
|
|
11
|
-
from kstlib.cli.common import console
|
|
11
|
+
from kstlib.cli.common import console, err_console
|
|
12
12
|
from kstlib.config.export import (
|
|
13
13
|
ConfigExportError,
|
|
14
14
|
ConfigExportOptions,
|
|
@@ -59,18 +59,18 @@ def export_command(
|
|
|
59
59
|
try:
|
|
60
60
|
result = export_configuration(options)
|
|
61
61
|
except ConfigExportError as exc:
|
|
62
|
-
|
|
62
|
+
err_console.print(f"[bold red]{exc}[/bold red]")
|
|
63
63
|
raise typer.Exit(code=1) from exc
|
|
64
64
|
|
|
65
65
|
if stdout:
|
|
66
66
|
if result.content is None:
|
|
67
|
-
|
|
67
|
+
err_console.print("[bold red]Export failed: empty content.[/bold red]")
|
|
68
68
|
raise typer.Exit(code=1)
|
|
69
69
|
console.print(result.content)
|
|
70
70
|
return
|
|
71
71
|
|
|
72
72
|
if result.destination is None:
|
|
73
|
-
|
|
73
|
+
err_console.print("[bold red]Export failed: missing destination file.[/bold red]")
|
|
74
74
|
raise typer.Exit(code=1)
|
|
75
75
|
console.print(
|
|
76
76
|
Panel.fit(
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
import typer
|
|
8
8
|
from typer.core import TyperGroup
|
|
9
9
|
|
|
10
|
-
from kstlib.cli.common import
|
|
10
|
+
from kstlib.cli.common import err_console
|
|
11
11
|
from kstlib.rapi import load_rapi_config
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
@@ -35,7 +35,7 @@ def _load_config_or_exit() -> RapiConfigManager:
|
|
|
35
35
|
try:
|
|
36
36
|
return load_rapi_config()
|
|
37
37
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
38
|
-
|
|
38
|
+
err_console.print(f"[red]Failed to load rapi config: {exc}[/]")
|
|
39
39
|
raise typer.Exit(code=1) from exc
|
|
40
40
|
|
|
41
41
|
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
import re
|
|
7
7
|
from typing import TYPE_CHECKING, Annotated, Any, cast
|
|
8
8
|
|
|
9
|
+
import httpx
|
|
9
10
|
import typer
|
|
10
11
|
from typer.core import TyperCommand
|
|
11
12
|
|
|
@@ -16,7 +17,9 @@ from kstlib.rapi import (
|
|
|
16
17
|
CredentialError,
|
|
17
18
|
EndpointAmbiguousError,
|
|
18
19
|
EndpointNotFoundError,
|
|
20
|
+
PathParameterError,
|
|
19
21
|
RapiClient,
|
|
22
|
+
RapiError,
|
|
20
23
|
RapiResponse,
|
|
21
24
|
RequestError,
|
|
22
25
|
ResponseTooLargeError,
|
|
@@ -739,6 +742,44 @@ def _render_response(
|
|
|
739
742
|
_format_output(response, fmt, quiet, out, raw=raw, minify=minify)
|
|
740
743
|
|
|
741
744
|
|
|
745
|
+
def _payload(key: str, value: object) -> dict[str, Any] | None:
|
|
746
|
+
"""Build a single-key error payload, or None when the value is empty."""
|
|
747
|
+
return {key: value} if value else None
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _build_call_error_result(e: RapiError | httpx.InvalidURL) -> CommandResult:
|
|
751
|
+
"""Map a failed rapi call error to its ERROR CommandResult.
|
|
752
|
+
|
|
753
|
+
Handles every exception caught by the ``call`` command except
|
|
754
|
+
``AuthExpiredError`` (which keeps its dedicated exit-code-4 handling).
|
|
755
|
+
Each branch preserves the exact message and payload of the original
|
|
756
|
+
per-type handler.
|
|
757
|
+
"""
|
|
758
|
+
payload: dict[str, Any] | None = None
|
|
759
|
+
if isinstance(e, EndpointNotFoundError):
|
|
760
|
+
message = f"Endpoint not found: {e.endpoint_ref}"
|
|
761
|
+
payload = _payload("searched_apis", e.searched_apis)
|
|
762
|
+
elif isinstance(e, EndpointAmbiguousError):
|
|
763
|
+
message = f"Ambiguous endpoint: '{e.endpoint_name}' exists in multiple APIs"
|
|
764
|
+
payload = {"matching_apis": e.matching_apis}
|
|
765
|
+
elif isinstance(e, ServerNotFoundError):
|
|
766
|
+
message = f"Server profile not found: '{e.server_name}'. Available: {e.available or '(none configured)'}"
|
|
767
|
+
payload = _payload("available_servers", e.available)
|
|
768
|
+
elif isinstance(e, CredentialError):
|
|
769
|
+
message = f"Credential error: {e}"
|
|
770
|
+
payload = _payload("credential_name", e.credential_name)
|
|
771
|
+
elif isinstance(e, RequestError):
|
|
772
|
+
message = f"Request failed: {e}"
|
|
773
|
+
payload = {"status_code": e.status_code, "retryable": e.retryable}
|
|
774
|
+
elif isinstance(e, ResponseTooLargeError):
|
|
775
|
+
message = f"Response too large: {e.response_size} bytes (max: {e.max_size})"
|
|
776
|
+
elif isinstance(e, PathParameterError):
|
|
777
|
+
message = f"Path parameter error: {e}"
|
|
778
|
+
else:
|
|
779
|
+
message = f"Invalid URL: {e}"
|
|
780
|
+
return CommandResult(status=CommandStatus.ERROR, message=message, payload=payload)
|
|
781
|
+
|
|
782
|
+
|
|
742
783
|
def call(
|
|
743
784
|
endpoint: Annotated[
|
|
744
785
|
str,
|
|
@@ -965,78 +1006,19 @@ def call(
|
|
|
965
1006
|
if not response.ok:
|
|
966
1007
|
raise typer.Exit(code=1)
|
|
967
1008
|
|
|
968
|
-
except EndpointNotFoundError as e:
|
|
969
|
-
exit_with_result(
|
|
970
|
-
CommandResult(
|
|
971
|
-
status=CommandStatus.ERROR,
|
|
972
|
-
message=f"Endpoint not found: {e.endpoint_ref}",
|
|
973
|
-
payload={"searched_apis": e.searched_apis} if e.searched_apis else None,
|
|
974
|
-
),
|
|
975
|
-
quiet=quiet,
|
|
976
|
-
exit_code=1,
|
|
977
|
-
cause=e,
|
|
978
|
-
)
|
|
979
|
-
except EndpointAmbiguousError as e:
|
|
980
|
-
exit_with_result(
|
|
981
|
-
CommandResult(
|
|
982
|
-
status=CommandStatus.ERROR,
|
|
983
|
-
message=f"Ambiguous endpoint: '{e.endpoint_name}' exists in multiple APIs",
|
|
984
|
-
payload={"matching_apis": e.matching_apis},
|
|
985
|
-
),
|
|
986
|
-
quiet=quiet,
|
|
987
|
-
exit_code=1,
|
|
988
|
-
cause=e,
|
|
989
|
-
)
|
|
990
|
-
except ServerNotFoundError as e:
|
|
991
|
-
exit_with_result(
|
|
992
|
-
CommandResult(
|
|
993
|
-
status=CommandStatus.ERROR,
|
|
994
|
-
message=(
|
|
995
|
-
f"Server profile not found: '{e.server_name}'. Available: {e.available or '(none configured)'}"
|
|
996
|
-
),
|
|
997
|
-
payload={"available_servers": e.available} if e.available else None,
|
|
998
|
-
),
|
|
999
|
-
quiet=quiet,
|
|
1000
|
-
exit_code=1,
|
|
1001
|
-
cause=e,
|
|
1002
|
-
)
|
|
1003
|
-
except CredentialError as e:
|
|
1004
|
-
exit_with_result(
|
|
1005
|
-
CommandResult(
|
|
1006
|
-
status=CommandStatus.ERROR,
|
|
1007
|
-
message=f"Credential error: {e}",
|
|
1008
|
-
payload={"credential_name": e.credential_name} if e.credential_name else None,
|
|
1009
|
-
),
|
|
1010
|
-
quiet=quiet,
|
|
1011
|
-
exit_code=1,
|
|
1012
|
-
cause=e,
|
|
1013
|
-
)
|
|
1014
1009
|
except AuthExpiredError as e:
|
|
1015
1010
|
_handle_auth_expired_error(e, quiet=quiet)
|
|
1016
|
-
except
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
exit_code=1,
|
|
1028
|
-
cause=e,
|
|
1029
|
-
)
|
|
1030
|
-
except ResponseTooLargeError as e:
|
|
1031
|
-
exit_with_result(
|
|
1032
|
-
CommandResult(
|
|
1033
|
-
status=CommandStatus.ERROR,
|
|
1034
|
-
message=f"Response too large: {e.response_size} bytes (max: {e.max_size})",
|
|
1035
|
-
),
|
|
1036
|
-
quiet=quiet,
|
|
1037
|
-
exit_code=1,
|
|
1038
|
-
cause=e,
|
|
1039
|
-
)
|
|
1011
|
+
except (
|
|
1012
|
+
EndpointNotFoundError,
|
|
1013
|
+
EndpointAmbiguousError,
|
|
1014
|
+
ServerNotFoundError,
|
|
1015
|
+
CredentialError,
|
|
1016
|
+
RequestError,
|
|
1017
|
+
ResponseTooLargeError,
|
|
1018
|
+
PathParameterError,
|
|
1019
|
+
httpx.InvalidURL,
|
|
1020
|
+
) as e:
|
|
1021
|
+
exit_with_result(_build_call_error_result(e), quiet=quiet, exit_code=1, cause=e)
|
|
1040
1022
|
|
|
1041
1023
|
|
|
1042
1024
|
__all__ = ["call"]
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Annotated
|
|
|
7
7
|
import typer
|
|
8
8
|
from rich.table import Table
|
|
9
9
|
|
|
10
|
-
from kstlib.cli.common import console
|
|
10
|
+
from kstlib.cli.common import console, err_console
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from kstlib.rapi.config import ApiConfig, EndpointConfig
|
|
@@ -228,8 +228,8 @@ def list_endpoints(
|
|
|
228
228
|
# Filter by API name if specified
|
|
229
229
|
if api:
|
|
230
230
|
if api not in apis:
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
err_console.print(f"[red]API '{api}' not found.[/]")
|
|
232
|
+
err_console.print(f"[dim]Available APIs: {', '.join(apis.keys())}[/]")
|
|
233
233
|
raise typer.Exit(code=1)
|
|
234
234
|
apis = {api: apis[api]}
|
|
235
235
|
|
|
@@ -11,7 +11,7 @@ from rich.markup import escape
|
|
|
11
11
|
from rich.panel import Panel
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
|
-
from kstlib.cli.common import console
|
|
14
|
+
from kstlib.cli.common import console, err_console
|
|
15
15
|
from kstlib.limits import HARD_MAX_DISPLAY_VALUE_LENGTH, HARD_MAX_ENDPOINT_REF_LENGTH
|
|
16
16
|
from kstlib.rapi import EndpointNotFoundError
|
|
17
17
|
from kstlib.rapi.config import _PATH_PARAM_PATTERN
|
|
@@ -33,12 +33,12 @@ def _truncate(value: str, max_length: int = HARD_MAX_DISPLAY_VALUE_LENGTH) -> st
|
|
|
33
33
|
def _validate_endpoint_ref(endpoint_ref: str) -> None:
|
|
34
34
|
"""Validate endpoint reference for security (deep defense)."""
|
|
35
35
|
if len(endpoint_ref) > HARD_MAX_ENDPOINT_REF_LENGTH:
|
|
36
|
-
|
|
36
|
+
err_console.print(f"[red]Endpoint reference too long: {len(endpoint_ref)} > {HARD_MAX_ENDPOINT_REF_LENGTH}[/]")
|
|
37
37
|
raise typer.Exit(code=1)
|
|
38
38
|
|
|
39
39
|
if not _ENDPOINT_REF_PATTERN.match(endpoint_ref):
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
err_console.print("[red]Endpoint reference contains invalid characters.[/]")
|
|
41
|
+
err_console.print("[dim]Allowed: alphanumeric, underscore, dot, hyphen[/]")
|
|
42
42
|
raise typer.Exit(code=1)
|
|
43
43
|
|
|
44
44
|
|
|
@@ -183,8 +183,8 @@ def show_endpoint(
|
|
|
183
183
|
try:
|
|
184
184
|
api_config, ep_config = config_manager.resolve(endpoint_ref)
|
|
185
185
|
except EndpointNotFoundError as e:
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
err_console.print(f"[red]Endpoint not found: {endpoint_ref}[/]")
|
|
187
|
+
err_console.print(f"[dim]Available APIs: {', '.join(e.searched_apis)}[/]")
|
|
188
188
|
raise typer.Exit(code=1) from e
|
|
189
189
|
|
|
190
190
|
path_params = _PATH_PARAM_PATTERN.findall(ep_config.path)
|
|
@@ -12,6 +12,7 @@ from rich.panel import Panel
|
|
|
12
12
|
from rich.pretty import Pretty
|
|
13
13
|
|
|
14
14
|
console = Console()
|
|
15
|
+
err_console = Console(stderr=True)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class CommandStatus(str, Enum):
|
|
@@ -39,17 +40,23 @@ STYLE_MAP = {
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def render_result(result: CommandResult) -> None:
|
|
42
|
-
"""Render a command result using Rich components.
|
|
43
|
+
"""Render a command result using Rich components.
|
|
44
|
+
|
|
45
|
+
ERROR results render to stderr (keeping stdout clean for shell capture);
|
|
46
|
+
OK and WARNING results render to stdout.
|
|
47
|
+
"""
|
|
43
48
|
style = STYLE_MAP[result.status]
|
|
44
|
-
|
|
49
|
+
target = err_console if result.status is CommandStatus.ERROR else console
|
|
50
|
+
target.print(Panel(result.message, title=result.status.value.upper(), style=style, border_style=style))
|
|
45
51
|
if result.payload is not None:
|
|
46
|
-
|
|
52
|
+
target.print(Pretty(result.payload))
|
|
47
53
|
|
|
48
54
|
|
|
49
55
|
def emit_result(result: CommandResult, quiet: bool) -> None:
|
|
50
56
|
"""Output a command result honoring the quiet flag."""
|
|
51
57
|
if quiet:
|
|
52
|
-
|
|
58
|
+
target = err_console if result.status is CommandStatus.ERROR else console
|
|
59
|
+
target.print(result.message, style=STYLE_MAP[result.status])
|
|
53
60
|
else:
|
|
54
61
|
render_result(result)
|
|
55
62
|
|
|
@@ -80,6 +87,7 @@ __all__ = [
|
|
|
80
87
|
"CommandStatus",
|
|
81
88
|
"console",
|
|
82
89
|
"emit_result",
|
|
90
|
+
"err_console",
|
|
83
91
|
"exit_error",
|
|
84
92
|
"exit_with_result",
|
|
85
93
|
"render_result",
|
|
@@ -98,6 +98,7 @@ from kstlib.rapi.exceptions import (
|
|
|
98
98
|
EndpointNotFoundError,
|
|
99
99
|
EnvVarError,
|
|
100
100
|
MinifyRequiresRawError,
|
|
101
|
+
PathParameterError,
|
|
101
102
|
RapiError,
|
|
102
103
|
RequestError,
|
|
103
104
|
ResponseTooLargeError,
|
|
@@ -119,6 +120,7 @@ __all__ = [
|
|
|
119
120
|
"HmacConfig",
|
|
120
121
|
"MinifyRequiresRawError",
|
|
121
122
|
"MultipartConfig",
|
|
123
|
+
"PathParameterError",
|
|
122
124
|
"RapiClient",
|
|
123
125
|
"RapiConfigManager",
|
|
124
126
|
"RapiError",
|
|
@@ -28,6 +28,7 @@ from kstlib.rapi.exceptions import (
|
|
|
28
28
|
EndpointCollisionError,
|
|
29
29
|
EndpointNotFoundError,
|
|
30
30
|
EnvVarError,
|
|
31
|
+
PathParameterError,
|
|
31
32
|
SafeguardMissingError,
|
|
32
33
|
ServerNotFoundError,
|
|
33
34
|
)
|
|
@@ -53,13 +54,16 @@ def _validate_path_param(name: str, value: str) -> None:
|
|
|
53
54
|
value: Substituted value to check.
|
|
54
55
|
|
|
55
56
|
Raises:
|
|
56
|
-
|
|
57
|
+
PathParameterError: If the value contains null bytes, control
|
|
58
|
+
characters, or path traversal segments.
|
|
57
59
|
|
|
58
60
|
"""
|
|
59
61
|
if "\x00" in value:
|
|
60
|
-
raise
|
|
62
|
+
raise PathParameterError(f"Null bytes not allowed in path parameter '{name}'")
|
|
63
|
+
if any(ord(c) < 0x20 or ord(c) == 0x7F for c in value):
|
|
64
|
+
raise PathParameterError(f"Control characters not allowed in path parameter '{name}'")
|
|
61
65
|
if any(seg in value for seg in _DANGEROUS_PATH_SEGMENTS):
|
|
62
|
-
raise
|
|
66
|
+
raise PathParameterError(f"Path traversal not allowed in parameter '{name}': {value!r}")
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
# Deep defense: allowed values for HMAC config (hardcoded limits)
|
|
@@ -788,7 +792,7 @@ class EndpointConfig:
|
|
|
788
792
|
Formatted path string.
|
|
789
793
|
|
|
790
794
|
Raises:
|
|
791
|
-
|
|
795
|
+
PathParameterError: If required parameters are missing or invalid.
|
|
792
796
|
|
|
793
797
|
Examples:
|
|
794
798
|
>>> config = EndpointConfig(
|
|
@@ -816,7 +820,7 @@ class EndpointConfig:
|
|
|
816
820
|
_validate_path_param(placeholder, value)
|
|
817
821
|
path = path.replace(f"{{{placeholder}}}", value)
|
|
818
822
|
else:
|
|
819
|
-
raise
|
|
823
|
+
raise PathParameterError(f"Missing positional argument {idx} for path {self.path}")
|
|
820
824
|
elif placeholder in kwargs:
|
|
821
825
|
# Named: {name}
|
|
822
826
|
value = str(kwargs[placeholder])
|
|
@@ -829,7 +833,7 @@ class EndpointConfig:
|
|
|
829
833
|
path = path.replace(f"{{{placeholder}}}", value)
|
|
830
834
|
args = args[1:]
|
|
831
835
|
else:
|
|
832
|
-
raise
|
|
836
|
+
raise PathParameterError(f"Missing parameter '{placeholder}' for path {self.path}")
|
|
833
837
|
|
|
834
838
|
return path
|
|
835
839
|
|
|
@@ -462,6 +462,32 @@ class MinifyRequiresRawError(RapiError):
|
|
|
462
462
|
super().__init__(message or self.DEFAULT_MESSAGE)
|
|
463
463
|
|
|
464
464
|
|
|
465
|
+
class PathParameterError(RapiError, ValueError):
|
|
466
|
+
"""Raised when a path parameter is missing or contains an invalid value.
|
|
467
|
+
|
|
468
|
+
Covers both failure modes of :meth:`EndpointConfig.build_path`: a required
|
|
469
|
+
``{name}`` / ``{0}`` placeholder has no supplied value, or a supplied value
|
|
470
|
+
is rejected by validation (null bytes, control characters, path traversal).
|
|
471
|
+
|
|
472
|
+
Subclasses :class:`ValueError` as well as :class:`RapiError` so callers that
|
|
473
|
+
catch ``ValueError`` keep working while the CLI routes it through the typed
|
|
474
|
+
RAPI error chain for a clean message instead of a raw traceback.
|
|
475
|
+
|
|
476
|
+
Attributes:
|
|
477
|
+
message: Human-readable error message.
|
|
478
|
+
details: Additional context (e.g. the offending parameter name).
|
|
479
|
+
|
|
480
|
+
Examples:
|
|
481
|
+
>>> raise PathParameterError("invalid path parameter")
|
|
482
|
+
Traceback (most recent call last):
|
|
483
|
+
...
|
|
484
|
+
kstlib.rapi.exceptions.PathParameterError: invalid path parameter
|
|
485
|
+
>>> isinstance(PathParameterError("x"), ValueError)
|
|
486
|
+
True
|
|
487
|
+
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
|
|
465
491
|
__all__ = [
|
|
466
492
|
"ConfirmationRequiredError",
|
|
467
493
|
"CredentialError",
|
|
@@ -470,6 +496,7 @@ __all__ = [
|
|
|
470
496
|
"EndpointNotFoundError",
|
|
471
497
|
"EnvVarError",
|
|
472
498
|
"MinifyRequiresRawError",
|
|
499
|
+
"PathParameterError",
|
|
473
500
|
"RapiError",
|
|
474
501
|
"RequestError",
|
|
475
502
|
"ResponseTooLargeError",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kstlib
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.1
|
|
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>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|