nui-python-shared-utils 2.2__tar.gz → 2.4__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/CHANGELOG.md +251 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/PKG-INFO +1 -1
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/powertools-integration.md +61 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_python_shared_utils.egg-info/SOURCES.txt +1 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/powertools_helpers.py +38 -2
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_powertools_helpers.py +222 -1
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/.editorconfig +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/.github/workflows/ci.yml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/.github/workflows/publish.yml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/.github/workflows/test.yml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/.markdownlint-cli2.yaml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/AGENTS.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/CLAUDE.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/CONTRIBUTING.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/LICENSE +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/MANIFEST.in +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/README.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/README.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-001-structured-logging-field-vocabulary.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-002-keep-shared-utilities-generic.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-003-lazy-load-integrations.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-004-credential-resolution-precedence.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-005-explicit-secrets-manager-region.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-006-snowflake-sql-api-client.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-007-elasticsearch-client-version-range.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/ADR-008-stdlib-zoneinfo-over-pytz.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/decisions/README.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/development/testing.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/configuration.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/installation.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/quickstart.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/cli-tools.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/elasticsearch-integration.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/jwt-authentication.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/lambda-utilities.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/llm-integration.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/log-processing.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/shared-types.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/slack-integration.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/snowflake-integration.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/structured-logging-contract.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/mypy.ini +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/__init__.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/config.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/db_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/es_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/llm.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/__init__.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/binding.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/fields.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/redaction.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/secrets_helper.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/snowflake_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/timezone.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/utils.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/pyproject.toml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/pytest.ini +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/redirect/README.md +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/redirect/pyproject.toml +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/redirect/setup.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/requirements-test.txt +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/scripts/bench_imports.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/setup.cfg +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/setup.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/__init__.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_aws_utils.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_base_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_config.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_db_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_error_handler.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_es_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_es_client_compat.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_es_query_builder.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_jwt_auth.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_lambda_helpers.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_lazy_imports.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_llm.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_log_processors.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_logging_binding.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_logging_fields.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_redaction.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_secrets_helper.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_slack_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_slack_formatter.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_snowflake_client.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_timezone.py +0 -0
- {nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and breaking changes are called out explicitly below, including historical minor releases
|
|
7
|
+
that contained them.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add an opt-in `reraise_exceptions` parameter to `powertools_handler`. When enabled,
|
|
14
|
+
handler failures are logged and alerted before the original exception is re-raised so
|
|
15
|
+
stream and asynchronous event sources can apply their configured failure handling.
|
|
16
|
+
The default remains the existing 500 response, while SQS events continue to re-raise
|
|
17
|
+
unconditionally.
|
|
18
|
+
|
|
19
|
+
## [2.3] - 2026-10-12
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `powertools_handler` re-raises exceptions for SQS event-source mappings after logging and
|
|
24
|
+
alerting, so failed batches are redelivered and can reach a configured DLQ instead of
|
|
25
|
+
being acknowledged by the wrapper's 500 response.
|
|
26
|
+
|
|
27
|
+
## [2.2] - 2026-09-14
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Add response-returning Slack write methods (`post_message`, `post_thread_reply`,
|
|
32
|
+
`post_update`, and `post_file`) for callers that need message timestamps, channel IDs,
|
|
33
|
+
or the raw upload response. The existing bool-returning `send_*` methods retain their
|
|
34
|
+
error-swallowing behavior.
|
|
35
|
+
- Add `SentMessage` as the result type for posted messages and thread replies.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- `send_file` and `post_file` accept bytes and can upload into a thread with `thread_ts`.
|
|
40
|
+
|
|
41
|
+
## [2.1] - 2026-08-03
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- **BREAKING**: replace `pytz` with the standard-library `zoneinfo` API and remove `pytz`
|
|
46
|
+
from the base dependencies. Code importing timezone constants must replace `.localize()`
|
|
47
|
+
with `tzinfo=...`, passing `fold=1` for repeated wall times where the old `is_dst=False`
|
|
48
|
+
default mattered, and replace `.zone` with `.key`. Declare `pytz` directly if application
|
|
49
|
+
code still imports it. The `[timezone]` extra supplies `tzdata` for minimal images without
|
|
50
|
+
a system IANA database.
|
|
51
|
+
- Allow Elasticsearch client majors 7.17, 8.x, and 9.x through the `[elasticsearch]` and
|
|
52
|
+
`all` extras. See
|
|
53
|
+
[ADR-007](docs/decisions/ADR-007-elasticsearch-client-version-range.md).
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Send Elasticsearch search `size` inside a copied request body, avoiding caller-dict
|
|
58
|
+
mutation and duplicate-value errors on client majors 8.x and 9.x.
|
|
59
|
+
- Return a plain `list` from `ElasticsearchClient.get_indices_info()` on every supported
|
|
60
|
+
Elasticsearch client major.
|
|
61
|
+
- Make no-argument `slack_formatter.format_nz_time()` independent of the host timezone.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- Add an Elasticsearch compatibility test matrix for client majors 7.17, 8.x, and 9.x.
|
|
66
|
+
- Add ADRs covering Elasticsearch version support and the move to `zoneinfo`.
|
|
67
|
+
|
|
68
|
+
## [2.0] - 2026-07-13
|
|
69
|
+
|
|
70
|
+
### Removed
|
|
71
|
+
|
|
72
|
+
- **BREAKING**: the `nui_lambda_shared_utils` import shim. Import from `nui_shared_utils` instead (`from nui_shared_utils import ...`). The shim forwarded to `nui_shared_utils` with a `DeprecationWarning` since the 1.3.1 import-root rename and was always slated for removal in 2.0.0.
|
|
73
|
+
- The old distribution name still installs: `pip install nui-lambda-shared-utils` continues to resolve via the redirect package (which depends on `nui-python-shared-utils`). Only the old import root is gone.
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- Add ADRs for the package's generic scope, lazy integrations, credential precedence,
|
|
78
|
+
explicit Secrets Manager region resolution, and Snowflake SQL API choice.
|
|
79
|
+
|
|
80
|
+
## [1.6.0] - 2026-07-13
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- Add a backend-neutral structured logging field registry, request-scoped Powertools and
|
|
85
|
+
stdlib binders, binding helpers, field validation, and recursive payload redaction.
|
|
86
|
+
- Add the structured logging contract guide and ADR-001.
|
|
87
|
+
|
|
88
|
+
## [1.5.0] - 2026-07-02
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- Add optional `unfurl_links` and `unfurl_media` controls to `SlackClient.send_message`.
|
|
93
|
+
Unset values preserve Slack's existing defaults.
|
|
94
|
+
|
|
95
|
+
## [1.4.1] - 2026-06-29
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- Replace the Snowflake extra's direct Git dependency with the published
|
|
100
|
+
`snowflake-sql-api>=0.1.1,<0.2.0` range so PyPI accepts the package metadata.
|
|
101
|
+
|
|
102
|
+
## [1.4.0] - 2026-06-29
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- Add the pure-Python Snowflake SQL API client behind the optional `[snowflake]` extra,
|
|
107
|
+
including sync and async factories, key-pair credential resolution, query logging, and
|
|
108
|
+
offline tests.
|
|
109
|
+
- Add a generic Anthropic client helper behind the optional `[llm]` extra, with API-key and
|
|
110
|
+
Bedrock authentication, forced tool-use calls, and text calls.
|
|
111
|
+
- Add Snowflake and LLM integration guides.
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
|
|
115
|
+
- **BREAKING**: resolve the Secrets Manager region from the boto3 session, `AWS_REGION`, or
|
|
116
|
+
`AWS_DEFAULT_REGION`, and fail explicitly instead of silently defaulting to
|
|
117
|
+
`ap-southeast-2`.
|
|
118
|
+
|
|
119
|
+
## [1.3.3] - 2026-05-06
|
|
120
|
+
|
|
121
|
+
### Performance
|
|
122
|
+
|
|
123
|
+
- Cold-start init for Lambda consumers reduced by ~400ms.
|
|
124
|
+
- `boto3` and `botocore` are no longer imported at module load. They are deferred to first use inside `secrets_helper.get_secret`, `utils.create_aws_client`/`resolve_aws_region`, and `cloudwatch_metrics.MetricsPublisher` (now lazy-constructs its boto3 client on first metric publish).
|
|
125
|
+
- The package `__init__` uses PEP 562 `__getattr__` so submodules (`slack_client`, `es_client`, `db_client`, `jwt_auth`, `powertools_helpers`, etc.) only load when their attributes are first accessed. `import nui_shared_utils` is ~10ms, down from ~700ms+.
|
|
126
|
+
- Public API is unchanged. Optional integrations still resolve to `None` when their backing dependency is not installed (matching the previous behaviour).
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- `scripts/bench_imports.py` for measuring `python -X importtime` cumulative cost across common entry points.
|
|
131
|
+
- `tests/test_lazy_imports.py` regression tests (boto3 must not appear in `sys.modules` after a bare package import or after importing `jwt_auth`/`powertools_helpers`).
|
|
132
|
+
|
|
133
|
+
## [1.3.2] - 2026-04-10
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- `get_elasticsearch_credentials()` now checks env vars (`ES_PASSWORD`/`ES_PASS`) before Secrets Manager, aligning with `BaseServiceClient` credential precedence
|
|
138
|
+
- `ESClient._resolve_credentials_from_env()` accepts both `ES_PASSWORD`/`ES_USERNAME` and `ES_PASS`/`ES_USER` conventions
|
|
139
|
+
|
|
140
|
+
## [1.3.1] - 2026-04-05
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
|
|
144
|
+
- Renamed internal import from `nui_lambda_shared_utils` to `nui_shared_utils`
|
|
145
|
+
|
|
146
|
+
## [1.3.0] - 2026-04-05
|
|
147
|
+
|
|
148
|
+
### Changed
|
|
149
|
+
|
|
150
|
+
- Renamed PyPI package from `nui-lambda-shared-utils` to `nui-python-shared-utils`
|
|
151
|
+
- Added explicit license field for twine 6.x compatibility
|
|
152
|
+
- Updated CI publish workflow for new package name and tag pattern
|
|
153
|
+
|
|
154
|
+
## [1.0.0 - 1.2.3] - 2025-11 to 2026-03
|
|
155
|
+
|
|
156
|
+
### Added
|
|
157
|
+
|
|
158
|
+
- **Log Processors Module** - `log_processors` for Kinesis/CloudWatch log extraction
|
|
159
|
+
- **AWS Powertools Integration** - `powertools_helpers` module with `get_powertools_logger()` and `powertools_handler()`
|
|
160
|
+
- **JWT Authentication** - RS256 token validation for API Gateway Lambdas
|
|
161
|
+
- **Slack Block Kit improvements** - Enhanced block builder patterns
|
|
162
|
+
- **`slack-channel-setup` CLI tool** for Slack workspace automation
|
|
163
|
+
- `service_name` parameter to `SlackClient` for custom display names
|
|
164
|
+
- `[powertools]` optional dependency group
|
|
165
|
+
- Streaming bulk operations for Elasticsearch
|
|
166
|
+
- Environment-based credential resolution in `BaseServiceClient`
|
|
167
|
+
- Pass-through account names support
|
|
168
|
+
|
|
169
|
+
### Changed
|
|
170
|
+
|
|
171
|
+
- Rebranded as generic production-ready package (not NUI-specific)
|
|
172
|
+
- Improved Slack Lambda header formatting (cleaner 2-line layout)
|
|
173
|
+
- Documentation reorganization and expansion
|
|
174
|
+
|
|
175
|
+
### Breaking Changes
|
|
176
|
+
|
|
177
|
+
- Removed `SERVICE_EMOJI` from public API exports
|
|
178
|
+
- Removed `get_entity_stats()` and `get_record_stats()` from `DatabaseClient`
|
|
179
|
+
- `format_currency()` now requires explicit currency parameter
|
|
180
|
+
|
|
181
|
+
## [0.0.6] - 2025-11-16
|
|
182
|
+
|
|
183
|
+
### Added
|
|
184
|
+
|
|
185
|
+
- Configuration YAML support for account info
|
|
186
|
+
- RabbitMQ integration utilities
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
|
|
190
|
+
- Enhanced configuration system
|
|
191
|
+
|
|
192
|
+
## [0.0.5] - 2025-09-29
|
|
193
|
+
|
|
194
|
+
### Added
|
|
195
|
+
|
|
196
|
+
- Comprehensive test improvements
|
|
197
|
+
- Better test coverage across modules
|
|
198
|
+
|
|
199
|
+
### Fixed
|
|
200
|
+
|
|
201
|
+
- Various test fixes and improvements
|
|
202
|
+
|
|
203
|
+
## [0.0.4] - 2025-09-05
|
|
204
|
+
|
|
205
|
+
### Changed
|
|
206
|
+
|
|
207
|
+
- Minor improvements and fixes
|
|
208
|
+
|
|
209
|
+
## [0.0.3] - 2025-09-05
|
|
210
|
+
|
|
211
|
+
### Changed
|
|
212
|
+
|
|
213
|
+
- Initial improvements
|
|
214
|
+
|
|
215
|
+
## [0.0.2] - 2025-09-05
|
|
216
|
+
|
|
217
|
+
### Added
|
|
218
|
+
|
|
219
|
+
- Initial package structure
|
|
220
|
+
- Core client implementations (Slack, Elasticsearch, Database)
|
|
221
|
+
- Metrics publishing utilities
|
|
222
|
+
- Error handling decorators
|
|
223
|
+
- Timezone utilities for NZ timezone handling
|
|
224
|
+
|
|
225
|
+
## [0.0.1] - 2025-09-05
|
|
226
|
+
|
|
227
|
+
### Added
|
|
228
|
+
|
|
229
|
+
- Initial release
|
|
230
|
+
- Basic project structure
|
|
231
|
+
- Python package configuration
|
|
232
|
+
|
|
233
|
+
[Unreleased]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.3...HEAD
|
|
234
|
+
[2.3]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.2...2.3
|
|
235
|
+
[2.2]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.1...2.2
|
|
236
|
+
[2.1]: https://github.com/nuimarkets/nui-python-shared-utils/compare/2.0...2.1
|
|
237
|
+
[2.0]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.6.0...2.0
|
|
238
|
+
[1.6.0]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.5.0...1.6.0
|
|
239
|
+
[1.5.0]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.4.1...1.5.0
|
|
240
|
+
[1.4.1]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.4.0...1.4.1
|
|
241
|
+
[1.4.0]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.3.3...1.4.0
|
|
242
|
+
[1.3.3]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.3.2...1.3.3
|
|
243
|
+
[1.3.2]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.3.1...1.3.2
|
|
244
|
+
[1.3.1]: https://github.com/nuimarkets/nui-python-shared-utils/compare/1.3.0...1.3.1
|
|
245
|
+
[1.3.0]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.6...1.3.0
|
|
246
|
+
[0.0.6]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.5...0.0.6
|
|
247
|
+
[0.0.5]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.4...0.0.5
|
|
248
|
+
[0.0.4]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.3...0.0.4
|
|
249
|
+
[0.0.3]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.2...0.0.3
|
|
250
|
+
[0.0.2]: https://github.com/nuimarkets/nui-python-shared-utils/compare/0.0.1...0.0.2
|
|
251
|
+
[0.0.1]: https://github.com/nuimarkets/nui-python-shared-utils/releases/tag/0.0.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-python-shared-utils
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4
|
|
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
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/powertools-integration.md
RENAMED
|
@@ -168,6 +168,9 @@ def powertools_handler(
|
|
|
168
168
|
service_name: str,
|
|
169
169
|
metrics_namespace: Optional[str] = None,
|
|
170
170
|
slack_alert_channel: Optional[str] = None,
|
|
171
|
+
slack_account_names: Optional[Dict[str, str]] = None,
|
|
172
|
+
slack_account_names_config: Optional[str] = None,
|
|
173
|
+
reraise_exceptions: bool = False,
|
|
171
174
|
):
|
|
172
175
|
"""
|
|
173
176
|
Decorator for Lambda handlers with logging, metrics, and error handling.
|
|
@@ -176,9 +179,67 @@ def powertools_handler(
|
|
|
176
179
|
service_name: Service identifier for logging and metrics dimensions
|
|
177
180
|
metrics_namespace: CloudWatch namespace (e.g., "NUI/TenderAnalyser")
|
|
178
181
|
slack_alert_channel: Slack channel for error alerts (e.g., "#alerts")
|
|
182
|
+
slack_account_names: AWS account ID to display-name mapping for alerts
|
|
183
|
+
slack_account_names_config: YAML file containing account-name defaults
|
|
184
|
+
reraise_exceptions: Re-raise after logging and alerting. Defaults to False.
|
|
179
185
|
"""
|
|
180
186
|
```
|
|
181
187
|
|
|
188
|
+
### When the Handler Raises
|
|
189
|
+
|
|
190
|
+
The decorator logs the exception and sends the Slack alert (if configured), then:
|
|
191
|
+
|
|
192
|
+
- **SQS events** (`Records[0].eventSource == "aws:sqs"`, checked before the handler runs):
|
|
193
|
+
re-raises. A returned error dict tells the event source mapping the batch succeeded (with
|
|
194
|
+
`ReportBatchItemFailures`, a response without `batchItemFailures` means every record did),
|
|
195
|
+
so the batch would be deleted. Re-raising leaves it on the queue for redelivery; it moves
|
|
196
|
+
to a DLQ after `maxReceiveCount` only if the queue has a redrive policy. The whole batch
|
|
197
|
+
comes back, records the handler already processed included, so a handler with
|
|
198
|
+
`batchSize` above 1 must be idempotent or catch per record. On a FIFO queue the message
|
|
199
|
+
group waits until the batch succeeds or redrives, and the Slack alert fires on every
|
|
200
|
+
receive. For per-record retries, enable `ReportBatchItemFailures` on the mapping and
|
|
201
|
+
return `{"batchItemFailures": [...]}` from the handler; the decorator passes it through.
|
|
202
|
+
- **Every other event, by default**: returns
|
|
203
|
+
`{"statusCode": 500, "body": "Internal Server Error"}` to preserve existing behavior.
|
|
204
|
+
- **Every other event with `reraise_exceptions=True`**: re-raises after logging the
|
|
205
|
+
exception and sending the configured Slack alert. Use this for Kinesis and DynamoDB
|
|
206
|
+
Streams, where a raised exception prevents the checkpoint from advancing, and for
|
|
207
|
+
asynchronous sources such as SNS, S3, and EventBridge, where Lambda failure handling
|
|
208
|
+
depends on the invocation failing.
|
|
209
|
+
|
|
210
|
+
The option is handler-wide rather than an event-source list because the handler's trigger
|
|
211
|
+
configuration determines whether Lambda failure semantics are appropriate. SQS does not
|
|
212
|
+
require the option and continues to re-raise unconditionally.
|
|
213
|
+
|
|
214
|
+
For synchronous triggers such as API Gateway, enabling this option changes failures from
|
|
215
|
+
the decorator's 500 response into an unhandled Lambda invocation error. Enable it only when
|
|
216
|
+
that caller-visible behavior is intended.
|
|
217
|
+
|
|
218
|
+
### Re-raise Failures for Stream and Async Sources
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from nui_shared_utils import powertools_handler
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@powertools_handler(
|
|
225
|
+
service_name="event-processor",
|
|
226
|
+
slack_alert_channel="#production-alerts",
|
|
227
|
+
reraise_exceptions=True,
|
|
228
|
+
)
|
|
229
|
+
def handler(event, context):
|
|
230
|
+
records = event.get("Records", [])
|
|
231
|
+
for record in records:
|
|
232
|
+
print(record)
|
|
233
|
+
return {"processed": len(records)}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Logging and Slack alerting run before the original exception is re-raised. Configure the
|
|
237
|
+
trigger's retry, bisect, failure destination, or dead-letter behavior separately in AWS.
|
|
238
|
+
Every retry invokes the decorator again, so a configured Slack alert can be sent once per
|
|
239
|
+
attempt. For Kinesis and DynamoDB Streams, maximum retry attempts and maximum record age
|
|
240
|
+
bound retries. Bisect-on-error isolates a failing record by adding invocations, which do not
|
|
241
|
+
consume the retry quota and can produce additional alerts.
|
|
242
|
+
|
|
182
243
|
### Basic Usage (Logging Only)
|
|
183
244
|
|
|
184
245
|
```python
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/powertools_helpers.py
RENAMED
|
@@ -145,12 +145,24 @@ def get_powertools_logger(
|
|
|
145
145
|
return powertools_logger
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
def _is_sqs_event(event: Any) -> bool:
|
|
149
|
+
"""True when ``event`` is a batch delivered by an SQS event source mapping."""
|
|
150
|
+
if not isinstance(event, dict):
|
|
151
|
+
return False
|
|
152
|
+
records = event.get("Records")
|
|
153
|
+
if not isinstance(records, list) or not records:
|
|
154
|
+
return False
|
|
155
|
+
first = records[0]
|
|
156
|
+
return isinstance(first, dict) and first.get("eventSource") == "aws:sqs"
|
|
157
|
+
|
|
158
|
+
|
|
148
159
|
def powertools_handler(
|
|
149
160
|
service_name: str,
|
|
150
161
|
metrics_namespace: Optional[str] = None,
|
|
151
162
|
slack_alert_channel: Optional[str] = None,
|
|
152
163
|
slack_account_names: Optional[Dict[str, str]] = None,
|
|
153
164
|
slack_account_names_config: Optional[str] = None,
|
|
165
|
+
reraise_exceptions: bool = False,
|
|
154
166
|
):
|
|
155
167
|
"""
|
|
156
168
|
Decorator for Lambda handlers with logging, metrics, and error handling.
|
|
@@ -164,6 +176,7 @@ def powertools_handler(
|
|
|
164
176
|
- Optional metrics.log_metrics integration (if metrics_namespace provided)
|
|
165
177
|
- Structured exception logging with traceback
|
|
166
178
|
- Optional Slack alerts on failures (if slack_alert_channel provided)
|
|
179
|
+
- Optional exception re-raising for event sources that use Lambda failure semantics
|
|
167
180
|
- Graceful degradation if Slack client unavailable
|
|
168
181
|
- Proper Lambda error response formatting
|
|
169
182
|
|
|
@@ -179,6 +192,11 @@ def powertools_handler(
|
|
|
179
192
|
Values loaded from this file are used as defaults, but any keys
|
|
180
193
|
provided in ``slack_account_names`` take precedence and override
|
|
181
194
|
the YAML-loaded values.
|
|
195
|
+
reraise_exceptions: Re-raise handler exceptions after logging and Slack
|
|
196
|
+
alerting. Enable this for event sources such as Kinesis, DynamoDB
|
|
197
|
+
Streams, SNS, S3, and EventBridge that rely on a raised exception to
|
|
198
|
+
record a failed invocation and apply their retry or failure handling.
|
|
199
|
+
Defaults to False to preserve the existing 500 response behavior.
|
|
182
200
|
|
|
183
201
|
Returns:
|
|
184
202
|
Decorator function for Lambda handlers
|
|
@@ -199,8 +217,18 @@ def powertools_handler(
|
|
|
199
217
|
... return {"statusCode": 200}
|
|
200
218
|
|
|
201
219
|
Note:
|
|
202
|
-
The
|
|
203
|
-
|
|
220
|
+
The handler's return value passes through unchanged: a ``statusCode`` dict
|
|
221
|
+
for API Gateway, ``{"batchItemFailures": [...]}`` for an SQS mapping with
|
|
222
|
+
``ReportBatchItemFailures``.
|
|
223
|
+
|
|
224
|
+
On exception, returns ``{"statusCode": 500, "body": "Internal Server Error"}``
|
|
225
|
+
by default. Set ``reraise_exceptions=True`` to re-raise after logging and
|
|
226
|
+
alerting when the event source relies on Lambda failure semantics. SQS events
|
|
227
|
+
are always re-raised, regardless of this option: a returned value tells the
|
|
228
|
+
SQS mapping the batch succeeded (with ``ReportBatchItemFailures``, a response
|
|
229
|
+
without ``batchItemFailures`` means every record did), so the 500 dict would
|
|
230
|
+
delete the batch. Re-raising leaves it on the queue for redelivery, and in the
|
|
231
|
+
DLQ after ``maxReceiveCount`` when the queue has a redrive policy.
|
|
204
232
|
"""
|
|
205
233
|
|
|
206
234
|
def decorator(func: Callable) -> Callable:
|
|
@@ -231,6 +259,9 @@ def powertools_handler(
|
|
|
231
259
|
if slack_client:
|
|
232
260
|
slack_client.set_handler_context(context)
|
|
233
261
|
|
|
262
|
+
# Classify before the handler runs: it may consume or mutate event["Records"].
|
|
263
|
+
is_sqs = _is_sqs_event(event)
|
|
264
|
+
|
|
234
265
|
try:
|
|
235
266
|
# Apply logger context injection
|
|
236
267
|
# Note: inject_lambda_context is added dynamically to logging.Logger (line 95)
|
|
@@ -274,6 +305,11 @@ def powertools_handler(
|
|
|
274
305
|
except Exception as slack_error:
|
|
275
306
|
logger.warning("Failed to send Slack alert: %s", slack_error)
|
|
276
307
|
|
|
308
|
+
# Re-raise for SQS so the batch stays on the queue, or when the caller
|
|
309
|
+
# opts into Lambda failure semantics; see the Note above.
|
|
310
|
+
if is_sqs or reraise_exceptions:
|
|
311
|
+
raise
|
|
312
|
+
|
|
277
313
|
# Return proper Lambda error response
|
|
278
314
|
return {
|
|
279
315
|
"statusCode": 500,
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_powertools_helpers.py
RENAMED
|
@@ -7,7 +7,6 @@ from unittest.mock import MagicMock, patch
|
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
# Test markers
|
|
12
11
|
pytestmark = pytest.mark.unit
|
|
13
12
|
|
|
@@ -334,6 +333,228 @@ class TestPowertoolsHandler:
|
|
|
334
333
|
assert result["body"] == "Internal Server Error"
|
|
335
334
|
|
|
336
335
|
|
|
336
|
+
def _sqs_event(*message_ids):
|
|
337
|
+
return {"Records": [{"messageId": mid, "eventSource": "aws:sqs", "body": "{}"} for mid in message_ids]}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _kinesis_event():
|
|
341
|
+
return {
|
|
342
|
+
"Records": [
|
|
343
|
+
{
|
|
344
|
+
"eventID": "shardId-000000000000:1",
|
|
345
|
+
"eventSource": "aws:kinesis",
|
|
346
|
+
"kinesis": {"sequenceNumber": "1", "data": "e30="},
|
|
347
|
+
}
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class TestPowertoolsHandlerSqs:
|
|
353
|
+
"""An SQS batch must stay on the queue when the handler raises.
|
|
354
|
+
|
|
355
|
+
Any value returned to an SQS event source mapping counts as success, so
|
|
356
|
+
returning the 500 dict deletes the batch: no redelivery, no DLQ.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
def test_sqs_exception_is_reraised(self, monkeypatch):
|
|
360
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
361
|
+
|
|
362
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
363
|
+
|
|
364
|
+
error = ConnectionError("Error reading SSH protocol banner")
|
|
365
|
+
|
|
366
|
+
@powertools_handler(service_name="test-service")
|
|
367
|
+
def handler(event, context):
|
|
368
|
+
raise error
|
|
369
|
+
|
|
370
|
+
with pytest.raises(ConnectionError) as excinfo:
|
|
371
|
+
handler(_sqs_event("m-1", "m-2"), MagicMock())
|
|
372
|
+
|
|
373
|
+
assert excinfo.value is error
|
|
374
|
+
|
|
375
|
+
def test_sqs_exception_alerts_slack_before_reraising(self, monkeypatch):
|
|
376
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
377
|
+
|
|
378
|
+
mock_slack_client = MagicMock()
|
|
379
|
+
|
|
380
|
+
with patch("nui_shared_utils.powertools_helpers.SLACK_CLIENT_AVAILABLE", True):
|
|
381
|
+
with patch("nui_shared_utils.powertools_helpers.SlackClient", return_value=mock_slack_client):
|
|
382
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
383
|
+
|
|
384
|
+
@powertools_handler(service_name="test-service", slack_alert_channel="#errors")
|
|
385
|
+
def handler(event, context):
|
|
386
|
+
raise ValueError("Test error")
|
|
387
|
+
|
|
388
|
+
with pytest.raises(ValueError):
|
|
389
|
+
handler(_sqs_event("m-1"), MagicMock())
|
|
390
|
+
|
|
391
|
+
mock_slack_client.send_message.assert_called_once()
|
|
392
|
+
|
|
393
|
+
def test_sqs_reraises_original_exception_when_slack_fails(self, monkeypatch):
|
|
394
|
+
"""A failed alert must not replace the handler's exception or swallow it."""
|
|
395
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
396
|
+
|
|
397
|
+
mock_slack_client = MagicMock()
|
|
398
|
+
mock_slack_client.send_message.side_effect = RuntimeError("Slack API error")
|
|
399
|
+
|
|
400
|
+
with patch("nui_shared_utils.powertools_helpers.SLACK_CLIENT_AVAILABLE", True):
|
|
401
|
+
with patch("nui_shared_utils.powertools_helpers.SlackClient", return_value=mock_slack_client):
|
|
402
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
403
|
+
|
|
404
|
+
@powertools_handler(service_name="test-service", slack_alert_channel="#errors")
|
|
405
|
+
def handler(event, context):
|
|
406
|
+
raise ValueError("Test error")
|
|
407
|
+
|
|
408
|
+
with pytest.raises(ValueError, match="Test error"):
|
|
409
|
+
handler(_sqs_event("m-1"), MagicMock())
|
|
410
|
+
|
|
411
|
+
def test_sqs_detected_before_the_handler_consumes_records(self, monkeypatch):
|
|
412
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
413
|
+
|
|
414
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
415
|
+
|
|
416
|
+
@powertools_handler(service_name="test-service")
|
|
417
|
+
def handler(event, context):
|
|
418
|
+
event.pop("Records")
|
|
419
|
+
raise ValueError("Test error")
|
|
420
|
+
|
|
421
|
+
with pytest.raises(ValueError):
|
|
422
|
+
handler(_sqs_event("m-1"), MagicMock())
|
|
423
|
+
|
|
424
|
+
def test_sqs_reraises_after_metrics_flush(self, monkeypatch):
|
|
425
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
426
|
+
|
|
427
|
+
flushed = []
|
|
428
|
+
|
|
429
|
+
def log_metrics(func):
|
|
430
|
+
def inner(event, context):
|
|
431
|
+
try:
|
|
432
|
+
return func(event, context)
|
|
433
|
+
finally:
|
|
434
|
+
flushed.append(True)
|
|
435
|
+
|
|
436
|
+
return inner
|
|
437
|
+
|
|
438
|
+
mock_metrics = MagicMock()
|
|
439
|
+
mock_metrics.log_metrics = log_metrics
|
|
440
|
+
|
|
441
|
+
with patch("nui_shared_utils.powertools_helpers.POWERTOOLS_AVAILABLE", True):
|
|
442
|
+
with patch("nui_shared_utils.powertools_helpers.Metrics", return_value=mock_metrics):
|
|
443
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
444
|
+
|
|
445
|
+
@powertools_handler(service_name="test-service", metrics_namespace="Test/Metrics")
|
|
446
|
+
def handler(event, context):
|
|
447
|
+
raise ValueError("Test error")
|
|
448
|
+
|
|
449
|
+
with pytest.raises(ValueError):
|
|
450
|
+
handler(_sqs_event("m-1"), MagicMock())
|
|
451
|
+
|
|
452
|
+
assert flushed == [True]
|
|
453
|
+
|
|
454
|
+
def test_sqs_success_returns_handler_result(self, monkeypatch):
|
|
455
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
456
|
+
|
|
457
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
458
|
+
|
|
459
|
+
@powertools_handler(service_name="test-service")
|
|
460
|
+
def handler(event, context):
|
|
461
|
+
return {"batchItemFailures": [{"itemIdentifier": "m-2"}]}
|
|
462
|
+
|
|
463
|
+
result = handler(_sqs_event("m-1", "m-2"), MagicMock())
|
|
464
|
+
|
|
465
|
+
assert result == {"batchItemFailures": [{"itemIdentifier": "m-2"}]}
|
|
466
|
+
|
|
467
|
+
@pytest.mark.parametrize(
|
|
468
|
+
"event",
|
|
469
|
+
[
|
|
470
|
+
{"Records": [{"EventSource": "aws:sns", "Sns": {}}]},
|
|
471
|
+
{"Records": [{"eventSource": "aws:kinesis", "kinesis": {}}]},
|
|
472
|
+
{"Records": [{"eventSource": "aws:dynamodb", "dynamodb": {}}]},
|
|
473
|
+
{"Records": [{"eventSource": "aws:s3", "s3": {}}]},
|
|
474
|
+
{"Records": []},
|
|
475
|
+
{"Records": "not-a-list"},
|
|
476
|
+
{"source": "aws.events", "detail-type": "Scheduled Event"},
|
|
477
|
+
["not", "a", "dict"],
|
|
478
|
+
],
|
|
479
|
+
ids=["sns", "kinesis", "dynamodb", "s3", "empty-records", "records-not-list", "scheduled", "non-dict"],
|
|
480
|
+
)
|
|
481
|
+
def test_non_sqs_events_keep_returning_500(self, monkeypatch, event):
|
|
482
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
483
|
+
|
|
484
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
485
|
+
|
|
486
|
+
@powertools_handler(service_name="test-service")
|
|
487
|
+
def handler(event, context):
|
|
488
|
+
raise ValueError("Test error")
|
|
489
|
+
|
|
490
|
+
result = handler(event, MagicMock())
|
|
491
|
+
|
|
492
|
+
assert result == {"statusCode": 500, "body": "Internal Server Error"}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class TestPowertoolsHandlerReraise:
|
|
496
|
+
def test_kinesis_exception_is_reraised_when_enabled(self, monkeypatch):
|
|
497
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
498
|
+
|
|
499
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
500
|
+
|
|
501
|
+
error = RuntimeError("Kinesis record failed")
|
|
502
|
+
|
|
503
|
+
@powertools_handler(service_name="test-service", reraise_exceptions=True)
|
|
504
|
+
def handler(event, context):
|
|
505
|
+
raise error
|
|
506
|
+
|
|
507
|
+
with pytest.raises(RuntimeError) as excinfo:
|
|
508
|
+
handler(_kinesis_event(), MagicMock())
|
|
509
|
+
|
|
510
|
+
assert excinfo.value is error
|
|
511
|
+
|
|
512
|
+
def test_kinesis_exception_keeps_500_by_default(self, monkeypatch):
|
|
513
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
514
|
+
|
|
515
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
516
|
+
|
|
517
|
+
@powertools_handler(service_name="test-service", reraise_exceptions=False)
|
|
518
|
+
def handler(event, context):
|
|
519
|
+
raise RuntimeError("Kinesis record failed")
|
|
520
|
+
|
|
521
|
+
result = handler(_kinesis_event(), MagicMock())
|
|
522
|
+
|
|
523
|
+
assert result == {"statusCode": 500, "body": "Internal Server Error"}
|
|
524
|
+
|
|
525
|
+
def test_eventbridge_exception_alerts_before_reraising(self, monkeypatch):
|
|
526
|
+
monkeypatch.delenv("AWS_LAMBDA_RUNTIME_API", raising=False)
|
|
527
|
+
|
|
528
|
+
from nui_shared_utils.powertools_helpers import powertools_handler
|
|
529
|
+
|
|
530
|
+
calls = []
|
|
531
|
+
mock_logger = MagicMock()
|
|
532
|
+
mock_logger.inject_lambda_context.side_effect = lambda func: func
|
|
533
|
+
mock_logger.exception.side_effect = lambda *args, **kwargs: calls.append("log")
|
|
534
|
+
mock_slack_client = MagicMock()
|
|
535
|
+
mock_slack_client.send_message.side_effect = lambda **kwargs: calls.append("slack")
|
|
536
|
+
|
|
537
|
+
with patch("nui_shared_utils.powertools_helpers.get_powertools_logger", return_value=mock_logger):
|
|
538
|
+
with patch("nui_shared_utils.powertools_helpers.SLACK_CLIENT_AVAILABLE", True):
|
|
539
|
+
with patch("nui_shared_utils.powertools_helpers.SlackClient", return_value=mock_slack_client):
|
|
540
|
+
|
|
541
|
+
@powertools_handler(
|
|
542
|
+
service_name="test-service",
|
|
543
|
+
slack_alert_channel="#errors",
|
|
544
|
+
reraise_exceptions=True,
|
|
545
|
+
)
|
|
546
|
+
def handler(event, context):
|
|
547
|
+
raise ValueError("EventBridge event failed")
|
|
548
|
+
|
|
549
|
+
with pytest.raises(ValueError, match="EventBridge event failed"):
|
|
550
|
+
handler(
|
|
551
|
+
{"source": "example.orders", "detail-type": "Order Created", "detail": {}},
|
|
552
|
+
MagicMock(),
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
assert calls == ["log", "slack"]
|
|
556
|
+
|
|
557
|
+
|
|
337
558
|
class TestOptionalImports:
|
|
338
559
|
"""Tests for graceful degradation of optional dependencies"""
|
|
339
560
|
|
|
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
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/configuration.md
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/installation.md
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/getting-started/quickstart.md
RENAMED
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/elasticsearch-integration.md
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/jwt-authentication.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/slack-integration.md
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/docs/guides/snowflake-integration.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/cloudwatch_metrics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/error_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/es_query_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/lambda_helpers.py
RENAMED
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/log_processors.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/__init__.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/binding.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/fields.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/logging/redaction.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/secrets_helper.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_client.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_formatter.py
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/slack_setup/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/nui_shared_utils/snowflake_client.py
RENAMED
|
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
|
{nui_python_shared_utils-2.2 → nui_python_shared_utils-2.4}/tests/test_cloudwatch_metrics.py
RENAMED
|
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
|