nui-python-shared-utils 1.3.2__py3-none-any.whl → 1.3.3__py3-none-any.whl
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_lambda_shared_utils/__init__.py +22 -3
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/METADATA +1 -1
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/RECORD +12 -12
- nui_shared_utils/__init__.py +236 -225
- nui_shared_utils/cloudwatch_metrics.py +13 -3
- nui_shared_utils/powertools_helpers.py +37 -15
- nui_shared_utils/secrets_helper.py +3 -2
- nui_shared_utils/utils.py +74 -88
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/WHEEL +0 -0
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/entry_points.txt +0 -0
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/licenses/LICENSE +0 -0
- {nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/top_level.txt +0 -0
|
@@ -4,15 +4,21 @@ Backwards-compatibility shim for nui-lambda-shared-utils.
|
|
|
4
4
|
This package has been renamed to nui-python-shared-utils.
|
|
5
5
|
The import name has changed from nui_lambda_shared_utils to nui_shared_utils.
|
|
6
6
|
|
|
7
|
-
This shim
|
|
7
|
+
This shim forwards attribute access to nui_shared_utils so existing consumers
|
|
8
8
|
continue to work without changes. New code should use:
|
|
9
9
|
|
|
10
10
|
from nui_shared_utils import ...
|
|
11
11
|
|
|
12
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.
|
|
13
18
|
"""
|
|
14
19
|
|
|
15
20
|
import warnings
|
|
21
|
+
from typing import Any, List
|
|
16
22
|
|
|
17
23
|
warnings.warn(
|
|
18
24
|
"nui_lambda_shared_utils is deprecated. Use nui_shared_utils instead. "
|
|
@@ -21,5 +27,18 @@ warnings.warn(
|
|
|
21
27
|
stacklevel=2,
|
|
22
28
|
)
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
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__))
|
{nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/METADATA
RENAMED
|
@@ -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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
nui_lambda_shared_utils/__init__.py,sha256=
|
|
1
|
+
nui_lambda_shared_utils/__init__.py,sha256=FaaYBqgNxSQ0x7jlf5fw2pQoMh8Pbmw7uZiVfavI_IY,1320
|
|
2
2
|
nui_lambda_shared_utils/base_client.py,sha256=c7t2_IfISQ39Y1AY6MOj-SlX2tkpzNpp47RRMfu5xqY,135
|
|
3
3
|
nui_lambda_shared_utils/cli.py,sha256=3TtfQy0x3P0QKiNDiz0j3kze7Hs26CMeQE8X_948OEQ,127
|
|
4
4
|
nui_lambda_shared_utils/cloudwatch_metrics.py,sha256=Qwj52AIVrxg7HEEMjmgvDptCJ5Csr_w3NJBI1v34sFw,142
|
|
@@ -20,11 +20,11 @@ nui_lambda_shared_utils/slack_setup/__init__.py,sha256=q78NzSznsSd3ri5OUohWxQt7r
|
|
|
20
20
|
nui_lambda_shared_utils/slack_setup/channel_creator.py,sha256=xaeqzyEAqVqYj2EhN69UvcyZQ6LdFKYODPPCwxHInLA,172
|
|
21
21
|
nui_lambda_shared_utils/slack_setup/channel_definitions.py,sha256=3TkdLzWvEMBMDy5jqOPMUGRgKQ3z9TN58D43V7O7LpE,180
|
|
22
22
|
nui_lambda_shared_utils/slack_setup/setup_helpers.py,sha256=ex_RiUfiZqH9qGLQwmedezMbfp35uI-VQPevducfbBk,168
|
|
23
|
-
nui_python_shared_utils-1.3.
|
|
24
|
-
nui_shared_utils/__init__.py,sha256=
|
|
23
|
+
nui_python_shared_utils-1.3.3.dist-info/licenses/LICENSE,sha256=vGe2mC5yLUb8toYlY3T36ZwCB5zQUW5hlCtEMiqokhM,1071
|
|
24
|
+
nui_shared_utils/__init__.py,sha256=Myt_55yTIZO71iyz2wkM9wySd7xhOCCky4EBOs-aI0A,10964
|
|
25
25
|
nui_shared_utils/base_client.py,sha256=I1lKQGhrKyvujV2zps0TrtEdPDqMCwRQaIh6ceGONXQ,11016
|
|
26
26
|
nui_shared_utils/cli.py,sha256=JJpSoQWKvAz4b8cO30yFNi5vY9jmqrCHzbFpvrVTkbU,8747
|
|
27
|
-
nui_shared_utils/cloudwatch_metrics.py,sha256=
|
|
27
|
+
nui_shared_utils/cloudwatch_metrics.py,sha256=p0J-s63UJDGK14TRGt0k8yoj7aWSSM55UKdgcuaT1dU,12098
|
|
28
28
|
nui_shared_utils/config.py,sha256=ZbkUKDxnHMJiHNv-TlWKjotmf2mH1Vz3GaIXgGfWg7E,4524
|
|
29
29
|
nui_shared_utils/db_client.py,sha256=HyVKJ1Su0dVPV1QI2MU5UtT8myDRCjKgV60xRL4SEdE,21726
|
|
30
30
|
nui_shared_utils/error_handler.py,sha256=pJ6b_mI0Ait8QRbs9UmLzYUN-Ft0l_dnpudVO_6LRSE,12171
|
|
@@ -33,18 +33,18 @@ nui_shared_utils/es_query_builder.py,sha256=UuheQf8b2UXaOiJlYCKYIfKfVERDZ3ag1P0O
|
|
|
33
33
|
nui_shared_utils/jwt_auth.py,sha256=2Ag1zZKxd2R8QBz3aQA4h8OtKHQvUVKrjUp6lXpJxJU,9143
|
|
34
34
|
nui_shared_utils/lambda_helpers.py,sha256=psHVotpmOfnmyQCoOt4MSIEj7VwWcy6gZM3vYSZwjOk,3041
|
|
35
35
|
nui_shared_utils/log_processors.py,sha256=x5gz1LEkbmoMCZ3ZB6q-mbn26dSzwh1trz4wSkBGxqk,5538
|
|
36
|
-
nui_shared_utils/powertools_helpers.py,sha256=
|
|
37
|
-
nui_shared_utils/secrets_helper.py,sha256=
|
|
36
|
+
nui_shared_utils/powertools_helpers.py,sha256=qc-lYLfY-QX20gALfPOJcihrtJhiCU-fJW8rsPT7LJE,11037
|
|
37
|
+
nui_shared_utils/secrets_helper.py,sha256=jtQZSbghNTyWYylGxFiHWeL28Q1JUpLTrnGIICU4R3k,6815
|
|
38
38
|
nui_shared_utils/slack_client.py,sha256=_qR7Q1GU7gvYhUxiaBxEohhoEYznqr8kz9enGfeDtn8,24759
|
|
39
39
|
nui_shared_utils/slack_formatter.py,sha256=95g6XfAJst7RVhd0M0ahiF3gWWW5j96WYkCg5tLS6Zg,10894
|
|
40
40
|
nui_shared_utils/timezone.py,sha256=TvtSZV7w3Vvz3NbMTcJSNbUf0ZxPwpFS-xfgye1h-4Q,3583
|
|
41
|
-
nui_shared_utils/utils.py,sha256=
|
|
41
|
+
nui_shared_utils/utils.py,sha256=gOpw80tZGC24QsyesR8EySRODY-TFXG8cXUvggPBsRI,8184
|
|
42
42
|
nui_shared_utils/slack_setup/__init__.py,sha256=OElyS3xk4F_YKH5uUUTDpN0ah1dOO3e52muIPjAMPW8,320
|
|
43
43
|
nui_shared_utils/slack_setup/channel_creator.py,sha256=0gyCBIS0EC96SVn1Z2dD4Fpzk0xNdHSWyiCxoUQIUe8,10667
|
|
44
44
|
nui_shared_utils/slack_setup/channel_definitions.py,sha256=atfz5ZhpqefOeLh1gShbWd-TLzgjPmhv95bfuTdmZog,5676
|
|
45
45
|
nui_shared_utils/slack_setup/setup_helpers.py,sha256=pzzXMs12GI9sdZttWeYzgLPgC0xqPz7ZLHM1GUPNNXc,7219
|
|
46
|
-
nui_python_shared_utils-1.3.
|
|
47
|
-
nui_python_shared_utils-1.3.
|
|
48
|
-
nui_python_shared_utils-1.3.
|
|
49
|
-
nui_python_shared_utils-1.3.
|
|
50
|
-
nui_python_shared_utils-1.3.
|
|
46
|
+
nui_python_shared_utils-1.3.3.dist-info/METADATA,sha256=y_FgR8-pq8PWqEayZQfYAtOqzijcsdG2UOQDVxd4-7E,19387
|
|
47
|
+
nui_python_shared_utils-1.3.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
48
|
+
nui_python_shared_utils-1.3.3.dist-info/entry_points.txt,sha256=TrJ3Z4kz3oXfy6InXn20jHu7rnTwo-4EA0KRFX3fkL0,66
|
|
49
|
+
nui_python_shared_utils-1.3.3.dist-info/top_level.txt,sha256=sNceq5okmEB54L-gm4a0OgGhnPlU62zYmlUoXKn-Fa8,41
|
|
50
|
+
nui_python_shared_utils-1.3.3.dist-info/RECORD,,
|
nui_shared_utils/__init__.py
CHANGED
|
@@ -1,252 +1,263 @@
|
|
|
1
1
|
"""
|
|
2
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.
|
|
3
7
|
"""
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
Config,
|
|
8
|
-
get_config,
|
|
9
|
-
set_config,
|
|
10
|
-
configure,
|
|
11
|
-
get_es_host,
|
|
12
|
-
get_es_credentials_secret,
|
|
13
|
-
get_db_credentials_secret,
|
|
14
|
-
get_slack_credentials_secret,
|
|
15
|
-
)
|
|
9
|
+
import importlib
|
|
10
|
+
from typing import TYPE_CHECKING, Any, List
|
|
16
11
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
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
|
+
}
|
|
26
115
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
}
|
|
35
129
|
|
|
36
|
-
# Base client architecture
|
|
37
|
-
from .base_client import BaseClient, ServiceHealthMixin, RetryableOperationMixin
|
|
38
130
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
44
143
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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}")
|
|
49
158
|
|
|
50
|
-
try:
|
|
51
|
-
from .db_client import DatabaseClient, PostgreSQLClient, get_pool_stats
|
|
52
|
-
except ImportError:
|
|
53
|
-
DatabaseClient = None # type: ignore
|
|
54
|
-
PostgreSQLClient = None # type: ignore
|
|
55
|
-
get_pool_stats = None # type: ignore
|
|
56
159
|
|
|
57
|
-
|
|
160
|
+
def __dir__() -> List[str]:
|
|
161
|
+
return sorted(set(globals()) | set(_LAZY_EXPORTS) | {"slack_setup"})
|
|
58
162
|
|
|
59
|
-
# Slack formatting utilities (no external dependencies)
|
|
60
|
-
from .slack_formatter import (
|
|
61
|
-
SlackBlockBuilder,
|
|
62
|
-
format_currency,
|
|
63
|
-
format_percentage,
|
|
64
|
-
format_number,
|
|
65
|
-
format_nz_time as format_nz_time_slack,
|
|
66
|
-
format_date_range,
|
|
67
|
-
format_daily_header,
|
|
68
|
-
format_weekly_header,
|
|
69
|
-
format_error_alert,
|
|
70
|
-
SEVERITY_EMOJI,
|
|
71
|
-
STATUS_EMOJI,
|
|
72
|
-
)
|
|
73
163
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
76
240
|
from .es_query_builder import (
|
|
77
241
|
ESQueryBuilder,
|
|
78
242
|
build_error_rate_query,
|
|
79
|
-
|
|
243
|
+
build_pattern_detection_query,
|
|
80
244
|
build_response_time_query,
|
|
81
245
|
build_service_volume_query,
|
|
82
|
-
build_user_activity_query,
|
|
83
|
-
build_pattern_detection_query,
|
|
84
246
|
build_tender_participant_query,
|
|
247
|
+
build_top_errors_query,
|
|
248
|
+
build_user_activity_query,
|
|
85
249
|
)
|
|
86
|
-
|
|
87
|
-
ESQueryBuilder = None # type: ignore
|
|
88
|
-
build_error_rate_query = None # type: ignore
|
|
89
|
-
build_top_errors_query = None # type: ignore
|
|
90
|
-
build_response_time_query = None # type: ignore
|
|
91
|
-
build_service_volume_query = None # type: ignore
|
|
92
|
-
build_user_activity_query = None # type: ignore
|
|
93
|
-
build_pattern_detection_query = None # type: ignore
|
|
94
|
-
build_tender_participant_query = None # type: ignore
|
|
95
|
-
from .error_handler import (
|
|
96
|
-
RetryableError,
|
|
97
|
-
NonRetryableError,
|
|
98
|
-
ErrorPatternMatcher,
|
|
99
|
-
ErrorAggregator,
|
|
100
|
-
with_retry,
|
|
101
|
-
retry_on_network_error,
|
|
102
|
-
retry_on_db_error,
|
|
103
|
-
retry_on_es_error,
|
|
104
|
-
handle_lambda_error,
|
|
105
|
-
categorize_retryable_error,
|
|
106
|
-
)
|
|
107
|
-
from .cloudwatch_metrics import (
|
|
108
|
-
MetricsPublisher,
|
|
109
|
-
MetricAggregator,
|
|
110
|
-
StandardMetrics,
|
|
111
|
-
TimedMetric,
|
|
112
|
-
track_lambda_performance,
|
|
113
|
-
create_service_dimensions,
|
|
114
|
-
publish_health_metric,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# AWS Powertools integration - optional import
|
|
118
|
-
try:
|
|
250
|
+
from .db_client import DatabaseClient, PostgreSQLClient, get_pool_stats
|
|
119
251
|
from .powertools_helpers import get_powertools_logger, powertools_handler
|
|
120
|
-
except ImportError:
|
|
121
|
-
get_powertools_logger = None # type: ignore
|
|
122
|
-
powertools_handler = None # type: ignore
|
|
123
|
-
|
|
124
|
-
# Log processing utilities (no external dependencies)
|
|
125
|
-
from .log_processors import (
|
|
126
|
-
CloudWatchLogEvent,
|
|
127
|
-
CloudWatchLogsData,
|
|
128
|
-
derive_index_name,
|
|
129
|
-
extract_cloudwatch_logs_from_kinesis,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
# Lambda context helpers (no external dependencies)
|
|
133
|
-
from .lambda_helpers import get_lambda_environment_info
|
|
134
|
-
|
|
135
|
-
# JWT authentication - optional import
|
|
136
|
-
try:
|
|
137
252
|
from .jwt_auth import (
|
|
138
|
-
|
|
139
|
-
|
|
253
|
+
AuthenticationError,
|
|
254
|
+
JWTValidationError,
|
|
140
255
|
check_auth,
|
|
141
256
|
get_jwt_public_key,
|
|
142
|
-
|
|
143
|
-
|
|
257
|
+
require_auth,
|
|
258
|
+
validate_jwt,
|
|
144
259
|
)
|
|
145
|
-
except ImportError:
|
|
146
|
-
validate_jwt = None # type: ignore
|
|
147
|
-
require_auth = None # type: ignore
|
|
148
|
-
check_auth = None # type: ignore
|
|
149
|
-
get_jwt_public_key = None # type: ignore
|
|
150
|
-
JWTValidationError = None # type: ignore
|
|
151
|
-
AuthenticationError = None # type: ignore
|
|
152
|
-
|
|
153
|
-
# Slack setup utilities (for CLI usage) - optional import
|
|
154
|
-
try:
|
|
155
260
|
from . import slack_setup
|
|
156
|
-
except ImportError:
|
|
157
|
-
slack_setup = None # type: ignore
|
|
158
261
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"Config",
|
|
162
|
-
"get_config",
|
|
163
|
-
"set_config",
|
|
164
|
-
"configure",
|
|
165
|
-
"get_es_host",
|
|
166
|
-
"get_es_credentials_secret",
|
|
167
|
-
"get_db_credentials_secret",
|
|
168
|
-
"get_slack_credentials_secret",
|
|
169
|
-
# Core utilities
|
|
170
|
-
"get_secret",
|
|
171
|
-
"get_database_credentials",
|
|
172
|
-
"get_elasticsearch_credentials",
|
|
173
|
-
"get_slack_credentials",
|
|
174
|
-
"get_api_key",
|
|
175
|
-
"clear_cache",
|
|
176
|
-
# Common utilities
|
|
177
|
-
"resolve_config_value",
|
|
178
|
-
"create_aws_client",
|
|
179
|
-
"handle_client_errors",
|
|
180
|
-
"merge_dimensions",
|
|
181
|
-
"validate_required_param",
|
|
182
|
-
# Base client architecture
|
|
183
|
-
"BaseClient",
|
|
184
|
-
"ServiceHealthMixin",
|
|
185
|
-
"RetryableOperationMixin",
|
|
186
|
-
# Client implementations
|
|
187
|
-
"SlackClient",
|
|
188
|
-
"ElasticsearchClient",
|
|
189
|
-
"DatabaseClient",
|
|
190
|
-
"PostgreSQLClient",
|
|
191
|
-
"get_pool_stats", # Legacy compatibility (None)
|
|
192
|
-
"nz_time",
|
|
193
|
-
"format_nz_time",
|
|
194
|
-
"slack_setup",
|
|
195
|
-
# Slack formatting
|
|
196
|
-
"SlackBlockBuilder",
|
|
197
|
-
"format_currency",
|
|
198
|
-
"format_percentage",
|
|
199
|
-
"format_number",
|
|
200
|
-
"format_nz_time_slack",
|
|
201
|
-
"format_date_range",
|
|
202
|
-
"format_daily_header",
|
|
203
|
-
"format_weekly_header",
|
|
204
|
-
"format_error_alert",
|
|
205
|
-
"SEVERITY_EMOJI",
|
|
206
|
-
"STATUS_EMOJI",
|
|
207
|
-
# ES query building
|
|
208
|
-
"ESQueryBuilder",
|
|
209
|
-
"build_error_rate_query",
|
|
210
|
-
"build_top_errors_query",
|
|
211
|
-
"build_response_time_query",
|
|
212
|
-
"build_service_volume_query",
|
|
213
|
-
"build_user_activity_query",
|
|
214
|
-
"build_pattern_detection_query",
|
|
215
|
-
"build_tender_participant_query",
|
|
216
|
-
# Error handling
|
|
217
|
-
"RetryableError",
|
|
218
|
-
"NonRetryableError",
|
|
219
|
-
"ErrorPatternMatcher",
|
|
220
|
-
"ErrorAggregator",
|
|
221
|
-
"with_retry",
|
|
222
|
-
"retry_on_network_error",
|
|
223
|
-
"retry_on_db_error",
|
|
224
|
-
"retry_on_es_error",
|
|
225
|
-
"handle_lambda_error",
|
|
226
|
-
"categorize_retryable_error",
|
|
227
|
-
# CloudWatch metrics
|
|
228
|
-
"MetricsPublisher",
|
|
229
|
-
"MetricAggregator",
|
|
230
|
-
"StandardMetrics",
|
|
231
|
-
"TimedMetric",
|
|
232
|
-
"track_lambda_performance",
|
|
233
|
-
"create_service_dimensions",
|
|
234
|
-
"publish_health_metric",
|
|
235
|
-
# AWS Powertools integration
|
|
236
|
-
"get_powertools_logger",
|
|
237
|
-
"powertools_handler",
|
|
238
|
-
# Log processing
|
|
239
|
-
"extract_cloudwatch_logs_from_kinesis",
|
|
240
|
-
"derive_index_name",
|
|
241
|
-
"CloudWatchLogEvent",
|
|
242
|
-
"CloudWatchLogsData",
|
|
243
|
-
# Lambda context helpers
|
|
244
|
-
"get_lambda_environment_info",
|
|
245
|
-
# JWT authentication
|
|
246
|
-
"validate_jwt",
|
|
247
|
-
"require_auth",
|
|
248
|
-
"check_auth",
|
|
249
|
-
"get_jwt_public_key",
|
|
250
|
-
"JWTValidationError",
|
|
251
|
-
"AuthenticationError",
|
|
252
|
-
]
|
|
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):
|
|
@@ -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:
|
|
@@ -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")
|
nui_shared_utils/utils.py
CHANGED
|
@@ -7,53 +7,49 @@ import time
|
|
|
7
7
|
import logging
|
|
8
8
|
import functools
|
|
9
9
|
from typing import Union, List, Optional, Any, Dict
|
|
10
|
-
import boto3
|
|
11
|
-
from botocore.exceptions import ClientError, NoCredentialsError
|
|
12
10
|
|
|
13
11
|
from .config import get_config
|
|
14
12
|
|
|
15
13
|
log = logging.getLogger(__name__)
|
|
16
14
|
|
|
17
|
-
# AWS region
|
|
15
|
+
# AWS region fallback. Used only when no explicit region, env var, config,
|
|
16
|
+
# or boto3 session region is available. Override at deploy time via the
|
|
17
|
+
# ``AWS_REGION_FALLBACK`` environment variable.
|
|
18
18
|
DEFAULT_AWS_REGION = "ap-southeast-2"
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def resolve_config_value(
|
|
22
|
-
param_value: Optional[Any],
|
|
23
|
-
env_var_names: Union[str, List[str]],
|
|
24
|
-
config_default: Any
|
|
25
|
-
) -> Any:
|
|
21
|
+
def resolve_config_value(param_value: Optional[Any], env_var_names: Union[str, List[str]], config_default: Any) -> Any:
|
|
26
22
|
"""
|
|
27
23
|
Resolve configuration value with priority: param > env vars > config default.
|
|
28
|
-
|
|
24
|
+
|
|
29
25
|
Args:
|
|
30
26
|
param_value: Explicitly provided parameter value
|
|
31
27
|
env_var_names: Environment variable name(s) to check (string or list)
|
|
32
28
|
config_default: Default value from configuration
|
|
33
|
-
|
|
29
|
+
|
|
34
30
|
Returns:
|
|
35
31
|
Resolved configuration value
|
|
36
|
-
|
|
32
|
+
|
|
37
33
|
Example:
|
|
38
34
|
host = resolve_config_value(
|
|
39
|
-
host_param,
|
|
40
|
-
["ES_HOST", "ELASTICSEARCH_HOST"],
|
|
35
|
+
host_param,
|
|
36
|
+
["ES_HOST", "ELASTICSEARCH_HOST"],
|
|
41
37
|
"localhost:9200"
|
|
42
38
|
)
|
|
43
39
|
"""
|
|
44
40
|
# Parameter takes highest precedence
|
|
45
41
|
if param_value is not None:
|
|
46
42
|
return param_value
|
|
47
|
-
|
|
43
|
+
|
|
48
44
|
# Check environment variables
|
|
49
45
|
if isinstance(env_var_names, str):
|
|
50
46
|
env_var_names = [env_var_names]
|
|
51
|
-
|
|
47
|
+
|
|
52
48
|
for env_var in env_var_names:
|
|
53
49
|
value = os.environ.get(env_var)
|
|
54
50
|
if value is not None:
|
|
55
51
|
return value
|
|
56
|
-
|
|
52
|
+
|
|
57
53
|
# Fall back to config default
|
|
58
54
|
return config_default
|
|
59
55
|
|
|
@@ -61,70 +57,72 @@ def resolve_config_value(
|
|
|
61
57
|
def resolve_aws_region(explicit_region: Optional[str] = None) -> str:
|
|
62
58
|
"""
|
|
63
59
|
Resolve AWS region with priority: param > env > config > session > default.
|
|
64
|
-
|
|
60
|
+
|
|
65
61
|
Args:
|
|
66
62
|
explicit_region: Explicitly provided region
|
|
67
|
-
|
|
63
|
+
|
|
68
64
|
Returns:
|
|
69
65
|
AWS region string
|
|
70
66
|
"""
|
|
71
67
|
# Explicit parameter wins
|
|
72
68
|
if explicit_region:
|
|
73
69
|
return explicit_region
|
|
74
|
-
|
|
70
|
+
|
|
75
71
|
# Check environment variables
|
|
76
|
-
env_region = resolve_config_value(
|
|
77
|
-
None,
|
|
78
|
-
["AWS_REGION", "AWS_DEFAULT_REGION"],
|
|
79
|
-
None
|
|
80
|
-
)
|
|
72
|
+
env_region = resolve_config_value(None, ["AWS_REGION", "AWS_DEFAULT_REGION"], None)
|
|
81
73
|
if env_region:
|
|
82
74
|
return env_region
|
|
83
|
-
|
|
75
|
+
|
|
84
76
|
# Check config
|
|
85
77
|
config = get_config()
|
|
86
|
-
if hasattr(config,
|
|
78
|
+
if hasattr(config, "aws_region") and config.aws_region:
|
|
87
79
|
return config.aws_region
|
|
88
|
-
|
|
80
|
+
|
|
89
81
|
# Check boto3 session default
|
|
90
82
|
try:
|
|
83
|
+
import boto3
|
|
84
|
+
from botocore.exceptions import NoCredentialsError
|
|
85
|
+
|
|
91
86
|
session = boto3.session.Session()
|
|
92
87
|
if session.region_name:
|
|
93
88
|
return session.region_name
|
|
94
|
-
except
|
|
95
|
-
log.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
except ImportError as e:
|
|
90
|
+
log.warning(f"boto3 not available for session-based region resolution: {e}")
|
|
91
|
+
except NoCredentialsError as e:
|
|
92
|
+
log.warning(f"No AWS credentials configured for session-based region resolution: {e}")
|
|
93
|
+
|
|
94
|
+
# Final fallback. Operators can override the package default via
|
|
95
|
+
# AWS_REGION_FALLBACK without forking or monkey-patching the constant.
|
|
96
|
+
return os.environ.get("AWS_REGION_FALLBACK") or DEFAULT_AWS_REGION
|
|
99
97
|
|
|
100
98
|
|
|
101
99
|
def create_aws_client(service_name: str, region: Optional[str] = None):
|
|
102
100
|
"""
|
|
103
101
|
Create AWS client with consistent region resolution and error handling.
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
Args:
|
|
106
104
|
service_name: AWS service name (e.g., 'secretsmanager', 'cloudwatch')
|
|
107
105
|
region: Optional explicit region
|
|
108
|
-
|
|
106
|
+
|
|
109
107
|
Returns:
|
|
110
108
|
AWS service client
|
|
111
|
-
|
|
109
|
+
|
|
112
110
|
Raises:
|
|
113
111
|
NoCredentialsError: When AWS credentials are not configured
|
|
114
112
|
ClientError: When client creation fails
|
|
115
113
|
"""
|
|
116
114
|
resolved_region = resolve_aws_region(region)
|
|
117
|
-
|
|
115
|
+
|
|
116
|
+
import boto3
|
|
117
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
118
|
+
|
|
118
119
|
try:
|
|
119
120
|
session = boto3.session.Session()
|
|
120
|
-
client = session.client(
|
|
121
|
-
|
|
122
|
-
region_name=resolved_region
|
|
123
|
-
)
|
|
124
|
-
|
|
121
|
+
client = session.client(service_name=service_name, region_name=resolved_region)
|
|
122
|
+
|
|
125
123
|
log.debug(f"Created {service_name} client for region {resolved_region}")
|
|
126
124
|
return client
|
|
127
|
-
|
|
125
|
+
|
|
128
126
|
except NoCredentialsError:
|
|
129
127
|
log.error(f"AWS credentials not configured for {service_name} client")
|
|
130
128
|
raise
|
|
@@ -137,65 +135,59 @@ def create_aws_client(service_name: str, region: Optional[str] = None):
|
|
|
137
135
|
|
|
138
136
|
|
|
139
137
|
def handle_client_errors(
|
|
140
|
-
default_return: Any = None,
|
|
141
|
-
log_context: Optional[Dict[str, Any]] = None,
|
|
142
|
-
reraise: bool = False
|
|
138
|
+
default_return: Any = None, log_context: Optional[Dict[str, Any]] = None, reraise: bool = False
|
|
143
139
|
):
|
|
144
140
|
"""
|
|
145
141
|
Decorator for standardized client error handling.
|
|
146
|
-
|
|
142
|
+
|
|
147
143
|
Args:
|
|
148
144
|
default_return: Value to return on error (if not reraising)
|
|
149
145
|
log_context: Additional context for error logging
|
|
150
146
|
reraise: Whether to re-raise exceptions after logging
|
|
151
|
-
|
|
147
|
+
|
|
152
148
|
Example:
|
|
153
149
|
@handle_client_errors(default_return=[])
|
|
154
150
|
def search_documents(self, query):
|
|
155
151
|
# Implementation that might fail
|
|
156
152
|
return results
|
|
157
153
|
"""
|
|
154
|
+
|
|
158
155
|
def decorator(func):
|
|
159
156
|
@functools.wraps(func)
|
|
160
|
-
def wrapper(*args, **kwargs):
|
|
157
|
+
def wrapper(*args, **kwargs) -> Any:
|
|
161
158
|
try:
|
|
162
159
|
return func(*args, **kwargs)
|
|
163
160
|
except Exception as e:
|
|
164
161
|
# Build log context
|
|
165
|
-
context = {
|
|
166
|
-
"function": func.__name__,
|
|
167
|
-
"error_type": type(e).__name__,
|
|
168
|
-
"error_message": str(e)
|
|
169
|
-
}
|
|
162
|
+
context = {"function": func.__name__, "error_type": type(e).__name__, "error_message": str(e)}
|
|
170
163
|
if log_context:
|
|
171
164
|
context.update(log_context)
|
|
172
|
-
|
|
173
|
-
log.error(
|
|
174
|
-
|
|
175
|
-
exc_info=True,
|
|
176
|
-
extra=context
|
|
177
|
-
)
|
|
178
|
-
|
|
165
|
+
|
|
166
|
+
log.error(f"{func.__name__} failed: {e}", exc_info=True, extra=context)
|
|
167
|
+
|
|
179
168
|
if reraise:
|
|
180
169
|
raise
|
|
181
|
-
|
|
170
|
+
|
|
182
171
|
return default_return
|
|
183
|
-
|
|
172
|
+
|
|
184
173
|
return wrapper
|
|
174
|
+
|
|
185
175
|
return decorator
|
|
186
176
|
|
|
187
177
|
|
|
188
|
-
def merge_dimensions(
|
|
178
|
+
def merge_dimensions(
|
|
179
|
+
base_dimensions: Dict[str, str], additional_dimensions: Optional[Dict[str, str]] = None
|
|
180
|
+
) -> List[Dict[str, str]]:
|
|
189
181
|
"""
|
|
190
182
|
Merge CloudWatch metric dimensions and format for API.
|
|
191
|
-
|
|
183
|
+
|
|
192
184
|
Args:
|
|
193
185
|
base_dimensions: Base dimensions dictionary
|
|
194
186
|
additional_dimensions: Additional dimensions to merge
|
|
195
|
-
|
|
187
|
+
|
|
196
188
|
Returns:
|
|
197
189
|
List of dimension dictionaries formatted for CloudWatch API
|
|
198
|
-
|
|
190
|
+
|
|
199
191
|
Example:
|
|
200
192
|
dimensions = merge_dimensions(
|
|
201
193
|
{"Service": "auth", "Environment": "prod"},
|
|
@@ -206,40 +198,37 @@ def merge_dimensions(base_dimensions: Dict[str, str], additional_dimensions: Opt
|
|
|
206
198
|
all_dimensions = {**base_dimensions}
|
|
207
199
|
if additional_dimensions:
|
|
208
200
|
all_dimensions.update(additional_dimensions)
|
|
209
|
-
|
|
210
|
-
return [
|
|
211
|
-
{"Name": str(key), "Value": str(value)}
|
|
212
|
-
for key, value in all_dimensions.items()
|
|
213
|
-
]
|
|
201
|
+
|
|
202
|
+
return [{"Name": str(key), "Value": str(value)} for key, value in all_dimensions.items()]
|
|
214
203
|
|
|
215
204
|
|
|
216
205
|
def validate_required_param(param_value: Any, param_name: str) -> Any:
|
|
217
206
|
"""
|
|
218
207
|
Validate that a required parameter is provided.
|
|
219
|
-
|
|
208
|
+
|
|
220
209
|
Args:
|
|
221
210
|
param_value: Parameter value to validate
|
|
222
211
|
param_name: Parameter name for error messages
|
|
223
|
-
|
|
212
|
+
|
|
224
213
|
Returns:
|
|
225
214
|
The parameter value if valid
|
|
226
|
-
|
|
215
|
+
|
|
227
216
|
Raises:
|
|
228
217
|
ValueError: If parameter is None or empty string
|
|
229
218
|
"""
|
|
230
219
|
if param_value is None:
|
|
231
220
|
raise ValueError(f"{param_name} is required")
|
|
232
|
-
|
|
221
|
+
|
|
233
222
|
if isinstance(param_value, str) and not param_value.strip():
|
|
234
223
|
raise ValueError(f"{param_name} cannot be empty")
|
|
235
|
-
|
|
224
|
+
|
|
236
225
|
return param_value
|
|
237
226
|
|
|
238
227
|
|
|
239
228
|
def safe_close_connection(connection) -> None:
|
|
240
229
|
"""
|
|
241
230
|
Safely close a database connection with proper error handling.
|
|
242
|
-
|
|
231
|
+
|
|
243
232
|
Args:
|
|
244
233
|
connection: Database connection to close
|
|
245
234
|
"""
|
|
@@ -251,29 +240,26 @@ def safe_close_connection(connection) -> None:
|
|
|
251
240
|
return
|
|
252
241
|
if hasattr(connection, "open") and not connection.open:
|
|
253
242
|
return
|
|
254
|
-
|
|
243
|
+
|
|
255
244
|
# Generic close
|
|
256
245
|
connection.close()
|
|
257
246
|
log.debug("Database connection closed successfully")
|
|
258
|
-
|
|
247
|
+
|
|
259
248
|
except Exception as e:
|
|
260
249
|
log.debug(f"Error closing connection (non-fatal): {e}")
|
|
261
250
|
|
|
262
251
|
|
|
263
|
-
def format_log_context(
|
|
264
|
-
operation: str,
|
|
265
|
-
**context_data
|
|
266
|
-
) -> Dict[str, Any]:
|
|
252
|
+
def format_log_context(operation: str, **context_data) -> Dict[str, Any]:
|
|
267
253
|
"""
|
|
268
254
|
Format consistent logging context for operations.
|
|
269
|
-
|
|
255
|
+
|
|
270
256
|
Args:
|
|
271
257
|
operation: Operation name
|
|
272
258
|
**context_data: Additional context key-value pairs
|
|
273
|
-
|
|
259
|
+
|
|
274
260
|
Returns:
|
|
275
261
|
Formatted context dictionary
|
|
276
|
-
|
|
262
|
+
|
|
277
263
|
Example:
|
|
278
264
|
context = format_log_context(
|
|
279
265
|
"database_query",
|
|
@@ -287,5 +273,5 @@ def format_log_context(
|
|
|
287
273
|
"timestamp": time.time(),
|
|
288
274
|
}
|
|
289
275
|
context.update(context_data)
|
|
290
|
-
|
|
291
|
-
return context
|
|
276
|
+
|
|
277
|
+
return context
|
|
File without changes
|
{nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{nui_python_shared_utils-1.3.2.dist-info → nui_python_shared_utils-1.3.3.dist-info}/top_level.txt
RENAMED
|
File without changes
|