boto3-assist 0.8.0__py3-none-any.whl → 0.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,17 +31,10 @@ class Connection:
31
31
  aws_secret_access_key: Optional[str] = None,
32
32
  aws_end_point_url: Optional[str] = None,
33
33
  ) -> None:
34
- # TODO: determine if we want to pull from environment vars or not
35
- self.aws_profile = aws_profile or EnvironmentVariables.AWS.profile()
36
- self.aws_region = aws_region or EnvironmentVariables.AWS.region()
37
-
38
- self.aws_access_key_id = (
39
- aws_access_key_id or EnvironmentVariables.AWS.DynamoDB.aws_access_key_id()
40
- )
41
- self.aws_secret_access_key = (
42
- aws_secret_access_key
43
- or EnvironmentVariables.AWS.DynamoDB.aws_secret_access_key()
44
- )
34
+ self.__aws_profile = aws_profile
35
+ self.__aws_region = aws_region
36
+ self.__aws_access_key_id = aws_access_key_id
37
+ self.__aws_secret_access_key = aws_secret_access_key
45
38
  self.end_point_url = aws_end_point_url
46
39
  self.__session: Boto3SessionManager | None = None
47
40
 
@@ -84,6 +77,45 @@ class Connection:
84
77
 
85
78
  tracker.add(service_name=self.service_name)
86
79
 
80
+ @property
81
+ def asw_profile(self) -> str | None:
82
+ """The AWS Profile"""
83
+ return self.__aws_profile or EnvironmentVariables.AWS.profile()
84
+
85
+ @asw_profile.setter
86
+ def aws_profile(self, value: str | None):
87
+ self.__aws_profile = value
88
+
89
+ @property
90
+ def aws_region(self) -> str | None:
91
+ """The AWS Region"""
92
+ return self.__aws_region or EnvironmentVariables.AWS.region()
93
+
94
+ @aws_region.setter
95
+ def aws_region(self, value: str | None):
96
+ self.__aws_region = value
97
+
98
+ @property
99
+ def aws_access_key_id(self) -> str | None:
100
+ """The AWS Access Key"""
101
+ return self.__aws_access_key_id or EnvironmentVariables.AWS.aws_access_key_id()
102
+
103
+ @aws_access_key_id.setter
104
+ def aws_access_key_id(self, value: str | None):
105
+ self.__aws_access_key_id = value
106
+
107
+ @property
108
+ def aws_secret_access_key(self) -> str | None:
109
+ """The AWS Access Key"""
110
+ return (
111
+ self.__aws_secret_access_key
112
+ or EnvironmentVariables.AWS.aws_secret_access_key()
113
+ )
114
+
115
+ @aws_secret_access_key.setter
116
+ def aws_secret_access_key(self, value: str | None):
117
+ self.__aws_secret_access_key = value
118
+
87
119
  @property
88
120
  def service_name(self) -> str:
89
121
  """Service Name"""
@@ -5,7 +5,7 @@ MIT License. See Project Root for the license information.
5
5
  """
6
6
 
7
7
  import os
8
-
8
+ import json
9
9
  from typing import List, Union, Optional, IO
10
10
  from pathlib import Path
11
11
  from dotenv import load_dotenv
@@ -109,3 +109,20 @@ class EnvironmentLoader:
109
109
  )
110
110
 
111
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,168 @@
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 Dict, Any, List
8
+ from boto3_assist.utilities.numbers_utility import NumberUtility
9
+
10
+
11
+ class EventData:
12
+ """An Event Data Object"""
13
+
14
+ def __init__(self, event: Dict[str, Any]):
15
+ self.__event = event
16
+
17
+ @property
18
+ def version(self) -> str:
19
+ """Event Version"""
20
+ return self.__event.get("version")
21
+
22
+ @property
23
+ def id(self) -> str:
24
+ """Event Id"""
25
+ return self.__event.get("id")
26
+
27
+ @property
28
+ def detail_type(self) -> str:
29
+ """Event Detail Type"""
30
+ return self.__event.get("detail-type")
31
+
32
+ @property
33
+ def source(self) -> str:
34
+ """Event Source"""
35
+ return self.__event.get("source")
36
+
37
+ @property
38
+ def account(self) -> str:
39
+ """Event Account"""
40
+ return self.__event.get("account")
41
+
42
+ @property
43
+ def time(self) -> str:
44
+ """Event Time"""
45
+ return self.__event.get("time")
46
+
47
+ @property
48
+ def region(self) -> str:
49
+ """Event Region"""
50
+ return self.__event.get("region")
51
+
52
+ @property
53
+ def resources(self) -> List[str]:
54
+ """Event Resources"""
55
+ return self.__event.get("resources", [])
56
+
57
+
58
+ class S3BucketData:
59
+ """S3 Bucket Data"""
60
+
61
+ def __init__(self, bucket_event_data: Dict[str, Any]):
62
+ self.__bucket = bucket_event_data
63
+
64
+ @property
65
+ def name(self) -> str | None:
66
+ """Bucket Name Key"""
67
+
68
+ return self.__bucket.get("name")
69
+
70
+
71
+ class S3ObjectData:
72
+ """S3 Object"""
73
+
74
+ def __init__(self, object_data: Dict[str, Any]):
75
+ self.__s3_object_data = object_data
76
+
77
+ @property
78
+ def key(self) -> str | None:
79
+ """Object Key"""
80
+ return self.__s3_object_data.get("key")
81
+
82
+ @property
83
+ def size(self) -> int:
84
+ """Object size in bytes"""
85
+ size = NumberUtility.to_number(self.__s3_object_data.get("size"))
86
+ return size
87
+
88
+ @property
89
+ def etag(self) -> str | None:
90
+ """Object eTag"""
91
+ return self.__s3_object_data.get("etag")
92
+
93
+ @property
94
+ def version_id(self) -> str | None:
95
+ """Object Version Id"""
96
+ return self.__s3_object_data.get("version-id")
97
+
98
+ @property
99
+ def sequencer(self) -> str | None:
100
+ """Object eTag"""
101
+ return self.__s3_object_data.get("sequencer")
102
+
103
+
104
+ class S3EventDetail:
105
+ """The Event Detail"""
106
+
107
+ def __init__(self, event: Dict[str, Any]):
108
+ self.__event = event
109
+ self.__s3_object_data: S3ObjectData | None = None
110
+ self.__s3_bucket_data: S3BucketData | None = None
111
+
112
+ @property
113
+ def version(self) -> str | None:
114
+ """Object Key"""
115
+
116
+ return self.__event.get("version")
117
+
118
+ @property
119
+ def bucket(self) -> S3BucketData:
120
+ """S# Bucket Information"""
121
+ if not self.__s3_bucket_data:
122
+ self.__s3_bucket_data = S3BucketData(self.__event.get("bucket", {}))
123
+ return self.__s3_bucket_data
124
+
125
+ @property
126
+ def object(self) -> S3ObjectData:
127
+ """S3 Object Information"""
128
+ if not self.__s3_object_data:
129
+ self.__s3_object_data = S3ObjectData(self.__event.get("object", {}))
130
+
131
+ return self.__s3_object_data
132
+
133
+ @property
134
+ def request_id(self) -> str | None:
135
+ """Detail Request Id"""
136
+
137
+ return self.__event.get("request-id")
138
+
139
+ @property
140
+ def requester(self) -> str | None:
141
+ """Detail Requestor"""
142
+
143
+ return self.__event.get("requester")
144
+
145
+ @property
146
+ def source_ip_address(self) -> str | None:
147
+ """Source IP Address"""
148
+
149
+ return self.__event.get("source-ip-address")
150
+
151
+ @property
152
+ def reason(self) -> str | None:
153
+ """Reason"""
154
+
155
+ return self.__event.get("reason")
156
+
157
+
158
+ class S3Event(EventData):
159
+ """S3 Data Event"""
160
+
161
+ def __init__(self, event):
162
+ super().__init__(event)
163
+ self.__detail: S3EventDetail = S3EventDetail(event=event.get("detail", {}))
164
+
165
+ @property
166
+ def detail(self) -> S3EventDetail:
167
+ """S3 Specific Detail"""
168
+ return self.__detail
@@ -183,7 +183,7 @@ class S3Object:
183
183
  )
184
184
 
185
185
  end = DatetimeUtility.get_utc_now()
186
- logger.debug(f"Signed URL created in {end-start}")
186
+ logger.debug(f"Signed URL created in {end - start}")
187
187
 
188
188
  response = {
189
189
  "signed_url": signed_url,
@@ -604,3 +604,65 @@ class S3Object:
604
604
  versions.extend(page["Versions"])
605
605
 
606
606
  return versions
607
+
608
+ def copy(
609
+ self,
610
+ source_bucket: str,
611
+ source_key: str,
612
+ destination_bucket: str,
613
+ destination_key: str,
614
+ ) -> Dict[str, Any]:
615
+ """
616
+ Copies an object from one location to another.
617
+ The original is kept.
618
+ """
619
+
620
+ if source_key.startswith("/"):
621
+ # remove the first slash
622
+ source_key = source_key[1:]
623
+
624
+ if destination_key.startswith("/"):
625
+ # remove the first slash
626
+ destination_key = destination_key[1:]
627
+
628
+ response = self.connection.client.copy_object(
629
+ CopySource={"Bucket": source_bucket, "Key": source_key},
630
+ Bucket=destination_bucket,
631
+ Key=destination_key,
632
+ )
633
+
634
+ return response
635
+
636
+ def move(
637
+ self,
638
+ source_bucket: str,
639
+ source_key: str,
640
+ destination_bucket: str,
641
+ destination_key: str,
642
+ ) -> Dict[str, Any]:
643
+ """
644
+ Copies an object from one location to another then deletes the source.
645
+ The source is only deleted if the copy is successful
646
+ """
647
+
648
+ copy_response = self.connection.client.copy_object(
649
+ CopySource={"Bucket": source_bucket, "Key": source_key},
650
+ Bucket=destination_bucket,
651
+ Key=destination_key,
652
+ )
653
+
654
+ status_code = copy_response.get("statusCode")
655
+ delete_response = {}
656
+ if status_code == 200:
657
+ if source_key.startswith("/"):
658
+ source_key = source_key[1:]
659
+ delete_response = self.delete(bucket_name=source_bucket, key=source_key)
660
+ status_code = copy_response.get("statusCode", status_code)
661
+
662
+ response = {
663
+ "status_code": status_code,
664
+ "copy": copy_response,
665
+ "delete": delete_response,
666
+ }
667
+
668
+ return response
@@ -0,0 +1,150 @@
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
+ from typing import Optional
9
+
10
+ from aws_lambda_powertools import Logger
11
+
12
+ from boto3_assist.securityhub.securityhub_connection import SecurityHubConnection
13
+
14
+ logger = Logger("security-hub-service")
15
+
16
+
17
+ class SecurityHub(SecurityHubConnection):
18
+ """Security Hub Service"""
19
+
20
+ def update_findings_status(
21
+ self,
22
+ region_name: str,
23
+ workflow_status: str,
24
+ note_text: Optional[str] = None,
25
+ updated_by: Optional[str] = None,
26
+ ):
27
+ """
28
+ Updates the workflow status for all findings in the specified region.
29
+
30
+ :param region_name: AWS region where Security Hub findings are located.
31
+ :param workflow_status: The new workflow status to apply (e.g., NEW, NOTIFIED, SUPPRESSED, RESOLVED).
32
+ """
33
+ # Initialize Security Hub client
34
+ client = self.client
35
+
36
+ findings_to_update = []
37
+ next_token = None
38
+
39
+ logger.info(f"Fetching findings in region: {region_name}...")
40
+
41
+ try:
42
+ # Paginate through findings
43
+ while True:
44
+ response = client.get_findings(
45
+ MaxResults=100,
46
+ Filters={
47
+ "Region": [
48
+ {"Value": region_name, "Comparison": "EQUALS"},
49
+ ]
50
+ },
51
+ NextToken=next_token if next_token else "",
52
+ )
53
+
54
+ for finding in response.get("Findings", []):
55
+ current_status = finding.get("Workflow", {}).get("Status")
56
+ if (
57
+ current_status
58
+ and str(current_status).lower() != str(workflow_status).lower()
59
+ ):
60
+ findings_to_update.append(
61
+ {
62
+ "Id": finding["Id"],
63
+ "ProductArn": finding["ProductArn"],
64
+ }
65
+ )
66
+ else:
67
+ logger.debug(
68
+ f"Skipping: {finding['Id']} with a status of {current_status}"
69
+ )
70
+
71
+ next_token = response.get("NextToken")
72
+ if not next_token:
73
+ break
74
+
75
+ print(f"Found {len(findings_to_update)} findings to update.")
76
+
77
+ note_text = note_text or "Automated Update"
78
+ updated_by = updated_by or "System"
79
+ # Update workflow status in batches of 100
80
+ for i in range(0, len(findings_to_update), 100):
81
+ batch = findings_to_update[i : i + 100]
82
+ response = client.batch_update_findings(
83
+ FindingIdentifiers=batch,
84
+ Workflow={"Status": str(workflow_status).upper()},
85
+ Note={"Text": note_text, "UpdatedBy": updated_by},
86
+ )
87
+ logger.debug(
88
+ f"Updated findings {i + 1} to {i + len(batch)} to status: {workflow_status}"
89
+ )
90
+ print(response)
91
+
92
+ logger.info("All findings updated successfully!")
93
+
94
+ except Exception as e:
95
+ logger.exception(f"An error occurred: {str(e)}")
96
+ raise
97
+
98
+
99
+ def main():
100
+ status = "RESOLVED" # Change to NEW, NOTIFIED, SUPPRESSED, or RESOLVED
101
+ note_text = "This region is now disabled."
102
+ # these are my linked regions and the only ones I care about
103
+ # if have SCP's in place for the other regions
104
+ regions_to_skip = ["us-east-1", "us-west-2", "eu-west-2"]
105
+
106
+ aws_profile = os.getenv("SECURITY_HUB_PROFILE")
107
+
108
+ aws_regions = {
109
+ "af-south-1": "Africa (Cape Town)",
110
+ "ap-east-1": "Asia Pacific (Hong Kong)",
111
+ "ap-northeast-1": "Asia Pacific (Tokyo)",
112
+ "ap-northeast-2": "Asia Pacific (Seoul)",
113
+ "ap-northeast-3": "Asia Pacific (Osaka)",
114
+ "ap-south-1": "Asia Pacific (Mumbai)",
115
+ "ap-south-2": "Asia Pacific (Hyderabad)",
116
+ "ap-southeast-1": "Asia Pacific (Singapore)",
117
+ "ap-southeast-2": "Asia Pacific (Sydney)",
118
+ "ap-southeast-3": "Asia Pacific (Jakarta)",
119
+ "ap-southeast-4": "Asia Pacific (Melbourne)",
120
+ "ap-southeast-5": "Asia Pacific (Auckland)",
121
+ "ca-central-1": "Canada (Central)",
122
+ "ca-west-1": "Canada (West)",
123
+ "eu-central-1": "Europe (Frankfurt)",
124
+ "eu-central-2": "Europe (Zurich)",
125
+ "eu-north-1": "Europe (Stockholm)",
126
+ "eu-south-1": "Europe (Milan)",
127
+ "eu-south-2": "Europe (Spain)",
128
+ "eu-west-1": "Europe (Ireland)",
129
+ "eu-west-2": "Europe (London)",
130
+ "eu-west-3": "Europe (Paris)",
131
+ "il-central-1": "Israel (Tel Aviv)",
132
+ "me-central-1": "Middle East (UAE)",
133
+ "sa-east-1": "South America (São Paulo)",
134
+ "us-east-1": "US East (N. Virginia)",
135
+ "us-east-2": "US East (Ohio)",
136
+ "us-west-1": "US West (N. California)",
137
+ "us-west-2": "US West (Oregon)",
138
+ }
139
+
140
+ sh: SecurityHub = SecurityHub(aws_profile=aws_profile)
141
+ for region in aws_regions:
142
+ print(region)
143
+ if region not in regions_to_skip:
144
+ sh.update_findings_status(region, status, note_text=note_text)
145
+ else:
146
+ print(f"Skipping region: {region}")
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()
@@ -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_securityhub import SecurityHubClient
16
+ else:
17
+ SecurityHubClient = object
18
+
19
+
20
+ logger = Logger()
21
+
22
+
23
+ class SecurityHubConnection(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="securityhub",
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: SecurityHubClient | None = None
45
+
46
+ @property
47
+ def client(self) -> SecurityHubClient:
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: SecurityHubClient):
56
+ logger.info("Setting Client")
57
+ self.__client = value
@@ -47,12 +47,20 @@ class NumberUtility:
47
47
  @staticmethod
48
48
  def to_number_or_none(value: str) -> float | int | None:
49
49
  """Converts a string to a number."""
50
- if value is not None and NumberUtility.is_numeric(value):
51
- if "." in str(value):
52
- return float(value)
53
- else:
54
- return int(value)
55
- return None
50
+ if value is None:
51
+ return None
52
+
53
+ if str(value).lower() == "nan":
54
+ return None
55
+
56
+ try:
57
+ numeric_value = float(value)
58
+ # Check if the number is an integer (e.g., 7.0) and return as int
59
+ if numeric_value.is_integer():
60
+ return int(numeric_value)
61
+ return numeric_value
62
+ except (ValueError, TypeError):
63
+ return None
56
64
 
57
65
  @staticmethod
58
66
  def to_float(
@@ -111,6 +119,7 @@ class NumberUtility:
111
119
  Returns:
112
120
  int: number of decimal places
113
121
  """
122
+
114
123
  number_str = f"{number}"
115
124
  if "." in number_str:
116
125
  to_the_right = number_str.split(".")[1]
@@ -178,6 +187,30 @@ class NumberUtility:
178
187
 
179
188
  return 0.0
180
189
 
190
+ @staticmethod
191
+ def to_number(
192
+ value: str | float | int,
193
+ raise_errors: Optional[bool] = False,
194
+ error_message: Optional[str] = None,
195
+ ) -> int | float:
196
+ """Converts a string to a number."""
197
+ try:
198
+ numeric_value = float(value)
199
+ # Check if the number is an integer (e.g., 7.0) and return as int
200
+ if numeric_value.is_integer():
201
+ return int(numeric_value)
202
+ return numeric_value
203
+ except Exception as e: # noqa: E722, pylint: disable=w0718
204
+ logger.error(f"Unable to convert {value} to number")
205
+ if raise_errors:
206
+ if error_message:
207
+ raise ValueError(
208
+ f"Unable to convert {value} to number, {error_message}"
209
+ ) from e
210
+ else:
211
+ raise ValueError(f"Unable to convert {value} to number") from e
212
+ return 0
213
+
181
214
  @staticmethod
182
215
  def to_significant_figure(
183
216
  number: int | float | str, sig_figs: int
@@ -201,10 +234,14 @@ class NumberUtility:
201
234
  return number
202
235
 
203
236
  if NumberUtility.is_numeric(number) and isinstance(number, str):
204
- if "." in str(number):
205
- number = float(number)
206
- else:
207
- number = int(number)
237
+ number = NumberUtility.to_number(
238
+ number,
239
+ raise_errors=True,
240
+ error_message=(
241
+ f"Error attempting to set significant figure for value {number}"
242
+ f", sigfig {sig_figs}"
243
+ ),
244
+ )
208
245
 
209
246
  if number == 0:
210
247
  if sig_figs > 1:
boto3_assist/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.0'
1
+ __version__ = '0.9.1'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.8.0
3
+ Version: 0.9.1
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
@@ -1,9 +1,9 @@
1
1
  boto3_assist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  boto3_assist/boto3session.py,sha256=Q9sByNC0r_aMQfHnIEnxtTaiCMUqikm8UeSTxV7-Np0,6632
3
- boto3_assist/connection.py,sha256=CNGkAIRyfrELoWrV0ziQBA3oHacNFuLL3i8faUPRiO0,3486
3
+ boto3_assist/connection.py,sha256=-z_OZtZmSVjSSECpoqx1FnqW7B9A_LovfN_cJ_nhHgg,4361
4
4
  boto3_assist/connection_tracker.py,sha256=UgfR9RlvXf3A4ssMr3gDMpw89ka8mSRvJn4M34SzhbU,4378
5
5
  boto3_assist/http_status_codes.py,sha256=G0zRSWenwavYKETvDF9tNVUXQz3Ae2gXdBETYbjvJe8,3284
6
- boto3_assist/version.py,sha256=27YY3zFpeaDh6JoC40AqkjBrn68SqFlsWZzjZtw5jwU,22
6
+ boto3_assist/version.py,sha256=pF8TnH2QJF8nfDmcD7oPTrAOnioy05dV4YmYDzkT5Es,22
7
7
  boto3_assist/aws_lambda/event_info.py,sha256=OkZ4WzuGaHEu_T8sB188KBgShAJhZpWASALKRGBOhMg,14648
8
8
  boto3_assist/cloudwatch/cloudwatch_connection.py,sha256=mnGWaLSQpHh5EeY7Ek_2o9JKHJxOELIYtQVMX1IaHn4,2480
9
9
  boto3_assist/cloudwatch/cloudwatch_connection_tracker.py,sha256=mzRtO1uHrcfJNh1XrGEiXdTqxwEP8d1RqJkraMNkgK0,410
@@ -31,24 +31,27 @@ boto3_assist/dynamodb/readme.md,sha256=wNMzdRwk0qRV0kE88UUYnJos3pEK0HNjEIVkq2PAT
31
31
  boto3_assist/dynamodb/troubleshooting.md,sha256=uGpBaBUt_MyzjzwFOLOe0udTgcvaOpiTFxfj7ilLNkM,136
32
32
  boto3_assist/ec2/ec2_connection.py,sha256=IrtaidH6_SF5l3OeNehRsTlC-sX7EURVqcO-U6P6ff8,1318
33
33
  boto3_assist/environment_services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- boto3_assist/environment_services/environment_loader.py,sha256=zCA4mRdVWMLKzjDRvrJbhQfRVP4HAMGpuQFi07zzULk,3396
34
+ boto3_assist/environment_services/environment_loader.py,sha256=1uVLqSPQriUWazGXpxPJBLveGL8rLZaT5ZsciqqjVek,3979
35
35
  boto3_assist/environment_services/environment_variables.py,sha256=4ccBKdPt6O7hcRT3zBHd8vqu8yQU8udmoD5RLAT3iMs,6801
36
36
  boto3_assist/errors/custom_exceptions.py,sha256=zC2V2Y4PUtKj3uLPn8mB-JessksKWJWvKM9kp1dmvt8,760
37
37
  boto3_assist/models/serializable_model.py,sha256=ZMrRJRvJWLY8PBSKK_nPCgYKv1qUxDPEVdcADKbIHsI,266
38
38
  boto3_assist/s3/s3.py,sha256=ESTPXtyDi8mrwHaYNWjQLNGTuTUV4CxKDqw-O_KGzKs,2052
39
39
  boto3_assist/s3/s3_bucket.py,sha256=GfyBbuI5BWz_ybwU_nDqUZiC0wt24PNt49GKZmb05OY,2018
40
40
  boto3_assist/s3/s3_connection.py,sha256=0JgEDNoDFPQTo5hQe-lS8mWnFBJ2S8MDSl0LPG__lZo,2008
41
- boto3_assist/s3/s3_object.py,sha256=kH9apiVZ-lWGtcSqcLvaAI-2g011ecrvg8wP7y9u140,19482
41
+ boto3_assist/s3/s3_event_data.py,sha256=vwty34zDgTeSLNCLAWVxvhSQKT7hIpM7fZrWm5w6znM,4063
42
+ boto3_assist/s3/s3_object.py,sha256=8diOon_TbH0BWpVNM84grDgXOmXq5C41N8wDa6E2B8c,21319
43
+ boto3_assist/securityhub/securityhub.py,sha256=nGmHd_R3awDeB_QRzPfNAtQauLdVA1hlRlkaZA4oZjg,5409
44
+ boto3_assist/securityhub/securityhub_connection.py,sha256=hWfcj9gjS2lNXUObyw4cShtveoqJPIp8kKFuz-fz1J4,1449
42
45
  boto3_assist/utilities/datetime_utility.py,sha256=dgAMB9VqakrYIPXlSoVQiLSsc_yhrJK4gMfJO9mX90w,11112
43
46
  boto3_assist/utilities/dictionaroy_utility.py,sha256=PjUrerEd6uhmw37A-k7xe_DWHvXZGGoMqT6xjUqmWBI,893
44
47
  boto3_assist/utilities/file_operations.py,sha256=Zy8fu8fpuVNf7U9NimrLdy5FRF71XSI159cnRdzmzGY,3411
45
48
  boto3_assist/utilities/http_utility.py,sha256=koFv7Va-8ng-47Nt1K2Sh7Ti95e62IYs9VMLlGh9Kt4,1173
46
49
  boto3_assist/utilities/logging_utility.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- boto3_assist/utilities/numbers_utility.py,sha256=KIiNkBSRbfNWvtXG5SdHp625LTiW12VtADUa4ZlWMFo,8709
50
+ boto3_assist/utilities/numbers_utility.py,sha256=EjCnQhSbD5TRnuReKBD_GQyynp7Wit5M2bSfOfQGJBU,10051
48
51
  boto3_assist/utilities/serialization_utility.py,sha256=DxVtJ8OPc25LagZxShH4cnXSmrwE8cVUGNTEIkj6stU,17050
49
52
  boto3_assist/utilities/string_utility.py,sha256=5BpDaqGZI8cSM-3YFQLU1fKcWcqG9r1_GPgDstCWFIs,10318
50
- boto3_assist-0.8.0.dist-info/METADATA,sha256=aSgObI42sVUE1by-oebSmnSJqAVpW-QGSsWVQy4oHjY,1728
51
- boto3_assist-0.8.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
- boto3_assist-0.8.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
53
- boto3_assist-0.8.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
54
- boto3_assist-0.8.0.dist-info/RECORD,,
53
+ boto3_assist-0.9.1.dist-info/METADATA,sha256=XBcXFjiU5ZtkYWMfSWiDGALH2G5ZqOAm-tSKULSTJ1k,1728
54
+ boto3_assist-0.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
55
+ boto3_assist-0.9.1.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
56
+ boto3_assist-0.9.1.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
57
+ boto3_assist-0.9.1.dist-info/RECORD,,