awsimple 3.11.0__py3-none-any.whl → 4.1.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 -1
- awsimple/__version__.py +1 -1
- awsimple/dynamodb.py +31 -57
- awsimple/exceptions.py +8 -0
- awsimple/pubsub.py +55 -19
- awsimple/s3.py +13 -12
- {awsimple-3.11.0.dist-info → awsimple-4.1.0.dist-info}/METADATA +2 -2
- awsimple-4.1.0.dist-info/RECORD +21 -0
- awsimple-3.11.0.dist-info/RECORD +0 -21
- {awsimple-3.11.0.dist-info → awsimple-4.1.0.dist-info}/WHEEL +0 -0
- {awsimple-3.11.0.dist-info → awsimple-4.1.0.dist-info}/licenses/LICENSE +0 -0
- {awsimple-3.11.0.dist-info → awsimple-4.1.0.dist-info}/licenses/LICENSE.txt +0 -0
- {awsimple-3.11.0.dist-info → awsimple-4.1.0.dist-info}/top_level.txt +0 -0
awsimple/__init__.py
CHANGED
|
@@ -1,6 +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
|
+
from .exceptions import DynamoDBItemAlreadyExists, S3BucketAlreadyExistsNotOwnedByYou
|
|
4
4
|
from .aws import AWSAccess, AWSimpleException, boto_error_to_string
|
|
5
5
|
from .cache import get_disk_free, get_directory_size, lru_cache_write, CacheAccess, CACHE_DIR_ENV_VAR
|
|
6
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__ = "
|
|
4
|
+
__version__ = "4.1.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
|
@@ -9,7 +9,7 @@ from collections import OrderedDict, defaultdict, namedtuple
|
|
|
9
9
|
import datetime
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from os.path import getsize, getmtime
|
|
12
|
-
from typing import List, Union, Any, Type, Dict, Callable
|
|
12
|
+
from typing import List, Union, Any, Type, Dict, Callable
|
|
13
13
|
from pprint import pformat
|
|
14
14
|
from itertools import islice
|
|
15
15
|
import json
|
|
@@ -21,12 +21,10 @@ from collections.abc import Iterable
|
|
|
21
21
|
from logging import getLogger
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
import boto3
|
|
25
24
|
from botocore.exceptions import EndpointConnectionError, ClientError
|
|
26
25
|
from boto3.dynamodb.conditions import Key
|
|
27
26
|
from typeguard import typechecked
|
|
28
27
|
from dictim import dictim # type: ignore
|
|
29
|
-
from yasf import sf
|
|
30
28
|
|
|
31
29
|
from . import CacheAccess, __application_name__, AWSimpleException
|
|
32
30
|
from .exceptions import DynamoDBItemAlreadyExists
|
|
@@ -48,6 +46,9 @@ handle_inexact_error = True
|
|
|
48
46
|
# for scan to dict
|
|
49
47
|
DictKey = namedtuple("DictKey", ["partition", "sort"]) # only for Primary Key with both partition and sort keys
|
|
50
48
|
|
|
49
|
+
metadata_table_name = f"__{__application_name__}_metadata__"
|
|
50
|
+
dynamodb_name = "dynamodb"
|
|
51
|
+
|
|
51
52
|
log = getLogger(__application_name__)
|
|
52
53
|
|
|
53
54
|
|
|
@@ -237,8 +238,12 @@ class DynamoDBAccess(CacheAccess):
|
|
|
237
238
|
self.secondary_index_postfix = "-index"
|
|
238
239
|
|
|
239
240
|
self.table_name = table_name # can be None (the default) if we're only doing things that don't require a table name such as get_table_names()
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
|
|
242
|
+
# avoid recursion
|
|
243
|
+
if self.table_name is not None and self.table_name != metadata_table_name:
|
|
244
|
+
self.metadata_table = _DynamoDBMetadataTable(dynamodb_name, self.table_name, **kwargs)
|
|
245
|
+
else:
|
|
246
|
+
self.metadata_table = None
|
|
242
247
|
|
|
243
248
|
super().__init__(resource_name="dynamodb", **kwargs)
|
|
244
249
|
|
|
@@ -513,7 +518,8 @@ class DynamoDBAccess(CacheAccess):
|
|
|
513
518
|
created = True
|
|
514
519
|
except ClientError as e:
|
|
515
520
|
log.warning(e)
|
|
516
|
-
self.metadata_table
|
|
521
|
+
if self.metadata_table is not None:
|
|
522
|
+
self.metadata_table.update_table_mtime()
|
|
517
523
|
|
|
518
524
|
return created
|
|
519
525
|
|
|
@@ -734,7 +740,8 @@ class DynamoDBAccess(CacheAccess):
|
|
|
734
740
|
try:
|
|
735
741
|
table = self.resource.Table(self.table_name)
|
|
736
742
|
table.put_item(Item=item)
|
|
737
|
-
self.metadata_table
|
|
743
|
+
if self.metadata_table is not None:
|
|
744
|
+
self.metadata_table.update_table_mtime()
|
|
738
745
|
except self.client.exceptions.ResourceNotFoundException:
|
|
739
746
|
raise DynamoDBTableNotFound(str(self.table_name))
|
|
740
747
|
|
|
@@ -757,7 +764,8 @@ class DynamoDBAccess(CacheAccess):
|
|
|
757
764
|
ConditionExpression="attribute_not_exists(#pk) AND attribute_not_exists(#sk)",
|
|
758
765
|
ExpressionAttributeNames={"#pk": primary_partition_key, "#sk": primary_sort_key},
|
|
759
766
|
)
|
|
760
|
-
self.metadata_table
|
|
767
|
+
if self.metadata_table is not None:
|
|
768
|
+
self.metadata_table.update_table_mtime()
|
|
761
769
|
already_exists = False
|
|
762
770
|
except self.client.exceptions.ResourceNotFoundException:
|
|
763
771
|
raise DynamoDBTableNotFound(str(self.table_name))
|
|
@@ -935,37 +943,21 @@ class DynamoDBAccess(CacheAccess):
|
|
|
935
943
|
log.info(f"wrote {len(filtered_rows)} rows to {file_path}")
|
|
936
944
|
|
|
937
945
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
class _DynamoDBMetadataTable:
|
|
946
|
+
class _DynamoDBMetadataTable(DynamoDBAccess):
|
|
942
947
|
"""
|
|
943
948
|
Access metadata (e.g. most recent modification time) for DynamoDB tables.
|
|
944
949
|
"""
|
|
945
950
|
|
|
946
951
|
@typechecked()
|
|
947
|
-
def __init__(self,
|
|
948
|
-
self.
|
|
949
|
-
self.
|
|
952
|
+
def __init__(self, service: str, service_name: str, **kwargs):
|
|
953
|
+
self.service = service # DynamoDB, SNS, SQS, etc.
|
|
954
|
+
self.service_name = service_name # table name, queue name, topic name, etc.
|
|
955
|
+
self.primary_partition_key = "service" # e.g. "dynamodb", "sqs", "sns", etc.
|
|
950
956
|
self.primary_sort_key = "name" # e.g. table name
|
|
951
957
|
self.mtime_f_string = "mtime_f"
|
|
952
958
|
self.mtime_human_string = "mtime_human"
|
|
953
|
-
|
|
954
|
-
self.
|
|
955
|
-
self.resource = boto3.resource(self.service)
|
|
956
|
-
self.table = self.resource.Table(metadata_table_name)
|
|
957
|
-
|
|
958
|
-
def create_table(self):
|
|
959
|
-
key_schema = [{"AttributeName": self.primary_partition_key, "KeyType": "HASH"}, {"AttributeName": self.primary_sort_key, "KeyType": "RANGE"}] # Partition key # Sort key
|
|
960
|
-
|
|
961
|
-
attribute_definitions = [{"AttributeName": self.primary_partition_key, "AttributeType": "S"}, {"AttributeName": self.primary_sort_key, "AttributeType": "S"}]
|
|
962
|
-
|
|
963
|
-
self.table = self.resource.create_table(
|
|
964
|
-
TableName=metadata_table_name, KeySchema=key_schema, AttributeDefinitions=attribute_definitions, BillingMode="PAY_PER_REQUEST" # on-demand capacity mode
|
|
965
|
-
)
|
|
966
|
-
|
|
967
|
-
# Wait until the table exists
|
|
968
|
-
self.table.meta.client.get_waiter("table_exists").wait(TableName=metadata_table_name)
|
|
959
|
+
super().__init__(metadata_table_name, **kwargs)
|
|
960
|
+
self.create_table(partition_key=self.primary_partition_key, sort_key=self.primary_sort_key, partition_key_type=str, sort_key_type=str)
|
|
969
961
|
|
|
970
962
|
def update_table_mtime(self):
|
|
971
963
|
"""
|
|
@@ -975,27 +967,12 @@ class _DynamoDBMetadataTable:
|
|
|
975
967
|
item = dict_to_dynamodb(
|
|
976
968
|
{
|
|
977
969
|
self.primary_partition_key: self.service,
|
|
978
|
-
self.primary_sort_key: self.
|
|
970
|
+
self.primary_sort_key: self.service_name,
|
|
979
971
|
self.mtime_f_string: m_time,
|
|
980
972
|
self.mtime_human_string: datetime.datetime.fromtimestamp(m_time).astimezone().isoformat(),
|
|
981
973
|
}
|
|
982
974
|
)
|
|
983
|
-
|
|
984
|
-
retry_put = False
|
|
985
|
-
try:
|
|
986
|
-
self.table.put_item(Item=item)
|
|
987
|
-
except ClientError as e:
|
|
988
|
-
error_code = e.response["Error"]["Code"]
|
|
989
|
-
if error_code == "ValidationException":
|
|
990
|
-
self.client.delete_table(TableName=metadata_table_name)
|
|
991
|
-
self.table.meta.client.get_waiter("table_not_exists").wait(TableName=metadata_table_name)
|
|
992
|
-
if error_code == "ValidationException" or error_code == "ResourceNotFoundException":
|
|
993
|
-
self.create_table() # table doesn't exist, so create it
|
|
994
|
-
retry_put = True # and retry the put
|
|
995
|
-
else:
|
|
996
|
-
raise # some other exception, so re-raise it
|
|
997
|
-
if retry_put:
|
|
998
|
-
self.table.put_item(Item=item)
|
|
975
|
+
self.put_item(item=item)
|
|
999
976
|
|
|
1000
977
|
@typechecked()
|
|
1001
978
|
def get_table_mtime_f(self) -> Union[float | None]:
|
|
@@ -1003,13 +980,10 @@ class _DynamoDBMetadataTable:
|
|
|
1003
980
|
Get a table's mtime from the metadata table.
|
|
1004
981
|
:return: table's mtime as a float or None if table hasn't been written to
|
|
1005
982
|
"""
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
mtime_f = float(mtime_f_as_decimal) # boto3 uses Decimal for numbers, but we want a float
|
|
1013
|
-
except DBItemNotFound:
|
|
1014
|
-
pass
|
|
983
|
+
item = self.get_item(self.primary_partition_key, self.service, self.primary_sort_key, self.service_name)
|
|
984
|
+
if (mtime := item.get(self.mtime_f_string)) is None:
|
|
985
|
+
mtime_f = None
|
|
986
|
+
else:
|
|
987
|
+
mtime_f = float(mtime)
|
|
988
|
+
|
|
1015
989
|
return mtime_f
|
awsimple/exceptions.py
CHANGED
|
@@ -14,3 +14,11 @@ class DynamoDBItemAlreadyExists(AWSimpleExceptionBase):
|
|
|
14
14
|
sort_message = f" and {primary_sort_value=}"
|
|
15
15
|
message = f"Item with {primary_partition_value=}{sort_message} already exists in table {table_name}"
|
|
16
16
|
super().__init__(message)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class S3BucketAlreadyExistsNotOwnedByYou(AWSimpleExceptionBase):
|
|
20
|
+
"""Raised when an S3 bucket already exists and is not owned by you."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, bucket_name: str):
|
|
23
|
+
message = f'S3 Bucket "{bucket_name}" already exists and is not owned by you'
|
|
24
|
+
super().__init__(message)
|
awsimple/pubsub.py
CHANGED
|
@@ -3,7 +3,7 @@ pub/sub abstraction on top of AWS SNS and SQS using boto3.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
|
-
from typing import Any, Dict, List, Callable
|
|
6
|
+
from typing import Any, Dict, List, Callable, Union
|
|
7
7
|
from datetime import timedelta
|
|
8
8
|
from multiprocessing import Process, Queue, Event
|
|
9
9
|
from threading import Thread
|
|
@@ -24,9 +24,13 @@ log = Logger(__application_name__)
|
|
|
24
24
|
|
|
25
25
|
queue_timeout = timedelta(days=30).total_seconds()
|
|
26
26
|
|
|
27
|
+
sqs_name = "sqs"
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
@typechecked()
|
|
29
|
-
def remove_old_queues(
|
|
31
|
+
def remove_old_queues(
|
|
32
|
+
channel: str, profile_name: Union[str, None] = None, aws_access_key_id: Union[str, None] = None, aws_secret_access_key: Union[str, None] = None, region_name: Union[str, None] = None
|
|
33
|
+
) -> list[str]:
|
|
30
34
|
"""
|
|
31
35
|
Remove old SQS queues that have not been used recently.
|
|
32
36
|
"""
|
|
@@ -35,10 +39,12 @@ def remove_old_queues(channel: str) -> list[str]:
|
|
|
35
39
|
log.warning(f"blank channel ({channel=}) - not deleting any queues")
|
|
36
40
|
return removed
|
|
37
41
|
for sqs_queue_name in get_all_sqs_queues(channel):
|
|
38
|
-
sqs_metadata = _DynamoDBMetadataTable(
|
|
42
|
+
sqs_metadata = _DynamoDBMetadataTable(
|
|
43
|
+
sqs_name, sqs_queue_name, profile_name=profile_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, region_name=region_name
|
|
44
|
+
)
|
|
39
45
|
mtime = sqs_metadata.get_table_mtime_f()
|
|
40
46
|
if mtime is not None and time.time() - mtime > queue_timeout:
|
|
41
|
-
sqs = SQSPollAccess(sqs_queue_name)
|
|
47
|
+
sqs = SQSPollAccess(sqs_queue_name, profile_name=profile_name, aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, region_name=region_name)
|
|
42
48
|
try:
|
|
43
49
|
sqs.delete_queue()
|
|
44
50
|
log.info(f'deleted "{sqs_queue_name}",{mtime=}')
|
|
@@ -49,20 +55,18 @@ def remove_old_queues(channel: str) -> list[str]:
|
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
@typechecked()
|
|
52
|
-
def _connect_sns_to_sqs(
|
|
58
|
+
def _connect_sns_to_sqs(sqs: SQSPollAccess, sns: SNSAccess) -> None:
|
|
53
59
|
"""
|
|
54
60
|
Connect an SQS queue to an SNS topic.
|
|
55
61
|
|
|
56
|
-
:param
|
|
57
|
-
:param
|
|
58
|
-
:param sqs: SQSPollAccess instance for the SQS queue.
|
|
62
|
+
:param sqs: SQS access object
|
|
63
|
+
:param sns: SNS access object
|
|
59
64
|
:return: None
|
|
60
65
|
"""
|
|
61
66
|
|
|
62
67
|
sqs_arn = sqs.get_arn()
|
|
63
68
|
|
|
64
69
|
# Find the topic by name
|
|
65
|
-
sns = SNSAccess(channel_name)
|
|
66
70
|
sns.create_topic()
|
|
67
71
|
topic_arn = sns.get_arn()
|
|
68
72
|
assert sns.resource is not None
|
|
@@ -71,7 +75,7 @@ def _connect_sns_to_sqs(channel_name: str, sqs_queue_name: str, sqs: SQSPollAcce
|
|
|
71
75
|
# Subscribe queue to topic
|
|
72
76
|
queue_arn = sqs.get_arn()
|
|
73
77
|
subscription = topic.subscribe(Protocol="sqs", Endpoint=queue_arn)
|
|
74
|
-
log.info(f"Subscribed {
|
|
78
|
+
log.info(f"Subscribed {sqs.queue_name} to topic {topic_arn}. Subscription ARN: {subscription.arn}")
|
|
75
79
|
|
|
76
80
|
# Update queue policy to allow SNS -> SQS
|
|
77
81
|
policy = {
|
|
@@ -90,7 +94,7 @@ def _connect_sns_to_sqs(channel_name: str, sqs_queue_name: str, sqs: SQSPollAcce
|
|
|
90
94
|
}
|
|
91
95
|
assert sqs.queue is not None
|
|
92
96
|
sqs.queue.set_attributes(Attributes={"Policy": json.dumps(policy)})
|
|
93
|
-
log.debug(f"Queue {
|
|
97
|
+
log.debug(f"Queue {sqs.queue_name} policy updated to allow topic {topic_arn}.")
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
class _SubscriptionThread(Thread):
|
|
@@ -118,7 +122,16 @@ class _SubscriptionThread(Thread):
|
|
|
118
122
|
class PubSub(Process):
|
|
119
123
|
|
|
120
124
|
@typechecked()
|
|
121
|
-
def __init__(
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
channel: str,
|
|
128
|
+
node_name: str = get_node_name(),
|
|
129
|
+
sub_callback: Callable | None = None,
|
|
130
|
+
profile_name: Union[str, None] = None,
|
|
131
|
+
aws_access_key_id: Union[str, None] = None,
|
|
132
|
+
aws_secret_access_key: Union[str, None] = None,
|
|
133
|
+
region_name: Union[str, None] = None,
|
|
134
|
+
) -> None:
|
|
122
135
|
"""
|
|
123
136
|
Pub and Sub.
|
|
124
137
|
Create in a separate process to offload from main thread. Also facilitates use of moto mock in tests.
|
|
@@ -127,10 +140,15 @@ class PubSub(Process):
|
|
|
127
140
|
:param node_name: Node name (SQS queue name suffix). Defaults to a combination of computer name and username, but can be passed in for customization and/or testing.
|
|
128
141
|
:param sub_callback: Optional thread and process safe callback function to be called when a new message is received. The function should accept a single argument, which will be the message as a dictionary.
|
|
129
142
|
"""
|
|
130
|
-
self.channel = channel
|
|
131
|
-
self.node_name = node_name # e.g., computer name
|
|
143
|
+
self.channel = channel.lower() # when subscribing SQS queues to SNS topics, the names must all be lowercase (bizarre AWS "gotcha")
|
|
144
|
+
self.node_name = node_name.lower() # e.g., computer name or user and computer name
|
|
132
145
|
self.sub_callback = sub_callback
|
|
133
146
|
|
|
147
|
+
self.profile_name = profile_name
|
|
148
|
+
self.aws_access_key_id = aws_access_key_id
|
|
149
|
+
self.aws_secret_access_key = aws_secret_access_key
|
|
150
|
+
self.region_name = region_name
|
|
151
|
+
|
|
134
152
|
self._pub_queue = Queue() # type: Queue[Dict[str, Any]]
|
|
135
153
|
self._sub_queue = Queue() # type: Queue[str]
|
|
136
154
|
|
|
@@ -143,13 +161,31 @@ class PubSub(Process):
|
|
|
143
161
|
sqs_prefix = f"{self.channel}-"
|
|
144
162
|
sqs_queue_name = f"{sqs_prefix}{self.node_name}"
|
|
145
163
|
|
|
146
|
-
sns = SNSAccess(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
sns = SNSAccess(
|
|
165
|
+
self.channel,
|
|
166
|
+
auto_create=True,
|
|
167
|
+
profile_name=self.profile_name,
|
|
168
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
169
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
170
|
+
region_name=self.region_name,
|
|
171
|
+
)
|
|
172
|
+
sqs_metadata = _DynamoDBMetadataTable(
|
|
173
|
+
sqs_name, sqs_queue_name, profile_name=self.profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.region_name
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
sqs = SQSPollAccess(
|
|
177
|
+
sqs_queue_name, profile_name=self.profile_name, aws_access_key_id=self.aws_access_key_id, aws_secret_access_key=self.aws_secret_access_key, region_name=self.region_name
|
|
178
|
+
)
|
|
150
179
|
if not sqs.exists():
|
|
151
180
|
sqs.create_queue()
|
|
152
|
-
|
|
181
|
+
sns = SNSAccess(
|
|
182
|
+
topic_name=self.channel,
|
|
183
|
+
profile_name=self.profile_name,
|
|
184
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
185
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
186
|
+
region_name=self.region_name,
|
|
187
|
+
)
|
|
188
|
+
_connect_sns_to_sqs(sqs, sns)
|
|
153
189
|
|
|
154
190
|
sqs_metadata.update_table_mtime() # update SQS use time (the existing infrastructure calls it a "table", but we're using it for the SQS queue)
|
|
155
191
|
remove_old_queues(sqs_prefix) # clean up old queues
|
awsimple/s3.py
CHANGED
|
@@ -23,7 +23,7 @@ from typeguard import typechecked
|
|
|
23
23
|
from hashy import get_string_sha512, get_file_sha512, get_bytes_sha512, get_dls_sha512
|
|
24
24
|
from yasf import sf
|
|
25
25
|
|
|
26
|
-
from awsimple import CacheAccess, __application_name__, lru_cache_write, AWSimpleException, convert_serializable_special_cases
|
|
26
|
+
from awsimple import CacheAccess, __application_name__, lru_cache_write, AWSimpleException, convert_serializable_special_cases, S3BucketAlreadyExistsNotOwnedByYou
|
|
27
27
|
|
|
28
28
|
# Use this project's name as a prefix to avoid string collisions. Use dashes instead of underscore since that's AWS's convention.
|
|
29
29
|
sha512_string = f"{__application_name__}-sha512"
|
|
@@ -518,17 +518,18 @@ class S3Access(CacheAccess):
|
|
|
518
518
|
else:
|
|
519
519
|
location = {"LocationConstraint": region}
|
|
520
520
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
521
|
+
try:
|
|
522
|
+
if self.public_readable:
|
|
523
|
+
self.client.create_bucket(Bucket=self.bucket_name, CreateBucketConfiguration=location, ACL="public-read")
|
|
524
|
+
else:
|
|
525
|
+
self.client.create_bucket(Bucket=self.bucket_name, CreateBucketConfiguration=location)
|
|
526
|
+
self.client.get_waiter("bucket_exists").wait(Bucket=self.bucket_name)
|
|
527
|
+
created = True
|
|
528
|
+
except self.client.exceptions.BucketAlreadyOwnedByYou:
|
|
529
|
+
created = False # already exists and owned by you
|
|
530
|
+
except self.client.exceptions.BucketAlreadyExists as e:
|
|
531
|
+
# bucket already exists and is owned by someone else
|
|
532
|
+
raise S3BucketAlreadyExistsNotOwnedByYou(str(self.bucket_name)) from e
|
|
532
533
|
return created
|
|
533
534
|
|
|
534
535
|
@typechecked()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awsimple
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.1.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,7 +14,7 @@ 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
|
|
17
|
+
Requires-Dist: typeguard
|
|
18
18
|
Requires-Dist: hashy>=0.1.1
|
|
19
19
|
Requires-Dist: dictim
|
|
20
20
|
Requires-Dist: appdirs
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
awsimple/__init__.py,sha256=RT9hATlcKnnvJYnSh7tCWEeeT2LyleL_vT1Zdz0nSkM,1051
|
|
2
|
+
awsimple/__version__.py,sha256=_8-4yMvCFU2QJpF5V0ofTEvVAuRaDuwWi01mBIjSNkU,323
|
|
3
|
+
awsimple/aws.py,sha256=NbRu1v_J5j2-pcefNZ5Xggii3mM9nHpeHMz9L9K9r-U,7653
|
|
4
|
+
awsimple/cache.py,sha256=_LvPx76215t8KhAJOin6Pe2b4lWpB6kbKpdzgR4FeA4,7206
|
|
5
|
+
awsimple/dynamodb.py,sha256=7MNxAutOCMTS4JSX-DLOwzaImJ2TzIc7kfQzQPAy5y8,41193
|
|
6
|
+
awsimple/dynamodb_miv.py,sha256=4xPxQDYkIM-BVDGyAre6uqwJHsxguEbHbof8ztt-V7g,4645
|
|
7
|
+
awsimple/exceptions.py,sha256=Ew-S8YkHVWrZFI_Yik5n0cJ7Ss4Kig5JsEPQ-9z18SU,922
|
|
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=exWZWWI9NrE36fESZprhxU5Qj9BSgK5diYKQZHBOqBs,9546
|
|
12
|
+
awsimple/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
awsimple/s3.py,sha256=OhWF1uv4oLmBF5jAhKIi918iUQxx4CX8bTEvQ5wLYr8,24050
|
|
14
|
+
awsimple/sns.py,sha256=T_FyN8eSmBPo213HOfB3UBlMrvtBK768IaRo_ks-7do,3526
|
|
15
|
+
awsimple/sqs.py,sha256=9ZY7161CpmYpcxlCFIfW8bvMn9SGl4cgGR79I4MFLDk,17281
|
|
16
|
+
awsimple-4.1.0.dist-info/licenses/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
|
|
17
|
+
awsimple-4.1.0.dist-info/licenses/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
|
|
18
|
+
awsimple-4.1.0.dist-info/METADATA,sha256=AYF-IUhIYqU2zGs9K_L6J3XCq4RVzOaf2RP5lu1YRek,6638
|
|
19
|
+
awsimple-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
+
awsimple-4.1.0.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
|
|
21
|
+
awsimple-4.1.0.dist-info/RECORD,,
|
awsimple-3.11.0.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|