nui-python-shared-utils 2.4__tar.gz → 2.4.2__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.
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/.github/workflows/ci.yml +1 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/.github/workflows/publish.yml +1 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/.github/workflows/test.yml +1 -1
- nui_python_shared_utils-2.4.2/.markdownlint-base.yaml +66 -0
- nui_python_shared_utils-2.4.2/.markdownlint-cli2.yaml +17 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/AGENTS.md +5 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/CHANGELOG.md +43 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/CONTRIBUTING.md +1 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/PKG-INFO +4 -5
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/README.md +2 -2
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-004-credential-resolution-precedence.md +20 -5
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-008-stdlib-zoneinfo-over-pytz.md +4 -3
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/getting-started/configuration.md +18 -2
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/getting-started/installation.md +3 -3
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/powertools-integration.md +2 -2
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/shared-types.md +15 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/mypy.ini +1 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_python_shared_utils.egg-info/SOURCES.txt +1 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/config.py +3 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/db_client.py +7 -86
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/es_client.py +15 -17
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/logging/binding.py +0 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/powertools_helpers.py +2 -2
- nui_python_shared_utils-2.4.2/nui_shared_utils/secrets_helper.py +334 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_client.py +7 -8
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/timezone.py +4 -4
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/pyproject.toml +3 -4
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/redirect/pyproject.toml +1 -1
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/setup.py +1 -2
- nui_python_shared_utils-2.4.2/tests/test_aws_utils.py +119 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_db_client.py +97 -23
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_es_client.py +142 -4
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_powertools_helpers.py +17 -7
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_secrets_helper.py +148 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_slack_client.py +66 -51
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_timezone.py +37 -0
- nui_python_shared_utils-2.4/.markdownlint-cli2.yaml +0 -47
- nui_python_shared_utils-2.4/nui_shared_utils/secrets_helper.py +0 -208
- nui_python_shared_utils-2.4/tests/test_aws_utils.py +0 -325
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/.editorconfig +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/CLAUDE.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/LICENSE +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/MANIFEST.in +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/README.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-001-structured-logging-field-vocabulary.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-002-keep-shared-utilities-generic.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-003-lazy-load-integrations.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-005-explicit-secrets-manager-region.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-006-snowflake-sql-api-client.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/ADR-007-elasticsearch-client-version-range.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/decisions/README.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/development/testing.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/getting-started/quickstart.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/cli-tools.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/elasticsearch-integration.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/jwt-authentication.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/lambda-utilities.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/llm-integration.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/log-processing.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/slack-integration.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/snowflake-integration.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/structured-logging-contract.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/__init__.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/llm.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/logging/__init__.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/logging/fields.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/logging/redaction.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/snowflake_client.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/nui_shared_utils/utils.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/pytest.ini +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/redirect/README.md +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/redirect/setup.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/requirements-test.txt +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/scripts/bench_imports.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/setup.cfg +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/__init__.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_base_client.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_config.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_error_handler.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_es_client_compat.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_es_query_builder.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_jwt_auth.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_lambda_helpers.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_lazy_imports.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_llm.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_log_processors.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_logging_binding.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_logging_fields.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_redaction.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_slack_formatter.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_snowflake_client.py +0 -0
- {nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# === managed by sync-markdownlint.sh — DO NOT EDIT ===
|
|
2
|
+
# Source of truth: shared tooling config/markdownlint-base.yaml
|
|
3
|
+
# Change rules in the source, then run: sync-markdownlint.sh --update
|
|
4
|
+
# stamp: e9f1a3abeab6
|
|
5
|
+
# === end managed header (sync-markdownlint.sh) ===
|
|
6
|
+
# Shared markdownlint ruleset (FLAT format).
|
|
7
|
+
# See: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md
|
|
8
|
+
#
|
|
9
|
+
# SOURCE OF TRUTH. Repos adopt this by vendoring a stamped copy as
|
|
10
|
+
# `.markdownlint-base.yaml` (via sync-markdownlint.sh) and pointing their own
|
|
11
|
+
# `.markdownlint-cli2.yaml` at it with `config: { extends: .markdownlint-base.yaml }`,
|
|
12
|
+
# then adding repo-specific `globs`/`ignores` (and any rare repo-only rule tweaks).
|
|
13
|
+
#
|
|
14
|
+
# This file is FLAT (rules at top level, no `config:` wrapper) because that is
|
|
15
|
+
# the format markdownlint's `extends` requires. Keep it rules-only: file
|
|
16
|
+
# selection (globs) and exclusions (ignores) are per-repo.
|
|
17
|
+
|
|
18
|
+
# Enable all rules by default; disables/customisations follow.
|
|
19
|
+
default: true
|
|
20
|
+
|
|
21
|
+
# MD013: Line length - off. Code blocks, tables, and URLs routinely exceed any
|
|
22
|
+
# sane width; wrapping prose to a column is not a standard we enforce.
|
|
23
|
+
MD013: false
|
|
24
|
+
|
|
25
|
+
# MD033: Inline HTML - allowed. Needed for details/summary, anchors, and the
|
|
26
|
+
# HTML comment markers some docs use (companions metadata, stay markers).
|
|
27
|
+
MD033: false
|
|
28
|
+
|
|
29
|
+
# MD034: Bare URLs - off. Bare URLs in reference docs are intentional.
|
|
30
|
+
MD034: false
|
|
31
|
+
|
|
32
|
+
# MD041: First line in file should be a top-level heading - off. Many docs open
|
|
33
|
+
# with YAML frontmatter or a metadata block, not an H1.
|
|
34
|
+
MD041: false
|
|
35
|
+
|
|
36
|
+
# MD024: Multiple headings with the same content - allowed when not siblings
|
|
37
|
+
# (e.g. a repeated "Example" under different sections).
|
|
38
|
+
MD024:
|
|
39
|
+
siblings_only: true
|
|
40
|
+
|
|
41
|
+
# MD046: Code block style - require fenced (not indented) blocks.
|
|
42
|
+
MD046:
|
|
43
|
+
style: "fenced"
|
|
44
|
+
|
|
45
|
+
# MD026: Trailing punctuation in headings - off. "Note:" / "Warning!" are fine.
|
|
46
|
+
MD026: false
|
|
47
|
+
|
|
48
|
+
# MD029: Ordered list item prefix - off. Docs mix "1. 1. 1." and "1. 2. 3." and
|
|
49
|
+
# both render correctly; not worth churning.
|
|
50
|
+
MD029: false
|
|
51
|
+
|
|
52
|
+
# MD036: Emphasis used instead of a heading - off. Bold-as-label
|
|
53
|
+
# ("**Root cause:**", date stamps) is an accepted convention here.
|
|
54
|
+
MD036: false
|
|
55
|
+
|
|
56
|
+
# MD040: Fenced code language - off. Not every code block has (or needs) a
|
|
57
|
+
# language tag; forcing one produces noise on output samples and snippets.
|
|
58
|
+
MD040: false
|
|
59
|
+
|
|
60
|
+
# MD051: Link fragments should be valid - off. Cross-doc anchor validation is
|
|
61
|
+
# unreliable across the workspace's mixed heading styles and generated pages.
|
|
62
|
+
MD051: false
|
|
63
|
+
|
|
64
|
+
# MD060: Table column style - off. Minor pipe-alignment preference; the corpus
|
|
65
|
+
# has always mixed styles.
|
|
66
|
+
MD060: false
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Markdownlint configuration for nui-python-shared-utils
|
|
2
|
+
# Shared rules are vendored by sync-markdownlint.sh.
|
|
3
|
+
|
|
4
|
+
config:
|
|
5
|
+
extends: .markdownlint-base.yaml
|
|
6
|
+
|
|
7
|
+
# File patterns to check
|
|
8
|
+
globs:
|
|
9
|
+
- "**/*.md"
|
|
10
|
+
|
|
11
|
+
# Exclusions
|
|
12
|
+
ignores:
|
|
13
|
+
- "node_modules/**"
|
|
14
|
+
- "vendor/**"
|
|
15
|
+
- ".git/**"
|
|
16
|
+
- "venv/**"
|
|
17
|
+
- ".venv/**"
|
|
@@ -194,6 +194,11 @@ Clients resolve credentials in this order (first match wins):
|
|
|
194
194
|
2. **Environment variables** — per-client patterns, skips Secrets Manager
|
|
195
195
|
3. **AWS Secrets Manager** — default behavior
|
|
196
196
|
|
|
197
|
+
The `get_*_credentials` helpers in `secrets_helper.py` follow tiers 2 and 3 through the same
|
|
198
|
+
per-service env resolver and secret normaliser the clients use. Elasticsearch host:
|
|
199
|
+
`host` argument > `ES_HOST`/`ELASTICSEARCH_HOST` > `configure(es_host=...)` > secret `host` >
|
|
200
|
+
`localhost:9200`.
|
|
201
|
+
|
|
197
202
|
### Environment Variables
|
|
198
203
|
|
|
199
204
|
Runtime configuration:
|
|
@@ -8,6 +8,47 @@ that contained them.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **BREAKING**: `get_database_credentials` and `get_slack_credentials` check environment
|
|
14
|
+
variables before Secrets Manager, matching `DatabaseClient`, `SlackClient` and
|
|
15
|
+
`get_elasticsearch_credentials`. With `DB_HOST` and `DB_PASSWORD` set, or `SLACK_BOT_TOKEN`
|
|
16
|
+
set, they return the env values (MySQL defaults for unset `DB_PORT`, `DB_USERNAME`,
|
|
17
|
+
`DB_DATABASE`) and make no AWS call, even when a `secret_name` is passed, and they no longer
|
|
18
|
+
raise for a missing secret name in that case. A caller that reads a second database or
|
|
19
|
+
workspace through `get_database_credentials("<other secret>")` or
|
|
20
|
+
`get_slack_credentials("<other secret>")` while the `DB_*` or `SLACK_BOT_TOKEN` env vars
|
|
21
|
+
describe the first gets the first; read that secret with `get_secret` and map its fields.
|
|
22
|
+
- `ElasticsearchClient` reads the `host` field of its resolved credentials. Host order on the
|
|
23
|
+
client and `get_elasticsearch_credentials` is: `host` argument (client only), `ES_HOST` or
|
|
24
|
+
`ELASTICSEARCH_HOST`, a host set with `configure(es_host=...)`, the credentials' `host`, then
|
|
25
|
+
`localhost:9200`. A client built from a secret that carries a host, with neither `ES_HOST` nor
|
|
26
|
+
`configure(es_host=...)` set, connects to that host instead of `localhost:9200`. The helper
|
|
27
|
+
appends `:9200` to any host that is not a URL and names no port, including one that starts
|
|
28
|
+
with `http` (`httpd-es`), as the client does.
|
|
29
|
+
- `ElasticsearchClient` and `SlackClient` add the normalised fields the helpers return to
|
|
30
|
+
credentials fetched from Secrets Manager (`host`/`username`/`password`, and
|
|
31
|
+
`bot_token`/`webhook_url`), keeping the secret's other keys. A legacy `token` field is used
|
|
32
|
+
when `bot_token` is missing, null or empty; the helper used to keep a null or empty
|
|
33
|
+
`bot_token`. `SlackClient.credentials` from env vars always carries a `webhook_url` key,
|
|
34
|
+
`None` when `SLACK_WEBHOOK_URL` is unset.
|
|
35
|
+
|
|
36
|
+
- **BREAKING**: require Python 3.10 or newer in both the main package and the legacy
|
|
37
|
+
redirect package. Python 3.9 is no longer supported or included in CI test matrices.
|
|
38
|
+
- Powertools docstring and guide examples use generic service and namespace names
|
|
39
|
+
(`order-processor`, `Ecommerce/Orders`) instead of internal repo names. Docs only, no
|
|
40
|
+
behaviour change.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `PostgreSQLClient` given a secret with no `port` or `database` fills PostgreSQL defaults
|
|
45
|
+
(`5432`, `postgres`) instead of the MySQL ones (`3306`, `app`).
|
|
46
|
+
- Use canonical IANA names `America/New_York` and `America/Los_Angeles` for US timezone
|
|
47
|
+
constants and option labels, so importing timezone utilities and Slack formatting works
|
|
48
|
+
on systems without legacy timezone links.
|
|
49
|
+
|
|
50
|
+
## [2.4] - 2026-10-20
|
|
51
|
+
|
|
11
52
|
### Added
|
|
12
53
|
|
|
13
54
|
- Add an opt-in `reraise_exceptions` parameter to `powertools_handler`. When enabled,
|
|
@@ -230,7 +271,8 @@ that contained them.
|
|
|
230
271
|
- Basic project structure
|
|
231
272
|
- Python package configuration
|
|
232
273
|
|
|
233
|
-
[Unreleased]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.
|
|
274
|
+
[Unreleased]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.4...HEAD
|
|
275
|
+
[2.4]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.3...2.4
|
|
234
276
|
[2.3]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.2...2.3
|
|
235
277
|
[2.2]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.1...2.2
|
|
236
278
|
[2.1]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.0...2.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-python-shared-utils
|
|
3
|
-
Version: 2.4
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: Shared Python utilities for AWS Lambda, CLI tools, and agents with Slack, Elasticsearch, and monitoring integrations
|
|
5
5
|
Home-page: https://github.com/nuimarkets/nui-python-shared-utils
|
|
6
6
|
Author: NUI Markets
|
|
@@ -36,7 +36,6 @@ Classifier: Intended Audience :: Developers
|
|
|
36
36
|
Classifier: License :: OSI Approved :: MIT License
|
|
37
37
|
Classifier: Operating System :: OS Independent
|
|
38
38
|
Classifier: Programming Language :: Python :: 3
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
40
|
Classifier: Programming Language :: Python :: 3.11
|
|
42
41
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -45,7 +44,7 @@ Classifier: Topic :: System :: Monitoring
|
|
|
45
44
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
46
45
|
Classifier: Environment :: Console
|
|
47
46
|
Classifier: Framework :: AWS CDK
|
|
48
|
-
Requires-Python: >=3.
|
|
47
|
+
Requires-Python: >=3.10
|
|
49
48
|
Description-Content-Type: text/markdown
|
|
50
49
|
License-File: LICENSE
|
|
51
50
|
Requires-Dist: boto3>=1.20.0
|
|
@@ -102,7 +101,7 @@ Dynamic: requires-python
|
|
|
102
101
|
# NUI Python Shared Utilities
|
|
103
102
|
|
|
104
103
|
[](https://badge.fury.io/py/nui-python-shared-utils)
|
|
105
|
-
[](https://www.python.org/downloads/)
|
|
106
105
|
[](https://opensource.org/licenses/MIT)
|
|
107
106
|
|
|
108
107
|
Production-ready shared Python utilities for AWS Lambda functions, CLI tools, and agents. Provides standardized integrations for Slack, Elasticsearch, database, and monitoring. Built and battle-tested on the NUI platform with sensible defaults configurable for any environment.
|
|
@@ -222,7 +221,7 @@ from nui_shared_utils import get_secret, get_slack_credentials
|
|
|
222
221
|
api_keys = get_secret("my-service/api-keys")
|
|
223
222
|
|
|
224
223
|
# Specialized getters with normalized field names
|
|
225
|
-
slack_creds = get_slack_credentials() #
|
|
224
|
+
slack_creds = get_slack_credentials() # SLACK_BOT_TOKEN if set, else the configured secret
|
|
226
225
|
```
|
|
227
226
|
|
|
228
227
|
**[→ See full secrets management guide](docs/getting-started/configuration.md#aws-secrets-manager)**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# NUI Python Shared Utilities
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/py/nui-python-shared-utils)
|
|
4
|
-
[](https://www.python.org/downloads/)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
6
|
|
|
7
7
|
Production-ready shared Python utilities for AWS Lambda functions, CLI tools, and agents. Provides standardized integrations for Slack, Elasticsearch, database, and monitoring. Built and battle-tested on the NUI platform with sensible defaults configurable for any environment.
|
|
@@ -121,7 +121,7 @@ from nui_shared_utils import get_secret, get_slack_credentials
|
|
|
121
121
|
api_keys = get_secret("my-service/api-keys")
|
|
122
122
|
|
|
123
123
|
# Specialized getters with normalized field names
|
|
124
|
-
slack_creds = get_slack_credentials() #
|
|
124
|
+
slack_creds = get_slack_credentials() # SLACK_BOT_TOKEN if set, else the configured secret
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
**[→ See full secrets management guide](docs/getting-started/configuration.md#aws-secrets-manager)**
|
|
@@ -24,14 +24,19 @@ The function-based helper historically undercut this. `get_elasticsearch_credent
|
|
|
24
24
|
(`secrets_helper.py`) called Secrets Manager first even when `ES_PASSWORD` was set, so CLI and
|
|
25
25
|
local-dev paths paid an avoidable AWS call. Commit `0d3f8e8` (PR #23) added the env-var check
|
|
26
26
|
ahead of the store lookup, aligning the helper with the precedence the clients already enforced.
|
|
27
|
+
The database and Slack helpers kept calling the store first until they were routed through the
|
|
28
|
+
clients' own env resolvers, and the Elasticsearch client ignored a secret's `host` field that the
|
|
29
|
+
helper honoured.
|
|
27
30
|
|
|
28
31
|
## Decision
|
|
29
32
|
|
|
30
33
|
Credentials resolve in a fixed order, first match wins, owned by `BaseClient._resolve_credentials`
|
|
31
34
|
in `base_client.py`:
|
|
32
35
|
|
|
33
|
-
1. **Explicit `credentials` dict** passed to the constructor. Returned as-is;
|
|
34
|
-
consulted. The caller owns these values and the package does not merge or
|
|
36
|
+
1. **Explicit `credentials` dict** passed to the constructor. Returned as-is; no other credential
|
|
37
|
+
source is consulted. The caller owns these values and the package does not merge or
|
|
38
|
+
second-guess them. An Elasticsearch `host` key in the dict is the one exception: it follows
|
|
39
|
+
the host order below.
|
|
35
40
|
2. **Per-client environment variables**, via each client's `_resolve_credentials_from_env` override
|
|
36
41
|
(Elasticsearch reads `ES_PASSWORD`/`ES_PASS` in `es_client.py`; database requires
|
|
37
42
|
`DB_HOST` + `DB_PASSWORD` in `db_client.py`; Slack requires `SLACK_BOT_TOKEN` in
|
|
@@ -42,9 +47,19 @@ in `base_client.py`:
|
|
|
42
47
|
|
|
43
48
|
A later tier fires only when every earlier tier is absent. An AWS call happens only when neither
|
|
44
49
|
an explicit dict nor env vars resolve, so local-dev, CLI, and test paths that supply explicit or
|
|
45
|
-
environment credentials make **zero** Secrets Manager calls. The direct function
|
|
46
|
-
the tail of this ladder for callers that bypass the client classes:
|
|
47
|
-
`
|
|
50
|
+
environment credentials make **zero** Secrets Manager calls. The direct function helpers mirror
|
|
51
|
+
the tail of this ladder for callers that bypass the client classes: `get_elasticsearch_credentials`,
|
|
52
|
+
`get_database_credentials` and `get_slack_credentials` each check the same env vars as their
|
|
53
|
+
client, then the store. Client hook and helper call the same per-service functions in
|
|
54
|
+
`secrets_helper.py` (an env resolver and a secret normaliser per service, with per-engine defaults
|
|
55
|
+
for MySQL and PostgreSQL), so the two paths cannot drift apart.
|
|
56
|
+
|
|
57
|
+
The Elasticsearch host follows its own order on both paths: the client's `host` argument, then
|
|
58
|
+
`ES_HOST` (or `ELASTICSEARCH_HOST`), then the `host` field of the resolved credentials (the
|
|
59
|
+
secret's, or an explicit dict's), then `localhost:9200`. A host set with `configure(es_host=...)`
|
|
60
|
+
ranks between the env var and the credentials' `host`, because a caller that set it chose it; an
|
|
61
|
+
`ES_HOST` the config picked up from the environment at startup does not. To pin the host of one
|
|
62
|
+
client whatever the environment holds, pass its `host` argument.
|
|
48
63
|
|
|
49
64
|
## Alternatives considered
|
|
50
65
|
|
|
@@ -11,7 +11,7 @@ that wanted only the logging contract or the metrics helper still shipped it.
|
|
|
11
11
|
|
|
12
12
|
The cost is not the code. `pytz` bundles its own copy of the Olson/IANA database (0.86 MB of a
|
|
13
13
|
1.0 MB installed package, measured on `pytz==2025.2`), and that copy is redundant on
|
|
14
|
-
every interpreter this package supports: `requires-python = ">=3.
|
|
14
|
+
every interpreter this package supports: `requires-python = ">=3.10"`, and `zoneinfo` has been in
|
|
15
15
|
the standard library since 3.9, reading the system database that Linux, macOS, and the AWS Lambda
|
|
16
16
|
runtimes all ship.
|
|
17
17
|
|
|
@@ -82,8 +82,9 @@ a database to everyone:
|
|
|
82
82
|
|
|
83
83
|
## Load-bearing premises (supersede triggers)
|
|
84
84
|
|
|
85
|
-
- **
|
|
86
|
-
would need `backports.zoneinfo`, at
|
|
85
|
+
- **All supported interpreters include stdlib `zoneinfo`.** The package requires Python 3.10
|
|
86
|
+
or higher. Supporting an interpreter older than 3.9 would need `backports.zoneinfo`, at
|
|
87
|
+
which point the calculus changes.
|
|
87
88
|
- **Target runtimes ship a system IANA database.** True of Linux, macOS, and the AWS Lambda
|
|
88
89
|
runtimes today. If the primary deployment target became one that does not, `tzdata` belongs in
|
|
89
90
|
the base dependencies unconditionally and this ADR's marker split is obsolete.
|
{nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/getting-started/configuration.md
RENAMED
|
@@ -18,6 +18,18 @@ All clients (Slack, Elasticsearch, Database) resolve credentials in this order:
|
|
|
18
18
|
2. **Credential environment variables** - Per-client env vars, skips Secrets Manager
|
|
19
19
|
3. **AWS Secrets Manager** - Default behavior (unchanged)
|
|
20
20
|
|
|
21
|
+
The helper functions `get_elasticsearch_credentials`, `get_database_credentials` and
|
|
22
|
+
`get_slack_credentials` follow the same order from step 2: when the client's env vars are set
|
|
23
|
+
they return those values without calling Secrets Manager. `get_database_credentials` applies the
|
|
24
|
+
MySQL defaults; `PostgreSQLClient` applies the PostgreSQL ones on both the env and the secret path.
|
|
25
|
+
|
|
26
|
+
The Elasticsearch host resolves as: the client's `host` argument, then `ES_HOST` (or
|
|
27
|
+
`ELASTICSEARCH_HOST`), then a host set with `nui.configure(es_host=...)`, then the secret's
|
|
28
|
+
`host` field, then the built-in default
|
|
29
|
+
(`localhost:9200`). The client and `get_elasticsearch_credentials` use the same order. A `host`
|
|
30
|
+
key in an explicit `credentials` dict ranks where the secret's does, so to pin one client's host
|
|
31
|
+
pass `host=` instead.
|
|
32
|
+
|
|
21
33
|
## Environment Variables
|
|
22
34
|
|
|
23
35
|
### Core Configuration
|
|
@@ -163,7 +175,8 @@ The package expects AWS secrets in specific JSON formats:
|
|
|
163
175
|
|
|
164
176
|
**Optional Fields:**
|
|
165
177
|
|
|
166
|
-
- `host` -
|
|
178
|
+
- `host` - Used when the credentials come from this secret (no `ES_PASSWORD` or `ES_PASS`) and
|
|
179
|
+
no `host=` argument, `ES_HOST`, `ELASTICSEARCH_HOST` or `configure(es_host=...)` names one
|
|
167
180
|
- `username` - Defaults to "elastic" if not provided
|
|
168
181
|
|
|
169
182
|
#### Database Credentials
|
|
@@ -178,7 +191,8 @@ The package expects AWS secrets in specific JSON formats:
|
|
|
178
191
|
}
|
|
179
192
|
```
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
`host`, `username` and `password` are required. A missing `port` or `database` takes the
|
|
195
|
+
engine's default: `3306`/`app` for MySQL, `5432`/`postgres` for `PostgreSQLClient`.
|
|
182
196
|
|
|
183
197
|
#### Slack Credentials
|
|
184
198
|
|
|
@@ -274,6 +288,8 @@ slack = nui.SlackClient(secret_name="custom-slack-secret")
|
|
|
274
288
|
es_creds = nui.get_elasticsearch_credentials("custom-es-secret")
|
|
275
289
|
db_creds = nui.get_database_credentials("custom-db-secret")
|
|
276
290
|
slack_creds = nui.get_slack_credentials("custom-slack-secret")
|
|
291
|
+
# These three return the env values instead, ignoring the secret name, when the
|
|
292
|
+
# client's credential env vars are set (ES_PASSWORD, DB_HOST + DB_PASSWORD, SLACK_BOT_TOKEN).
|
|
277
293
|
|
|
278
294
|
# Generic secret retrieval
|
|
279
295
|
api_keys = nui.get_secret("my-service/api-keys")
|
{nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/getting-started/installation.md
RENAMED
|
@@ -4,7 +4,7 @@ This guide covers installation, dependency management, and environment setup for
|
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
|
-
- Python 3.
|
|
7
|
+
- Python 3.10 or higher
|
|
8
8
|
- AWS credentials configured for Secrets Manager access
|
|
9
9
|
- Optional: AWS CLI for credential management
|
|
10
10
|
|
|
@@ -139,7 +139,7 @@ pip install nui-python-shared-utils[all]
|
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
141
|
# Create environment
|
|
142
|
-
conda create -n nui-utils python=3.
|
|
142
|
+
conda create -n nui-utils python=3.10
|
|
143
143
|
|
|
144
144
|
# Activate environment
|
|
145
145
|
conda activate nui-utils
|
|
@@ -209,7 +209,7 @@ aws lambda publish-layer-version \
|
|
|
209
209
|
--layer-name nui-python-shared-utils \
|
|
210
210
|
--description "NUI Lambda Shared Utilities" \
|
|
211
211
|
--zip-file fileb://nui-utils-layer.zip \
|
|
212
|
-
--compatible-runtimes python3.
|
|
212
|
+
--compatible-runtimes python3.10 python3.11
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
### Using in Lambda Function
|
{nui_python_shared_utils-2.4 → nui_python_shared_utils-2.4.2}/docs/guides/powertools-integration.md
RENAMED
|
@@ -80,7 +80,7 @@ def get_powertools_logger(
|
|
|
80
80
|
Create AWS Powertools Logger with Elasticsearch-compatible formatting.
|
|
81
81
|
|
|
82
82
|
Args:
|
|
83
|
-
service_name: Service identifier (e.g., "
|
|
83
|
+
service_name: Service identifier (e.g., "order-processor")
|
|
84
84
|
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
85
85
|
local_dev_colors: Enable coloredlogs for local development
|
|
86
86
|
|
|
@@ -177,7 +177,7 @@ def powertools_handler(
|
|
|
177
177
|
|
|
178
178
|
Args:
|
|
179
179
|
service_name: Service identifier for logging and metrics dimensions
|
|
180
|
-
metrics_namespace: CloudWatch namespace (e.g., "
|
|
180
|
+
metrics_namespace: CloudWatch namespace (e.g., "Ecommerce/Orders")
|
|
181
181
|
slack_alert_channel: Slack channel for error alerts (e.g., "#alerts")
|
|
182
182
|
slack_account_names: AWS account ID to display-name mapping for alerts
|
|
183
183
|
slack_account_names_config: YAML file containing account-name defaults
|
|
@@ -103,6 +103,7 @@ client.get_connection_info() -> Dict
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
**Connection Pool Entry Structure:**
|
|
106
|
+
|
|
106
107
|
```python
|
|
107
108
|
{
|
|
108
109
|
"connection": pymysql.Connection,
|
|
@@ -111,6 +112,7 @@ client.get_connection_info() -> Dict
|
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
**Connection Info Response:**
|
|
115
|
+
|
|
114
116
|
```python
|
|
115
117
|
{
|
|
116
118
|
"host": str,
|
|
@@ -169,6 +171,7 @@ client.get_indices_info(pattern="*") -> List[Dict]
|
|
|
169
171
|
```
|
|
170
172
|
|
|
171
173
|
**Service Stats Response:**
|
|
174
|
+
|
|
172
175
|
```python
|
|
173
176
|
{
|
|
174
177
|
"service": str,
|
|
@@ -183,6 +186,7 @@ client.get_indices_info(pattern="*") -> List[Dict]
|
|
|
183
186
|
```
|
|
184
187
|
|
|
185
188
|
**Cluster Info Response:**
|
|
189
|
+
|
|
186
190
|
```python
|
|
187
191
|
{
|
|
188
192
|
"version": str,
|
|
@@ -222,6 +226,7 @@ client.get_bot_info() -> Dict
|
|
|
222
226
|
```
|
|
223
227
|
|
|
224
228
|
**Lambda Context Structure:**
|
|
229
|
+
|
|
225
230
|
```python
|
|
226
231
|
{
|
|
227
232
|
"function_name": str,
|
|
@@ -240,6 +245,7 @@ client.get_bot_info() -> Dict
|
|
|
240
245
|
```
|
|
241
246
|
|
|
242
247
|
**Bot Info Response:**
|
|
248
|
+
|
|
243
249
|
```python
|
|
244
250
|
{
|
|
245
251
|
"bot_id": str,
|
|
@@ -274,6 +280,7 @@ result = matcher.categorize_error(exception)
|
|
|
274
280
|
```
|
|
275
281
|
|
|
276
282
|
**Categorized Error Response:**
|
|
283
|
+
|
|
277
284
|
```python
|
|
278
285
|
{
|
|
279
286
|
"error": str,
|
|
@@ -286,6 +293,7 @@ result = matcher.categorize_error(exception)
|
|
|
286
293
|
```
|
|
287
294
|
|
|
288
295
|
**Built-in Error Categories:**
|
|
296
|
+
|
|
289
297
|
- `data_format` - JSON parsing errors
|
|
290
298
|
- `authentication` - Auth failures
|
|
291
299
|
- `database` - DB timeouts
|
|
@@ -310,6 +318,7 @@ summary = aggregator.get_summary()
|
|
|
310
318
|
```
|
|
311
319
|
|
|
312
320
|
**Error Summary Response:**
|
|
321
|
+
|
|
313
322
|
```python
|
|
314
323
|
{
|
|
315
324
|
"total_errors": int,
|
|
@@ -360,6 +369,7 @@ publisher.flush() -> bool
|
|
|
360
369
|
```
|
|
361
370
|
|
|
362
371
|
**Metric Data Structure (internal buffer):**
|
|
372
|
+
|
|
363
373
|
```python
|
|
364
374
|
{
|
|
365
375
|
"MetricName": str,
|
|
@@ -429,6 +439,7 @@ with TimedMetric(publisher, "DatabaseQuery", unit="Milliseconds", dimensions=Non
|
|
|
429
439
|
### Credential Structures
|
|
430
440
|
|
|
431
441
|
**Database Credentials:**
|
|
442
|
+
|
|
432
443
|
```python
|
|
433
444
|
{
|
|
434
445
|
"host": str,
|
|
@@ -440,6 +451,7 @@ with TimedMetric(publisher, "DatabaseQuery", unit="Milliseconds", dimensions=Non
|
|
|
440
451
|
```
|
|
441
452
|
|
|
442
453
|
**Elasticsearch Credentials:**
|
|
454
|
+
|
|
443
455
|
```python
|
|
444
456
|
{
|
|
445
457
|
"host": str, # With port (e.g., "elastic:9200")
|
|
@@ -449,6 +461,7 @@ with TimedMetric(publisher, "DatabaseQuery", unit="Milliseconds", dimensions=Non
|
|
|
449
461
|
```
|
|
450
462
|
|
|
451
463
|
**Slack Credentials:**
|
|
464
|
+
|
|
452
465
|
```python
|
|
453
466
|
{
|
|
454
467
|
"bot_token": str,
|
|
@@ -492,6 +505,7 @@ stats = get_pool_stats()
|
|
|
492
505
|
```
|
|
493
506
|
|
|
494
507
|
**Pool Stats Response:**
|
|
508
|
+
|
|
495
509
|
```python
|
|
496
510
|
{
|
|
497
511
|
"total_pools": int,
|
|
@@ -514,6 +528,7 @@ response = handle_lambda_error(error, context)
|
|
|
514
528
|
```
|
|
515
529
|
|
|
516
530
|
**Lambda Error Response:**
|
|
531
|
+
|
|
517
532
|
```python
|
|
518
533
|
{
|
|
519
534
|
"statusCode": 500,
|
|
@@ -34,7 +34,9 @@ class Config:
|
|
|
34
34
|
slack_credentials_secret: AWS secret name for Slack credentials
|
|
35
35
|
aws_region: AWS region for secrets/services
|
|
36
36
|
"""
|
|
37
|
-
# Elasticsearch settings
|
|
37
|
+
# Elasticsearch settings. An es_host passed here outranks a host carried in a
|
|
38
|
+
# credentials secret; the env-derived and built-in values do not.
|
|
39
|
+
self._es_host_explicit = es_host is not None
|
|
38
40
|
if es_host is not None:
|
|
39
41
|
self.es_host = es_host
|
|
40
42
|
else:
|