nui-python-shared-utils 2.0__py3-none-any.whl → 2.1__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_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/METADATA +7 -5
- {nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/RECORD +9 -9
- nui_shared_utils/es_client.py +22 -8
- nui_shared_utils/slack_formatter.py +10 -11
- nui_shared_utils/timezone.py +13 -9
- {nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/WHEEL +0 -0
- {nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/entry_points.txt +0 -0
- {nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/licenses/LICENSE +0 -0
- {nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nui-python-shared-utils
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1
|
|
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
|
|
@@ -49,11 +49,11 @@ Requires-Python: >=3.9
|
|
|
49
49
|
Description-Content-Type: text/markdown
|
|
50
50
|
License-File: LICENSE
|
|
51
51
|
Requires-Dist: boto3>=1.20.0
|
|
52
|
-
Requires-Dist: pytz>=2021.3
|
|
53
52
|
Requires-Dist: click>=8.0.0
|
|
54
53
|
Requires-Dist: pyyaml>=6.0
|
|
54
|
+
Requires-Dist: tzdata>=2023.3; platform_system == "Windows"
|
|
55
55
|
Provides-Extra: elasticsearch
|
|
56
|
-
Requires-Dist: elasticsearch<
|
|
56
|
+
Requires-Dist: elasticsearch<10.0.0,>=7.17.0; extra == "elasticsearch"
|
|
57
57
|
Provides-Extra: database
|
|
58
58
|
Requires-Dist: pymysql>=1.0.0; extra == "database"
|
|
59
59
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "database"
|
|
@@ -64,12 +64,14 @@ Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "powertools"
|
|
|
64
64
|
Requires-Dist: coloredlogs>=15.0; extra == "powertools"
|
|
65
65
|
Provides-Extra: jwt
|
|
66
66
|
Requires-Dist: rsa>=4.9; extra == "jwt"
|
|
67
|
+
Provides-Extra: timezone
|
|
68
|
+
Requires-Dist: tzdata>=2023.3; extra == "timezone"
|
|
67
69
|
Provides-Extra: snowflake
|
|
68
70
|
Requires-Dist: snowflake-sql-api<0.2.0,>=0.1.1; extra == "snowflake"
|
|
69
71
|
Provides-Extra: llm
|
|
70
72
|
Requires-Dist: anthropic[bedrock]<1.0.0,>=0.45.0; extra == "llm"
|
|
71
73
|
Provides-Extra: all
|
|
72
|
-
Requires-Dist: elasticsearch<
|
|
74
|
+
Requires-Dist: elasticsearch<10.0.0,>=7.17.0; extra == "all"
|
|
73
75
|
Requires-Dist: pymysql>=1.0.0; extra == "all"
|
|
74
76
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
75
77
|
Requires-Dist: slack-sdk>=3.19.0; extra == "all"
|
|
@@ -85,11 +87,11 @@ Requires-Dist: black>=22.0.0; extra == "dev"
|
|
|
85
87
|
Requires-Dist: mypy>=0.990; extra == "dev"
|
|
86
88
|
Requires-Dist: boto3-stubs[essential]>=1.20.0; extra == "dev"
|
|
87
89
|
Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
|
|
88
|
-
Requires-Dist: types-pytz>=2021.3.0; extra == "dev"
|
|
89
90
|
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
90
91
|
Requires-Dist: build>=0.8.0; extra == "dev"
|
|
91
92
|
Requires-Dist: rsa>=4.9; extra == "dev"
|
|
92
93
|
Requires-Dist: cryptography>=41.0.0; extra == "dev"
|
|
94
|
+
Requires-Dist: tzdata>=2023.3; extra == "dev"
|
|
93
95
|
Requires-Dist: snowflake-sql-api<0.2.0,>=0.1.1; extra == "dev"
|
|
94
96
|
Requires-Dist: anthropic[bedrock]<1.0.0,>=0.45.0; extra == "dev"
|
|
95
97
|
Dynamic: author
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
nui_python_shared_utils-2.
|
|
1
|
+
nui_python_shared_utils-2.1.dist-info/licenses/LICENSE,sha256=vGe2mC5yLUb8toYlY3T36ZwCB5zQUW5hlCtEMiqokhM,1071
|
|
2
2
|
nui_shared_utils/__init__.py,sha256=vFS5oaw7_iHgHnLRifDYYyb7au6G2nYJ6ztqMs-hrQs,13289
|
|
3
3
|
nui_shared_utils/base_client.py,sha256=I1lKQGhrKyvujV2zps0TrtEdPDqMCwRQaIh6ceGONXQ,11016
|
|
4
4
|
nui_shared_utils/cli.py,sha256=JJpSoQWKvAz4b8cO30yFNi5vY9jmqrCHzbFpvrVTkbU,8747
|
|
@@ -6,7 +6,7 @@ nui_shared_utils/cloudwatch_metrics.py,sha256=p0J-s63UJDGK14TRGt0k8yoj7aWSSM55UK
|
|
|
6
6
|
nui_shared_utils/config.py,sha256=ZbkUKDxnHMJiHNv-TlWKjotmf2mH1Vz3GaIXgGfWg7E,4524
|
|
7
7
|
nui_shared_utils/db_client.py,sha256=HyVKJ1Su0dVPV1QI2MU5UtT8myDRCjKgV60xRL4SEdE,21726
|
|
8
8
|
nui_shared_utils/error_handler.py,sha256=pJ6b_mI0Ait8QRbs9UmLzYUN-Ft0l_dnpudVO_6LRSE,12171
|
|
9
|
-
nui_shared_utils/es_client.py,sha256=
|
|
9
|
+
nui_shared_utils/es_client.py,sha256=3MlrbddTrRN2mEH1b3TNzcmwYycLdujoIq_5_j-Cyfc,16714
|
|
10
10
|
nui_shared_utils/es_query_builder.py,sha256=UuheQf8b2UXaOiJlYCKYIfKfVERDZ3ag1P0OOOWm9do,12158
|
|
11
11
|
nui_shared_utils/jwt_auth.py,sha256=2Ag1zZKxd2R8QBz3aQA4h8OtKHQvUVKrjUp6lXpJxJU,9143
|
|
12
12
|
nui_shared_utils/lambda_helpers.py,sha256=psHVotpmOfnmyQCoOt4MSIEj7VwWcy6gZM3vYSZwjOk,3041
|
|
@@ -15,9 +15,9 @@ nui_shared_utils/log_processors.py,sha256=x5gz1LEkbmoMCZ3ZB6q-mbn26dSzwh1trz4wSk
|
|
|
15
15
|
nui_shared_utils/powertools_helpers.py,sha256=qc-lYLfY-QX20gALfPOJcihrtJhiCU-fJW8rsPT7LJE,11037
|
|
16
16
|
nui_shared_utils/secrets_helper.py,sha256=_jQkMWzjayZ8vvyHG2yuuxur-SQYUUONfcsZ0MF8qOw,7025
|
|
17
17
|
nui_shared_utils/slack_client.py,sha256=wXE0D4T4FWt1giz9Xmp71RustpaJYeLAZo6JxulDVko,25550
|
|
18
|
-
nui_shared_utils/slack_formatter.py,sha256=
|
|
18
|
+
nui_shared_utils/slack_formatter.py,sha256=QT9VXApE0q09n_PmEVvaLCSfLW24vmowlCXF3Dzimfk,10850
|
|
19
19
|
nui_shared_utils/snowflake_client.py,sha256=e3jH6_2mMqbtP1TE8-mUFFYrfGW9eqUPT5tlcgeCqx8,16181
|
|
20
|
-
nui_shared_utils/timezone.py,sha256=
|
|
20
|
+
nui_shared_utils/timezone.py,sha256=gTiSK5NUG0Z8UY_AsB4ktIzLrZ0wwTXtdTUH-Npdvik,3837
|
|
21
21
|
nui_shared_utils/utils.py,sha256=gOpw80tZGC24QsyesR8EySRODY-TFXG8cXUvggPBsRI,8184
|
|
22
22
|
nui_shared_utils/logging/__init__.py,sha256=gVC853q3ja4RhcBVbf5jKO10lFX4upfsTvkKZx4wu54,1805
|
|
23
23
|
nui_shared_utils/logging/binding.py,sha256=I5VNmtf4U2yt54aS4F0Djw4u22mfjwCpWvORUnJNkPU,13577
|
|
@@ -27,8 +27,8 @@ nui_shared_utils/slack_setup/__init__.py,sha256=OElyS3xk4F_YKH5uUUTDpN0ah1dOO3e5
|
|
|
27
27
|
nui_shared_utils/slack_setup/channel_creator.py,sha256=0gyCBIS0EC96SVn1Z2dD4Fpzk0xNdHSWyiCxoUQIUe8,10667
|
|
28
28
|
nui_shared_utils/slack_setup/channel_definitions.py,sha256=atfz5ZhpqefOeLh1gShbWd-TLzgjPmhv95bfuTdmZog,5676
|
|
29
29
|
nui_shared_utils/slack_setup/setup_helpers.py,sha256=pzzXMs12GI9sdZttWeYzgLPgC0xqPz7ZLHM1GUPNNXc,7219
|
|
30
|
-
nui_python_shared_utils-2.
|
|
31
|
-
nui_python_shared_utils-2.
|
|
32
|
-
nui_python_shared_utils-2.
|
|
33
|
-
nui_python_shared_utils-2.
|
|
34
|
-
nui_python_shared_utils-2.
|
|
30
|
+
nui_python_shared_utils-2.1.dist-info/METADATA,sha256=fWK7ffhd2g2e4rKPJeAhStSKUECYAxImYWsvGZyTbvM,22688
|
|
31
|
+
nui_python_shared_utils-2.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
32
|
+
nui_python_shared_utils-2.1.dist-info/entry_points.txt,sha256=TrJ3Z4kz3oXfy6InXn20jHu7rnTwo-4EA0KRFX3fkL0,66
|
|
33
|
+
nui_python_shared_utils-2.1.dist-info/top_level.txt,sha256=YMRu5cnn3hxHoYq9wNvaI9atVHnTNVdswO5_cQSfnJI,17
|
|
34
|
+
nui_python_shared_utils-2.1.dist-info/RECORD,,
|
nui_shared_utils/es_client.py
CHANGED
|
@@ -97,24 +97,37 @@ class ElasticsearchClient(BaseClient, ServiceHealthMixin):
|
|
|
97
97
|
retry_on_timeout=self.client_config.get("retry_on_timeout", True),
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
@staticmethod
|
|
101
|
+
def _body_with_size(body: Dict, size: int) -> Dict:
|
|
102
|
+
"""Return a copy of ``body`` carrying ``size``.
|
|
103
|
+
|
|
104
|
+
``size`` travels inside the request body rather than as a sibling keyword
|
|
105
|
+
argument, which is the one call shape every supported client major accepts.
|
|
106
|
+
On elasticsearch-py 8.x/9.x, passing ``body=`` alongside a body field as a
|
|
107
|
+
keyword folds that keyword into the caller's dict (mutating it) and raises
|
|
108
|
+
``ValueError`` if the key is already there, so reusing a body dict for two
|
|
109
|
+
searches breaks. A body that carries its own ``size`` is accepted unchanged
|
|
110
|
+
by 7.17, 8.x and 9.x, and leaves the caller's dict alone.
|
|
111
|
+
"""
|
|
112
|
+
return {**body, "size": size}
|
|
113
|
+
|
|
100
114
|
@handle_client_errors(default_return=[])
|
|
101
115
|
def search(self, index: str, body: Dict, size: int = 100) -> List[Dict]:
|
|
102
116
|
"""
|
|
103
117
|
Execute search query with error handling.
|
|
104
|
-
|
|
118
|
+
|
|
105
119
|
Args:
|
|
106
120
|
index: Index pattern to search
|
|
107
121
|
body: Elasticsearch query body
|
|
108
|
-
size: Maximum results to return
|
|
109
|
-
|
|
122
|
+
size: Maximum results to return (overrides any ``size`` in ``body``)
|
|
123
|
+
|
|
110
124
|
Returns:
|
|
111
125
|
List of hit documents
|
|
112
126
|
"""
|
|
113
127
|
def _search_operation():
|
|
114
128
|
response = self._service_client.search(
|
|
115
129
|
index=index,
|
|
116
|
-
body=body,
|
|
117
|
-
size=size,
|
|
130
|
+
body=self._body_with_size(body, size),
|
|
118
131
|
ignore_unavailable=True
|
|
119
132
|
)
|
|
120
133
|
return [hit["_source"] for hit in response["hits"]["hits"]]
|
|
@@ -141,8 +154,7 @@ class ElasticsearchClient(BaseClient, ServiceHealthMixin):
|
|
|
141
154
|
def _aggregate_operation():
|
|
142
155
|
response = self._service_client.search(
|
|
143
156
|
index=index,
|
|
144
|
-
body=body,
|
|
145
|
-
size=0, # Only need aggregations
|
|
157
|
+
body=self._body_with_size(body, 0), # Only need aggregations
|
|
146
158
|
ignore_unavailable=True
|
|
147
159
|
)
|
|
148
160
|
return response.get("aggregations", {})
|
|
@@ -374,7 +386,9 @@ class ElasticsearchClient(BaseClient, ServiceHealthMixin):
|
|
|
374
386
|
format="json",
|
|
375
387
|
h="index,health,status,docs.count,store.size"
|
|
376
388
|
)
|
|
377
|
-
return response
|
|
389
|
+
# 8.x/9.x return a response wrapper around the JSON array; normalise to
|
|
390
|
+
# a plain list so callers get the same type on every client major.
|
|
391
|
+
return list(response) if response else []
|
|
378
392
|
|
|
379
393
|
return self._execute_with_error_handling(
|
|
380
394
|
"get_indices_info",
|
|
@@ -4,8 +4,9 @@ Provides a builder pattern for creating Slack Block Kit messages.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from typing import Dict, List, Any, Optional, Union
|
|
7
|
-
from datetime import datetime, timedelta
|
|
8
|
-
|
|
7
|
+
from datetime import datetime, timedelta, timezone
|
|
8
|
+
|
|
9
|
+
from .timezone import NZ_TZ
|
|
9
10
|
|
|
10
11
|
# Severity emoji mapping
|
|
11
12
|
SEVERITY_EMOJI = {"critical": "🚨", "warning": "⚠️", "info": "ℹ️", "success": "✅", "error": "❌"}
|
|
@@ -42,25 +43,23 @@ def format_number(value: Union[int, float], decimals: int = 0) -> str:
|
|
|
42
43
|
def format_nz_time(dt: Optional[datetime] = None) -> str:
|
|
43
44
|
"""Format datetime in NZ timezone."""
|
|
44
45
|
if dt is None:
|
|
45
|
-
dt = datetime.
|
|
46
|
+
dt = datetime.now(timezone.utc)
|
|
46
47
|
elif not dt.tzinfo:
|
|
47
|
-
dt = dt.replace(tzinfo=
|
|
48
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
nz_time = dt.astimezone(nz_tz)
|
|
50
|
+
nz_time = dt.astimezone(NZ_TZ)
|
|
51
51
|
return nz_time.strftime("%I:%M %p %Z")
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def format_date_range(start: datetime, end: datetime) -> str:
|
|
55
55
|
"""Format a date range in NZ timezone."""
|
|
56
|
-
nz_tz = pytz.timezone("Pacific/Auckland")
|
|
57
56
|
if not start.tzinfo:
|
|
58
|
-
start = start.replace(tzinfo=
|
|
57
|
+
start = start.replace(tzinfo=timezone.utc)
|
|
59
58
|
if not end.tzinfo:
|
|
60
|
-
end = end.replace(tzinfo=
|
|
59
|
+
end = end.replace(tzinfo=timezone.utc)
|
|
61
60
|
|
|
62
|
-
start_nz = start.astimezone(
|
|
63
|
-
end_nz = end.astimezone(
|
|
61
|
+
start_nz = start.astimezone(NZ_TZ)
|
|
62
|
+
end_nz = end.astimezone(NZ_TZ)
|
|
64
63
|
|
|
65
64
|
if start_nz.date() == end_nz.date():
|
|
66
65
|
return f"{start_nz.strftime('%b %d')} {start_nz.strftime('%I:%M %p')} - {end_nz.strftime('%I:%M %p %Z')}"
|
nui_shared_utils/timezone.py
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Timezone utilities for multi-timezone handling (NZ primary, AUS/US/EUR secondary).
|
|
3
|
+
|
|
4
|
+
Zone data comes from the standard library's ``zoneinfo``, which reads the system
|
|
5
|
+
IANA database. Environments without one (Windows, minimal images such as Alpine)
|
|
6
|
+
need the ``tzdata`` package: `pip install nui-python-shared-utils[timezone]`.
|
|
3
7
|
"""
|
|
4
8
|
|
|
5
9
|
from datetime import datetime, timezone
|
|
6
|
-
import
|
|
7
|
-
from
|
|
10
|
+
from typing import List, Optional, Tuple
|
|
11
|
+
from zoneinfo import ZoneInfo
|
|
8
12
|
|
|
9
13
|
# Supported timezones
|
|
10
|
-
NZ_TZ =
|
|
11
|
-
AUS_TZ =
|
|
12
|
-
US_EAST_TZ =
|
|
13
|
-
US_WEST_TZ =
|
|
14
|
-
EUR_TZ =
|
|
14
|
+
NZ_TZ = ZoneInfo("Pacific/Auckland") # Primary timezone
|
|
15
|
+
AUS_TZ = ZoneInfo("Australia/Sydney")
|
|
16
|
+
US_EAST_TZ = ZoneInfo("US/Eastern")
|
|
17
|
+
US_WEST_TZ = ZoneInfo("US/Pacific")
|
|
18
|
+
EUR_TZ = ZoneInfo("Europe/London")
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
def nz_time(utc_dt: Optional[datetime] = None) -> datetime:
|
|
@@ -28,7 +32,7 @@ def nz_time(utc_dt: Optional[datetime] = None) -> datetime:
|
|
|
28
32
|
utc_dt = datetime.now(timezone.utc)
|
|
29
33
|
|
|
30
34
|
if utc_dt.tzinfo is None:
|
|
31
|
-
utc_dt =
|
|
35
|
+
utc_dt = utc_dt.replace(tzinfo=timezone.utc)
|
|
32
36
|
|
|
33
37
|
return utc_dt.astimezone(NZ_TZ)
|
|
34
38
|
|
|
@@ -66,7 +70,7 @@ def format_multi_timezone(dt: Optional[datetime] = None, primary_tz: str = "nz",
|
|
|
66
70
|
dt = datetime.now(timezone.utc)
|
|
67
71
|
|
|
68
72
|
if dt.tzinfo is None:
|
|
69
|
-
dt =
|
|
73
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
70
74
|
|
|
71
75
|
# Timezone mapping
|
|
72
76
|
tz_map = {"nz": NZ_TZ, "aus": AUS_TZ, "us_east": US_EAST_TZ, "us_west": US_WEST_TZ, "eur": EUR_TZ}
|
|
File without changes
|
{nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{nui_python_shared_utils-2.0.dist-info → nui_python_shared_utils-2.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|