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 +1 -0
- awsimple/__version__.py +1 -1
- awsimple/dynamodb.py +77 -1
- awsimple/exceptions.py +16 -0
- {awsimple-3.9.1.dist-info → awsimple-3.11.0.dist-info}/METADATA +1 -1
- awsimple-3.11.0.dist-info/RECORD +21 -0
- awsimple-3.9.1.dist-info/RECORD +0 -20
- {awsimple-3.9.1.dist-info → awsimple-3.11.0.dist-info}/WHEEL +0 -0
- {awsimple-3.9.1.dist-info → awsimple-3.11.0.dist-info}/licenses/LICENSE +0 -0
- {awsimple-3.9.1.dist-info → awsimple-3.11.0.dist-info}/licenses/LICENSE.txt +0 -0
- {awsimple-3.9.1.dist-info → awsimple-3.11.0.dist-info}/top_level.txt +0 -0
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.
|
|
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
|
|
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)
|
|
@@ -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,,
|
awsimple-3.9.1.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|