boto3-assist 0.32.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.
Files changed (67) hide show
  1. boto3_assist/__init__.py +0 -0
  2. boto3_assist/aws_config.py +199 -0
  3. boto3_assist/aws_lambda/event_info.py +414 -0
  4. boto3_assist/aws_lambda/mock_context.py +5 -0
  5. boto3_assist/boto3session.py +87 -0
  6. boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
  7. boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
  8. boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
  9. boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
  10. boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
  11. boto3_assist/cognito/cognito_authorizer.py +169 -0
  12. boto3_assist/cognito/cognito_connection.py +59 -0
  13. boto3_assist/cognito/cognito_utility.py +514 -0
  14. boto3_assist/cognito/jwks_cache.py +21 -0
  15. boto3_assist/cognito/user.py +27 -0
  16. boto3_assist/connection.py +146 -0
  17. boto3_assist/connection_tracker.py +120 -0
  18. boto3_assist/dynamodb/dynamodb.py +1206 -0
  19. boto3_assist/dynamodb/dynamodb_connection.py +113 -0
  20. boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
  21. boto3_assist/dynamodb/dynamodb_importer.py +102 -0
  22. boto3_assist/dynamodb/dynamodb_index.py +507 -0
  23. boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
  24. boto3_assist/dynamodb/dynamodb_key.py +130 -0
  25. boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
  26. boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
  27. boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
  28. boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
  29. boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
  30. boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
  31. boto3_assist/dynamodb/readme.md +68 -0
  32. boto3_assist/dynamodb/troubleshooting.md +7 -0
  33. boto3_assist/ec2/ec2_connection.py +57 -0
  34. boto3_assist/environment_services/__init__.py +0 -0
  35. boto3_assist/environment_services/environment_loader.py +128 -0
  36. boto3_assist/environment_services/environment_variables.py +219 -0
  37. boto3_assist/erc/__init__.py +64 -0
  38. boto3_assist/erc/ecr_connection.py +57 -0
  39. boto3_assist/errors/custom_exceptions.py +46 -0
  40. boto3_assist/http_status_codes.py +80 -0
  41. boto3_assist/models/serializable_model.py +9 -0
  42. boto3_assist/role_assumption_mixin.py +38 -0
  43. boto3_assist/s3/s3.py +64 -0
  44. boto3_assist/s3/s3_bucket.py +67 -0
  45. boto3_assist/s3/s3_connection.py +76 -0
  46. boto3_assist/s3/s3_event_data.py +168 -0
  47. boto3_assist/s3/s3_object.py +695 -0
  48. boto3_assist/securityhub/securityhub.py +150 -0
  49. boto3_assist/securityhub/securityhub_connection.py +57 -0
  50. boto3_assist/session_setup_mixin.py +70 -0
  51. boto3_assist/ssm/connection.py +57 -0
  52. boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
  53. boto3_assist/utilities/datetime_utility.py +349 -0
  54. boto3_assist/utilities/decimal_conversion_utility.py +140 -0
  55. boto3_assist/utilities/dictionary_utility.py +32 -0
  56. boto3_assist/utilities/file_operations.py +135 -0
  57. boto3_assist/utilities/http_utility.py +48 -0
  58. boto3_assist/utilities/logging_utility.py +0 -0
  59. boto3_assist/utilities/numbers_utility.py +329 -0
  60. boto3_assist/utilities/serialization_utility.py +664 -0
  61. boto3_assist/utilities/string_utility.py +337 -0
  62. boto3_assist/version.py +1 -0
  63. boto3_assist-0.32.0.dist-info/METADATA +76 -0
  64. boto3_assist-0.32.0.dist-info/RECORD +67 -0
  65. boto3_assist-0.32.0.dist-info/WHEEL +4 -0
  66. boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
  67. boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,128 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import os
8
+ import json
9
+ from typing import List, Union, Optional, IO
10
+ from pathlib import Path
11
+ from dotenv import load_dotenv
12
+ from aws_lambda_powertools import Logger
13
+
14
+
15
+ logger = Logger(__name__)
16
+
17
+ DEBUGGING = os.getenv("DEBUGGING", "false").lower() == "true"
18
+
19
+ StrPath = Union[str, "os.PathLike[str]"]
20
+
21
+
22
+ class EnvironmentLoader:
23
+ """Environment Loader"""
24
+
25
+ def __init__(self) -> None:
26
+ pass
27
+
28
+ def load_environment_file(
29
+ self,
30
+ *,
31
+ starting_path: Optional[str] = None,
32
+ file_name: Optional[str] = None,
33
+ path: Optional[StrPath] = None,
34
+ stream: Optional[IO[str]] = None,
35
+ verbose: bool = False,
36
+ override: bool = True,
37
+ interpolate: bool = True,
38
+ encoding: Optional[str] = "utf-8",
39
+ raise_error_if_not_found: bool = False,
40
+ ) -> bool:
41
+ """
42
+ Loads an environment file into memory. This simply passes off to load_dotenv in dotenv.
43
+ However one small change is that I'm defaulting override to True instead of False.
44
+
45
+
46
+ Args:
47
+ path: Absolute or relative path to .env file.
48
+ stream: Text stream (such as `io.StringIO`) with .env content, used if
49
+ `dotenv_path` is `None`.
50
+ verbose: Whether to output a warning the .env file is missing.
51
+ override: Whether to override the system environment variables with the variables
52
+ from the `.env` file.
53
+ encoding: Encoding to be used to read the file.
54
+ Returns:
55
+ Bool: True if at least one environment variable is set else False
56
+
57
+ If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
58
+ .env file.
59
+ """
60
+
61
+ if not starting_path:
62
+ starting_path = __file__
63
+
64
+ if file_name is None:
65
+ file_name = ".env"
66
+
67
+ new_path: str | StrPath | None = path or self.find_file(
68
+ starting_path=starting_path,
69
+ file_name=file_name,
70
+ raise_error_if_not_found=raise_error_if_not_found,
71
+ )
72
+
73
+ loaded = load_dotenv(
74
+ dotenv_path=new_path,
75
+ stream=stream,
76
+ verbose=verbose,
77
+ override=override,
78
+ interpolate=interpolate,
79
+ encoding=encoding,
80
+ )
81
+
82
+ if DEBUGGING:
83
+ env_vars = os.environ
84
+ logger.debug(f"Loaded environment file: {path}")
85
+ print(env_vars)
86
+
87
+ return loaded
88
+
89
+ def find_file(
90
+ self, starting_path: str, file_name: str, raise_error_if_not_found: bool = True
91
+ ) -> str | None:
92
+ """Searches the project directory structor for a file"""
93
+ parents = 10
94
+ starting_path = starting_path or __file__
95
+
96
+ paths: List[str] = []
97
+ for parent in range(parents):
98
+ path = Path(starting_path).parents[parent].absolute()
99
+ logger.debug(f"searching for {file_name} in: {path}")
100
+ tmp = os.path.join(path, file_name)
101
+ paths.append(tmp)
102
+ if os.path.exists(tmp):
103
+ return tmp
104
+
105
+ if raise_error_if_not_found:
106
+ searched_paths = "\n".join(paths)
107
+ raise RuntimeError(
108
+ f"Failed to locate environment file: {file_name} in: \n {searched_paths}"
109
+ )
110
+
111
+ return None
112
+
113
+ def load_event_file(self, full_path: str) -> dict:
114
+ """Loads an AWS event file"""
115
+ if not os.path.exists(full_path):
116
+ raise RuntimeError(f"Failed to locate event file: {full_path}")
117
+
118
+ event = {}
119
+ with open(full_path, mode="r", encoding="utf-8") as json_file:
120
+ event = json.load(json_file)
121
+
122
+ if isinstance(event, dict) and "message" in event:
123
+ event = event.get("message", {})
124
+
125
+ if isinstance(event, dict) and "event" in event:
126
+ event = event.get("event", {})
127
+
128
+ return event
@@ -0,0 +1,219 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import os
8
+
9
+ from aws_lambda_powertools import Logger
10
+
11
+ logger = Logger(__name__)
12
+
13
+
14
+ class EnvironmentVariables:
15
+ """
16
+ Easy access to allo the environment variables we use in the appliction.
17
+ It's a best practice to use this vs doing and os.getevn in each application.
18
+ This helps us track all the enviroment variables in use
19
+ """
20
+
21
+ class AWS:
22
+ """AWS Specific Environment Vars"""
23
+
24
+ @staticmethod
25
+ def region() -> str | None:
26
+ """
27
+ gets the aws region from an environment var
28
+ """
29
+ value = os.getenv("AWS_REGION")
30
+ if not value:
31
+ value = None
32
+ return value
33
+
34
+ @staticmethod
35
+ def profile() -> str | None:
36
+ """
37
+ Get the aws profile used for cli/boto3 commands
38
+ This should only be set with temporty creds and only for development purposes
39
+ """
40
+ value = os.getenv("AWS_PROFILE")
41
+ if not value:
42
+ value = None
43
+ return value
44
+
45
+ @staticmethod
46
+ def account_id() -> str | None:
47
+ """
48
+ gets the aws account id from an environment var
49
+ """
50
+ value = os.getenv("AWS_ACCOUNT_ID")
51
+ return value
52
+
53
+ @staticmethod
54
+ def amazon_trace_id() -> str:
55
+ """
56
+ gets the amazon trace id from an environment var
57
+ """
58
+ value = os.getenv("_X_AMZN_TRACE_ID", "NA")
59
+ return value
60
+
61
+ @staticmethod
62
+ def endpoint_url() -> str | None:
63
+ """
64
+ Gets the AWS_ENDPOINT_URL environment var
65
+ """
66
+ value = os.getenv("AWS_ENDPOINT_URL")
67
+ return value
68
+
69
+ @staticmethod
70
+ def display_aws_access_key_id() -> bool:
71
+ """
72
+ Determines if you want to display the aws access key
73
+ """
74
+ value = (
75
+ str(os.getenv("DISPLAY_AWS_ACCESS_KEY_ID", "false")).lower() == "true"
76
+ )
77
+ return value
78
+
79
+ @staticmethod
80
+ def display_aws_secret_access_key() -> bool:
81
+ """
82
+ Determines if you want to display the aws access key
83
+ """
84
+ value = (
85
+ str(os.getenv("DISPLAY_AWS_SECRET_ACCESS_KEY", "false")).lower()
86
+ == "true"
87
+ )
88
+ return value
89
+
90
+ @staticmethod
91
+ def aws_access_key_id() -> str | None:
92
+ """
93
+ The aws_access_key_id. Often used for local development.
94
+ """
95
+ value = os.getenv("ACCESS_KEY_ID")
96
+ return value
97
+
98
+ @staticmethod
99
+ def aws_secret_access_key() -> str | None:
100
+ """
101
+ The aws_secret_access_key. Often used for local development.
102
+ """
103
+ value = os.getenv("SECRET_ACCESS_KEY")
104
+ return value
105
+
106
+ class SES:
107
+ """SES Settings"""
108
+
109
+ @staticmethod
110
+ def user_name() -> str | None:
111
+ """
112
+ gets the ses user-name from an environment var
113
+ """
114
+ value = os.getenv("SES_USER_NAME")
115
+ return value
116
+
117
+ @staticmethod
118
+ def password() -> str | None:
119
+ """
120
+ gets the ses password from an environment var
121
+ """
122
+ value = os.getenv("SES_PASSWORD")
123
+ return value
124
+
125
+ @staticmethod
126
+ def endpoint() -> str | None:
127
+ """
128
+ gets the ses endpoint from an environment var
129
+ """
130
+ value = os.getenv("SES_END_POINT")
131
+ return value
132
+
133
+ class Cognito:
134
+ """Cognito Settings"""
135
+
136
+ @staticmethod
137
+ def user_pool() -> str | None:
138
+ """
139
+ gets the cognito user pool from an environment var
140
+ """
141
+ value = os.getenv("COGNITO_USER_POOL")
142
+ return value
143
+
144
+ class DynamoDB:
145
+ """DynamoDB Settings"""
146
+
147
+ @staticmethod
148
+ def raise_on_error_setting() -> bool:
149
+ """
150
+ determines if we raise errors on saves, gets, etc
151
+ this is useful to turn off for some local testing. but otherwise I
152
+ would recommend to leave it on
153
+ """
154
+ value = str(os.getenv("RAISE_ON_DB_ERROR", "true")).lower() == "true"
155
+
156
+ return value
157
+
158
+ @staticmethod
159
+ def single_table() -> str | None:
160
+ """
161
+ If a single table design is used this can be a usefull way to send it around
162
+ """
163
+ value = os.getenv("DYNAMODB_SINGLE_TABLE")
164
+ return value
165
+
166
+ @staticmethod
167
+ def endpoint_url() -> str | None:
168
+ """
169
+ The DynamoDB Endpoint url. Often used for local development.
170
+ For example a docker containers defaults to http://localhost:8000
171
+ """
172
+ value = os.getenv("AWS_DYNAMODB_ENDPOINT_URL")
173
+ return value
174
+
175
+ @staticmethod
176
+ def aws_access_key_id() -> str | None:
177
+ """
178
+ The DynamoDB aws_access_key_id. Often used for local development.
179
+ For example a docker containers defaults to dummy_access_key
180
+ """
181
+ value = os.getenv("AWS_DYNAMODB_ACCESS_KEY_ID")
182
+ return value
183
+
184
+ @staticmethod
185
+ def aws_secret_access_key() -> str | None:
186
+ """
187
+ The DynamoDB aws_secret_access_key. Often used for local development.
188
+ For example a docker containers defaults to dummy_secret_key
189
+ """
190
+ value = os.getenv("AWS_DYNAMODB_SECRET_ACCESS_KEY")
191
+ return value
192
+
193
+ @staticmethod
194
+ def get_integration_tests_setting() -> bool:
195
+ """
196
+ deteremine if integration tests are run from an environment var
197
+ """
198
+ value = str(os.getenv("RUN_INTEGRATION_TESTS", "False")).lower() == "true"
199
+ env = EnvironmentVariables.get_environment_setting()
200
+
201
+ if env.lower().startswith("prod"):
202
+ value = False
203
+
204
+ return value
205
+
206
+ @staticmethod
207
+ def get_environment_setting() -> str:
208
+ """
209
+ gets the environment name from an environment var
210
+ """
211
+ value = os.getenv("ENVIRONMENT")
212
+
213
+ if not value:
214
+ logger.warning(
215
+ "ENVIRONMENT var is not set. A future version will throw an error."
216
+ )
217
+ return ""
218
+
219
+ return value
@@ -0,0 +1,64 @@
1
+ import boto3
2
+ from botocore.exceptions import ClientError
3
+ from .ecr_connection import ECRConnection
4
+
5
+
6
+ class ECR(ECRConnection):
7
+ pass
8
+
9
+ def retag_image(self, repository_name: str, source_tag: str, target_tag: str):
10
+ """
11
+ ReTag an ECR Image without the overhead of pulling it down, tagging and pushing.
12
+ Simply retags by getting the manifest, and pushing up changes.
13
+ Args:
14
+ repository_name (str):
15
+
16
+ """
17
+ ecr = self.client
18
+
19
+ try:
20
+ # 1) Get the manifest for the source tag
21
+ resp = ecr.batch_get_image(
22
+ repositoryName=repository_name,
23
+ imageIds=[{"imageTag": source_tag}],
24
+ # optional, but helps ensure you get the right manifest type:
25
+ acceptedMediaTypes=[
26
+ "application/vnd.docker.distribution.manifest.v2+json",
27
+ "application/vnd.oci.image.manifest.v1+json",
28
+ ],
29
+ )
30
+ images = resp.get("images", [])
31
+ if not images:
32
+ raise RuntimeError(f"No image found with tag {source_tag!r}")
33
+
34
+ manifest = images[0]["imageManifest"]
35
+
36
+ # 2) Push the same manifest under the new tag
37
+ put_resp = ecr.put_image(
38
+ repositoryName=repository_name,
39
+ imageManifest=manifest,
40
+ imageTag=target_tag,
41
+ )
42
+
43
+ failures = put_resp.get("failures")
44
+
45
+ if failures and len(failures) > 0:
46
+ raise RuntimeError(
47
+ f"Failed to tag {repository_name}:{target_tag} - {failures}"
48
+ )
49
+
50
+ print(f"Successfully tagged {repository_name}:{target_tag}")
51
+ return put_resp
52
+
53
+ except ClientError as e:
54
+ print(
55
+ f"ECR error: {e.response.get('Error', {}).get('Code', 'NA')} "
56
+ f"- {e.response.get('Error', {}).get('Message', 'NA')}"
57
+ )
58
+ raise
59
+
60
+
61
+ if __name__ == "__main__":
62
+ # Example: retag version 1.16.54 → dev
63
+ ecr: ECR = ECR(aws_profile="geek-cafe", aws_region="us-east-1")
64
+ ecr.retag_image("my-repo", "1.16.54", "dev")
@@ -0,0 +1,57 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import Optional
8
+ from typing import TYPE_CHECKING
9
+
10
+ from aws_lambda_powertools import Logger
11
+
12
+ from boto3_assist.connection import Connection
13
+
14
+ if TYPE_CHECKING:
15
+ from mypy_boto3_ecr import Client
16
+ else:
17
+ Client = object
18
+
19
+
20
+ logger = Logger()
21
+
22
+
23
+ class ECRConnection(Connection):
24
+ """Connection"""
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ aws_profile: Optional[str] = None,
30
+ aws_region: Optional[str] = None,
31
+ aws_end_point_url: Optional[str] = None,
32
+ aws_access_key_id: Optional[str] = None,
33
+ aws_secret_access_key: Optional[str] = None,
34
+ ) -> None:
35
+ super().__init__(
36
+ service_name="ecr",
37
+ aws_profile=aws_profile,
38
+ aws_region=aws_region,
39
+ aws_access_key_id=aws_access_key_id,
40
+ aws_secret_access_key=aws_secret_access_key,
41
+ aws_end_point_url=aws_end_point_url,
42
+ )
43
+
44
+ self.__client: Client | None = None
45
+
46
+ @property
47
+ def client(self) -> Client:
48
+ """Client Connection"""
49
+ if self.__client is None:
50
+ self.__client = self.session.client
51
+
52
+ return self.__client
53
+
54
+ @client.setter
55
+ def client(self, value: Client):
56
+ logger.info("Setting Client")
57
+ self.__client = value
@@ -0,0 +1,46 @@
1
+ class Error(Exception):
2
+ """Base class for exceptions in this module."""
3
+
4
+
5
+ class DbFailures(Error):
6
+ """DB Failure Error"""
7
+
8
+
9
+ class InvalidHttpMethod(Exception):
10
+ """Invalid Http Method"""
11
+
12
+ def __init__(
13
+ self,
14
+ code=422,
15
+ message="Invalid Http Method",
16
+ ):
17
+ """The user account is not valid"""
18
+ self.message = {
19
+ "status_code": code,
20
+ "message": message,
21
+ }
22
+ super().__init__(self.message)
23
+
24
+
25
+ class InvalidRoutePath(Exception):
26
+ """Invalid Http Route"""
27
+
28
+ def __init__(self, message="Invalid Route"):
29
+ """Invalid Route"""
30
+ self.message = {
31
+ "status_code": 404,
32
+ "message": message,
33
+ }
34
+ super().__init__(self.message)
35
+
36
+
37
+ class FileNotFound(Exception):
38
+ """File Not Found Error"""
39
+
40
+ def __init__(self, message="File Not Found"):
41
+ """Invalid Route"""
42
+ self.message = {
43
+ "status_code": 404,
44
+ "message": message,
45
+ }
46
+ super().__init__(self.message)
@@ -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
+ """
@@ -0,0 +1,9 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from boto3_assist.utilities.serialization_utility import SerializableModel # noqa: F401 # pylint: disable=unused-import
@@ -0,0 +1,38 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import boto3
8
+ from typing import List
9
+
10
+
11
+ class RoleAssumptionMixin:
12
+ def _assume_roles_in_chain(
13
+ self,
14
+ base_session: boto3.Session,
15
+ role_chain: List[str],
16
+ session_name: str,
17
+ duration_seconds: int,
18
+ region: str,
19
+ ) -> boto3.Session:
20
+ session = base_session
21
+
22
+ for role_arn in role_chain:
23
+ sts_client = session.client("sts")
24
+ response = sts_client.assume_role(
25
+ RoleArn=role_arn,
26
+ RoleSessionName=session_name,
27
+ DurationSeconds=duration_seconds,
28
+ )
29
+ creds = response["Credentials"]
30
+
31
+ session = boto3.Session(
32
+ aws_access_key_id=creds["AccessKeyId"],
33
+ aws_secret_access_key=creds["SecretAccessKey"],
34
+ aws_session_token=creds["SessionToken"],
35
+ region_name=region,
36
+ )
37
+
38
+ return session