boto3-assist 0.2.12__py3-none-any.whl → 0.3.0__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.
@@ -0,0 +1,414 @@
1
+ import json
2
+ import re
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from aws_lambda_powertools import Logger
6
+
7
+ from boto3_assist.cognito.cognito_authorizer import CognitoCustomAuthorizer
8
+ from boto3_assist.errors.custom_exceptions import Error
9
+ from boto3_assist.http_status_codes import HttpStatusCodes
10
+
11
+ logger = Logger()
12
+
13
+
14
+ class LambdaEventInfo:
15
+ """
16
+ Utility class for parsing and interacting with AWS Lambda event payloads.
17
+ Contains methods to extract data from API Gateway payloads, path parameters,
18
+ and headers.
19
+ """
20
+
21
+ class ApiGatewayPayload:
22
+ """
23
+ Handles API Gateway-specific event payloads.
24
+ Provides methods to extract HTTP method types, resource paths, and
25
+ authorizer claims.
26
+ """
27
+
28
+ @staticmethod
29
+ def get_http_method_type(event: Dict[str, Any]) -> Optional[str]:
30
+ """
31
+ Extracts the HTTP method type (e.g., GET, POST) from the event.
32
+
33
+ Args:
34
+ event: The Lambda event payload.
35
+
36
+ Returns:
37
+ The HTTP method type as a string, or None if not found.
38
+ """
39
+ return LambdaEventInfo._get_value(event, "method_type", str) # pylint: disable=w0212
40
+
41
+ @staticmethod
42
+ def get_resource_path(event: Dict[str, Any]) -> Optional[str]:
43
+ """
44
+ Extracts the resource path from the event.
45
+
46
+ Args:
47
+ event: The Lambda event payload.
48
+
49
+ Returns:
50
+ The resource path as a string, or None if not found.
51
+ """
52
+ return LambdaEventInfo._get_value_ex(event, "path", str) # pylint: disable=w0212
53
+
54
+ @staticmethod
55
+ def get_resource_pattern(event: Dict[str, Any]) -> Optional[str]:
56
+ """
57
+ Extracts the resource pattern from the event, replacing path variables
58
+ with placeholders (e.g., /users/{user-id}).
59
+
60
+ Args:
61
+ event: The Lambda event payload.
62
+
63
+ Returns:
64
+ The resource pattern as a string, or None if not found.
65
+ """
66
+ return LambdaEventInfo._get_value_ex(event, "resourcePath", str) # pylint: disable=w0212
67
+
68
+ class AuthorizerPayload:
69
+ """
70
+ Handles claims and tokens in API Gateway authorizer payloads.
71
+ """
72
+
73
+ @staticmethod
74
+ def get_authenticated_email(event: Dict[str, Any]) -> Optional[str]:
75
+ """
76
+ Extracts the authenticated email or client ID from the event based
77
+ on the token use.
78
+
79
+ Args:
80
+ event: The Lambda event payload.
81
+
82
+ Returns:
83
+ The email or client ID as a string, or None if not found.
84
+ """
85
+ token_use = (
86
+ LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_token_use(
87
+ event
88
+ )
89
+ ) # pylint: disable=w0212
90
+ key = "email" if token_use == "access" else "client_id"
91
+ return (
92
+ LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_claims_data(
93
+ event, key
94
+ )
95
+ ) # pylint: disable=w0212
96
+
97
+ @staticmethod
98
+ def get_token_use(event: Dict[str, Any]) -> Optional[str]:
99
+ """
100
+ Extracts the token use (e.g., "access" or "id") from the event.
101
+
102
+ Args:
103
+ event: The Lambda event payload.
104
+
105
+ Returns:
106
+ The token use as a string, or None if not found.
107
+ """
108
+ return LambdaEventInfo._get_value( # pylint: disable=w0212
109
+ event, "requestContext/authorizer/claims/token_use", str
110
+ ) # pylint: disable=w0212
111
+
112
+ @staticmethod
113
+ def get_claims_data(event: Dict[str, Any], key: str) -> Optional[str]:
114
+ """
115
+ Extracts a specific claim from the authorizer payload.
116
+
117
+ Args:
118
+ event: The Lambda event payload.
119
+ key: The claim key to extract.
120
+
121
+ Returns:
122
+ The claim value as a string, or None if not found.
123
+ """
124
+ try:
125
+ value = LambdaEventInfo._get_value( # pylint: disable=w0212
126
+ event, f"requestContext/authorizer/claims/{key}", str
127
+ ) # pylint: disable=w0212
128
+ if not value:
129
+ value = LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_value_from_token(
130
+ event, key
131
+ ) # pylint: disable=w0212
132
+
133
+ if value is None:
134
+ raise Error(
135
+ {
136
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
137
+ "message": f"Failed to locate {key} info in JWT Token",
138
+ }
139
+ )
140
+ return value
141
+
142
+ except Exception as e:
143
+ raise Error(
144
+ {
145
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
146
+ "message": f"Failed to locate {key} info in JWT Token",
147
+ "exception": str(e),
148
+ }
149
+ ) from e
150
+
151
+ @staticmethod
152
+ def get_value_from_token(event: Dict[str, Any], key: str) -> Optional[str]:
153
+ """
154
+ Extracts a value from the JWT token in the event.
155
+
156
+ Args:
157
+ event: The Lambda event payload.
158
+ key: The key to extract from the token.
159
+
160
+ Returns:
161
+ The extracted value as a string, or None if not found.
162
+ """
163
+ try:
164
+ jwt_token = LambdaEventInfo._get_value( # pylint: disable=w0212
165
+ event, "headers/Authorization", str
166
+ ) # pylint: disable=w0212
167
+ if jwt_token:
168
+ ccas = CognitoCustomAuthorizer()
169
+ decoded_token = ccas.parse_jwt(token=jwt_token)
170
+ return decoded_token.get(key)
171
+
172
+ raise Error(
173
+ {
174
+ "status_code": HttpStatusCodes.HTTP_404_NOT_FOUND.value,
175
+ "message": f"Failed to locate {key} info in JWT Token",
176
+ }
177
+ )
178
+ except Exception as e:
179
+ raise Error(
180
+ {
181
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
182
+ "message": f"Failed to locate {key} info in JWT Token",
183
+ "exception": str(e),
184
+ }
185
+ ) from e
186
+
187
+ class HttpPathParameters:
188
+ """
189
+ Handles path parameters in API Gateway events.
190
+ """
191
+
192
+ @staticmethod
193
+ def get_target_user_id(
194
+ event: Dict[str, Any], key: str = "user-id"
195
+ ) -> Optional[str]:
196
+ """
197
+ Extracts the target user ID from the path parameters.
198
+
199
+ Args:
200
+ event: The Lambda event payload.
201
+ key: The key representing the user ID.
202
+
203
+ Returns:
204
+ The user ID as a string, or None if not found.
205
+ """
206
+ return LambdaEventInfo._get_value_from_path_parameters(event, key) # pylint: disable=w0212
207
+
208
+ @staticmethod
209
+ def get_target_tenant_id(
210
+ event: Dict[str, Any], key: str = "tenant-id"
211
+ ) -> Optional[str]:
212
+ """
213
+ Extracts the target tenant ID from the path parameters.
214
+
215
+ Args:
216
+ event: The Lambda event payload.
217
+ key: The key representing the tenant ID.
218
+
219
+ Returns:
220
+ The tenant ID as a string, or None if not found.
221
+ """
222
+ return LambdaEventInfo._get_value_from_path_parameters(event, key) # pylint: disable=w0212
223
+
224
+ @staticmethod
225
+ def get_message_id(event: Dict[str, Any], index: int = 0) -> Optional[str]:
226
+ """
227
+ Extracts the message ID from an event record.
228
+
229
+ Args:
230
+ event: The Lambda event payload.
231
+ index: The index of the record to extract the message ID from.
232
+
233
+ Returns:
234
+ The message ID as a string, or None if not found.
235
+ """
236
+ records: List[Dict[str, Any]] = event.get("Records", [])
237
+ if records and len(records) > index:
238
+ return records[index].get("messageId")
239
+ return None
240
+
241
+ @staticmethod
242
+ def _get_value_ex(
243
+ event: Dict[str, Any], key: str, expected_type: type
244
+ ) -> Optional[Any]:
245
+ """
246
+ Extracts a value from the event, checking additional paths if necessary.
247
+
248
+ Args:
249
+ event: The Lambda event payload.
250
+ key: The key to extract.
251
+ expected_type: The expected type of the value.
252
+
253
+ Returns:
254
+ The extracted value, or None if not found.
255
+ """
256
+ value = LambdaEventInfo._get_value(event, key, expected_type) # pylint: disable=w0212
257
+ if value is None:
258
+ value = LambdaEventInfo._get_value(
259
+ event, f"requestContext/{key}", expected_type
260
+ ) # pylint: disable=w0212
261
+ return value
262
+
263
+ @staticmethod
264
+ def _get_value(
265
+ event: Dict[str, Any], key: Union[str, List[str]], expected_type: type
266
+ ) -> Optional[Any]:
267
+ """
268
+ Extracts a value from the event based on the key.
269
+
270
+ Args:
271
+ event: The Lambda event payload.
272
+ key: The key to extract, which can be a string or a list of strings for nested keys.
273
+ expected_type: The expected type of the value.
274
+
275
+ Returns:
276
+ The extracted value, or None if not found.
277
+ """
278
+ logger.debug({"source": "_get_value", "event": event, "key": key})
279
+ if not event:
280
+ return None
281
+
282
+ if isinstance(key, str):
283
+ key = re.split(r"[./]", key)
284
+
285
+ value = event
286
+ for k in key:
287
+ if isinstance(value, dict):
288
+ value = value.get(k)
289
+ else:
290
+ return None
291
+
292
+ if isinstance(value, expected_type):
293
+ return value
294
+ return None
295
+
296
+ @staticmethod
297
+ def _get_value_from_path_parameters(
298
+ event: Dict[str, Any], key: str, default: Optional[Any] = None
299
+ ) -> Optional[str]:
300
+ """
301
+ Extracts a value from the path parameters in the event.
302
+
303
+ Args:
304
+ event: The Lambda event payload.
305
+ key: The key to extract from the path parameters.
306
+ default: The default value to return if the key is not found.
307
+
308
+ Returns:
309
+ The extracted value, or None if not found.
310
+ """
311
+ value = LambdaEventInfo._search_key(event, "pathParameters", key, default) # pylint: disable=w0212
312
+ if value is None:
313
+ path = LambdaEventInfo.ApiGatewayPayload.get_resource_path(event) # pylint: disable=w0212
314
+ pattern = LambdaEventInfo.ApiGatewayPayload.get_resource_pattern(event) # pylint: disable=w0212
315
+ if path and pattern:
316
+ value = LambdaEventInfo._extract_value_from_path(path, pattern, key) # pylint: disable=w0212
317
+ return value
318
+
319
+ @staticmethod
320
+ def _extract_value_from_path(
321
+ path: str, pattern: str, variable_name: str
322
+ ) -> Optional[str]:
323
+ """
324
+ Extracts a value from a path using a regex pattern.
325
+
326
+ Args:
327
+ path: The actual path from the event.
328
+ pattern: The pattern with placeholders (e.g., /users/{user-id}).
329
+ variable_name: The variable name to extract.
330
+
331
+ Returns:
332
+ The extracted value, or None if not found.
333
+ """
334
+ regex_pattern = re.sub(
335
+ r"\{([^}]+)\}",
336
+ lambda m: f"(?P<{m.group(1).replace('-', '_')}>[^/]+)",
337
+ pattern,
338
+ )
339
+ variable_name = variable_name.replace("-", "_")
340
+ match = re.match(regex_pattern, path)
341
+ if match:
342
+ return match.group(variable_name)
343
+ return None
344
+
345
+ @staticmethod
346
+ def _search_key(
347
+ event: Dict[str, Any], container: str, key: str, default: Optional[Any] = None
348
+ ) -> Optional[Any]:
349
+ """
350
+ Searches for a key within a specified container in the event.
351
+
352
+ Args:
353
+ event: The Lambda event payload.
354
+ container: The container key to search within (e.g., "headers").
355
+ key: The key to search for within the container.
356
+ default: The default value to return if the key is not found.
357
+
358
+ Returns:
359
+ The extracted value, or the default value if not found.
360
+ """
361
+ events = [event]
362
+
363
+ if "Records" in event:
364
+ record = event["Records"][0]
365
+ if "body" in record:
366
+ body = json.loads(record["body"])
367
+ events.append(body)
368
+ if "requestContext" in body:
369
+ events.append(body["requestContext"])
370
+
371
+ for e in events:
372
+ container_data = e.get(container)
373
+ if container_data and key in container_data:
374
+ return container_data[key]
375
+
376
+ return default
377
+
378
+ @staticmethod
379
+ def get_body(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
380
+ """
381
+ Extracts the body of the event payload.
382
+
383
+ Args:
384
+ event: The Lambda event payload.
385
+
386
+ Returns:
387
+ The body as a dictionary, or None if not found.
388
+ """
389
+ tmp = event.get("Records", [{}])[0].get("body", event)
390
+ if isinstance(tmp, str):
391
+ try:
392
+ return json.loads(tmp)
393
+ except json.JSONDecodeError as e:
394
+ raise ValueError("Invalid JSON body in the payload") from e
395
+ return tmp if isinstance(tmp, dict) else None
396
+
397
+ @staticmethod
398
+ def override_event_info(
399
+ event: Dict[str, Any], key: str, value: Any
400
+ ) -> Dict[str, Any]:
401
+ """
402
+ Overrides a value in the event payload.
403
+
404
+ Args:
405
+ event: The Lambda event payload.
406
+ key: The key to override.
407
+ value: The value to set.
408
+
409
+ Returns:
410
+ The updated event payload.
411
+ """
412
+ body = LambdaEventInfo.get_body(event) or {}
413
+ body[key] = value
414
+ return body
@@ -0,0 +1,169 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import time
8
+ from typing import Any, Dict, List
9
+
10
+ import jwt # PyJWT
11
+ from aws_lambda_powertools import Logger
12
+ from jwt import InvalidTokenError, PyJWKClient
13
+
14
+ from boto3_assist.boto3session import Boto3SessionManager
15
+ from boto3_assist.cognito.jwks_cache import JwksCache
16
+
17
+ logger = Logger()
18
+
19
+ jwks_cache = JwksCache()
20
+
21
+
22
+ class CognitoCustomAuthorizer:
23
+ """Cognito Custom Authorizer"""
24
+
25
+ def __init__(self):
26
+ self.__client_connections: Dict[str, Any] = {}
27
+
28
+ def __get_client_connection(
29
+ self, user_pool_id: str, refresh_client: bool = False
30
+ ) -> Any:
31
+ """Get the client connection to cognito"""
32
+ region = user_pool_id.split("_")[0]
33
+ client = self.__client_connections.get(region)
34
+ if refresh_client:
35
+ client = None
36
+ if not client:
37
+ session = Boto3SessionManager(service_name="cognito-idp", aws_region=region)
38
+ client = session.client
39
+ # boto3.client("cognito-idp", region_name=region)
40
+ self.__client_connections[region] = client
41
+
42
+ return client
43
+
44
+ def generate_policy(
45
+ self, user_pools: str | List[str], event: Dict[str, Any]
46
+ ) -> Dict[str, Any]:
47
+ """Generates the policy for the authorizer"""
48
+
49
+ token = event["authorizationToken"]
50
+ user_pools = self.__to_list(user_pools=user_pools)
51
+ for user_pool_id in user_pools:
52
+ try:
53
+ if not user_pool_id:
54
+ continue
55
+ # up_id = self.__to_id(user_pool_id=user_pool_id)
56
+ # Decode the token, assuming RS256 (used by Cognito)
57
+ # decoded_token = self.decode_jwt(token=token, user_pool_id=up_id)
58
+ issuer = self.build_issuer_url(user_pool_id)
59
+ claims = self.decode_jwt(token, issuer)
60
+ # Token is valid, return an IAM policy
61
+ return self.__generate_policy_doc(
62
+ principal_id=claims["sub"],
63
+ effect="Allow",
64
+ method_arn=event["methodArn"],
65
+ )
66
+
67
+ except InvalidTokenError as e:
68
+ # Token is not valid for this user pool, try the next one
69
+ logger.debug(str(e))
70
+ continue
71
+ except Exception as e: # pylint: disable=w0718
72
+ logger.error(str(e))
73
+
74
+ # if we get here we deny it
75
+ return self.__generate_policy_doc(
76
+ principal_id="user",
77
+ effect="Deny",
78
+ method_arn=event["methodArn"],
79
+ )
80
+
81
+ def __generate_policy_doc(self, *, principal_id, effect, method_arn):
82
+ """Generate the policy doc"""
83
+ auth_response: Dict[str, Any] = {"principalId": principal_id}
84
+
85
+ if effect and method_arn:
86
+ policy_document = {
87
+ "Version": "2012-10-17",
88
+ "Statement": [
89
+ {
90
+ "Action": "execute-api:Invoke",
91
+ "Effect": effect,
92
+ "Resource": method_arn,
93
+ }
94
+ ],
95
+ }
96
+ auth_response["policyDocument"] = policy_document
97
+
98
+ return auth_response
99
+
100
+ def build_issuer_url(self, user_pool_id: str) -> str:
101
+ """Build the issuer URL"""
102
+
103
+ # Extract region from user pool ID format, e.g., "us-east-1_ABC123"
104
+ region = user_pool_id.split("_")[0]
105
+ return f"https://cognito-idp.{region}.amazonaws.com/{user_pool_id}"
106
+
107
+ def __to_list(self, user_pools: str | List[str]) -> List[str]:
108
+ if isinstance(user_pools, str):
109
+ user_pools = str(user_pools).replace(";", ",").replace(" ", "")
110
+ user_pools = str(user_pools).split(",")
111
+ elif isinstance(user_pools, list):
112
+ pass
113
+ else:
114
+ logger.warning(
115
+ f"Missing/ Invalid user pool: {user_pools}, type: {type(user_pools)}"
116
+ )
117
+
118
+ return user_pools
119
+
120
+ def parse_jwt(self, token: str) -> dict:
121
+ """Parse the JWT"""
122
+ if "Bearer" in token:
123
+ token = token.replace("Bearer ", "")
124
+
125
+ decoded_jwt: dict = jwt.decode(token, options={"verify_signature": False})
126
+
127
+ return decoded_jwt
128
+
129
+ def decode_jwt(self, token: str, issuer) -> dict:
130
+ """Decode the JWT"""
131
+ # Get the public keys
132
+ # Get the JWKS client
133
+ jwks_client = self.get_jwks_client(issuer)
134
+ if "Bearer" in token:
135
+ token = token.replace("Bearer ", "")
136
+ # Fetch the signing key using the PyJWKClient
137
+ signing_key = jwks_client.get_signing_key_from_jwt(token)
138
+
139
+ # Decode and verify the token
140
+ claims = jwt.decode(
141
+ token,
142
+ signing_key.key,
143
+ algorithms=["RS256"],
144
+ # audience=user_pool_id,
145
+ issuer=issuer,
146
+ options={"verify_aud": False}, # Disable audience verification
147
+ )
148
+
149
+ # Optional claim checks
150
+ if claims["token_use"] != "id":
151
+ # we are currently only using ID tokens
152
+ raise RuntimeError("Not an id token")
153
+
154
+ return claims
155
+
156
+ def get_jwks_client(self, issuer) -> PyJWKClient:
157
+ """Get the JWT Client"""
158
+ if (
159
+ issuer in jwks_cache.cache
160
+ and (time.time() - jwks_cache.cache.get(issuer, {})["timestamp"]) < 3600
161
+ ):
162
+ # Return cached JWKS client if it’s less than an hour old
163
+ return jwks_cache.cache[issuer]["client"]
164
+ else:
165
+ # Create a new PyJWKClient and cache it
166
+ jwks_url = f"{issuer}/.well-known/jwks.json"
167
+ jwks_client = PyJWKClient(jwks_url)
168
+ jwks_cache.cache[issuer] = {"client": jwks_client, "timestamp": time.time()}
169
+ return jwks_client
@@ -0,0 +1,21 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+
8
+ class JwksCache:
9
+ """A JWT Caching object"""
10
+
11
+ def __init__(self):
12
+ self.__cache = {}
13
+
14
+ @property
15
+ def cache(self):
16
+ """The Cache"""
17
+ return self.__cache
18
+
19
+ @cache.setter
20
+ def cache(self, value):
21
+ self.__cache = value
@@ -0,0 +1,80 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from enum import Enum
8
+
9
+
10
+ class HttpStatusCodes(Enum):
11
+ """Http Status Codes"""
12
+
13
+ HTTP_400_BAD_REQUEST = 400
14
+ """
15
+ The server cannot or will not process the request due to something that is perceived to be a client error
16
+ (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)."""
17
+ HTTP_401_UNAUTHENTICATED = 401
18
+ """
19
+ Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated".
20
+ That is, the client must authenticate itself to get the requested response.
21
+ """
22
+ HTTP_403_FORBIDDEN = 403
23
+ """
24
+ The client does not have access rights to the content; that is, it is "unauthorized", so the server is refusing
25
+ to give the requested resource. Unlike 401 Unauthorized (which is technically UnAuthenticated);
26
+ here, the client's identity is known to the server.
27
+ """
28
+ HTTP_404_NOT_FOUND = 404
29
+ """
30
+ The server cannot find the requested resource. In the browser, this means the URL is not recognized.
31
+ In an API, this can also mean that the endpoint is valid but the resource itself does not exist.
32
+ Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from
33
+ an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.
34
+ """
35
+ HTTP_405_METHOD_NOT_ALLOWED = 405
36
+ """
37
+ The request method is known by the server but is not supported by the target resource.
38
+ For example, an API may not allow calling DELETE to remove a resource.
39
+ """
40
+ HTTP_406_NOT_ACCEPTABLE = 406
41
+ """
42
+ This response is sent when the web server, after performing server-driven content negotiation, doesn't find any
43
+ content that conforms to the criteria given by the user agent.
44
+ """
45
+ HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
46
+ """
47
+ This is similar to 401 Unauthorized but authentication is needed to be done by a proxy.
48
+ """
49
+ HTTP_408_REQUEST_TIMEOUT = 408
50
+ """
51
+ This response is sent on an idle connection by some servers, even without any previous request by the client.
52
+ It means that the server would like to shut down this unused connection. This response is used much more since
53
+ some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing.
54
+ Also note that some servers merely shut down the connection without sending this message.
55
+ """
56
+
57
+ HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
58
+ """
59
+ The media format of the requested data is not supported by the server, so the server is rejecting the request.
60
+ """
61
+
62
+ HTTP_418_IM_A_TEAPOT = 418
63
+ """
64
+ The server refuses the attempt to brew coffee with a teapot.
65
+ """
66
+
67
+ HTTP_422_UNEXPECTED_OUTCOME = 422
68
+ """
69
+ The request was well-formed but was unable to be followed due to semantic errors.
70
+ """
71
+
72
+ HTTP_429_TOO_MANY_REQUESTS = 429
73
+ """
74
+ The user has sent too many requests in a given amount of time ("rate limiting").
75
+ """
76
+
77
+ HTTP_500_INTERNAL_SERVER_ERROR = 500
78
+ """
79
+ The server has encountered a situation it does not know how to handle.
80
+ """
@@ -36,7 +36,7 @@ class JsonEncoder(json.JSONEncoder):
36
36
  elif isinstance(o, Decimal):
37
37
  return float(o)
38
38
 
39
- logger.info(f"AplosJsonEncoder failing back: ${type(o)}")
39
+ logger.info(f"JsonEncoder failing back: ${type(o)}")
40
40
 
41
41
  # Fallback to the base class implementation for other types
42
42
 
@@ -39,7 +39,7 @@ class JsonEncoder(json.JSONEncoder):
39
39
  elif isinstance(o, Decimal):
40
40
  return float(o)
41
41
 
42
- logger.info(f"AplosJsonEncoder failing back: ${type(o)}")
42
+ logger.info(f"JsonEncoder failing back: ${type(o)}")
43
43
 
44
44
  # Fallback to the base class implementation for other types
45
45
 
boto3_assist/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.2.12'
1
+ __version__ = '0.3.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.2.12
3
+ Version: 0.3.0
4
4
  Summary: Additional boto3 wrappers to make your life a little easier
5
5
  Author-email: Eric Wilson <boto3-assist@geekcafe.com>
6
6
  License-File: LICENSE-EXPLAINED.txt
@@ -13,6 +13,7 @@ Requires-Dist: aws-lambda-powertools
13
13
  Requires-Dist: aws-xray-sdk
14
14
  Requires-Dist: boto3
15
15
  Requires-Dist: jsons
16
+ Requires-Dist: pyjwt
16
17
  Requires-Dist: python-dateutil
17
18
  Requires-Dist: python-dotenv
18
19
  Requires-Dist: pytz
@@ -2,12 +2,16 @@ boto3_assist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  boto3_assist/boto3session.py,sha256=NWhWtYR3143thEbTpoklkwdz77-fTMs-QsoQdqfRm6E,6430
3
3
  boto3_assist/connection.py,sha256=EJlGueLIYqMSKs7aQlThK1S0Zkb8dOYBWch1iRZdgUI,3233
4
4
  boto3_assist/connection_tracker.py,sha256=_s1t7h2DOi3CCIHIr_HIKyGjku65WR-HJ_v8vJHDvO0,2977
5
- boto3_assist/version.py,sha256=GOlqYpAbeXBU8pYgXsAgMtKusKeeO1iC_GrwyupHF50,23
5
+ boto3_assist/http_status_codes.py,sha256=G0zRSWenwavYKETvDF9tNVUXQz3Ae2gXdBETYbjvJe8,3284
6
+ boto3_assist/version.py,sha256=3wVEs2QD_7OcTlD97cZdCeizd2hUbJJ0GeIO8wQIjrk,22
7
+ boto3_assist/aws_lambda/event_info.py,sha256=OkZ4WzuGaHEu_T8sB188KBgShAJhZpWASALKRGBOhMg,14648
6
8
  boto3_assist/cloudwatch/cloudwatch_connection.py,sha256=mnGWaLSQpHh5EeY7Ek_2o9JKHJxOELIYtQVMX1IaHn4,2480
7
9
  boto3_assist/cloudwatch/cloudwatch_connection_tracker.py,sha256=mzRtO1uHrcfJNh1XrGEiXdTqxwEP8d1RqJkraMNkgK0,410
8
10
  boto3_assist/cloudwatch/cloudwatch_log_connection.py,sha256=qQMZHjUJ6gA8wU9utjQhOURXNSPH2RjxSoAy83bvoCs,1737
9
11
  boto3_assist/cloudwatch/cloudwatch_logs.py,sha256=VtI0OnFjX1l4RYVvA8tvveGkPwAogtrplnflZ4dQSNM,1204
10
12
  boto3_assist/cloudwatch/cloudwatch_query.py,sha256=uNhSb1Gfp99v8BaHmCnCKs63j4MMU4WveqBavCJyhGY,6409
13
+ boto3_assist/cognito/cognito_authorizer.py,sha256=ONcxzjTACgVYl6qI9kJAQ5SoRMtVHYGDeuKi5QqJvOY,5837
14
+ boto3_assist/cognito/jwks_cache.py,sha256=1Y9r-YfQ8qrgZN5xYPvjUEEV0vthbdcPdAIaPbZP7kU,373
11
15
  boto3_assist/dynamodb/dynamodb.py,sha256=q3U4uYqnKX1_u5TXv8Iq94JrBad16q0rL_vKxQyR_3k,15772
12
16
  boto3_assist/dynamodb/dynamodb_connection.py,sha256=hr4IGbbEE73fh375tj3_XtYAwwDV0s7z1-6JK_1Tc30,5263
13
17
  boto3_assist/dynamodb/dynamodb_connection_tracker.py,sha256=0BWHRfi5_vjkJLuCSX6sYwvA6wc7BSYCQnGrzbhfyKA,404
@@ -34,10 +38,10 @@ boto3_assist/utilities/datetime_utility.py,sha256=TbqGQkJDTahqvaZAIV550nhYnW1Bsq
34
38
  boto3_assist/utilities/file_operations.py,sha256=Zy8fu8fpuVNf7U9NimrLdy5FRF71XSI159cnRdzmzGY,3411
35
39
  boto3_assist/utilities/http_utility.py,sha256=koFv7Va-8ng-47Nt1K2Sh7Ti95e62IYs9VMLlGh9Kt4,1173
36
40
  boto3_assist/utilities/logging_utility.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- boto3_assist/utilities/serialization_utility.py,sha256=AWm8s0E62C35Zb9SLfW6DFA05e8sgCVyHwVl67mdu24,5749
38
- boto3_assist/utilities/string_utility.py,sha256=w8l063UT3GE48tuJopETyZrjG4CgAzWkyDWMAYMg5Og,7432
39
- boto3_assist-0.2.12.dist-info/METADATA,sha256=ow0GD-6cOzGWBfnfoLQdbFBN0Lto5emSlqQBhfuYP8A,1708
40
- boto3_assist-0.2.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- boto3_assist-0.2.12.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
42
- boto3_assist-0.2.12.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
43
- boto3_assist-0.2.12.dist-info/RECORD,,
41
+ boto3_assist/utilities/serialization_utility.py,sha256=vsipCQZu91hApkJ8tKRTxBa8kNRvn06ED-isquqAVQQ,5744
42
+ boto3_assist/utilities/string_utility.py,sha256=fzsZkldRYtdkIqR0kD-TlHH4fh4iMtYvo_U3UNZhN7U,7427
43
+ boto3_assist-0.3.0.dist-info/METADATA,sha256=I0A61iFtOX2xoUIhqpGH8zmrD-q5QvwWj-r2ANahT0Y,1728
44
+ boto3_assist-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ boto3_assist-0.3.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
46
+ boto3_assist-0.3.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
47
+ boto3_assist-0.3.0.dist-info/RECORD,,