awsimple 3.9.1__py3-none-any.whl → 3.11.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.

Potentially problematic release.


This version of awsimple might be problematic. Click here for more details.

awsimple/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from .__version__ import __application_name__, __version__, __author__, __title__
2
2
  from .mock import use_moto_mock_env_var, is_mock, use_localstack_env_var, is_using_localstack
3
+ from .exceptions import DynamoDBItemAlreadyExists
3
4
  from .aws import AWSAccess, AWSimpleException, boto_error_to_string
4
5
  from .cache import get_disk_free, get_directory_size, lru_cache_write, CacheAccess, CACHE_DIR_ENV_VAR
5
6
  from .dynamodb import DynamoDBAccess, dict_to_dynamodb, DBItemNotFound, DynamoDBTableNotFound, dynamodb_to_json, dynamodb_to_dict, QuerySelection, DictKey, convert_serializable_special_cases
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.9.1"
4
+ __version__ = "3.11.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/dynamodb.py CHANGED
@@ -17,6 +17,7 @@ from enum import Enum
17
17
  import decimal
18
18
  from decimal import Decimal
19
19
  from functools import lru_cache
20
+ from collections.abc import Iterable
20
21
  from logging import getLogger
21
22
 
22
23
 
@@ -27,7 +28,8 @@ from typeguard import typechecked
27
28
  from dictim import dictim # type: ignore
28
29
  from yasf import sf
29
30
 
30
- from awsimple import CacheAccess, __application_name__, AWSimpleException
31
+ from . import CacheAccess, __application_name__, AWSimpleException
32
+ from .exceptions import DynamoDBItemAlreadyExists
31
33
 
32
34
  # don't require pillow, but convert images with it if it exists
33
35
  pil_exists = False
@@ -218,6 +220,10 @@ def _is_valid_db_pickled_file(file_path: Path, cache_life: Union[float, int, Non
218
220
 
219
221
 
220
222
  class DynamoDBAccess(CacheAccess):
223
+ """
224
+ AWS DynamoDB access
225
+ """
226
+
221
227
  @typechecked()
222
228
  def __init__(self, table_name: Union[str, None] = None, **kwargs):
223
229
  """
@@ -732,6 +738,36 @@ class DynamoDBAccess(CacheAccess):
732
738
  except self.client.exceptions.ResourceNotFoundException:
733
739
  raise DynamoDBTableNotFound(str(self.table_name))
734
740
 
741
+ def put_item_if_not_exists(self, item: dict):
742
+ """
743
+ Put (write) a DynamoDB table dict item if it doesn't already exist. If the item already exists, it is not modified and DynamoDBItemAlreadyExists is raised.
744
+
745
+ :param item: item
746
+ """
747
+ assert self.resource is not None
748
+ primary_partition_key = self.get_primary_partition_key()
749
+ primary_sort_key = self.get_primary_sort_key()
750
+ try:
751
+ table = self.resource.Table(self.table_name)
752
+ if primary_sort_key is None:
753
+ table.put_item(Item=item, ConditionExpression="attribute_not_exists(#pk)", ExpressionAttributeNames={"#pk": primary_partition_key})
754
+ else:
755
+ table.put_item(
756
+ Item=item,
757
+ ConditionExpression="attribute_not_exists(#pk) AND attribute_not_exists(#sk)",
758
+ ExpressionAttributeNames={"#pk": primary_partition_key, "#sk": primary_sort_key},
759
+ )
760
+ self.metadata_table.update_table_mtime()
761
+ already_exists = False
762
+ except self.client.exceptions.ResourceNotFoundException:
763
+ raise DynamoDBTableNotFound(str(self.table_name))
764
+ except self.client.exceptions.ConditionalCheckFailedException:
765
+ already_exists = True
766
+ if already_exists:
767
+ primary_partition_value = str(item.get(primary_partition_key, ""))
768
+ primary_sort_value = str(item.get(primary_sort_key, ""))
769
+ raise DynamoDBItemAlreadyExists(str(self.table_name), primary_partition_value, primary_sort_value)
770
+
735
771
  # cant' do a @typechecked() since optional item requires a single type
736
772
  def get_item(
737
773
  self, partition_key: Union[str, None] = None, partition_value: Union[str, int, None] = None, sort_key: Union[str, None] = None, sort_value: Union[str, int, None] = None
@@ -858,6 +894,46 @@ class DynamoDBAccess(CacheAccess):
858
894
  self.metadata_table.update_table_mtime()
859
895
  return count
860
896
 
897
+ @typechecked()
898
+ def dump_to_csv(self, file_path: Path):
899
+ """
900
+ Dump the table to a CSV file.
901
+
902
+ :param file_path: output CSV file path
903
+ """
904
+ import csv
905
+
906
+ original_rows = self.scan_table()
907
+ if len(original_rows) == 0:
908
+ log.warning(f"table {self.table_name} is empty, not writing {file_path}")
909
+ return
910
+
911
+ # get all field names with simple data types (no iterables)
912
+ field_names_set = set()
913
+ filtered_rows = []
914
+ for original_row in original_rows:
915
+ filtered_row = {}
916
+ for key, value in original_row.items():
917
+ # only include "simple" data types (i.e. no lists, sets, dicts, etc.)
918
+ if not isinstance(value, Iterable) or isinstance(value, (str, bytes, bytearray)):
919
+ field_names_set.add(key)
920
+ filtered_row[key] = value
921
+ if len(filtered_row) > 0:
922
+ filtered_rows.append(filtered_row)
923
+ field_names = sorted(field_names_set)
924
+ primary_partition_key = self.get_primary_partition_key()
925
+ primary_sort_key = self.get_primary_sort_key()
926
+ filtered_rows.sort(key=lambda x: (x[primary_partition_key], x.get(primary_sort_key))) # sort by primary partition key, then sort key if it exists
927
+
928
+ file_path.parent.mkdir(parents=True, exist_ok=True)
929
+ with open(file_path, "w", newline="") as csvfile:
930
+ writer = csv.DictWriter(csvfile, fieldnames=field_names)
931
+ writer.writeheader()
932
+ for filtered_row in filtered_rows:
933
+ writer.writerow(dynamodb_to_dict(filtered_row))
934
+
935
+ log.info(f"wrote {len(filtered_rows)} rows to {file_path}")
936
+
861
937
 
862
938
  metadata_table_name = f"__{__application_name__}_metadata__"
863
939
 
awsimple/exceptions.py ADDED
@@ -0,0 +1,16 @@
1
+ class AWSimpleExceptionBase(Exception):
2
+ """Base exception for AWSimple errors."""
3
+
4
+ pass
5
+
6
+
7
+ class DynamoDBItemAlreadyExists(AWSimpleExceptionBase):
8
+ """Raised when an item already exists in DynamoDB."""
9
+
10
+ def __init__(self, table_name: str, primary_partition_value: str, primary_sort_value: str):
11
+ if len(primary_sort_value) < 1:
12
+ sort_message = ""
13
+ else:
14
+ sort_message = f" and {primary_sort_value=}"
15
+ message = f"Item with {primary_partition_value=}{sort_message} already exists in table {table_name}"
16
+ super().__init__(message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awsimple
3
- Version: 3.9.1
3
+ Version: 3.11.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
@@ -0,0 +1,21 @@
1
+ awsimple/__init__.py,sha256=X7MV9cg5V1dYf86tf3TonaU-LMQWenPIW2PnjxoXYB4,1015
2
+ awsimple/__version__.py,sha256=ey8QJTcIT2kFHhegrTZ0d01xX-GQD2kvye8fTm8hdp4,324
3
+ awsimple/aws.py,sha256=NbRu1v_J5j2-pcefNZ5Xggii3mM9nHpeHMz9L9K9r-U,7653
4
+ awsimple/cache.py,sha256=_LvPx76215t8KhAJOin6Pe2b4lWpB6kbKpdzgR4FeA4,7206
5
+ awsimple/dynamodb.py,sha256=FZTk_BWOZYr3wYW7jfIgQOYaKQ_SMRMfozAT1UbMXiM,42662
6
+ awsimple/dynamodb_miv.py,sha256=4xPxQDYkIM-BVDGyAre6uqwJHsxguEbHbof8ztt-V7g,4645
7
+ awsimple/exceptions.py,sha256=dZQn8BKmZVlIl2-MIT5SAHRcwYK_bjzzWeG1AFbDUEg,609
8
+ awsimple/logs.py,sha256=s9FhdDFWjfxGCVDx-FNTPgJ-YN1AOAgz4HNxTVfRARE,4108
9
+ awsimple/mock.py,sha256=eScbnxFF9xAosOAsL-NZgp_P-fezB6StQMkb85Y3TNo,574
10
+ awsimple/platform.py,sha256=TObvLIVgRGh-Mh4ZCxFfxAmrn8KNMHktr6XkDZX8JYE,376
11
+ awsimple/pubsub.py,sha256=HzwkjIwFicoV--30ctov8oVlKfXWe7eaLOF1dieV7qk,7692
12
+ awsimple/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ awsimple/s3.py,sha256=ydYLDj51b38lYI6LsJlc4i0PDj2vnWBuSkzdRKcWAUg,23858
14
+ awsimple/sns.py,sha256=T_FyN8eSmBPo213HOfB3UBlMrvtBK768IaRo_ks-7do,3526
15
+ awsimple/sqs.py,sha256=9ZY7161CpmYpcxlCFIfW8bvMn9SGl4cgGR79I4MFLDk,17281
16
+ awsimple-3.11.0.dist-info/licenses/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
17
+ awsimple-3.11.0.dist-info/licenses/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
18
+ awsimple-3.11.0.dist-info/METADATA,sha256=eIOptt5qOoQFZMW6qipEzGLbIuBhA46K3JZNqSabPq0,6641
19
+ awsimple-3.11.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ awsimple-3.11.0.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
21
+ awsimple-3.11.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- awsimple/__init__.py,sha256=yo9COyuJZonzuW_5h2yvsQOtTqnwh8e_iMsMBqQxir0,964
2
- awsimple/__version__.py,sha256=j9qPCE9XWQi2f7AWe1m9-8MYilnfMgESTbkSf6OE2LE,323
3
- awsimple/aws.py,sha256=NbRu1v_J5j2-pcefNZ5Xggii3mM9nHpeHMz9L9K9r-U,7653
4
- awsimple/cache.py,sha256=_LvPx76215t8KhAJOin6Pe2b4lWpB6kbKpdzgR4FeA4,7206
5
- awsimple/dynamodb.py,sha256=8OlGbMg7uU1rpWbkjK9HdHMSfopRBrLb3AFALGgfLHg,39156
6
- awsimple/dynamodb_miv.py,sha256=4xPxQDYkIM-BVDGyAre6uqwJHsxguEbHbof8ztt-V7g,4645
7
- awsimple/logs.py,sha256=s9FhdDFWjfxGCVDx-FNTPgJ-YN1AOAgz4HNxTVfRARE,4108
8
- awsimple/mock.py,sha256=eScbnxFF9xAosOAsL-NZgp_P-fezB6StQMkb85Y3TNo,574
9
- awsimple/platform.py,sha256=TObvLIVgRGh-Mh4ZCxFfxAmrn8KNMHktr6XkDZX8JYE,376
10
- awsimple/pubsub.py,sha256=HzwkjIwFicoV--30ctov8oVlKfXWe7eaLOF1dieV7qk,7692
11
- awsimple/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- awsimple/s3.py,sha256=ydYLDj51b38lYI6LsJlc4i0PDj2vnWBuSkzdRKcWAUg,23858
13
- awsimple/sns.py,sha256=T_FyN8eSmBPo213HOfB3UBlMrvtBK768IaRo_ks-7do,3526
14
- awsimple/sqs.py,sha256=9ZY7161CpmYpcxlCFIfW8bvMn9SGl4cgGR79I4MFLDk,17281
15
- awsimple-3.9.1.dist-info/licenses/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
16
- awsimple-3.9.1.dist-info/licenses/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
17
- awsimple-3.9.1.dist-info/METADATA,sha256=KL-CjrA-YV-N3eY0YypcWlsbEQX3-kH5I6zMbL8swmY,6640
18
- awsimple-3.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- awsimple-3.9.1.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
20
- awsimple-3.9.1.dist-info/RECORD,,