my-aws-helpers 3.1.0.dev4__tar.gz → 4.0.0__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.
Potentially problematic release.
This version of my-aws-helpers might be problematic. Click here for more details.
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/PKG-INFO +3 -1
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/api.py +2 -2
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/bedrock.py +65 -43
- my_aws_helpers-4.0.0/my_aws_helpers/cognito.py +124 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/dynamo.py +14 -10
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/event.py +15 -13
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/s3.py +10 -3
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/PKG-INFO +3 -1
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/SOURCES.txt +1 -0
- my_aws_helpers-4.0.0/my_aws_helpers.egg-info/requires.txt +15 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/setup.py +22 -7
- my_aws_helpers-4.0.0/tests/test_cognito.py +20 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/tests/test_event.py +5 -4
- my_aws_helpers-3.1.0.dev4/my_aws_helpers/cognito.py +0 -144
- my_aws_helpers-3.1.0.dev4/my_aws_helpers.egg-info/requires.txt +0 -1
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/MANIFEST.in +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/README.md +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/auth.py +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/errors.py +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/logging.py +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/__init__.py +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/markdown_system_prompt.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_headers_prompt.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_headers_prompt_v2.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_prompt.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/sfn.py +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/dependency_links.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/top_level.txt +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/zip-safe +0 -0
- {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: my_aws_helpers
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: AWS Helpers
|
|
5
5
|
Home-page: https://github.com/JarrodMccarthy/aws_helpers.git
|
|
6
6
|
Author: Jarrod McCarthy
|
|
@@ -11,6 +11,8 @@ Classifier: Programming Language :: Python
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.8
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Provides-Extra: bedrock
|
|
14
16
|
|
|
15
17
|
UNKNOWN
|
|
16
18
|
|
|
@@ -10,7 +10,7 @@ class API:
|
|
|
10
10
|
if isinstance(obj, datetime) or isinstance(obj, date):
|
|
11
11
|
return obj.isoformat()
|
|
12
12
|
return obj
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
def response_serialiser(response: Any):
|
|
15
15
|
if isinstance(response, list):
|
|
16
16
|
return [API.response_serialiser(obj) for obj in response]
|
|
@@ -18,7 +18,7 @@ class API:
|
|
|
18
18
|
for k, v in response.items():
|
|
19
19
|
response[k] = API.response_serialiser(v)
|
|
20
20
|
return response
|
|
21
|
-
return API._serialise(response)
|
|
21
|
+
return API._serialise(response)
|
|
22
22
|
|
|
23
23
|
def response(code: int, body: Optional[str] = None):
|
|
24
24
|
return {
|
|
@@ -11,6 +11,7 @@ import pymupdf
|
|
|
11
11
|
import concurrent.futures
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
class PromptType(str, Enum):
|
|
15
16
|
transaction_headers = "transactions_headers_prompt_v2.txt"
|
|
16
17
|
transactions = "transactions_prompt.txt"
|
|
@@ -27,9 +28,9 @@ class TokenUsage:
|
|
|
27
28
|
@classmethod
|
|
28
29
|
def from_dict(cls, data: Dict[str, int]) -> TokenUsage:
|
|
29
30
|
return cls(
|
|
30
|
-
input_tokens=data.get(
|
|
31
|
-
output_tokens=data.get(
|
|
32
|
-
total_tokens=data.get(
|
|
31
|
+
input_tokens=data.get("inputTokens", 0),
|
|
32
|
+
output_tokens=data.get("outputTokens", 0),
|
|
33
|
+
total_tokens=data.get("totalTokens", 0),
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
@@ -42,9 +43,9 @@ class OCRResult:
|
|
|
42
43
|
@classmethod
|
|
43
44
|
def from_dict(cls, data: Dict) -> OCRResult:
|
|
44
45
|
return cls(
|
|
45
|
-
content
|
|
46
|
-
token_usage
|
|
47
|
-
page_number
|
|
46
|
+
content=data.get("content", []),
|
|
47
|
+
token_usage=TokenUsage.from_dict(data.get("token_usage", {})),
|
|
48
|
+
page_number=data.get("page_number", 0),
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
|
|
@@ -52,7 +53,7 @@ class Bedrock:
|
|
|
52
53
|
def __init__(
|
|
53
54
|
self,
|
|
54
55
|
model_id: str = "apac.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
55
|
-
logger
|
|
56
|
+
logger=None,
|
|
56
57
|
sleep_time: float = 1.0,
|
|
57
58
|
):
|
|
58
59
|
|
|
@@ -60,16 +61,18 @@ class Bedrock:
|
|
|
60
61
|
self.logger = logger
|
|
61
62
|
region_name = "ap-southeast-2"
|
|
62
63
|
if self.session is None:
|
|
63
|
-
self.session = boto3.Session(region_name
|
|
64
|
+
self.session = boto3.Session(region_name=region_name)
|
|
64
65
|
self.sleep_time = sleep_time
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
custom_config = Config(
|
|
67
68
|
retries={
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
"max_attempts": 2, # Total attempts = 1 initial + 1 retry
|
|
70
|
+
"mode": "standard", # or 'adaptive'
|
|
70
71
|
}
|
|
71
|
-
)
|
|
72
|
-
self.client = self.session.client(
|
|
72
|
+
)
|
|
73
|
+
self.client = self.session.client(
|
|
74
|
+
"bedrock-runtime", region_name=region_name, config=custom_config
|
|
75
|
+
)
|
|
73
76
|
self.model_id = model_id
|
|
74
77
|
|
|
75
78
|
@staticmethod
|
|
@@ -83,7 +86,7 @@ class Bedrock:
|
|
|
83
86
|
aws_access_key_id=aws_access_key_id,
|
|
84
87
|
aws_secret_access_key=aws_secret_access_key,
|
|
85
88
|
aws_session_token=aws_session_token,
|
|
86
|
-
region_name=region_name
|
|
89
|
+
region_name=region_name,
|
|
87
90
|
)
|
|
88
91
|
except Exception as e:
|
|
89
92
|
print(e)
|
|
@@ -92,7 +95,7 @@ class Bedrock:
|
|
|
92
95
|
def _get_prompt(self, prompt_type: str) -> Optional[str]:
|
|
93
96
|
if prompt_type not in list(PromptType):
|
|
94
97
|
raise Exception(f"Error: Invalid prompt type")
|
|
95
|
-
|
|
98
|
+
|
|
96
99
|
path = os.path.join(os.path.dirname(__file__), "prompts", prompt_type)
|
|
97
100
|
try:
|
|
98
101
|
with open(path, "r") as f:
|
|
@@ -103,10 +106,7 @@ class Bedrock:
|
|
|
103
106
|
return None
|
|
104
107
|
|
|
105
108
|
def _ocr(
|
|
106
|
-
self,
|
|
107
|
-
prompt: str,
|
|
108
|
-
image_bytes: bytes,
|
|
109
|
-
page_number: Optional[int] = 0
|
|
109
|
+
self, prompt: str, image_bytes: bytes, page_number: Optional[int] = 0
|
|
110
110
|
) -> Optional[OCRResult]:
|
|
111
111
|
system_prompt = [{"text": prompt}]
|
|
112
112
|
message = [
|
|
@@ -118,36 +118,41 @@ class Bedrock:
|
|
|
118
118
|
"format": "png",
|
|
119
119
|
"source": {
|
|
120
120
|
"bytes": image_bytes,
|
|
121
|
-
}
|
|
121
|
+
},
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
]
|
|
124
|
+
],
|
|
125
125
|
}
|
|
126
126
|
]
|
|
127
127
|
retries = 3
|
|
128
128
|
for i in range(retries):
|
|
129
129
|
self.logger.info(f"Attempt number {i} for {self.model_id} converse")
|
|
130
130
|
try:
|
|
131
|
-
response = self.client.converse(
|
|
132
|
-
|
|
131
|
+
response = self.client.converse(
|
|
132
|
+
modelId=self.model_id, messages=message, system=system_prompt
|
|
133
|
+
)
|
|
134
|
+
if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
|
|
133
135
|
break
|
|
134
136
|
except Exception as e:
|
|
135
137
|
self.logger.exception(f"Error during conversation due to {e}")
|
|
136
|
-
if i >= len(retries) - 1:
|
|
138
|
+
if i >= len(retries) - 1:
|
|
139
|
+
raise Exception(e)
|
|
137
140
|
time.sleep(self.sleep_time)
|
|
138
141
|
continue
|
|
139
|
-
|
|
142
|
+
|
|
140
143
|
result = {}
|
|
141
|
-
result["content"] = json.loads(
|
|
144
|
+
result["content"] = json.loads(
|
|
145
|
+
response["output"]["message"]["content"][0]["text"]
|
|
146
|
+
)
|
|
142
147
|
result["token_usage"] = response["usage"]
|
|
143
148
|
result["page_number"] = page_number
|
|
144
|
-
return OCRResult.from_dict(data
|
|
145
|
-
|
|
149
|
+
return OCRResult.from_dict(data=result)
|
|
150
|
+
|
|
146
151
|
def _parallel_ocr(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
self,
|
|
153
|
+
image_bytes_list: List[bytes],
|
|
154
|
+
prompt: str,
|
|
155
|
+
max_workers: int = 10,
|
|
151
156
|
):
|
|
152
157
|
execution_futures = []
|
|
153
158
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
@@ -158,12 +163,16 @@ class Bedrock:
|
|
|
158
163
|
execution_futures.append(future)
|
|
159
164
|
|
|
160
165
|
# Wait for all tasks and collect results in order of submission
|
|
161
|
-
results = [
|
|
166
|
+
results = [
|
|
167
|
+
future.result()
|
|
168
|
+
for future in execution_futures
|
|
169
|
+
if future.result() is not None
|
|
170
|
+
]
|
|
162
171
|
return results
|
|
163
|
-
|
|
172
|
+
|
|
164
173
|
def get_ocr_result(
|
|
165
174
|
self,
|
|
166
|
-
pdf_bytes: io.BytesIO,
|
|
175
|
+
pdf_bytes: io.BytesIO,
|
|
167
176
|
prompt_type: str,
|
|
168
177
|
zoom: int = 7,
|
|
169
178
|
) -> List[OCRResult]:
|
|
@@ -175,7 +184,9 @@ class Bedrock:
|
|
|
175
184
|
image_bytes_list: List[bytes] = list()
|
|
176
185
|
for i, p in enumerate(pages):
|
|
177
186
|
try:
|
|
178
|
-
image_bytes: bytes = p.get_pixmap(
|
|
187
|
+
image_bytes: bytes = p.get_pixmap(
|
|
188
|
+
matrix=pymupdf.Matrix(zoom, zoom)
|
|
189
|
+
).tobytes("png")
|
|
179
190
|
image_bytes_list.append(image_bytes)
|
|
180
191
|
except Exception as e:
|
|
181
192
|
self.logger.error(f"Could not get pix map for page {i}")
|
|
@@ -183,12 +194,18 @@ class Bedrock:
|
|
|
183
194
|
skip_page_zero = False
|
|
184
195
|
header_ocr_result = None
|
|
185
196
|
if len(image_bytes_list) > 1:
|
|
186
|
-
headers_prompt = self._get_prompt(
|
|
197
|
+
headers_prompt = self._get_prompt(
|
|
198
|
+
prompt_type=PromptType.transaction_headers.value
|
|
199
|
+
)
|
|
187
200
|
for i in range(2):
|
|
188
201
|
# try to get headers from the first or second page
|
|
189
|
-
header_ocr_result = self._ocr(
|
|
202
|
+
header_ocr_result = self._ocr(
|
|
203
|
+
prompt=headers_prompt, image_bytes=image_bytes_list[i]
|
|
204
|
+
)
|
|
190
205
|
if header_ocr_result is None:
|
|
191
|
-
self.logger.info(
|
|
206
|
+
self.logger.info(
|
|
207
|
+
f"No ocr result returned when getting headers {PromptType.transaction_headers.value}"
|
|
208
|
+
)
|
|
192
209
|
headers = header_ocr_result.content.get("headers")
|
|
193
210
|
if (len(headers) < 1) or (headers is None):
|
|
194
211
|
skip_page_zero = True
|
|
@@ -198,14 +215,20 @@ class Bedrock:
|
|
|
198
215
|
|
|
199
216
|
transactions_prompt = self._get_prompt(prompt_type=prompt_type)
|
|
200
217
|
if header_ocr_result:
|
|
201
|
-
transactions_prompt = transactions_prompt.replace(
|
|
218
|
+
transactions_prompt = transactions_prompt.replace(
|
|
219
|
+
"#### TABLE HEADERS ####", json.dumps(header_ocr_result.content)
|
|
220
|
+
)
|
|
202
221
|
|
|
203
222
|
self.logger.info("Got Prompt")
|
|
204
223
|
results = list()
|
|
205
224
|
|
|
206
225
|
if skip_page_zero:
|
|
207
|
-
image_bytes_list = image_bytes_list[
|
|
208
|
-
|
|
226
|
+
image_bytes_list = image_bytes_list[
|
|
227
|
+
1:
|
|
228
|
+
] # page zero often has account summary info
|
|
229
|
+
results = self._parallel_ocr(
|
|
230
|
+
image_bytes_list=image_bytes_list, prompt=transactions_prompt
|
|
231
|
+
)
|
|
209
232
|
|
|
210
233
|
# for i, image_bytes in enumerate(image_bytes_list):
|
|
211
234
|
# self.logger.info(f"Starting OCR for page: {i}")
|
|
@@ -214,4 +237,3 @@ class Bedrock:
|
|
|
214
237
|
except Exception as e:
|
|
215
238
|
self.logger.exception(e)
|
|
216
239
|
return []
|
|
217
|
-
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import boto3
|
|
5
|
+
import urllib.request
|
|
6
|
+
from jose import jwk, jwt
|
|
7
|
+
from jose.utils import base64url_decode
|
|
8
|
+
from my_aws_helpers.logging import select_powertools_logger
|
|
9
|
+
|
|
10
|
+
logger = select_powertools_logger("aws-helpers-cognito")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Cognito:
|
|
14
|
+
client: boto3.client
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
client: Optional[boto3.client] = None,
|
|
19
|
+
user_pool_id: Optional[str] = None,
|
|
20
|
+
):
|
|
21
|
+
self.cognito_user_pool_id = (
|
|
22
|
+
user_pool_id if user_pool_id else os.environ["COGNITO_USER_POOL_ID"]
|
|
23
|
+
)
|
|
24
|
+
self.region = os.environ.get("AWS_DEFAULT_REGION", "ap-southeast-2")
|
|
25
|
+
self.client = client if client else self._get_client(region=self.region)
|
|
26
|
+
|
|
27
|
+
def _get_client(self, region: str) -> boto3.client:
|
|
28
|
+
return boto3.client("cognito-idp", region_name=region)
|
|
29
|
+
|
|
30
|
+
def _verify_signature(self):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def _verify_audience(self):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def _verify_token_use(self):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def validate_token(self, token: str) -> bool:
|
|
40
|
+
try:
|
|
41
|
+
if "Bearer" in token:
|
|
42
|
+
token = token.split(" ")[-1]
|
|
43
|
+
headers = jwt.get_unverified_headers(token)
|
|
44
|
+
key_id = headers["kid"]
|
|
45
|
+
keys = self._get_keys()
|
|
46
|
+
key = next(k for k in keys if k["kid"] == key_id)
|
|
47
|
+
public_key = jwk.construct(key)
|
|
48
|
+
message, encoded_signature = token.rsplit(".", 1)
|
|
49
|
+
decoded_signature = base64url_decode(encoded_signature.encode("utf-8"))
|
|
50
|
+
if not public_key.verify(message.encode("utf-8"), decoded_signature):
|
|
51
|
+
raise Exception("Signature verification failed")
|
|
52
|
+
return True
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.exception(f"Failed to validate token due to {e}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def sign_up(self, username: str, password: str, app_client_id: str):
|
|
58
|
+
try:
|
|
59
|
+
response = self.client.sign_up(
|
|
60
|
+
ClientId=app_client_id,
|
|
61
|
+
Username=username,
|
|
62
|
+
Password=password,
|
|
63
|
+
UserAttributes=[
|
|
64
|
+
{"Name": "email", "Value": username},
|
|
65
|
+
],
|
|
66
|
+
)
|
|
67
|
+
return response
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.exception(f"Failed to sign up due to {e}")
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def admin_confirm_sign_up(
|
|
73
|
+
self,
|
|
74
|
+
username: str,
|
|
75
|
+
user_pool_id: str,
|
|
76
|
+
) -> dict:
|
|
77
|
+
try:
|
|
78
|
+
response = self.client.admin_confirm_sign_up(
|
|
79
|
+
UserPoolId=user_pool_id, Username=username
|
|
80
|
+
)
|
|
81
|
+
return response
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.exception(
|
|
84
|
+
f"Failed to confirm admin sign up username {username} due to {e}"
|
|
85
|
+
)
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def refresh_token(self, refresh_token: str, app_client_id: str):
|
|
89
|
+
response = self.client.initiate_auth(
|
|
90
|
+
ClientId=app_client_id,
|
|
91
|
+
AuthFlow="REFRESH_TOKEN_AUTH",
|
|
92
|
+
AuthParameters={"REFRESH_TOKEN": refresh_token},
|
|
93
|
+
)
|
|
94
|
+
return response
|
|
95
|
+
|
|
96
|
+
def login(self, username: str, password: str, app_client_id: str):
|
|
97
|
+
response = self.client.initiate_auth(
|
|
98
|
+
ClientId=app_client_id,
|
|
99
|
+
AuthFlow="USER_PASSWORD_AUTH",
|
|
100
|
+
AuthParameters={"USERNAME": username, "PASSWORD": password},
|
|
101
|
+
)
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
def _get_issuer(self) -> str:
|
|
105
|
+
return f"https://cognito-idp.{self.region}.amazonaws.com/{self.cognito_user_pool_id}"
|
|
106
|
+
|
|
107
|
+
def _get_keys(self):
|
|
108
|
+
issuer = self._get_issuer()
|
|
109
|
+
keys_url = f"{issuer}/.well-known/jwks.json"
|
|
110
|
+
with urllib.request.urlopen(keys_url) as f:
|
|
111
|
+
response = f.read()
|
|
112
|
+
keys = json.loads(response.decode("utf-8"))["keys"]
|
|
113
|
+
return keys
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def get_policy(allow: bool, method_arn: str = "*") -> dict:
|
|
117
|
+
allow = "Allow" if allow else "Deny"
|
|
118
|
+
return {
|
|
119
|
+
"principalId": "authenticated-user",
|
|
120
|
+
"policyDocument": {
|
|
121
|
+
"Version": "2012-10-17",
|
|
122
|
+
"Statement": [{"Action": "*", "Effect": allow, "Resource": "*"}],
|
|
123
|
+
},
|
|
124
|
+
}
|
|
@@ -2,12 +2,13 @@ from typing import List, Any, Optional
|
|
|
2
2
|
from datetime import datetime, date
|
|
3
3
|
import boto3
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
|
-
from decimal import Decimal, Context
|
|
5
|
+
from decimal import Decimal, Context
|
|
6
6
|
|
|
7
7
|
from my_aws_helpers.logging import select_powertools_logger
|
|
8
8
|
|
|
9
9
|
logger = select_powertools_logger("aws-helpers-dynamo")
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
class MetaData:
|
|
12
13
|
"""
|
|
13
14
|
This class is a convenience class,
|
|
@@ -76,7 +77,6 @@ class BaseTableObject:
|
|
|
76
77
|
|
|
77
78
|
def __init__(self) -> None:
|
|
78
79
|
pass
|
|
79
|
-
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
class DynamoSerialiser:
|
|
@@ -86,10 +86,10 @@ class DynamoSerialiser:
|
|
|
86
86
|
if isinstance(obj, datetime) or isinstance(obj, date):
|
|
87
87
|
return obj.isoformat()
|
|
88
88
|
if isinstance(obj, float):
|
|
89
|
-
ctx = Context(prec
|
|
89
|
+
ctx = Context(prec=38)
|
|
90
90
|
return ctx.create_decimal_from_float(obj)
|
|
91
91
|
return obj
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
@staticmethod
|
|
94
94
|
def object_serialiser(obj: Any):
|
|
95
95
|
if isinstance(obj, list):
|
|
@@ -98,6 +98,7 @@ class DynamoSerialiser:
|
|
|
98
98
|
return {k: DynamoSerialiser.object_serialiser(v) for k, v in obj.items()}
|
|
99
99
|
return DynamoSerialiser._serialise(obj=obj)
|
|
100
100
|
|
|
101
|
+
|
|
101
102
|
class Dynamo:
|
|
102
103
|
table: boto3.resource
|
|
103
104
|
|
|
@@ -130,14 +131,16 @@ class Dynamo:
|
|
|
130
131
|
response = self.table.scan()
|
|
131
132
|
items: List = response["Items"]
|
|
132
133
|
while response.get("LastEvaluatedKey") is not None:
|
|
133
|
-
response = self.table.scan(ExclusiveStartKey
|
|
134
|
+
response = self.table.scan(ExclusiveStartKey=response["LastEvaluatedKey"])
|
|
134
135
|
if response.get("Items") is not None:
|
|
135
136
|
items.extend(response["Items"])
|
|
136
137
|
if response.get("LastEvaluatedKey") is None:
|
|
137
138
|
break
|
|
138
139
|
return items
|
|
139
140
|
|
|
140
|
-
def delete_table_items(
|
|
141
|
+
def delete_table_items(
|
|
142
|
+
self, partition_key_name: str = "pk", sort_key_name: str = "sk"
|
|
143
|
+
) -> bool:
|
|
141
144
|
try:
|
|
142
145
|
items = self._deep_scan()
|
|
143
146
|
delete_repr_items = [
|
|
@@ -147,10 +150,10 @@ class Dynamo:
|
|
|
147
150
|
}
|
|
148
151
|
for item in items
|
|
149
152
|
]
|
|
150
|
-
self.batch_delete(items
|
|
153
|
+
self.batch_delete(items=delete_repr_items)
|
|
151
154
|
return True
|
|
152
155
|
except Exception as e:
|
|
153
|
-
logger.exception(f
|
|
156
|
+
logger.exception(f"Failed to delete table items due to {e}")
|
|
154
157
|
return False
|
|
155
158
|
|
|
156
159
|
def to_dynamo_representation(obj: dict):
|
|
@@ -177,6 +180,7 @@ def _datatype_map(value: Any):
|
|
|
177
180
|
return new_obj
|
|
178
181
|
return value
|
|
179
182
|
|
|
183
|
+
|
|
180
184
|
class BaseQueries(ABC):
|
|
181
185
|
table_name: str
|
|
182
186
|
|
|
@@ -186,7 +190,7 @@ class BaseQueries(ABC):
|
|
|
186
190
|
|
|
187
191
|
def _get_client(self):
|
|
188
192
|
return Dynamo(table_name=self.table_name)
|
|
189
|
-
|
|
193
|
+
|
|
190
194
|
def _iterative_query(self, query_kwargs: dict) -> List[dict]:
|
|
191
195
|
results = list()
|
|
192
196
|
last_evaluated_key = "not none"
|
|
@@ -198,4 +202,4 @@ class BaseQueries(ABC):
|
|
|
198
202
|
results += result["Items"]
|
|
199
203
|
last_evaluated_key = result.get("LastEvaluatedKey")
|
|
200
204
|
exclusive_start_key = last_evaluated_key
|
|
201
|
-
return results
|
|
205
|
+
return results
|
|
@@ -7,14 +7,15 @@ from copy import copy
|
|
|
7
7
|
from boto3.dynamodb.conditions import Key
|
|
8
8
|
from my_aws_helpers.dynamo import BaseTableObject, DynamoSerialiser, BaseQueries, Dynamo
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
class EventDynamoKeys:
|
|
11
12
|
@staticmethod
|
|
12
13
|
def get_event_pk(id: str):
|
|
13
14
|
return f"id##{id}"
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
@staticmethod
|
|
16
17
|
def get_event_sk(id: str):
|
|
17
|
-
return f"id##{id}"
|
|
18
|
+
return f"id##{id}"
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class EventStatus(str, Enum):
|
|
@@ -40,7 +41,9 @@ class Event(BaseTableObject):
|
|
|
40
41
|
self.status: str = self.set_status(status=status)
|
|
41
42
|
self.message: str = message
|
|
42
43
|
self.id: str = id if id else uuid4().hex
|
|
43
|
-
self.created_timestamp: datetime =
|
|
44
|
+
self.created_timestamp: datetime = (
|
|
45
|
+
created_timestamp if created_timestamp else datetime.now()
|
|
46
|
+
)
|
|
44
47
|
|
|
45
48
|
def set_status(self, status: str) -> str:
|
|
46
49
|
if status not in list(EventStatus):
|
|
@@ -49,9 +52,9 @@ class Event(BaseTableObject):
|
|
|
49
52
|
|
|
50
53
|
def _get_pk(self):
|
|
51
54
|
return EventDynamoKeys.get_event_pk(self.id)
|
|
52
|
-
|
|
55
|
+
|
|
53
56
|
def _get_sk(self):
|
|
54
|
-
return EventDynamoKeys.get_event_sk(self.id)
|
|
57
|
+
return EventDynamoKeys.get_event_sk(self.id)
|
|
55
58
|
|
|
56
59
|
@classmethod
|
|
57
60
|
def _from_dynamo_representation(cls, obj: dict):
|
|
@@ -59,8 +62,8 @@ class Event(BaseTableObject):
|
|
|
59
62
|
Deserialises this object from Dynamo Representation
|
|
60
63
|
"""
|
|
61
64
|
this_obj = copy(obj)
|
|
62
|
-
this_obj.pop(
|
|
63
|
-
this_obj.pop(
|
|
65
|
+
this_obj.pop("pk")
|
|
66
|
+
this_obj.pop("sk")
|
|
64
67
|
return cls(**this_obj)
|
|
65
68
|
|
|
66
69
|
def _to_dynamo_representation(self):
|
|
@@ -68,8 +71,8 @@ class Event(BaseTableObject):
|
|
|
68
71
|
Serialises this object to Dynamo Representation
|
|
69
72
|
"""
|
|
70
73
|
obj = copy(vars(self))
|
|
71
|
-
obj[
|
|
72
|
-
obj[
|
|
74
|
+
obj["pk"] = self._get_pk()
|
|
75
|
+
obj["sk"] = self._get_sk()
|
|
73
76
|
return DynamoSerialiser.object_serialiser(obj=obj)
|
|
74
77
|
|
|
75
78
|
|
|
@@ -78,11 +81,11 @@ class EventQueries(BaseQueries):
|
|
|
78
81
|
super().__init__(table_name, client)
|
|
79
82
|
|
|
80
83
|
def put_event(self, event: Event) -> Event:
|
|
81
|
-
response = self.client.put_item(item
|
|
84
|
+
response = self.client.put_item(item=event._to_dynamo_representation())
|
|
82
85
|
if response["ResponseMetadata"]["HTTPStatusCode"] == 200:
|
|
83
86
|
return event
|
|
84
87
|
raise Exception(f"Failed to put object into dynamo table")
|
|
85
|
-
|
|
88
|
+
|
|
86
89
|
def get_event_by_id(self, id: str):
|
|
87
90
|
pk = EventDynamoKeys.get_event_pk(id=id)
|
|
88
91
|
sk = EventDynamoKeys.get_event_sk(id=id)
|
|
@@ -93,5 +96,4 @@ class EventQueries(BaseQueries):
|
|
|
93
96
|
return None
|
|
94
97
|
if len(results["Items"]) > 1:
|
|
95
98
|
raise Exception(f"Should only be 1 event with id {id}")
|
|
96
|
-
return Event._from_dynamo_representation(obj
|
|
97
|
-
|
|
99
|
+
return Event._from_dynamo_representation(obj=results["Items"][0])
|
|
@@ -51,7 +51,11 @@ class S3:
|
|
|
51
51
|
resource: Optional[boto3.resource] = None,
|
|
52
52
|
) -> None:
|
|
53
53
|
self.client = client if client else self._get_client()
|
|
54
|
-
self.resource =
|
|
54
|
+
self.resource = (
|
|
55
|
+
resource
|
|
56
|
+
if resource
|
|
57
|
+
else boto3.resource("s3", region_name=os.environ["AWS_DEFAULT_REGION"])
|
|
58
|
+
)
|
|
55
59
|
|
|
56
60
|
def _get_client(self) -> boto3.client:
|
|
57
61
|
region_name = os.environ["AWS_DEFAULT_REGION"]
|
|
@@ -77,7 +81,10 @@ class S3:
|
|
|
77
81
|
return self._streaming_body_to_dict(response["Body"])
|
|
78
82
|
|
|
79
83
|
def get_presigned_url(
|
|
80
|
-
self,
|
|
84
|
+
self,
|
|
85
|
+
bucket_name: str,
|
|
86
|
+
file_name: str,
|
|
87
|
+
expires_in: int = 3600,
|
|
81
88
|
):
|
|
82
89
|
return self.client.generate_presigned_url(
|
|
83
90
|
"get_object",
|
|
@@ -128,7 +135,7 @@ class S3:
|
|
|
128
135
|
return s3_location
|
|
129
136
|
except Exception as e:
|
|
130
137
|
logger.exception(e)
|
|
131
|
-
return None
|
|
138
|
+
return None
|
|
132
139
|
|
|
133
140
|
def read_binary_from_s3(self, s3_location: S3Location) -> Optional[bytes]:
|
|
134
141
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: my-aws-helpers
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: AWS Helpers
|
|
5
5
|
Home-page: https://github.com/JarrodMccarthy/aws_helpers.git
|
|
6
6
|
Author: Jarrod McCarthy
|
|
@@ -11,6 +11,8 @@ Classifier: Programming Language :: Python
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.8
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Provides-Extra: bedrock
|
|
14
16
|
|
|
15
17
|
UNKNOWN
|
|
16
18
|
|
|
@@ -3,7 +3,19 @@ from setuptools import find_namespace_packages, setup
|
|
|
3
3
|
|
|
4
4
|
base_path = os.path.abspath(os.path.dirname(__file__))
|
|
5
5
|
|
|
6
|
-
version = "
|
|
6
|
+
version = "4.0.0"
|
|
7
|
+
|
|
8
|
+
core = [
|
|
9
|
+
"boto3==1.34.36",
|
|
10
|
+
"python-jose==3.5.0",
|
|
11
|
+
]
|
|
12
|
+
dev = [
|
|
13
|
+
"pytest==7.4.3",
|
|
14
|
+
"coverage==7.3.2",
|
|
15
|
+
"black<=25.1.0,<26.0.0",
|
|
16
|
+
]
|
|
17
|
+
bedrock = ["PyMuPDF==1.26.0"]
|
|
18
|
+
all = dev + core + bedrock
|
|
7
19
|
|
|
8
20
|
setup(
|
|
9
21
|
name="my_aws_helpers",
|
|
@@ -12,7 +24,11 @@ setup(
|
|
|
12
24
|
description="AWS Helpers",
|
|
13
25
|
url="https://github.com/JarrodMccarthy/aws_helpers.git",
|
|
14
26
|
platforms="any",
|
|
15
|
-
packages=[
|
|
27
|
+
packages=[
|
|
28
|
+
p
|
|
29
|
+
for p in find_namespace_packages(where=base_path)
|
|
30
|
+
if p.startswith("my_aws_helpers")
|
|
31
|
+
],
|
|
16
32
|
classifiers=[
|
|
17
33
|
"License :: Other/Proprietary License",
|
|
18
34
|
"Programming Language :: Python",
|
|
@@ -20,9 +36,8 @@ setup(
|
|
|
20
36
|
"Programming Language :: Python :: 3.9",
|
|
21
37
|
"Programming Language :: Python :: 3.10",
|
|
22
38
|
],
|
|
23
|
-
zip_safe
|
|
24
|
-
install_requires
|
|
25
|
-
"boto3==1.34.36"
|
|
26
|
-
],
|
|
39
|
+
zip_safe=True,
|
|
40
|
+
install_requires=core,
|
|
27
41
|
include_package_data=True,
|
|
28
|
-
|
|
42
|
+
extras_require={"bedrock": core + bedrock, "all": all},
|
|
43
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from aws_lambda_powertools.logging import Logger
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_init():
|
|
6
|
+
from my_aws_helpers.cognito import Cognito
|
|
7
|
+
|
|
8
|
+
os.environ["COGNITO_USER_POOL_ID"] = "fake-pool-id"
|
|
9
|
+
cognito = Cognito()
|
|
10
|
+
assert isinstance(cognito, Cognito) == True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_init_with_powertools_logger():
|
|
14
|
+
os.environ["POWERTOOLS_SERVICE_NAME"] = "handler-logger"
|
|
15
|
+
logger = Logger(service=os.environ["POWERTOOLS_SERVICE_NAME"])
|
|
16
|
+
from my_aws_helpers.cognito import Cognito
|
|
17
|
+
|
|
18
|
+
os.environ["COGNITO_USER_POOL_ID"] = "fake-pool-id"
|
|
19
|
+
cognito = Cognito()
|
|
20
|
+
assert isinstance(cognito, Cognito) == True
|
|
@@ -6,18 +6,19 @@ def test_event():
|
|
|
6
6
|
event = Event(status=EventStatus.success.value, message="test event")
|
|
7
7
|
assert event != None
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
def test_event_wrong_status():
|
|
10
11
|
with pytest.raises(Exception):
|
|
11
12
|
Event(status="not success", message="test event")
|
|
12
13
|
|
|
14
|
+
|
|
13
15
|
def test_serialiser():
|
|
14
16
|
event = Event(status=EventStatus.success.value, message="test event")
|
|
15
17
|
dynamo_repr = event._to_dynamo_representation()
|
|
16
18
|
|
|
17
|
-
assert dynamo_repr[
|
|
18
|
-
assert dynamo_repr[
|
|
19
|
+
assert dynamo_repr["pk"] == f"id##{event.id}"
|
|
20
|
+
assert dynamo_repr["sk"] == f"id##{event.id}"
|
|
19
21
|
|
|
20
|
-
obj = Event._from_dynamo_representation(obj
|
|
22
|
+
obj = Event._from_dynamo_representation(obj=dynamo_repr)
|
|
21
23
|
|
|
22
24
|
assert isinstance(obj, Event) == True
|
|
23
|
-
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# import boto3
|
|
2
|
-
# from authlib.jose import jwt
|
|
3
|
-
# import requests
|
|
4
|
-
# import json
|
|
5
|
-
|
|
6
|
-
# class Cognito:
|
|
7
|
-
# def __init__(self, pool_id: str, client_id: str) -> None:
|
|
8
|
-
# self.pool_id = pool_id
|
|
9
|
-
# self.client_id = client_id
|
|
10
|
-
# self.client = boto3.client('cognito-idp')
|
|
11
|
-
# self.cognito_pool_domain_url = None
|
|
12
|
-
# self.cognito_known_tokens_url = None
|
|
13
|
-
|
|
14
|
-
# def delete_user(self, username: str):
|
|
15
|
-
# try:
|
|
16
|
-
# response = self.client.admin_delete_user(
|
|
17
|
-
# UserPoolId=self.pool_id,
|
|
18
|
-
# Username=username
|
|
19
|
-
# )
|
|
20
|
-
# return response
|
|
21
|
-
# except Exception as e:
|
|
22
|
-
# raise Exception(f"Error {e}: Failed to delete User {username}")
|
|
23
|
-
|
|
24
|
-
# def create_user(self, user: User):
|
|
25
|
-
# try:
|
|
26
|
-
# response = self.client.admin_create_user(
|
|
27
|
-
# UserPoolId= self.pool_id,
|
|
28
|
-
# Username = user.email,
|
|
29
|
-
# UserAttributes=[
|
|
30
|
-
# {
|
|
31
|
-
# 'Name': 'Given Name',
|
|
32
|
-
# 'Value': user.first,
|
|
33
|
-
# 'Name': 'Family Name',
|
|
34
|
-
# 'Value': user.last,
|
|
35
|
-
# 'Name': 'Full Name',
|
|
36
|
-
# 'Value': user.fullname,
|
|
37
|
-
# 'Name': 'name',
|
|
38
|
-
# 'Value': user.fullname,
|
|
39
|
-
# 'Name': 'nickname',
|
|
40
|
-
# 'Value': user.nickname,
|
|
41
|
-
# 'Name': 'email',
|
|
42
|
-
# 'Value': user.email
|
|
43
|
-
# },
|
|
44
|
-
# ],
|
|
45
|
-
# ValidationData=[
|
|
46
|
-
# {
|
|
47
|
-
# 'Name': 'Given Name',
|
|
48
|
-
# 'Value': user.first,
|
|
49
|
-
# 'Name': 'Family Name',
|
|
50
|
-
# 'Value': user.last,
|
|
51
|
-
# 'Name': 'Full Name',
|
|
52
|
-
# 'Value': user.fullname,
|
|
53
|
-
# 'Name': 'name',
|
|
54
|
-
# 'Value': user.fullname,
|
|
55
|
-
# 'Name': 'nickname',
|
|
56
|
-
# 'Value': user.nickname,
|
|
57
|
-
# 'Name': 'email',
|
|
58
|
-
# 'Value': user.email
|
|
59
|
-
# },
|
|
60
|
-
# ],
|
|
61
|
-
# TemporaryPassword=user.password, #should come from user
|
|
62
|
-
# ForceAliasCreation=False,
|
|
63
|
-
# MessageAction='SUPPRESS',
|
|
64
|
-
# DesiredDeliveryMediums=[
|
|
65
|
-
# 'EMAIL',
|
|
66
|
-
# ],
|
|
67
|
-
# ClientMetadata={
|
|
68
|
-
# 'string': 'string'
|
|
69
|
-
# }
|
|
70
|
-
# )
|
|
71
|
-
# return response
|
|
72
|
-
# except Exception as e:
|
|
73
|
-
# raise Exception(f"Error {e}: Could not create user {user.fullname}")
|
|
74
|
-
|
|
75
|
-
# def _init_auth(self, cognito_username: str, user: User):
|
|
76
|
-
# """Initiate Auth (SRP) -> Returns Challenge"""
|
|
77
|
-
# try:
|
|
78
|
-
# response = self.client.initiate_auth(
|
|
79
|
-
# AuthFlow='USER_PASSWORD_AUTH', #'USER_SRP_AUTH'|'REFRESH_TOKEN_AUTH'|'REFRESH_TOKEN'|'CUSTOM_AUTH'|'ADMIN_NO_SRP_AUTH'| 'USER_PASSWORD_AUTH',
|
|
80
|
-
# AuthParameters={
|
|
81
|
-
# 'USERNAME': user.email,
|
|
82
|
-
# 'name': user.fullname,
|
|
83
|
-
# 'email':user.email,
|
|
84
|
-
# 'PASSWORD': user.password
|
|
85
|
-
# },
|
|
86
|
-
# ClientId=self.client_id,
|
|
87
|
-
# )
|
|
88
|
-
# return response
|
|
89
|
-
# except Exception as e:
|
|
90
|
-
# raise Exception(f"Error {e}: Issue during initialising auth for user {cognito_username}")
|
|
91
|
-
|
|
92
|
-
# def _respond_to_auth_challenge(self, init_auth_response, user: User, cognito_username: str):
|
|
93
|
-
# """Respond to Auth Challenge -> Cognito Tokens"""
|
|
94
|
-
# try:
|
|
95
|
-
# cognito_username = init_auth_response["ChallengeParameters"].get('USER_ID_FOR_SRP') #needs testing
|
|
96
|
-
# cn = init_auth_response["ChallengeName"]
|
|
97
|
-
# sess = init_auth_response["Session"]
|
|
98
|
-
# challenge_params = init_auth_response["ChallengeParameters"]
|
|
99
|
-
# challenge_resp = {
|
|
100
|
-
# 'USERNAME': user.email,
|
|
101
|
-
# 'userAttributes.name': user.fullname,
|
|
102
|
-
# 'NEW_PASSWORD':user.password
|
|
103
|
-
# }
|
|
104
|
-
# response = self.client.respond_to_auth_challenge(
|
|
105
|
-
# ClientId=self.client_id,
|
|
106
|
-
# ChallengeName=cn,
|
|
107
|
-
# Session=sess,
|
|
108
|
-
# ChallengeResponses=challenge_resp
|
|
109
|
-
# )
|
|
110
|
-
# return response
|
|
111
|
-
# except Exception as e:
|
|
112
|
-
# raise Exception(f"Error {e}: Issue responding to auth challenge for user {user.email}")
|
|
113
|
-
|
|
114
|
-
# # def get_token(self, user: User, cognito_username: str):
|
|
115
|
-
# # init_auth_response = self._init_auth(cognito_username, user)
|
|
116
|
-
# # response = self._respond_to_auth_challenge(init_auth_response, user, cognito_username)
|
|
117
|
-
# # return response
|
|
118
|
-
|
|
119
|
-
# # def sign_up(self, user: User):
|
|
120
|
-
# # response = self.create_user(user)
|
|
121
|
-
# # cognito_username = response['User'].get('Username')
|
|
122
|
-
# # get_token_response = self.get_token(user, cognito_username)
|
|
123
|
-
# # return get_token_response
|
|
124
|
-
|
|
125
|
-
# # def get_JWT_from_code(self, code):
|
|
126
|
-
# # try:
|
|
127
|
-
# # payload=f'grant_type=authorization_code&client_id={self.client_id}&code={code}'#&redirect_uri={call_back_uri}
|
|
128
|
-
# # headers = {
|
|
129
|
-
# # 'Content-Type': 'application/x-www-form-urlencoded',
|
|
130
|
-
# # }
|
|
131
|
-
# # response = requests.request("POST", self.cognito_pool_domain, headers=headers, data=payload)
|
|
132
|
-
# # tokens = json.loads(response.text)
|
|
133
|
-
# # return tokens
|
|
134
|
-
# # except Exception as e:
|
|
135
|
-
# # raise Exception(f"Error {e}: Couldn't get JWT from auth code")
|
|
136
|
-
|
|
137
|
-
# # def code_to_jwt_dict(self, code):
|
|
138
|
-
# # tokens = self.code_to_JWT(code)
|
|
139
|
-
# # refresh_token = tokens['refresh_token']
|
|
140
|
-
# # access_token = tokens['access_token']
|
|
141
|
-
# # JWT = tokens['id_token']
|
|
142
|
-
# # response = requests.request("GET", self.cognito_known_tokens_url, headers={}, data={})
|
|
143
|
-
# # public = json.loads(response.text) #loads a json str into dict
|
|
144
|
-
# # return jwt.decode(JWT, public) #decode jwt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
boto3==1.34.36
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/markdown_system_prompt.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_prompt.txt
RENAMED
|
File without changes
|
|
File without changes
|
{my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|