nui-python-shared-utils 1.3.1__tar.gz → 1.3.3__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-1.3.1 → nui_python_shared_utils-1.3.3}/PKG-INFO +1 -1
- nui_python_shared_utils-1.3.3/nui_lambda_shared_utils/__init__.py +44 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_python_shared_utils.egg-info/SOURCES.txt +2 -0
- nui_python_shared_utils-1.3.3/nui_shared_utils/__init__.py +263 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/cloudwatch_metrics.py +13 -3
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/es_client.py +4 -4
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/powertools_helpers.py +37 -15
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/secrets_helper.py +20 -3
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/utils.py +74 -88
- nui_python_shared_utils-1.3.3/scripts/bench_imports.py +107 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_cloudwatch_metrics.py +13 -13
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_es_client.py +11 -0
- nui_python_shared_utils-1.3.3/tests/test_lazy_imports.py +160 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_secrets_helper.py +37 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_utils.py +79 -70
- nui_python_shared_utils-1.3.1/nui_lambda_shared_utils/__init__.py +0 -25
- nui_python_shared_utils-1.3.1/nui_shared_utils/__init__.py +0 -252
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/.editorconfig +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/.github/workflows/ci.yml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/.github/workflows/publish.yml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/.github/workflows/test.yml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/.markdownlint-cli2.yaml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/CLAUDE.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/CONTRIBUTING.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/LICENSE +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/MANIFEST.in +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/README.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/README.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/development/testing.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/getting-started/configuration.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/getting-started/installation.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/getting-started/quickstart.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/cli-tools.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/elasticsearch-integration.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/jwt-authentication.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/lambda-utilities.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/log-processing.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/powertools-integration.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/shared-types.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/docs/guides/slack-integration.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/mypy.ini +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/cloudwatch_metrics.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/config.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/db_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/es_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/powertools_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/secrets_helper.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/timezone.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_lambda_shared_utils/utils.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/base_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/cli.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/config.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/db_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/error_handler.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/log_processors.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_setup/__init__.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_setup/channel_creator.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_setup/channel_definitions.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/slack_setup/setup_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/timezone.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/pyproject.toml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/pytest.ini +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/redirect/README.md +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/redirect/pyproject.toml +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/redirect/setup.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/requirements-test.txt +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/setup.cfg +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/setup.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/__init__.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_aws_utils.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_base_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_config.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_db_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_error_handler.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_es_query_builder.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_jwt_auth.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_lambda_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_log_processors.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_powertools_helpers.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_slack_client.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_slack_formatter.py +0 -0
- {nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/tests/test_timezone.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-python-shared-utils
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
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
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backwards-compatibility shim for nui-lambda-shared-utils.
|
|
3
|
+
|
|
4
|
+
This package has been renamed to nui-python-shared-utils.
|
|
5
|
+
The import name has changed from nui_lambda_shared_utils to nui_shared_utils.
|
|
6
|
+
|
|
7
|
+
This shim forwards attribute access to nui_shared_utils so existing consumers
|
|
8
|
+
continue to work without changes. New code should use:
|
|
9
|
+
|
|
10
|
+
from nui_shared_utils import ...
|
|
11
|
+
|
|
12
|
+
This shim will be removed in the next major version (2.0.0).
|
|
13
|
+
|
|
14
|
+
Forwarding is lazy (PEP 562 ``__getattr__``) to preserve the cold-start
|
|
15
|
+
optimisation in the underlying package: ``from nui_lambda_shared_utils.jwt_auth
|
|
16
|
+
import check_auth`` only imports ``jwt_auth`` and its dependencies, not the
|
|
17
|
+
full slack/es/db client surface.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import warnings
|
|
21
|
+
from typing import Any, List
|
|
22
|
+
|
|
23
|
+
warnings.warn(
|
|
24
|
+
"nui_lambda_shared_utils is deprecated. Use nui_shared_utils instead. "
|
|
25
|
+
"This shim will be removed in version 2.0.0.",
|
|
26
|
+
DeprecationWarning,
|
|
27
|
+
stacklevel=2,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
import nui_shared_utils as _target
|
|
31
|
+
|
|
32
|
+
__all__ = list(_target.__all__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __getattr__(name: str) -> Any:
|
|
36
|
+
# Delegate to the new package's lazy resolver. Cache on this module so
|
|
37
|
+
# subsequent accesses avoid the round-trip.
|
|
38
|
+
value = getattr(_target, name)
|
|
39
|
+
globals()[name] = value
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def __dir__() -> List[str]:
|
|
44
|
+
return sorted(set(globals()) | set(__all__))
|
|
@@ -73,6 +73,7 @@ nui_shared_utils/slack_setup/setup_helpers.py
|
|
|
73
73
|
redirect/README.md
|
|
74
74
|
redirect/pyproject.toml
|
|
75
75
|
redirect/setup.py
|
|
76
|
+
scripts/bench_imports.py
|
|
76
77
|
tests/__init__.py
|
|
77
78
|
tests/test_aws_utils.py
|
|
78
79
|
tests/test_base_client.py
|
|
@@ -84,6 +85,7 @@ tests/test_es_client.py
|
|
|
84
85
|
tests/test_es_query_builder.py
|
|
85
86
|
tests/test_jwt_auth.py
|
|
86
87
|
tests/test_lambda_helpers.py
|
|
88
|
+
tests/test_lazy_imports.py
|
|
87
89
|
tests/test_log_processors.py
|
|
88
90
|
tests/test_powertools_helpers.py
|
|
89
91
|
tests/test_secrets_helper.py
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enterprise-grade utilities for AWS Lambda functions with Slack, Elasticsearch, and monitoring integrations.
|
|
3
|
+
|
|
4
|
+
Public API is resolved lazily via PEP 562 ``__getattr__`` to keep package import
|
|
5
|
+
cheap. Submodules and their dependencies (boto3, slack-sdk, elasticsearch, etc.)
|
|
6
|
+
are only imported when an attribute is first accessed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib
|
|
10
|
+
from typing import TYPE_CHECKING, Any, List
|
|
11
|
+
|
|
12
|
+
# Map public name -> (submodule, attr_name).
|
|
13
|
+
# Optional integrations: keep their entries here; on ImportError during lazy
|
|
14
|
+
# resolution we return None to preserve historical behaviour where consumers
|
|
15
|
+
# could check ``if nui.SlackClient is not None``.
|
|
16
|
+
_LAZY_EXPORTS = {
|
|
17
|
+
# Configuration system
|
|
18
|
+
"Config": ("config", "Config"),
|
|
19
|
+
"get_config": ("config", "get_config"),
|
|
20
|
+
"set_config": ("config", "set_config"),
|
|
21
|
+
"configure": ("config", "configure"),
|
|
22
|
+
"get_es_host": ("config", "get_es_host"),
|
|
23
|
+
"get_es_credentials_secret": ("config", "get_es_credentials_secret"),
|
|
24
|
+
"get_db_credentials_secret": ("config", "get_db_credentials_secret"),
|
|
25
|
+
"get_slack_credentials_secret": ("config", "get_slack_credentials_secret"),
|
|
26
|
+
# Secrets
|
|
27
|
+
"get_secret": ("secrets_helper", "get_secret"),
|
|
28
|
+
"get_database_credentials": ("secrets_helper", "get_database_credentials"),
|
|
29
|
+
"get_elasticsearch_credentials": ("secrets_helper", "get_elasticsearch_credentials"),
|
|
30
|
+
"get_slack_credentials": ("secrets_helper", "get_slack_credentials"),
|
|
31
|
+
"get_api_key": ("secrets_helper", "get_api_key"),
|
|
32
|
+
"clear_cache": ("secrets_helper", "clear_cache"),
|
|
33
|
+
# Common utilities
|
|
34
|
+
"resolve_config_value": ("utils", "resolve_config_value"),
|
|
35
|
+
"resolve_aws_region": ("utils", "resolve_aws_region"),
|
|
36
|
+
"create_aws_client": ("utils", "create_aws_client"),
|
|
37
|
+
"handle_client_errors": ("utils", "handle_client_errors"),
|
|
38
|
+
"merge_dimensions": ("utils", "merge_dimensions"),
|
|
39
|
+
"validate_required_param": ("utils", "validate_required_param"),
|
|
40
|
+
"safe_close_connection": ("utils", "safe_close_connection"),
|
|
41
|
+
"format_log_context": ("utils", "format_log_context"),
|
|
42
|
+
"DEFAULT_AWS_REGION": ("utils", "DEFAULT_AWS_REGION"),
|
|
43
|
+
# Base client architecture
|
|
44
|
+
"BaseClient": ("base_client", "BaseClient"),
|
|
45
|
+
"ServiceHealthMixin": ("base_client", "ServiceHealthMixin"),
|
|
46
|
+
"RetryableOperationMixin": ("base_client", "RetryableOperationMixin"),
|
|
47
|
+
# Timezone helpers
|
|
48
|
+
"nz_time": ("timezone", "nz_time"),
|
|
49
|
+
"format_nz_time": ("timezone", "format_nz_time"),
|
|
50
|
+
# Slack formatting (no external deps)
|
|
51
|
+
"SlackBlockBuilder": ("slack_formatter", "SlackBlockBuilder"),
|
|
52
|
+
"format_currency": ("slack_formatter", "format_currency"),
|
|
53
|
+
"format_percentage": ("slack_formatter", "format_percentage"),
|
|
54
|
+
"format_number": ("slack_formatter", "format_number"),
|
|
55
|
+
"format_nz_time_slack": ("slack_formatter", "format_nz_time"),
|
|
56
|
+
"format_date_range": ("slack_formatter", "format_date_range"),
|
|
57
|
+
"format_daily_header": ("slack_formatter", "format_daily_header"),
|
|
58
|
+
"format_weekly_header": ("slack_formatter", "format_weekly_header"),
|
|
59
|
+
"format_error_alert": ("slack_formatter", "format_error_alert"),
|
|
60
|
+
"SEVERITY_EMOJI": ("slack_formatter", "SEVERITY_EMOJI"),
|
|
61
|
+
"STATUS_EMOJI": ("slack_formatter", "STATUS_EMOJI"),
|
|
62
|
+
# Error handling
|
|
63
|
+
"RetryableError": ("error_handler", "RetryableError"),
|
|
64
|
+
"NonRetryableError": ("error_handler", "NonRetryableError"),
|
|
65
|
+
"ErrorPatternMatcher": ("error_handler", "ErrorPatternMatcher"),
|
|
66
|
+
"ErrorAggregator": ("error_handler", "ErrorAggregator"),
|
|
67
|
+
"with_retry": ("error_handler", "with_retry"),
|
|
68
|
+
"retry_on_network_error": ("error_handler", "retry_on_network_error"),
|
|
69
|
+
"retry_on_db_error": ("error_handler", "retry_on_db_error"),
|
|
70
|
+
"retry_on_es_error": ("error_handler", "retry_on_es_error"),
|
|
71
|
+
"handle_lambda_error": ("error_handler", "handle_lambda_error"),
|
|
72
|
+
"categorize_retryable_error": ("error_handler", "categorize_retryable_error"),
|
|
73
|
+
# CloudWatch metrics
|
|
74
|
+
"MetricsPublisher": ("cloudwatch_metrics", "MetricsPublisher"),
|
|
75
|
+
"MetricAggregator": ("cloudwatch_metrics", "MetricAggregator"),
|
|
76
|
+
"StandardMetrics": ("cloudwatch_metrics", "StandardMetrics"),
|
|
77
|
+
"TimedMetric": ("cloudwatch_metrics", "TimedMetric"),
|
|
78
|
+
"track_lambda_performance": ("cloudwatch_metrics", "track_lambda_performance"),
|
|
79
|
+
"create_service_dimensions": ("cloudwatch_metrics", "create_service_dimensions"),
|
|
80
|
+
"publish_health_metric": ("cloudwatch_metrics", "publish_health_metric"),
|
|
81
|
+
# Log processing (no external deps)
|
|
82
|
+
"extract_cloudwatch_logs_from_kinesis": ("log_processors", "extract_cloudwatch_logs_from_kinesis"),
|
|
83
|
+
"derive_index_name": ("log_processors", "derive_index_name"),
|
|
84
|
+
"CloudWatchLogEvent": ("log_processors", "CloudWatchLogEvent"),
|
|
85
|
+
"CloudWatchLogsData": ("log_processors", "CloudWatchLogsData"),
|
|
86
|
+
# Lambda context helpers
|
|
87
|
+
"get_lambda_environment_info": ("lambda_helpers", "get_lambda_environment_info"),
|
|
88
|
+
# Optional: Slack client (slack-sdk)
|
|
89
|
+
"SlackClient": ("slack_client", "SlackClient"),
|
|
90
|
+
# Optional: Elasticsearch client + query builder
|
|
91
|
+
"ElasticsearchClient": ("es_client", "ElasticsearchClient"),
|
|
92
|
+
"ESQueryBuilder": ("es_query_builder", "ESQueryBuilder"),
|
|
93
|
+
"build_error_rate_query": ("es_query_builder", "build_error_rate_query"),
|
|
94
|
+
"build_top_errors_query": ("es_query_builder", "build_top_errors_query"),
|
|
95
|
+
"build_response_time_query": ("es_query_builder", "build_response_time_query"),
|
|
96
|
+
"build_service_volume_query": ("es_query_builder", "build_service_volume_query"),
|
|
97
|
+
"build_user_activity_query": ("es_query_builder", "build_user_activity_query"),
|
|
98
|
+
"build_pattern_detection_query": ("es_query_builder", "build_pattern_detection_query"),
|
|
99
|
+
"build_tender_participant_query": ("es_query_builder", "build_tender_participant_query"),
|
|
100
|
+
# Optional: Database client (pymysql / psycopg2)
|
|
101
|
+
"DatabaseClient": ("db_client", "DatabaseClient"),
|
|
102
|
+
"PostgreSQLClient": ("db_client", "PostgreSQLClient"),
|
|
103
|
+
"get_pool_stats": ("db_client", "get_pool_stats"),
|
|
104
|
+
# Optional: AWS Powertools
|
|
105
|
+
"get_powertools_logger": ("powertools_helpers", "get_powertools_logger"),
|
|
106
|
+
"powertools_handler": ("powertools_helpers", "powertools_handler"),
|
|
107
|
+
# Optional: JWT validation (rsa)
|
|
108
|
+
"validate_jwt": ("jwt_auth", "validate_jwt"),
|
|
109
|
+
"require_auth": ("jwt_auth", "require_auth"),
|
|
110
|
+
"check_auth": ("jwt_auth", "check_auth"),
|
|
111
|
+
"get_jwt_public_key": ("jwt_auth", "get_jwt_public_key"),
|
|
112
|
+
"JWTValidationError": ("jwt_auth", "JWTValidationError"),
|
|
113
|
+
"AuthenticationError": ("jwt_auth", "AuthenticationError"),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Submodules that are optional integrations; ImportError during lazy load
|
|
117
|
+
# resolves to None instead of propagating, matching pre-1.4 behaviour.
|
|
118
|
+
# Includes ``slack_setup`` which is also handled by a special-case branch in
|
|
119
|
+
# ``__getattr__`` (it is exposed as a submodule object, not an attribute).
|
|
120
|
+
_OPTIONAL_SUBMODULES = {
|
|
121
|
+
"slack_client",
|
|
122
|
+
"es_client",
|
|
123
|
+
"es_query_builder",
|
|
124
|
+
"db_client",
|
|
125
|
+
"powertools_helpers",
|
|
126
|
+
"jwt_auth",
|
|
127
|
+
"slack_setup",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def __getattr__(name: str) -> Any:
|
|
132
|
+
# ``slack_setup`` is exposed as a submodule attribute (``nui.slack_setup``).
|
|
133
|
+
if name == "slack_setup":
|
|
134
|
+
try:
|
|
135
|
+
mod = importlib.import_module(".slack_setup", __name__)
|
|
136
|
+
except ImportError:
|
|
137
|
+
if name in _OPTIONAL_SUBMODULES:
|
|
138
|
+
mod = None
|
|
139
|
+
else:
|
|
140
|
+
raise
|
|
141
|
+
globals()["slack_setup"] = mod
|
|
142
|
+
return mod
|
|
143
|
+
|
|
144
|
+
if name in _LAZY_EXPORTS:
|
|
145
|
+
submod_name, attr = _LAZY_EXPORTS[name]
|
|
146
|
+
try:
|
|
147
|
+
submod = importlib.import_module(f".{submod_name}", __name__)
|
|
148
|
+
value = getattr(submod, attr)
|
|
149
|
+
except ImportError:
|
|
150
|
+
if submod_name in _OPTIONAL_SUBMODULES:
|
|
151
|
+
value = None
|
|
152
|
+
else:
|
|
153
|
+
raise
|
|
154
|
+
globals()[name] = value
|
|
155
|
+
return value
|
|
156
|
+
|
|
157
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def __dir__() -> List[str]:
|
|
161
|
+
return sorted(set(globals()) | set(_LAZY_EXPORTS) | {"slack_setup"})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if TYPE_CHECKING:
|
|
165
|
+
# Imported for type checkers / IDE completion only; not executed at runtime.
|
|
166
|
+
from .config import (
|
|
167
|
+
Config,
|
|
168
|
+
configure,
|
|
169
|
+
get_config,
|
|
170
|
+
get_db_credentials_secret,
|
|
171
|
+
get_es_credentials_secret,
|
|
172
|
+
get_es_host,
|
|
173
|
+
get_slack_credentials_secret,
|
|
174
|
+
set_config,
|
|
175
|
+
)
|
|
176
|
+
from .secrets_helper import (
|
|
177
|
+
clear_cache,
|
|
178
|
+
get_api_key,
|
|
179
|
+
get_database_credentials,
|
|
180
|
+
get_elasticsearch_credentials,
|
|
181
|
+
get_secret,
|
|
182
|
+
get_slack_credentials,
|
|
183
|
+
)
|
|
184
|
+
from .utils import (
|
|
185
|
+
DEFAULT_AWS_REGION,
|
|
186
|
+
create_aws_client,
|
|
187
|
+
format_log_context,
|
|
188
|
+
handle_client_errors,
|
|
189
|
+
merge_dimensions,
|
|
190
|
+
resolve_aws_region,
|
|
191
|
+
resolve_config_value,
|
|
192
|
+
safe_close_connection,
|
|
193
|
+
validate_required_param,
|
|
194
|
+
)
|
|
195
|
+
from .base_client import BaseClient, RetryableOperationMixin, ServiceHealthMixin
|
|
196
|
+
from .timezone import format_nz_time, nz_time
|
|
197
|
+
from .slack_formatter import (
|
|
198
|
+
SEVERITY_EMOJI,
|
|
199
|
+
STATUS_EMOJI,
|
|
200
|
+
SlackBlockBuilder,
|
|
201
|
+
format_currency,
|
|
202
|
+
format_daily_header,
|
|
203
|
+
format_date_range,
|
|
204
|
+
format_error_alert,
|
|
205
|
+
format_number,
|
|
206
|
+
format_nz_time as format_nz_time_slack,
|
|
207
|
+
format_percentage,
|
|
208
|
+
format_weekly_header,
|
|
209
|
+
)
|
|
210
|
+
from .error_handler import (
|
|
211
|
+
ErrorAggregator,
|
|
212
|
+
ErrorPatternMatcher,
|
|
213
|
+
NonRetryableError,
|
|
214
|
+
RetryableError,
|
|
215
|
+
categorize_retryable_error,
|
|
216
|
+
handle_lambda_error,
|
|
217
|
+
retry_on_db_error,
|
|
218
|
+
retry_on_es_error,
|
|
219
|
+
retry_on_network_error,
|
|
220
|
+
with_retry,
|
|
221
|
+
)
|
|
222
|
+
from .cloudwatch_metrics import (
|
|
223
|
+
MetricAggregator,
|
|
224
|
+
MetricsPublisher,
|
|
225
|
+
StandardMetrics,
|
|
226
|
+
TimedMetric,
|
|
227
|
+
create_service_dimensions,
|
|
228
|
+
publish_health_metric,
|
|
229
|
+
track_lambda_performance,
|
|
230
|
+
)
|
|
231
|
+
from .log_processors import (
|
|
232
|
+
CloudWatchLogEvent,
|
|
233
|
+
CloudWatchLogsData,
|
|
234
|
+
derive_index_name,
|
|
235
|
+
extract_cloudwatch_logs_from_kinesis,
|
|
236
|
+
)
|
|
237
|
+
from .lambda_helpers import get_lambda_environment_info
|
|
238
|
+
from .slack_client import SlackClient
|
|
239
|
+
from .es_client import ElasticsearchClient
|
|
240
|
+
from .es_query_builder import (
|
|
241
|
+
ESQueryBuilder,
|
|
242
|
+
build_error_rate_query,
|
|
243
|
+
build_pattern_detection_query,
|
|
244
|
+
build_response_time_query,
|
|
245
|
+
build_service_volume_query,
|
|
246
|
+
build_tender_participant_query,
|
|
247
|
+
build_top_errors_query,
|
|
248
|
+
build_user_activity_query,
|
|
249
|
+
)
|
|
250
|
+
from .db_client import DatabaseClient, PostgreSQLClient, get_pool_stats
|
|
251
|
+
from .powertools_helpers import get_powertools_logger, powertools_handler
|
|
252
|
+
from .jwt_auth import (
|
|
253
|
+
AuthenticationError,
|
|
254
|
+
JWTValidationError,
|
|
255
|
+
check_auth,
|
|
256
|
+
get_jwt_public_key,
|
|
257
|
+
require_auth,
|
|
258
|
+
validate_jwt,
|
|
259
|
+
)
|
|
260
|
+
from . import slack_setup
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
__all__ = list(sorted(set(_LAZY_EXPORTS) | {"slack_setup"}))
|
|
@@ -9,8 +9,6 @@ import logging
|
|
|
9
9
|
from typing import Dict, List, Optional, Union
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from collections import defaultdict
|
|
12
|
-
import boto3
|
|
13
|
-
from botocore.exceptions import ClientError
|
|
14
12
|
|
|
15
13
|
log = logging.getLogger(__name__)
|
|
16
14
|
|
|
@@ -45,9 +43,19 @@ class MetricsPublisher:
|
|
|
45
43
|
self.namespace = namespace
|
|
46
44
|
self.default_dimensions = dimensions or {}
|
|
47
45
|
self.auto_flush_size = auto_flush_size
|
|
48
|
-
self.
|
|
46
|
+
self._region = region
|
|
47
|
+
self._client = None
|
|
49
48
|
self.metric_buffer: List[Dict] = []
|
|
50
49
|
|
|
50
|
+
@property
|
|
51
|
+
def client(self):
|
|
52
|
+
"""Lazily construct the boto3 CloudWatch client on first use."""
|
|
53
|
+
if self._client is None:
|
|
54
|
+
import boto3
|
|
55
|
+
|
|
56
|
+
self._client = boto3.client("cloudwatch", region_name=self._region)
|
|
57
|
+
return self._client
|
|
58
|
+
|
|
51
59
|
def put_metric(
|
|
52
60
|
self,
|
|
53
61
|
metric_name: str,
|
|
@@ -148,6 +156,8 @@ class MetricsPublisher:
|
|
|
148
156
|
if not self.metric_buffer:
|
|
149
157
|
return True
|
|
150
158
|
|
|
159
|
+
from botocore.exceptions import ClientError
|
|
160
|
+
|
|
151
161
|
try:
|
|
152
162
|
# CloudWatch allows max 20 metrics per request
|
|
153
163
|
for i in range(0, len(self.metric_buffer), 20):
|
{nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/es_client.py
RENAMED
|
@@ -52,14 +52,14 @@ class ElasticsearchClient(BaseClient, ServiceHealthMixin):
|
|
|
52
52
|
def _resolve_credentials_from_env(self) -> Optional[Dict[str, Any]]:
|
|
53
53
|
"""Resolve Elasticsearch credentials from environment variables.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
Accepts both conventions: ES_PASSWORD/ES_USERNAME and ES_PASS/ES_USER.
|
|
56
|
+
Password is required to trigger. Username defaults to "elastic".
|
|
57
57
|
"""
|
|
58
|
-
password = os.environ.get("ES_PASSWORD")
|
|
58
|
+
password = os.environ.get("ES_PASSWORD") or os.environ.get("ES_PASS")
|
|
59
59
|
if not password:
|
|
60
60
|
return None
|
|
61
61
|
return {
|
|
62
|
-
"username": os.environ.get("ES_USERNAME"
|
|
62
|
+
"username": os.environ.get("ES_USERNAME") or os.environ.get("ES_USER") or "elastic",
|
|
63
63
|
"password": password,
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -26,15 +26,34 @@ try:
|
|
|
26
26
|
except ImportError:
|
|
27
27
|
COLOREDLOGS_AVAILABLE = False
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
from .slack_client import SlackClient
|
|
29
|
+
from .lambda_helpers import get_lambda_environment_info
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
# SlackClient is loaded lazily on first use to keep this module's import
|
|
32
|
+
# cost low for callers that don't enable Slack alerting (it transitively
|
|
33
|
+
# pulls in slack_sdk, which is the dominant cost).
|
|
34
|
+
SLACK_CLIENT_AVAILABLE = False
|
|
35
|
+
SlackClient = None # type: ignore[assignment]
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
def _ensure_slack_client_loaded() -> None:
|
|
39
|
+
"""Lazy-import :class:`SlackClient` and update module-level flags.
|
|
40
|
+
|
|
41
|
+
Idempotent: returns immediately if ``SlackClient`` has already been
|
|
42
|
+
populated (real import or test mock).
|
|
43
|
+
"""
|
|
44
|
+
global SLACK_CLIENT_AVAILABLE, SlackClient
|
|
45
|
+
if SlackClient is not None:
|
|
46
|
+
# Already populated (real import or test mock) — keep the availability
|
|
47
|
+
# flag in sync so callers don't see a stale False.
|
|
48
|
+
SLACK_CLIENT_AVAILABLE = True
|
|
49
|
+
return
|
|
50
|
+
try:
|
|
51
|
+
from .slack_client import SlackClient as _SC
|
|
52
|
+
|
|
53
|
+
SlackClient = _SC
|
|
54
|
+
SLACK_CLIENT_AVAILABLE = True
|
|
55
|
+
except ImportError:
|
|
56
|
+
SLACK_CLIENT_AVAILABLE = False
|
|
38
57
|
|
|
39
58
|
|
|
40
59
|
__all__ = ["get_powertools_logger", "powertools_handler"]
|
|
@@ -99,6 +118,7 @@ def get_powertools_logger(
|
|
|
99
118
|
if func is not None:
|
|
100
119
|
return func
|
|
101
120
|
return lambda f: f
|
|
121
|
+
|
|
102
122
|
logger.inject_lambda_context = _mock_inject_lambda_context # type: ignore
|
|
103
123
|
|
|
104
124
|
return logger
|
|
@@ -194,14 +214,16 @@ def powertools_handler(
|
|
|
194
214
|
|
|
195
215
|
# Create Slack client if channel provided
|
|
196
216
|
slack_client = None
|
|
197
|
-
if slack_alert_channel
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
if slack_alert_channel:
|
|
218
|
+
_ensure_slack_client_loaded()
|
|
219
|
+
if SLACK_CLIENT_AVAILABLE and SlackClient is not None:
|
|
220
|
+
try:
|
|
221
|
+
slack_client = SlackClient(
|
|
222
|
+
account_names=slack_account_names,
|
|
223
|
+
account_names_config=slack_account_names_config,
|
|
224
|
+
)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.warning("Failed to initialize Slack client: %s", e)
|
|
205
227
|
|
|
206
228
|
@functools.wraps(func)
|
|
207
229
|
def wrapper(event: dict, context: Any) -> dict:
|
{nui_python_shared_utils-1.3.1 → nui_python_shared_utils-1.3.3}/nui_shared_utils/secrets_helper.py
RENAMED
|
@@ -7,8 +7,6 @@ import os
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Dict, Optional
|
|
10
|
-
import boto3
|
|
11
|
-
from botocore.exceptions import ClientError
|
|
12
10
|
|
|
13
11
|
from .config import get_config
|
|
14
12
|
|
|
@@ -35,6 +33,9 @@ def get_secret(secret_name: str) -> Dict:
|
|
|
35
33
|
if secret_name in _secrets_cache:
|
|
36
34
|
return _secrets_cache[secret_name]
|
|
37
35
|
|
|
36
|
+
import boto3
|
|
37
|
+
from botocore.exceptions import ClientError
|
|
38
|
+
|
|
38
39
|
# Create a Secrets Manager client
|
|
39
40
|
session = boto3.session.Session()
|
|
40
41
|
client = session.client(service_name="secretsmanager", region_name=session.region_name or "ap-southeast-2")
|
|
@@ -112,13 +113,29 @@ def get_elasticsearch_credentials(secret_name: Optional[str] = None) -> Dict:
|
|
|
112
113
|
"""
|
|
113
114
|
Get Elasticsearch credentials.
|
|
114
115
|
|
|
116
|
+
Precedence (matching BaseServiceClient pattern):
|
|
117
|
+
1. Environment variables (ES_PASSWORD/ES_PASS + ES_USERNAME/ES_USER)
|
|
118
|
+
2. AWS Secrets Manager
|
|
119
|
+
|
|
115
120
|
Args:
|
|
116
|
-
secret_name: Override default
|
|
121
|
+
secret_name: Override default secret name for Secrets Manager lookup.
|
|
122
|
+
Ignored when credentials are resolved from environment variables.
|
|
117
123
|
|
|
118
124
|
Returns:
|
|
119
125
|
Dict with host, username, password
|
|
120
126
|
"""
|
|
121
127
|
config = get_config()
|
|
128
|
+
|
|
129
|
+
# Environment variables first (local dev, CLI usage)
|
|
130
|
+
es_password = os.environ.get("ES_PASSWORD") or os.environ.get("ES_PASS")
|
|
131
|
+
if es_password:
|
|
132
|
+
es_user = os.environ.get("ES_USERNAME") or os.environ.get("ES_USER") or "elastic"
|
|
133
|
+
host = os.environ.get("ES_HOST") or config.es_host
|
|
134
|
+
if ":" not in host and not host.startswith("http"):
|
|
135
|
+
host = f"{host}:9200"
|
|
136
|
+
return {"host": host, "username": es_user, "password": es_password}
|
|
137
|
+
|
|
138
|
+
# Secrets Manager (Lambda runtime)
|
|
122
139
|
secret = secret_name or os.environ.get("ES_CREDENTIALS_SECRET") or config.es_credentials_secret
|
|
123
140
|
if not secret:
|
|
124
141
|
raise ValueError("No Elasticsearch secret name provided")
|