awsimple 3.2.0__py3-none-any.whl → 3.4.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.
awsimple/__version__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  __application_name__ = "awsimple"
2
2
  __title__ = __application_name__
3
3
  __author__ = "abel"
4
- __version__ = "3.2.0"
4
+ __version__ = "3.4.0"
5
5
  __author_email__ = "j@abel.co"
6
6
  __url__ = "https://github.com/jamesabel/awsimple"
7
7
  __download_url__ = "https://github.com/jamesabel/awsimple"
awsimple/aws.py CHANGED
@@ -30,12 +30,12 @@ def boto_error_to_string(boto_error) -> Union[str, None]:
30
30
  class AWSAccess:
31
31
  @typechecked()
32
32
  def __init__(
33
- self,
34
- resource_name: Union[str, None] = None,
35
- profile_name: Union[str, None] = None,
36
- aws_access_key_id: Union[str, None] = None,
37
- aws_secret_access_key: Union[str, None] = None,
38
- region_name: Union[str, None] = None,
33
+ self,
34
+ resource_name: Union[str, None] = None,
35
+ profile_name: Union[str, None] = None,
36
+ aws_access_key_id: Union[str, None] = None,
37
+ aws_secret_access_key: Union[str, None] = None,
38
+ region_name: Union[str, None] = None,
39
39
  ):
40
40
  """
41
41
  AWSAccess - takes care of basic AWS access (e.g. session, client, resource), getting some basic AWS information, and mock support for testing.
@@ -79,20 +79,9 @@ class AWSAccess:
79
79
  self._aws_keys_save[aws_key] = os.environ.get(aws_key) # will be None if not set
80
80
  os.environ[aws_key] = "testing"
81
81
 
82
- if self.resource_name == "s3":
83
- from moto import mock_s3 as moto_mock
84
- elif self.resource_name == "sns":
85
- from moto import mock_sns as moto_mock
86
- elif self.resource_name == "sqs":
87
- from moto import mock_sqs as moto_mock
88
- elif self.resource_name == "dynamodb":
89
- from moto import mock_dynamodb as moto_mock
90
- elif self.resource_name == "logs":
91
- from moto import mock_logs as moto_mock
92
- else:
93
- from moto import mock_iam as moto_mock
82
+ from moto import mock_aws
94
83
 
95
- self._moto_mock = moto_mock()
84
+ self._moto_mock = mock_aws()
96
85
  self._moto_mock.start()
97
86
  region = "us-east-1"
98
87
  if self.resource_name == "logs" or self.resource_name is None:
@@ -164,16 +153,11 @@ class AWSAccess:
164
153
 
165
154
  def get_account_id(self):
166
155
  """
167
- Get AWS account ID
156
+ Get AWS account ID *** HAS BEEN REMOVED ***
168
157
 
169
158
  :return: account ID
170
159
  """
171
- if is_using_localstack():
172
- arn = self.session.resource("iam", endpoint_url=self._get_localstack_endpoint_url()).CurrentUser().arn
173
- else:
174
- arn = self.session.resource("iam").CurrentUser().arn
175
- log.info("current user {arn=}")
176
- return arn.split(":")[4]
160
+ raise NotImplementedError(".get_account_id() has been removed")
177
161
 
178
162
  def test(self) -> bool:
179
163
  """
awsimple/s3.py CHANGED
@@ -13,10 +13,11 @@ from typing import Dict, List, Union
13
13
  import json
14
14
  from logging import getLogger
15
15
 
16
+ import boto3
17
+ from botocore.client import Config
16
18
  from botocore.exceptions import ClientError, EndpointConnectionError, ConnectionClosedError, SSLError
17
19
  from boto3.s3.transfer import TransferConfig
18
20
  from s3transfer import S3UploadFailedError
19
- import urllib3
20
21
  import urllib3.exceptions
21
22
  from typeguard import typechecked
22
23
  from hashy import get_string_sha512, get_file_sha512, get_bytes_sha512, get_dls_sha512 # type: ignore
@@ -31,6 +32,8 @@ json_extension = ".json"
31
32
 
32
33
  log = getLogger(__application_name__)
33
34
 
35
+ connection_errors = (S3UploadFailedError, ClientError, EndpointConnectionError, SSLError, urllib3.exceptions.ProtocolError, ConnectionClosedError)
36
+
34
37
 
35
38
  class BucketNotFound(AWSimpleException):
36
39
  def __init__(self, bucket_name):
@@ -230,7 +233,7 @@ class S3Access(CacheAccess):
230
233
  try:
231
234
  self.client.upload_file(str(file_path), self.bucket_name, s3_key, ExtraArgs=extra_args, Config=self.get_s3_transfer_config())
232
235
  uploaded_flag = True
233
- except (S3UploadFailedError, ClientError, EndpointConnectionError, SSLError, urllib3.exceptions.ProtocolError) as e:
236
+ except connection_errors as e:
234
237
  log.warning(f"{file_path} to {self.bucket_name}:{s3_key} : {transfer_retry_count=} : {e}")
235
238
  time.sleep(self.retry_sleep_time)
236
239
  except RuntimeError as e:
@@ -282,7 +285,7 @@ class S3Access(CacheAccess):
282
285
  else:
283
286
  s3_object.put(Body=json_as_bytes, Metadata=meta_data)
284
287
  uploaded_flag = True
285
- except (S3UploadFailedError, ClientError, EndpointConnectionError, SSLError, urllib3.exceptions.ProtocolError) as e:
288
+ except connection_errors as e:
286
289
  log.warning(f"{self.bucket_name}:{s3_key} : {transfer_retry_count=} : {e}")
287
290
  transfer_retry_count += 1
288
291
  time.sleep(self.retry_sleep_time)
@@ -298,17 +301,18 @@ class S3Access(CacheAccess):
298
301
  Download an S3 object
299
302
 
300
303
  :param s3_key: S3 key
301
- :param dest_path: destination file path
304
+ :param dest_path: destination file or directory path. If the path is a directory, the file will be downloaded to that directory with the same name as the S3 key.
302
305
  :return: True if downloaded successfully
303
306
  """
304
307
 
305
308
  if isinstance(dest_path, str):
306
309
  log.info(f"{dest_path} is not Path object. Non-Path objects will be deprecated in the future")
307
310
 
308
- if isinstance(dest_path, Path):
309
- dest_path = str(dest_path)
311
+ assert isinstance(dest_path, Path)
312
+ if dest_path.is_dir():
313
+ dest_path = Path(dest_path, s3_key)
310
314
 
311
- log.info(f'S3 download : {self.bucket_name}/{s3_key} to "{dest_path}" ({Path(dest_path).absolute()})')
315
+ log.info(f'S3 download : {self.bucket_name}:{s3_key} to "{dest_path}" ("{Path(dest_path).absolute()}")')
312
316
 
313
317
  Path(dest_path).parent.mkdir(parents=True, exist_ok=True)
314
318
 
@@ -324,7 +328,7 @@ class S3Access(CacheAccess):
324
328
  mtime_ts = s3_object_metadata.mtime.timestamp()
325
329
  os.utime(dest_path, (mtime_ts, mtime_ts)) # set the file mtime to the mtime in S3
326
330
  success = True
327
- except (ClientError, EndpointConnectionError, SSLError, ConnectionClosedError, urllib3.exceptions.ProtocolError) as e:
331
+ except connection_errors as e:
328
332
  # ProtocolError can happen for a broken connection
329
333
  log.warning(f"{self.bucket_name}/{s3_key} to {dest_path} ({Path(dest_path).absolute()}) : {transfer_retry_count=} : {e}")
330
334
  time.sleep(self.retry_sleep_time)
@@ -337,11 +341,15 @@ class S3Access(CacheAccess):
337
341
  """
338
342
  download from AWS S3 with caching
339
343
 
340
- :param dest_path: destination full path
344
+ :param dest_path: destination full path or directory. If the path is a directory, the file will be downloaded to that directory with the same name as the S3 key.
341
345
  :param s3_key: S3 key of source
342
346
  :return: S3DownloadStatus instance
343
347
  """
344
348
 
349
+ if dest_path.is_dir():
350
+ dest_path = Path(dest_path, s3_key)
351
+ log.info(f'S3 download_cached : {self.bucket_name}:{s3_key} to "{dest_path}" ("{dest_path.absolute()}")')
352
+
345
353
  self.download_status = S3DownloadStatus() # init
346
354
 
347
355
  s3_object_metadata = self.get_s3_object_metadata(s3_key)
@@ -382,7 +390,6 @@ class S3Access(CacheAccess):
382
390
  """
383
391
  download object from AWS S3 with caching
384
392
 
385
- :param dest_path: destination full path
386
393
  :param s3_key: S3 key of source
387
394
  :return: S3DownloadStatus instance
388
395
  """
@@ -467,7 +474,6 @@ class S3Access(CacheAccess):
467
474
  """
468
475
  determine if an s3 object exists
469
476
 
470
- :param s3_bucket: the S3 bucket
471
477
  :param s3_key: the S3 object key
472
478
  :return: True if object exists
473
479
  """
@@ -485,8 +491,13 @@ class S3Access(CacheAccess):
485
491
 
486
492
  :return: True if bucket exists
487
493
  """
494
+
495
+ # use a "custom" config so that .head_bucket() doesn't take a really long time if the bucket does not exist
496
+ config = Config(connect_timeout=5, retries={"max_attempts": 3, "mode": "standard"})
497
+ s3 = boto3.client("s3", config=config)
498
+ assert self.bucket_name is not None
488
499
  try:
489
- self.client.head_bucket(Bucket=self.bucket_name)
500
+ s3.head_bucket(Bucket=self.bucket_name)
490
501
  exists = True
491
502
  except ClientError as e:
492
503
  log.info(f"{self.bucket_name=}{e=}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: awsimple
3
- Version: 3.2.0
3
+ Version: 3.4.0
4
4
  Summary: Simple AWS API for S3, DynamoDB, SNS, and SQS
5
5
  Home-page: https://github.com/jamesabel/awsimple
6
6
  Download-URL: https://github.com/jamesabel/awsimple
@@ -14,8 +14,8 @@ Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  License-File: LICENSE.txt
16
16
  Requires-Dist: boto3
17
- Requires-Dist: typeguard <3
18
- Requires-Dist: hashy >=0.1.1
17
+ Requires-Dist: typeguard<3
18
+ Requires-Dist: hashy>=0.1.1
19
19
  Requires-Dist: dictim
20
20
  Requires-Dist: appdirs
21
21
  Requires-Dist: tobool
@@ -135,6 +135,8 @@ to test for file equivalency.
135
135
  S3 objects and DynamoDB tables can be cached locally to reduce network traffic, minimize AWS costs,
136
136
  and potentially offer a speedup.
137
137
 
138
+ DynamoDB cached table scans are particularly useful for tables that are infrequently updated.
139
+
138
140
  ## What`awsimple` Is Not
139
141
 
140
142
  - `awsimple` is not necessarily the most memory and CPU efficient
@@ -143,6 +145,11 @@ and potentially offer a speedup.
143
145
 
144
146
  - `awsimple` does not provide all the options and features that the regular AWS API (e.g. boto3) does
145
147
 
148
+ ## Updates/Releases
149
+
150
+ 3.x.x - Cache life for cached DynamoDB scans is now based on most recent table modification time (kept in a separate
151
+ table). Explict cache life is no longer required (parameter has been removed).
152
+
146
153
  ## Testing using moto mock and localstack
147
154
 
148
155
  moto mock-ing can improve performance and reduce AWS costs. `awsimple` supports both moto mock and localstack.
@@ -1,18 +1,18 @@
1
1
  awsimple/__init__.py,sha256=8aFfqWFAvRPweoZkKncvHAW2ytTW_5-AJ0nnmYqgUBw,916
2
- awsimple/__version__.py,sha256=d_wZxTTKvX8oZU2t00iF1rXmYcATj1co9ManOJHyFGg,323
3
- awsimple/aws.py,sha256=eL7wyLUYvP5IXMfmXMLU4ggCarr_DqhRYxHT-2RNfO0,8381
2
+ awsimple/__version__.py,sha256=QeAaw-gHVlOTWxX7VxuppELAYFtQHcjChg8cH2N11_Q,323
3
+ awsimple/aws.py,sha256=n5Mte2l0uUyLtxHx-Cv2RdVF2H2pvNiQPlrwrwddKcc,7636
4
4
  awsimple/cache.py,sha256=tdLeMw2IYW9Y4lGT2SAGUI7u_aTX_TFQs2udXcqW6fI,7163
5
5
  awsimple/dynamodb.py,sha256=xVPnRdedm19ORpmC1G0fMaQMnf9D72Ebq2lXKSLgtmc,39076
6
6
  awsimple/dynamodb_miv.py,sha256=4xPxQDYkIM-BVDGyAre6uqwJHsxguEbHbof8ztt-V7g,4645
7
7
  awsimple/logs.py,sha256=A2RmTT90pfFTthfENd7GSsEHSIBJXO8ICHPdA7sEsHY,4278
8
8
  awsimple/mock.py,sha256=eScbnxFF9xAosOAsL-NZgp_P-fezB6StQMkb85Y3TNo,574
9
9
  awsimple/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- awsimple/s3.py,sha256=_HoUhpmh3M4SfY_677kGjjo6CGjnY80-PwCzTrHImAw,23256
10
+ awsimple/s3.py,sha256=lwMS8Xr06TB2LkQ7z8yaK6pnH9oOFU3-DEI3Ba6dEwo,23874
11
11
  awsimple/sns.py,sha256=dOx3VUS04xxeG1krGudN4A5fqoIpXeHqXNkBvfbr_6Q,3292
12
12
  awsimple/sqs.py,sha256=ejV9twP15X8-mZ9IHGEUlYWqufEcasYuPf1xlGQt2a8,15506
13
- awsimple-3.2.0.dist-info/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
14
- awsimple-3.2.0.dist-info/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
15
- awsimple-3.2.0.dist-info/METADATA,sha256=mQye_Gs_b08WScaVCM5NCa9B2-c4WKmBSYmSOHRtOpk,5768
16
- awsimple-3.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
17
- awsimple-3.2.0.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
18
- awsimple-3.2.0.dist-info/RECORD,,
13
+ awsimple-3.4.0.dist-info/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
14
+ awsimple-3.4.0.dist-info/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
15
+ awsimple-3.4.0.dist-info/METADATA,sha256=nNYscDeRiwPL6X9UU36J7foOlyC9oiKUawl8VLdimGw,6087
16
+ awsimple-3.4.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
17
+ awsimple-3.4.0.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
18
+ awsimple-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5