boto3-assist 0.33.0__py3-none-any.whl → 0.35.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.
@@ -7,6 +7,7 @@ MIT License. See Project Root for the license information.
7
7
  from typing import Optional, List
8
8
 
9
9
  from aws_lambda_powertools import Logger
10
+ from botocore.config import Config
10
11
  from boto3_assist.boto3session import Boto3SessionManager
11
12
  from boto3_assist.environment_services.environment_variables import (
12
13
  EnvironmentVariables,
@@ -33,6 +34,7 @@ class Connection:
33
34
  assume_role_arn: Optional[str] = None,
34
35
  assume_role_chain: Optional[List[str]] = None,
35
36
  assume_role_duration_seconds: Optional[int] = 3600,
37
+ config: Optional[Config] = None,
36
38
  ) -> None:
37
39
  self.__aws_profile = aws_profile
38
40
  self.__aws_region = aws_region
@@ -44,6 +46,7 @@ class Connection:
44
46
  self.__service_name: str | None = service_name
45
47
  self.__assume_role_chain = assume_role_chain
46
48
  self.__assume_role_duration_seconds = assume_role_duration_seconds
49
+ self.__config = config
47
50
  if self.__service_name is None:
48
51
  raise RuntimeError(
49
52
  "Service Name is not available. The service name is required."
@@ -80,6 +83,7 @@ class Connection:
80
83
  assume_role_arn=self.__assume_role_arn,
81
84
  assume_role_chain=self.__assume_role_chain,
82
85
  assume_role_duration_seconds=self.__assume_role_duration_seconds,
86
+ config=self.__config,
83
87
  )
84
88
 
85
89
  tracker.add(service_name=self.service_name)
@@ -4,10 +4,12 @@ Maintainers: Eric Wilson
4
4
  MIT License. See Project Root for the license information.
5
5
  """
6
6
 
7
+ import os
7
8
  from typing import Optional
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  from aws_lambda_powertools import Logger
12
+ from botocore.config import Config
11
13
 
12
14
  from boto3_assist.connection import Connection
13
15
 
@@ -32,7 +34,14 @@ class S3Connection(Connection):
32
34
  aws_end_point_url: Optional[str] = None,
33
35
  aws_access_key_id: Optional[str] = None,
34
36
  aws_secret_access_key: Optional[str] = None,
37
+ signature_version: Optional[str] = None,
35
38
  ) -> None:
39
+ # Build S3-specific config if signature_version is specified
40
+ config: Optional[Config] = None
41
+ signature_version = signature_version or os.getenv("AWS_S3_SIGNATURE_VERSION")
42
+ if signature_version:
43
+ config = Config(signature_version=signature_version)
44
+
36
45
  super().__init__(
37
46
  service_name="s3",
38
47
  aws_profile=aws_profile,
@@ -40,6 +49,7 @@ class S3Connection(Connection):
40
49
  aws_access_key_id=aws_access_key_id,
41
50
  aws_secret_access_key=aws_secret_access_key,
42
51
  aws_end_point_url=aws_end_point_url,
52
+ config=config,
43
53
  )
44
54
 
45
55
  self.__client: S3Client | None = None
@@ -0,0 +1,12 @@
1
+ """
2
+ SQS module for boto3-assist.
3
+
4
+ Geek Cafe, LLC
5
+ Maintainers: Eric Wilson
6
+ MIT License. See Project Root for the license information.
7
+ """
8
+
9
+ from boto3_assist.sqs.sqs_connection import SQSConnection
10
+ from boto3_assist.sqs.sqs_queue import SQSQueue
11
+
12
+ __all__ = ["SQSConnection", "SQSQueue"]
@@ -0,0 +1,88 @@
1
+ """
2
+ SQS Connection module.
3
+
4
+ Geek Cafe, LLC
5
+ Maintainers: Eric Wilson
6
+ MIT License. See Project Root for the license information.
7
+ """
8
+
9
+ from typing import Optional, TYPE_CHECKING
10
+
11
+ from aws_lambda_powertools import Logger
12
+
13
+ from boto3_assist.connection import Connection
14
+
15
+ if TYPE_CHECKING:
16
+ from mypy_boto3_sqs import SQSClient, SQSServiceResource
17
+ else:
18
+ SQSClient = object
19
+ SQSServiceResource = object
20
+
21
+
22
+ logger = Logger(child=True)
23
+
24
+
25
+ class SQSConnection(Connection):
26
+ """SQS Connection wrapper."""
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ aws_profile: Optional[str] = None,
32
+ aws_region: Optional[str] = None,
33
+ aws_end_point_url: Optional[str] = None,
34
+ aws_access_key_id: Optional[str] = None,
35
+ aws_secret_access_key: Optional[str] = None,
36
+ ) -> None:
37
+ """
38
+ Initialize SQS connection.
39
+
40
+ Args:
41
+ aws_profile: AWS profile name
42
+ aws_region: AWS region
43
+ aws_end_point_url: Custom endpoint URL (for LocalStack, etc.)
44
+ aws_access_key_id: AWS access key ID
45
+ aws_secret_access_key: AWS secret access key
46
+ """
47
+ super().__init__(
48
+ service_name="sqs",
49
+ aws_profile=aws_profile,
50
+ aws_region=aws_region,
51
+ aws_access_key_id=aws_access_key_id,
52
+ aws_secret_access_key=aws_secret_access_key,
53
+ aws_end_point_url=aws_end_point_url,
54
+ )
55
+
56
+ self.__client: SQSClient | None = None
57
+ self.__resource: SQSServiceResource | None = None
58
+
59
+ @property
60
+ def client(self) -> SQSClient:
61
+ """Get SQS client."""
62
+ if self.__client is None:
63
+ self.__client = self.session.client
64
+ return self.__client
65
+
66
+ @client.setter
67
+ def client(self, value: SQSClient) -> None:
68
+ """Set SQS client."""
69
+ logger.info("Setting SQS Client")
70
+ self.__client = value
71
+
72
+ @property
73
+ def resource(self) -> SQSServiceResource:
74
+ """Get SQS resource."""
75
+ if self.__resource is None:
76
+ logger.info("Creating SQS Resource")
77
+ self.__resource = self.session.resource
78
+
79
+ if self.raise_on_error and self.__resource is None:
80
+ raise RuntimeError("SQS Resource is not available")
81
+
82
+ return self.__resource
83
+
84
+ @resource.setter
85
+ def resource(self, value: SQSServiceResource) -> None:
86
+ """Set SQS resource."""
87
+ logger.info("Setting SQS Resource")
88
+ self.__resource = value
@@ -0,0 +1,307 @@
1
+ """
2
+ SQS Queue operations module.
3
+
4
+ Geek Cafe, LLC
5
+ Maintainers: Eric Wilson
6
+ MIT License. See Project Root for the license information.
7
+ """
8
+
9
+ import json
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from aws_lambda_powertools import Logger
13
+
14
+ from boto3_assist.sqs.sqs_connection import SQSConnection
15
+
16
+ logger = Logger(child=True)
17
+
18
+
19
+ class SQSQueue(SQSConnection):
20
+ """SQS Queue operations wrapper."""
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ aws_profile: Optional[str] = None,
26
+ aws_region: Optional[str] = None,
27
+ aws_end_point_url: Optional[str] = None,
28
+ aws_access_key_id: Optional[str] = None,
29
+ aws_secret_access_key: Optional[str] = None,
30
+ ) -> None:
31
+ """
32
+ Initialize SQS Queue.
33
+
34
+ Args:
35
+ aws_profile: AWS profile name
36
+ aws_region: AWS region
37
+ aws_end_point_url: Custom endpoint URL (for LocalStack, etc.)
38
+ aws_access_key_id: AWS access key ID
39
+ aws_secret_access_key: AWS secret access key
40
+ """
41
+ super().__init__(
42
+ aws_profile=aws_profile,
43
+ aws_region=aws_region,
44
+ aws_end_point_url=aws_end_point_url,
45
+ aws_access_key_id=aws_access_key_id,
46
+ aws_secret_access_key=aws_secret_access_key,
47
+ )
48
+
49
+ def send_message(
50
+ self,
51
+ queue_url: str,
52
+ message_body: str,
53
+ delay_seconds: int = 0,
54
+ message_attributes: Optional[Dict[str, Any]] = None,
55
+ message_group_id: Optional[str] = None,
56
+ message_deduplication_id: Optional[str] = None,
57
+ ) -> str:
58
+ """
59
+ Send a message to an SQS queue.
60
+
61
+ Args:
62
+ queue_url: The URL of the SQS queue
63
+ message_body: The message body (string)
64
+ delay_seconds: Delay before message becomes visible (0-900 seconds)
65
+ message_attributes: Optional message attributes
66
+ message_group_id: Required for FIFO queues
67
+ message_deduplication_id: Required for FIFO queues without content-based deduplication
68
+
69
+ Returns:
70
+ The message ID of the sent message
71
+ """
72
+ params: Dict[str, Any] = {
73
+ "QueueUrl": queue_url,
74
+ "MessageBody": message_body,
75
+ }
76
+
77
+ if delay_seconds > 0:
78
+ params["DelaySeconds"] = min(delay_seconds, 900) # SQS max is 900 seconds
79
+
80
+ if message_attributes:
81
+ params["MessageAttributes"] = message_attributes
82
+
83
+ if message_group_id:
84
+ params["MessageGroupId"] = message_group_id
85
+
86
+ if message_deduplication_id:
87
+ params["MessageDeduplicationId"] = message_deduplication_id
88
+
89
+ response = self.client.send_message(**params)
90
+ message_id = response.get("MessageId", "")
91
+
92
+ logger.debug(f"Sent message to {queue_url}: {message_id}")
93
+ return message_id
94
+
95
+ def send_message_batch(
96
+ self,
97
+ queue_url: str,
98
+ entries: List[Dict[str, Any]],
99
+ ) -> Dict[str, Any]:
100
+ """
101
+ Send multiple messages to an SQS queue in a batch.
102
+
103
+ Args:
104
+ queue_url: The URL of the SQS queue
105
+ entries: List of message entries, each with 'Id' and 'MessageBody'
106
+
107
+ Returns:
108
+ Response containing 'Successful' and 'Failed' lists
109
+ """
110
+ response = self.client.send_message_batch(
111
+ QueueUrl=queue_url,
112
+ Entries=entries,
113
+ )
114
+
115
+ successful = response.get("Successful", [])
116
+ failed = response.get("Failed", [])
117
+
118
+ if failed:
119
+ logger.warning(f"Failed to send {len(failed)} messages to {queue_url}")
120
+
121
+ logger.debug(f"Sent batch of {len(successful)} messages to {queue_url}")
122
+ return response
123
+
124
+ def receive_messages(
125
+ self,
126
+ queue_url: str,
127
+ max_number_of_messages: int = 1,
128
+ wait_time_seconds: int = 0,
129
+ visibility_timeout: Optional[int] = None,
130
+ message_attribute_names: Optional[List[str]] = None,
131
+ ) -> List[Dict[str, Any]]:
132
+ """
133
+ Receive messages from an SQS queue.
134
+
135
+ Args:
136
+ queue_url: The URL of the SQS queue
137
+ max_number_of_messages: Maximum number of messages to receive (1-10)
138
+ wait_time_seconds: Long polling wait time (0-20 seconds)
139
+ visibility_timeout: Override the queue's default visibility timeout
140
+ message_attribute_names: List of message attribute names to retrieve
141
+
142
+ Returns:
143
+ List of messages
144
+ """
145
+ params: Dict[str, Any] = {
146
+ "QueueUrl": queue_url,
147
+ "MaxNumberOfMessages": min(max_number_of_messages, 10),
148
+ "WaitTimeSeconds": min(wait_time_seconds, 20),
149
+ }
150
+
151
+ if visibility_timeout is not None:
152
+ params["VisibilityTimeout"] = visibility_timeout
153
+
154
+ if message_attribute_names:
155
+ params["MessageAttributeNames"] = message_attribute_names
156
+
157
+ response = self.client.receive_message(**params)
158
+ messages = response.get("Messages", [])
159
+
160
+ logger.debug(f"Received {len(messages)} messages from {queue_url}")
161
+ return messages
162
+
163
+ def delete_message(
164
+ self,
165
+ queue_url: str,
166
+ receipt_handle: str,
167
+ ) -> None:
168
+ """
169
+ Delete a message from an SQS queue.
170
+
171
+ Args:
172
+ queue_url: The URL of the SQS queue
173
+ receipt_handle: The receipt handle of the message to delete
174
+ """
175
+ self.client.delete_message(
176
+ QueueUrl=queue_url,
177
+ ReceiptHandle=receipt_handle,
178
+ )
179
+ logger.debug(f"Deleted message from {queue_url}")
180
+
181
+ def delete_message_batch(
182
+ self,
183
+ queue_url: str,
184
+ entries: List[Dict[str, str]],
185
+ ) -> Dict[str, Any]:
186
+ """
187
+ Delete multiple messages from an SQS queue in a batch.
188
+
189
+ Args:
190
+ queue_url: The URL of the SQS queue
191
+ entries: List of entries with 'Id' and 'ReceiptHandle'
192
+
193
+ Returns:
194
+ Response containing 'Successful' and 'Failed' lists
195
+ """
196
+ response = self.client.delete_message_batch(
197
+ QueueUrl=queue_url,
198
+ Entries=entries,
199
+ )
200
+
201
+ logger.debug(f"Deleted batch of messages from {queue_url}")
202
+ return response
203
+
204
+ def get_queue_url(self, queue_name: str) -> str:
205
+ """
206
+ Get the URL of an SQS queue by name.
207
+
208
+ Args:
209
+ queue_name: The name of the queue
210
+
211
+ Returns:
212
+ The queue URL
213
+ """
214
+ response = self.client.get_queue_url(QueueName=queue_name)
215
+ return response["QueueUrl"]
216
+
217
+ def get_queue_attributes(
218
+ self,
219
+ queue_url: str,
220
+ attribute_names: Optional[List[str]] = None,
221
+ ) -> Dict[str, str]:
222
+ """
223
+ Get attributes of an SQS queue.
224
+
225
+ Args:
226
+ queue_url: The URL of the SQS queue
227
+ attribute_names: List of attribute names to retrieve (default: All)
228
+
229
+ Returns:
230
+ Dictionary of queue attributes
231
+ """
232
+ params: Dict[str, Any] = {"QueueUrl": queue_url}
233
+
234
+ if attribute_names:
235
+ params["AttributeNames"] = attribute_names
236
+ else:
237
+ params["AttributeNames"] = ["All"]
238
+
239
+ response = self.client.get_queue_attributes(**params)
240
+ return response.get("Attributes", {})
241
+
242
+ def purge_queue(self, queue_url: str) -> None:
243
+ """
244
+ Purge all messages from an SQS queue.
245
+
246
+ Args:
247
+ queue_url: The URL of the SQS queue
248
+
249
+ Note: This action can only be performed once every 60 seconds.
250
+ """
251
+ self.client.purge_queue(QueueUrl=queue_url)
252
+ logger.info(f"Purged queue: {queue_url}")
253
+
254
+ def change_message_visibility(
255
+ self,
256
+ queue_url: str,
257
+ receipt_handle: str,
258
+ visibility_timeout: int,
259
+ ) -> None:
260
+ """
261
+ Change the visibility timeout of a message.
262
+
263
+ Args:
264
+ queue_url: The URL of the SQS queue
265
+ receipt_handle: The receipt handle of the message
266
+ visibility_timeout: New visibility timeout in seconds (0-43200)
267
+ """
268
+ self.client.change_message_visibility(
269
+ QueueUrl=queue_url,
270
+ ReceiptHandle=receipt_handle,
271
+ VisibilityTimeout=visibility_timeout,
272
+ )
273
+ logger.debug(f"Changed visibility timeout for message in {queue_url}")
274
+
275
+ def send_json_message(
276
+ self,
277
+ queue_url: str,
278
+ message: Dict[str, Any],
279
+ delay_seconds: int = 0,
280
+ message_attributes: Optional[Dict[str, Any]] = None,
281
+ message_group_id: Optional[str] = None,
282
+ message_deduplication_id: Optional[str] = None,
283
+ ) -> str:
284
+ """
285
+ Send a JSON message to an SQS queue.
286
+
287
+ Convenience method that serializes a dict to JSON.
288
+
289
+ Args:
290
+ queue_url: The URL of the SQS queue
291
+ message: Dictionary to serialize and send
292
+ delay_seconds: Delay before message becomes visible
293
+ message_attributes: Optional message attributes
294
+ message_group_id: Required for FIFO queues
295
+ message_deduplication_id: Required for FIFO queues
296
+
297
+ Returns:
298
+ The message ID of the sent message
299
+ """
300
+ return self.send_message(
301
+ queue_url=queue_url,
302
+ message_body=json.dumps(message),
303
+ delay_seconds=delay_seconds,
304
+ message_attributes=message_attributes,
305
+ message_group_id=message_group_id,
306
+ message_deduplication_id=message_deduplication_id,
307
+ )
boto3_assist/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.33.0"
1
+ __version__ = "0.35.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.33.0
3
+ Version: 0.35.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
@@ -1,12 +1,12 @@
1
1
  boto3_assist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  boto3_assist/aws_config.py,sha256=evmk_blj498ugptJa8lxIDJOIursxY6mT4joLfEbrl0,6558
3
3
  boto3_assist/boto3session.py,sha256=p4FKVSX5A-xNHdpRan8pgMoY4iIywNfwriyTfjQ-zTQ,2967
4
- boto3_assist/connection.py,sha256=tqsNGLouAzCiTdtm9JylDTA6IiYqaQQiHmY-kH2bksU,4905
4
+ boto3_assist/connection.py,sha256=dfVq2nAwElmo0ek0PLBEf9HdULOuzKZG0fu7tDDewhA,5046
5
5
  boto3_assist/connection_tracker.py,sha256=UgfR9RlvXf3A4ssMr3gDMpw89ka8mSRvJn4M34SzhbU,4378
6
6
  boto3_assist/http_status_codes.py,sha256=G0zRSWenwavYKETvDF9tNVUXQz3Ae2gXdBETYbjvJe8,3284
7
7
  boto3_assist/role_assumption_mixin.py,sha256=PMUU5yC2FUBjFD1UokVkRY3CPB5zTw85AhIB5BMtbc8,1031
8
8
  boto3_assist/session_setup_mixin.py,sha256=X-JQKyyaWNA8Z8kKgf2V2I5vsiLAH8udLTX_xepnsdQ,3140
9
- boto3_assist/version.py,sha256=6K931DiWIOR94oP_7w9oZPyA0wEqkdgU1lkcLFyRTMU,23
9
+ boto3_assist/version.py,sha256=w3TvNFSYTcLj3nBuRCGicelGZOzvgYOfnmgrBAuIKqY,23
10
10
  boto3_assist/aws_lambda/event_info.py,sha256=OkZ4WzuGaHEu_T8sB188KBgShAJhZpWASALKRGBOhMg,14648
11
11
  boto3_assist/aws_lambda/mock_context.py,sha256=LPjHP-3YSoY6iPl1kPqJDwSVf1zLNTcukUunDtYcbK0,116
12
12
  boto3_assist/cloudwatch/cloudwatch_connection.py,sha256=mnGWaLSQpHh5EeY7Ek_2o9JKHJxOELIYtQVMX1IaHn4,2480
@@ -44,11 +44,14 @@ boto3_assist/errors/custom_exceptions.py,sha256=QAMW49NbClELVnRd00u4NHfzVtRS3Tc1
44
44
  boto3_assist/models/serializable_model.py,sha256=ZMrRJRvJWLY8PBSKK_nPCgYKv1qUxDPEVdcADKbIHsI,266
45
45
  boto3_assist/s3/s3.py,sha256=ESTPXtyDi8mrwHaYNWjQLNGTuTUV4CxKDqw-O_KGzKs,2052
46
46
  boto3_assist/s3/s3_bucket.py,sha256=GfyBbuI5BWz_ybwU_nDqUZiC0wt24PNt49GKZmb05OY,2018
47
- boto3_assist/s3/s3_connection.py,sha256=0JgEDNoDFPQTo5hQe-lS8mWnFBJ2S8MDSl0LPG__lZo,2008
47
+ boto3_assist/s3/s3_connection.py,sha256=iIWtOgTXaj4FQfkyWLI9jnD340XKkKT2UqPAMvItBM4,2421
48
48
  boto3_assist/s3/s3_event_data.py,sha256=Q7QUI1pwkc7g6yZ3IZWMXBIAfsMlPRC7wac2RvrQoA4,4112
49
49
  boto3_assist/s3/s3_object.py,sha256=77jZeIUFpQX3cFYGGwRFBvL-peCe54iILnthm-GFjMc,22518
50
50
  boto3_assist/securityhub/securityhub.py,sha256=ne-J_v4DaCVZm5YgJa_-LKVomLJQo5Gpw6wleAKSsws,5467
51
51
  boto3_assist/securityhub/securityhub_connection.py,sha256=hWfcj9gjS2lNXUObyw4cShtveoqJPIp8kKFuz-fz1J4,1449
52
+ boto3_assist/sqs/__init__.py,sha256=VRuPPit-hSSbumKL2pN5AUtZKr_c9MpLeuqQ5a6wBLk,285
53
+ boto3_assist/sqs/sqs_connection.py,sha256=Gacka0Aqco3qNSTVmPiJf8LeaLgNzhL1-npCOn85GmQ,2469
54
+ boto3_assist/sqs/sqs_queue.py,sha256=sjz2Q4BbJMCO-cxSNM02UxMmgnRfPLFAC3Pt1kiqc14,9542
52
55
  boto3_assist/ssm/connection.py,sha256=gYpKn5HsUR3hcRUqJzF5QcTITCk0DReq9KhoE_8-Htg,1370
53
56
  boto3_assist/ssm/parameter_store/parameter_store.py,sha256=2ISi-SmR29mESHFH-onJkxPX1aThIgBRojA3ZoNcP9s,3949
54
57
  boto3_assist/utilities/datetime_utility.py,sha256=yQa9winN661Gt837zeQQWl4ARMYtZcU4pQEYMnTESC0,11171
@@ -60,8 +63,8 @@ boto3_assist/utilities/logging_utility.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
60
63
  boto3_assist/utilities/numbers_utility.py,sha256=wzv9d0uXT_2_ZHHio7LBzibwxPqhGpvbq9HinrVn_4A,10160
61
64
  boto3_assist/utilities/serialization_utility.py,sha256=m5wRZNeWW9VltQPVNziR27OGKO3MDJm6mFmcDHwN-n4,24479
62
65
  boto3_assist/utilities/string_utility.py,sha256=XxUIz19L2LFFTRDAAmdPa8Qhn40u9yO7g4nULFuvg0M,11033
63
- boto3_assist-0.33.0.dist-info/METADATA,sha256=a78hRROMB4f7zUlueukU3MsasKQcytMd72QWC4aB_So,2879
64
- boto3_assist-0.33.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
65
- boto3_assist-0.33.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
66
- boto3_assist-0.33.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
67
- boto3_assist-0.33.0.dist-info/RECORD,,
66
+ boto3_assist-0.35.0.dist-info/METADATA,sha256=xa5U1elxKIetkG0QKmbCEt_3ge6uO5ZyhdGNsNUW1o8,2879
67
+ boto3_assist-0.35.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
68
+ boto3_assist-0.35.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
69
+ boto3_assist-0.35.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
70
+ boto3_assist-0.35.0.dist-info/RECORD,,