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.

Files changed (30) hide show
  1. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/PKG-INFO +3 -1
  2. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/api.py +2 -2
  3. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/bedrock.py +65 -43
  4. my_aws_helpers-4.0.0/my_aws_helpers/cognito.py +124 -0
  5. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/dynamo.py +14 -10
  6. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/event.py +15 -13
  7. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/s3.py +10 -3
  8. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/PKG-INFO +3 -1
  9. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/SOURCES.txt +1 -0
  10. my_aws_helpers-4.0.0/my_aws_helpers.egg-info/requires.txt +15 -0
  11. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/setup.py +22 -7
  12. my_aws_helpers-4.0.0/tests/test_cognito.py +20 -0
  13. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/tests/test_event.py +5 -4
  14. my_aws_helpers-3.1.0.dev4/my_aws_helpers/cognito.py +0 -144
  15. my_aws_helpers-3.1.0.dev4/my_aws_helpers.egg-info/requires.txt +0 -1
  16. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/MANIFEST.in +0 -0
  17. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/README.md +0 -0
  18. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/auth.py +0 -0
  19. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/errors.py +0 -0
  20. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/logging.py +0 -0
  21. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/__init__.py +0 -0
  22. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/markdown_system_prompt.txt +0 -0
  23. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_headers_prompt.txt +0 -0
  24. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_headers_prompt_v2.txt +0 -0
  25. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/prompts/transactions_prompt.txt +0 -0
  26. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers/sfn.py +0 -0
  27. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/dependency_links.txt +0 -0
  28. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/top_level.txt +0 -0
  29. {my_aws_helpers-3.1.0.dev4 → my_aws_helpers-4.0.0}/my_aws_helpers.egg-info/zip-safe +0 -0
  30. {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.1.0.dev4
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('inputTokens', 0),
31
- output_tokens=data.get('outputTokens', 0),
32
- total_tokens=data.get('totalTokens', 0),
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 = data.get("content", []),
46
- token_usage = TokenUsage.from_dict(data.get("token_usage", {})),
47
- page_number = data.get("page_number", 0)
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 = None,
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 = 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
- 'max_attempts': 2, # Total attempts = 1 initial + 1 retry
69
- 'mode': 'standard', # or 'adaptive'
69
+ "max_attempts": 2, # Total attempts = 1 initial + 1 retry
70
+ "mode": "standard", # or 'adaptive'
70
71
  }
71
- )
72
- self.client = self.session.client("bedrock-runtime", region_name=region_name, config = custom_config)
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(modelId = self.model_id, messages = message, system = system_prompt)
132
- if response['ResponseMetadata']['HTTPStatusCode'] == 200:
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: raise Exception(e)
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(response["output"]["message"]["content"][0]["text"])
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 = result)
145
-
149
+ return OCRResult.from_dict(data=result)
150
+
146
151
  def _parallel_ocr(
147
- self,
148
- image_bytes_list: List[bytes],
149
- prompt: str,
150
- max_workers: int = 10,
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 = [future.result() for future in execution_futures if future.result() is not None]
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(matrix = pymupdf.Matrix(zoom, zoom)).tobytes("png")
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(prompt_type=PromptType.transaction_headers.value)
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(prompt=headers_prompt, image_bytes=image_bytes_list[i])
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(f"No ocr result returned when getting headers {PromptType.transaction_headers.value}")
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("#### TABLE HEADERS ####", json.dumps(header_ocr_result.content))
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[1:] #page zero often has account summary info
208
- results = self._parallel_ocr(image_bytes_list=image_bytes_list, prompt=transactions_prompt)
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 = 38)
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 = response["LastEvaluatedKey"])
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(self, partition_key_name: str = "pk", sort_key_name: str = "sk") -> bool:
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 = delete_repr_items)
153
+ self.batch_delete(items=delete_repr_items)
151
154
  return True
152
155
  except Exception as e:
153
- logger.exception(f'Failed to delete table items due to {e}')
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 = created_timestamp if created_timestamp else datetime.now()
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('pk')
63
- this_obj.pop('sk')
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['pk'] = self._get_pk()
72
- obj['sk'] = self._get_sk()
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 = event._to_dynamo_representation())
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 = results["Items"][0])
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 = resource if resource else boto3.resource('s3')
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, bucket_name: str, file_name: str, expires_in: int = 3600
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.1.0.dev4
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
 
@@ -23,4 +23,5 @@ my_aws_helpers/prompts/markdown_system_prompt.txt
23
23
  my_aws_helpers/prompts/transactions_headers_prompt.txt
24
24
  my_aws_helpers/prompts/transactions_headers_prompt_v2.txt
25
25
  my_aws_helpers/prompts/transactions_prompt.txt
26
+ tests/test_cognito.py
26
27
  tests/test_event.py
@@ -0,0 +1,15 @@
1
+ boto3==1.34.36
2
+ python-jose==3.5.0
3
+
4
+ [all]
5
+ PyMuPDF==1.26.0
6
+ black<26.0.0,<=25.1.0
7
+ boto3==1.34.36
8
+ coverage==7.3.2
9
+ pytest==7.4.3
10
+ python-jose==3.5.0
11
+
12
+ [bedrock]
13
+ PyMuPDF==1.26.0
14
+ boto3==1.34.36
15
+ python-jose==3.5.0
@@ -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 = "3.1.0.dev4"
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=[p for p in find_namespace_packages(where=base_path) if p.startswith("my_aws_helpers")],
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 = True,
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['pk'] == f"id##{event.id}"
18
- assert dynamo_repr['sk'] == f"id##{event.id}"
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 = dynamo_repr)
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