awsimple 3.6.1__py3-none-any.whl → 3.9.1__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 +2 -1
- awsimple/__version__.py +1 -1
- awsimple/logs.py +2 -14
- awsimple/platform.py +20 -0
- awsimple/pubsub.py +213 -0
- awsimple/sns.py +13 -7
- awsimple/sqs.py +66 -19
- {awsimple-3.6.1.dist-info → awsimple-3.9.1.dist-info}/METADATA +13 -11
- awsimple-3.9.1.dist-info/RECORD +20 -0
- {awsimple-3.6.1.dist-info → awsimple-3.9.1.dist-info}/WHEEL +1 -1
- awsimple-3.6.1.dist-info/RECORD +0 -18
- {awsimple-3.6.1.dist-info → awsimple-3.9.1.dist-info}/licenses/LICENSE +0 -0
- {awsimple-3.6.1.dist-info → awsimple-3.9.1.dist-info}/licenses/LICENSE.txt +0 -0
- {awsimple-3.6.1.dist-info → awsimple-3.9.1.dist-info}/top_level.txt +0 -0
awsimple/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from .dynamodb import DynamoDBAccess, dict_to_dynamodb, DBItemNotFound, DynamoDB
|
|
|
6
6
|
from .dynamodb import KeyType, aws_name_to_key_type
|
|
7
7
|
from .dynamodb_miv import DynamoDBMIVUI, miv_string, get_time_us, miv_us_to_timestamp
|
|
8
8
|
from .s3 import S3Access, S3DownloadStatus, S3ObjectMetadata, BucketNotFound
|
|
9
|
-
from .sqs import SQSAccess, SQSPollAccess, aws_sqs_long_poll_max_wait_time, aws_sqs_max_messages
|
|
9
|
+
from .sqs import SQSAccess, SQSPollAccess, aws_sqs_long_poll_max_wait_time, aws_sqs_max_messages, get_all_sqs_queues
|
|
10
10
|
from .sns import SNSAccess
|
|
11
|
+
from .pubsub import PubSub
|
|
11
12
|
from .logs import LogsAccess
|
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.9.1"
|
|
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/logs.py
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
import time
|
|
2
|
-
import getpass
|
|
3
|
-
import platform
|
|
4
|
-
from functools import lru_cache
|
|
5
2
|
from typing import Union
|
|
6
3
|
from pathlib import Path
|
|
7
4
|
from datetime import datetime
|
|
8
5
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@lru_cache()
|
|
13
|
-
def get_user_name() -> str:
|
|
14
|
-
return getpass.getuser()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@lru_cache()
|
|
18
|
-
def get_computer_name() -> str:
|
|
19
|
-
return platform.node()
|
|
6
|
+
from .aws import AWSAccess
|
|
7
|
+
from .platform import get_user_name, get_computer_name
|
|
20
8
|
|
|
21
9
|
|
|
22
10
|
class LogsAccess(AWSAccess):
|
awsimple/platform.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from functools import cache
|
|
2
|
+
|
|
3
|
+
import getpass
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@cache
|
|
8
|
+
def get_user_name() -> str:
|
|
9
|
+
return getpass.getuser()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@cache
|
|
13
|
+
def get_computer_name() -> str:
|
|
14
|
+
return platform.node()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@cache
|
|
18
|
+
def get_node_name() -> str:
|
|
19
|
+
node_name = f"{get_computer_name()}-{get_user_name()}" # AWS SNS and SQS names can include hyphens
|
|
20
|
+
return node_name
|
awsimple/pubsub.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pub/sub abstraction on top of AWS SNS and SQS using boto3.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict, List, Callable
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from multiprocessing import Process, Queue, Event
|
|
9
|
+
from threading import Thread
|
|
10
|
+
from queue import Empty
|
|
11
|
+
from logging import Logger
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from typeguard import typechecked
|
|
15
|
+
from botocore.exceptions import ClientError
|
|
16
|
+
|
|
17
|
+
from .sns import SNSAccess
|
|
18
|
+
from .sqs import SQSPollAccess, get_all_sqs_queues
|
|
19
|
+
from .dynamodb import _DynamoDBMetadataTable
|
|
20
|
+
from .platform import get_node_name
|
|
21
|
+
from .__version__ import __application_name__
|
|
22
|
+
|
|
23
|
+
log = Logger(__application_name__)
|
|
24
|
+
|
|
25
|
+
queue_timeout = timedelta(days=30).total_seconds()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@typechecked()
|
|
29
|
+
def remove_old_queues(channel: str) -> list[str]:
|
|
30
|
+
"""
|
|
31
|
+
Remove old SQS queues that have not been used recently.
|
|
32
|
+
"""
|
|
33
|
+
removed = [] # type: list[str]
|
|
34
|
+
if len(channel) < 2: # avoid deleting all queues
|
|
35
|
+
log.warning(f"blank channel ({channel=}) - not deleting any queues")
|
|
36
|
+
return removed
|
|
37
|
+
for sqs_queue_name in get_all_sqs_queues(channel):
|
|
38
|
+
sqs_metadata = _DynamoDBMetadataTable(sqs_queue_name)
|
|
39
|
+
mtime = sqs_metadata.get_table_mtime_f()
|
|
40
|
+
if mtime is not None and time.time() - mtime > queue_timeout:
|
|
41
|
+
sqs = SQSPollAccess(sqs_queue_name)
|
|
42
|
+
try:
|
|
43
|
+
sqs.delete_queue()
|
|
44
|
+
log.info(f'deleted "{sqs_queue_name}",{mtime=}')
|
|
45
|
+
except ClientError:
|
|
46
|
+
log.info(f'"{sqs_queue_name}" already does not exist,{mtime=}') # already doesn't exist - this is benign
|
|
47
|
+
removed.append(sqs_queue_name)
|
|
48
|
+
return removed
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@typechecked()
|
|
52
|
+
def _connect_sns_to_sqs(channel_name: str, sqs_queue_name: str, sqs: SQSPollAccess) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Connect an SQS queue to an SNS topic.
|
|
55
|
+
|
|
56
|
+
:param channel_name: SNS topic name.
|
|
57
|
+
:param sqs_queue_name: SQS queue name.
|
|
58
|
+
:param sqs: SQSPollAccess instance for the SQS queue.
|
|
59
|
+
:return: None
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
sqs_arn = sqs.get_arn()
|
|
63
|
+
|
|
64
|
+
# Find the topic by name
|
|
65
|
+
sns = SNSAccess(channel_name)
|
|
66
|
+
sns.create_topic()
|
|
67
|
+
topic_arn = sns.get_arn()
|
|
68
|
+
assert sns.resource is not None
|
|
69
|
+
topic = sns.resource.Topic(topic_arn)
|
|
70
|
+
|
|
71
|
+
# Subscribe queue to topic
|
|
72
|
+
queue_arn = sqs.get_arn()
|
|
73
|
+
subscription = topic.subscribe(Protocol="sqs", Endpoint=queue_arn)
|
|
74
|
+
log.info(f"Subscribed {sqs_queue_name} to topic {topic_arn}. Subscription ARN: {subscription.arn}")
|
|
75
|
+
|
|
76
|
+
# Update queue policy to allow SNS -> SQS
|
|
77
|
+
policy = {
|
|
78
|
+
"Version": "2012-10-17",
|
|
79
|
+
"Id": "sns-sqs-subscription-policy",
|
|
80
|
+
"Statement": [
|
|
81
|
+
{
|
|
82
|
+
"Sid": "Allow-SNS-SendMessage",
|
|
83
|
+
"Effect": "Allow",
|
|
84
|
+
"Principal": {"Service": "sns.amazonaws.com"},
|
|
85
|
+
"Action": "sqs:SendMessage",
|
|
86
|
+
"Resource": sqs_arn,
|
|
87
|
+
"Condition": {"ArnEquals": {"aws:SourceArn": topic_arn}},
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
assert sqs.queue is not None
|
|
92
|
+
sqs.queue.set_attributes(Attributes={"Policy": json.dumps(policy)})
|
|
93
|
+
log.debug(f"Queue {sqs_queue_name} policy updated to allow topic {topic_arn}.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class _SubscriptionThread(Thread):
|
|
97
|
+
"""
|
|
98
|
+
Thread to poll SQS for new messages and put them in a queue for the parent process to read.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
@typechecked()
|
|
102
|
+
def __init__(self, sqs: SQSPollAccess, new_event) -> None:
|
|
103
|
+
super().__init__()
|
|
104
|
+
self._sqs = sqs
|
|
105
|
+
self.new_event = new_event
|
|
106
|
+
self.sub_queue = Queue() # type: Queue[str]
|
|
107
|
+
|
|
108
|
+
def run(self):
|
|
109
|
+
# exit by terminating the parent process
|
|
110
|
+
while True:
|
|
111
|
+
messages = self._sqs.receive_messages() # long poll
|
|
112
|
+
for message in messages:
|
|
113
|
+
message = json.loads(message.message)
|
|
114
|
+
self.sub_queue.put(message["Message"])
|
|
115
|
+
self.new_event.set() # notify parent process that a new message is available
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class PubSub(Process):
|
|
119
|
+
|
|
120
|
+
@typechecked()
|
|
121
|
+
def __init__(self, channel: str, node_name: str = get_node_name(), sub_callback: Callable | None = None) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Pub and Sub.
|
|
124
|
+
Create in a separate process to offload from main thread. Also facilitates use of moto mock in tests.
|
|
125
|
+
|
|
126
|
+
:param channel: Channel name (SNS topic name).
|
|
127
|
+
: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
|
+
: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
|
+
"""
|
|
130
|
+
self.channel = channel
|
|
131
|
+
self.node_name = node_name # e.g., computer name
|
|
132
|
+
self.sub_callback = sub_callback
|
|
133
|
+
|
|
134
|
+
self._pub_queue = Queue() # type: Queue[Dict[str, Any]]
|
|
135
|
+
self._sub_queue = Queue() # type: Queue[str]
|
|
136
|
+
|
|
137
|
+
self._new_event = Event() # pub or sub sets this when a new message is available or has been sent
|
|
138
|
+
|
|
139
|
+
super().__init__()
|
|
140
|
+
|
|
141
|
+
def run(self):
|
|
142
|
+
|
|
143
|
+
sqs_prefix = f"{self.channel}-"
|
|
144
|
+
sqs_queue_name = f"{sqs_prefix}{self.node_name}"
|
|
145
|
+
|
|
146
|
+
sns = SNSAccess(self.channel, auto_create=True)
|
|
147
|
+
sqs_metadata = _DynamoDBMetadataTable(sqs_queue_name)
|
|
148
|
+
|
|
149
|
+
sqs = SQSPollAccess(sqs_queue_name)
|
|
150
|
+
if not sqs.exists():
|
|
151
|
+
sqs.create_queue()
|
|
152
|
+
_connect_sns_to_sqs(self.channel, sqs_queue_name, sqs)
|
|
153
|
+
|
|
154
|
+
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
|
+
remove_old_queues(sqs_prefix) # clean up old queues
|
|
156
|
+
|
|
157
|
+
sqs_thread = _SubscriptionThread(sqs, self._new_event)
|
|
158
|
+
sqs_thread.start()
|
|
159
|
+
|
|
160
|
+
while True:
|
|
161
|
+
|
|
162
|
+
# pub
|
|
163
|
+
try:
|
|
164
|
+
message = self._pub_queue.get(False)
|
|
165
|
+
message_string = json.dumps(message)
|
|
166
|
+
sns.publish(message_string)
|
|
167
|
+
except Empty:
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
# sub
|
|
171
|
+
try:
|
|
172
|
+
message_string = sqs_thread.sub_queue.get(False)
|
|
173
|
+
# if a callback is provided, call it, otherwise put the message in the sub queue for later retrieval
|
|
174
|
+
if self.sub_callback is None:
|
|
175
|
+
self._sub_queue.put(message_string)
|
|
176
|
+
else:
|
|
177
|
+
message = json.loads(message_string)
|
|
178
|
+
self.sub_callback(message)
|
|
179
|
+
sqs_metadata.update_table_mtime()
|
|
180
|
+
except Empty:
|
|
181
|
+
pass # no message
|
|
182
|
+
|
|
183
|
+
# this helps responsiveness
|
|
184
|
+
self._new_event.clear()
|
|
185
|
+
self._new_event.wait(3) # race conditions can occur, so don't make this wait timeout too long
|
|
186
|
+
|
|
187
|
+
@typechecked()
|
|
188
|
+
def publish(self, message: dict) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Publish a message.
|
|
191
|
+
|
|
192
|
+
:param message: message as a dictionary
|
|
193
|
+
"""
|
|
194
|
+
self._pub_queue.put(message)
|
|
195
|
+
self._new_event.set()
|
|
196
|
+
|
|
197
|
+
@typechecked()
|
|
198
|
+
def get_messages(self) -> List[Dict[str, Any]]:
|
|
199
|
+
"""
|
|
200
|
+
Get all available messages.
|
|
201
|
+
|
|
202
|
+
:return: list of messages as dictionaries
|
|
203
|
+
"""
|
|
204
|
+
messages = []
|
|
205
|
+
while True:
|
|
206
|
+
try:
|
|
207
|
+
message_string = self._sub_queue.get(block=False)
|
|
208
|
+
message = json.loads(message_string)
|
|
209
|
+
log.debug(f"{message=}")
|
|
210
|
+
messages.append(message)
|
|
211
|
+
except Empty:
|
|
212
|
+
break
|
|
213
|
+
return messages
|
awsimple/sns.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
SNS Access
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Union, Dict
|
|
5
|
+
from typing import Union, Dict, Any
|
|
6
|
+
from functools import cache
|
|
6
7
|
|
|
7
8
|
from typeguard import typechecked
|
|
8
9
|
|
|
@@ -11,7 +12,7 @@ from awsimple import AWSAccess, SQSAccess
|
|
|
11
12
|
|
|
12
13
|
class SNSAccess(AWSAccess):
|
|
13
14
|
@typechecked()
|
|
14
|
-
def __init__(self, topic_name: str, **kwargs):
|
|
15
|
+
def __init__(self, topic_name: str, auto_create: bool = False, **kwargs):
|
|
15
16
|
"""
|
|
16
17
|
SNS Access
|
|
17
18
|
|
|
@@ -20,26 +21,31 @@ class SNSAccess(AWSAccess):
|
|
|
20
21
|
"""
|
|
21
22
|
super().__init__(resource_name="sns", **kwargs)
|
|
22
23
|
self.topic_name = topic_name
|
|
24
|
+
self.auto_create = auto_create
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
@cache
|
|
27
|
+
def get_topic(self) -> Any:
|
|
25
28
|
"""
|
|
26
29
|
gets the associated SNS Topic instance
|
|
27
30
|
|
|
28
|
-
:param topic_name: topic name
|
|
29
31
|
:return: sns.Topic instance
|
|
30
32
|
"""
|
|
31
33
|
topic = None
|
|
34
|
+
assert self.resource is not None
|
|
32
35
|
for t in self.resource.topics.all():
|
|
33
36
|
if t.arn.split(":")[-1] == self.topic_name:
|
|
34
37
|
topic = t
|
|
38
|
+
if self.auto_create and topic is None:
|
|
39
|
+
self.create_topic()
|
|
40
|
+
self.auto_create = False # only do this once
|
|
41
|
+
topic = self.get_topic()
|
|
35
42
|
return topic
|
|
36
43
|
|
|
37
|
-
@
|
|
44
|
+
@cache
|
|
38
45
|
def get_arn(self) -> str:
|
|
39
46
|
"""
|
|
40
47
|
get topic ARN from topic name
|
|
41
48
|
|
|
42
|
-
:param topic_name: topic name string
|
|
43
49
|
:return: topic ARN
|
|
44
50
|
"""
|
|
45
51
|
return self.get_topic().arn
|
|
@@ -68,7 +74,7 @@ class SNSAccess(AWSAccess):
|
|
|
68
74
|
"""
|
|
69
75
|
Subscribe to an SNS topic
|
|
70
76
|
|
|
71
|
-
:param subscriber: email
|
|
77
|
+
:param subscriber: email
|
|
72
78
|
:return: subscription ARN
|
|
73
79
|
"""
|
|
74
80
|
if isinstance(subscriber, str) and "@" in subscriber:
|
awsimple/sqs.py
CHANGED
|
@@ -49,7 +49,7 @@ aws_sqs_max_messages = 10
|
|
|
49
49
|
|
|
50
50
|
class SQSAccess(AWSAccess):
|
|
51
51
|
@typechecked()
|
|
52
|
-
def __init__(self, queue_name: str, immediate_delete: bool = True, visibility_timeout: Union[int, None] = None, minimum_visibility_timeout: int = 0, **kwargs):
|
|
52
|
+
def __init__(self, queue_name: str, immediate_delete: bool = True, visibility_timeout: Union[int, None] = None, minimum_visibility_timeout: int = 0, auto_create: bool = False, **kwargs):
|
|
53
53
|
"""
|
|
54
54
|
SQS access
|
|
55
55
|
|
|
@@ -66,6 +66,7 @@ class SQSAccess(AWSAccess):
|
|
|
66
66
|
self.immediate_delete = immediate_delete # True to immediately delete messages
|
|
67
67
|
self.user_provided_timeout = visibility_timeout # the queue will re-try a message (make it re-visible) if not deleted within this time
|
|
68
68
|
self.user_provided_minimum_timeout = minimum_visibility_timeout # the timeout will be at least this long
|
|
69
|
+
self.auto_create = auto_create # automatically create the queue if it doesn't exist
|
|
69
70
|
self.auto_timeout_multiplier = 10.0 # for automatic timeout calculations, multiply this times the median run time to get the timeout
|
|
70
71
|
|
|
71
72
|
self.sqs_call_wait_time = 0 # short (0) or long poll (> 0, usually 20)
|
|
@@ -80,21 +81,38 @@ class SQSAccess(AWSAccess):
|
|
|
80
81
|
# We write the history out as a file so don't make this too big. We take the median (for the nominal run time) so make this big enough to tolerate a fair number of outliers.
|
|
81
82
|
self.max_history = 20
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
self.was_created = False
|
|
85
|
+
|
|
86
|
+
def _get_queue(self) -> Any:
|
|
87
|
+
"""
|
|
88
|
+
Get the SQS queue instance. If the queue doesn't exist and auto_create is True, it will be created.
|
|
89
|
+
|
|
90
|
+
:return: SQS queue instance
|
|
91
|
+
"""
|
|
84
92
|
if self.queue is None:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
queue = None
|
|
94
|
+
try_count = 0
|
|
95
|
+
assert self.resource is not None
|
|
96
|
+
while self.queue is None and try_count < 3:
|
|
97
|
+
create_queue = False
|
|
98
|
+
try:
|
|
99
|
+
queue = self.resource.get_queue_by_name(QueueName=self.queue_name)
|
|
100
|
+
except self.client.exceptions.QueueDoesNotExist as e:
|
|
101
|
+
if self.auto_create:
|
|
102
|
+
create_queue = True
|
|
103
|
+
log.debug(f"{self.queue_name},{e=}")
|
|
94
104
|
queue = None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
except self.client.exceptions.ClientError as e:
|
|
106
|
+
error_code = e.response["Error"].get("Code")
|
|
107
|
+
if "NonExistentQueue" in error_code:
|
|
108
|
+
log.debug(f"{self.queue_name},{e=},{error_code=}")
|
|
109
|
+
queue = None
|
|
110
|
+
else:
|
|
111
|
+
# other errors (e.g. connection errors, etc.)
|
|
112
|
+
raise
|
|
113
|
+
if create_queue and self.create_queue() is None:
|
|
114
|
+
time.sleep(60) # if we just deleted the queue, we may have to wait 60 seconds before we can re-create it
|
|
115
|
+
try_count += 1
|
|
98
116
|
|
|
99
117
|
if queue is not None:
|
|
100
118
|
# kludge so when moto mocking we return None if it can't get the queue
|
|
@@ -119,14 +137,19 @@ class SQSAccess(AWSAccess):
|
|
|
119
137
|
return p
|
|
120
138
|
|
|
121
139
|
@typechecked()
|
|
122
|
-
def create_queue(self) -> str:
|
|
140
|
+
def create_queue(self) -> str | None:
|
|
123
141
|
"""
|
|
124
142
|
create SQS queue
|
|
125
143
|
|
|
126
|
-
:return: queue URL
|
|
144
|
+
:return: queue URL or None if not successful
|
|
127
145
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
try:
|
|
147
|
+
response = self.client.create_queue(QueueName=self.queue_name)
|
|
148
|
+
url = response.get("QueueUrl")
|
|
149
|
+
self.was_created = True
|
|
150
|
+
except self.client.exceptions.QueueDeletedRecently as e:
|
|
151
|
+
log.warning(f"{self.queue_name},{e}") # can happen if a queue was recently deleted, and we try to re-create it too quickly
|
|
152
|
+
url = None
|
|
130
153
|
return url
|
|
131
154
|
|
|
132
155
|
def delete_queue(self):
|
|
@@ -367,6 +390,30 @@ class SQSAccess(AWSAccess):
|
|
|
367
390
|
|
|
368
391
|
|
|
369
392
|
class SQSPollAccess(SQSAccess):
|
|
393
|
+
"""
|
|
394
|
+
SQS Access with long polling.
|
|
395
|
+
"""
|
|
396
|
+
|
|
370
397
|
def __init__(self, queue_name: str, **kwargs):
|
|
371
|
-
super().__init__(queue_name, **kwargs)
|
|
398
|
+
super().__init__(queue_name=queue_name, **kwargs)
|
|
372
399
|
self.sqs_call_wait_time = aws_sqs_long_poll_max_wait_time
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@typechecked()
|
|
403
|
+
def get_all_sqs_queues(prefix: str = "") -> List[str]:
|
|
404
|
+
"""
|
|
405
|
+
get all SQS queues
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
:param prefix: prefix to filter queue names (empty string for all queues)
|
|
409
|
+
:return: list of queue names
|
|
410
|
+
"""
|
|
411
|
+
queue_names = []
|
|
412
|
+
|
|
413
|
+
sqs = AWSAccess("sqs")
|
|
414
|
+
for queue in list(sqs.resource.queues.all()):
|
|
415
|
+
queue_name = queue.url.split("/")[-1]
|
|
416
|
+
if queue_name.startswith(prefix):
|
|
417
|
+
queue_names.append(queue_name)
|
|
418
|
+
|
|
419
|
+
return queue_names
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: awsimple
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.9.1
|
|
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
|
|
@@ -75,26 +75,28 @@ Full documentation available on [Read the Docs](https://awsimple.readthedocs.io/
|
|
|
75
75
|
|
|
76
76
|
### Features:
|
|
77
77
|
|
|
78
|
-
- Simple Object
|
|
78
|
+
- Simple Object-Oriented API on top of boto3.
|
|
79
|
+
- Eliminates the need to worry about `clients`, `resources`, `sessions`, and pagination.
|
|
79
80
|
|
|
80
|
-
-
|
|
81
|
+
- Locally cached S3 accesses. Reduces network traffic, AWS costs, and can speed up access.
|
|
81
82
|
|
|
82
|
-
-
|
|
83
|
+
- `pubsub` functionality (via SNS topics and SQS queues).
|
|
83
84
|
|
|
84
|
-
-
|
|
85
|
+
- DynamoDB full table scans (with local cache option that only rescans if the table has changed).
|
|
85
86
|
|
|
86
|
-
- True file hashing (SHA512) for S3 files (S3's etag is not a true file hash)
|
|
87
|
+
- True file hashing (SHA512) for S3 files (S3's etag is not a true file hash).
|
|
87
88
|
|
|
88
|
-
-
|
|
89
|
+
- Supports moto mock and localstack. Handy for testing and CI.
|
|
89
90
|
|
|
90
|
-
-
|
|
91
|
+
- Automatic S3 retries.
|
|
91
92
|
|
|
92
|
-
-
|
|
93
|
+
- One-line S3 file write, read, and delete.
|
|
93
94
|
|
|
94
|
-
-
|
|
95
|
+
- DynamoDB secondary indexes.
|
|
95
96
|
|
|
96
|
-
-
|
|
97
|
+
- Built-in pagination (e.g. for DynamoDB table scans and queries). Always get everything you asked for.
|
|
97
98
|
|
|
99
|
+
- Can automatically set SQS timeouts based on runtime data (can also be user-specified).
|
|
98
100
|
|
|
99
101
|
## Usage
|
|
100
102
|
|
|
@@ -0,0 +1,20 @@
|
|
|
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,,
|
awsimple-3.6.1.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
awsimple/__init__.py,sha256=8aFfqWFAvRPweoZkKncvHAW2ytTW_5-AJ0nnmYqgUBw,916
|
|
2
|
-
awsimple/__version__.py,sha256=jGiJ18Y9OeSn8YGis7tf4vYZ6ctQjR1W3uTiRheczhc,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=A2RmTT90pfFTthfENd7GSsEHSIBJXO8ICHPdA7sEsHY,4278
|
|
8
|
-
awsimple/mock.py,sha256=eScbnxFF9xAosOAsL-NZgp_P-fezB6StQMkb85Y3TNo,574
|
|
9
|
-
awsimple/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
awsimple/s3.py,sha256=ydYLDj51b38lYI6LsJlc4i0PDj2vnWBuSkzdRKcWAUg,23858
|
|
11
|
-
awsimple/sns.py,sha256=dOx3VUS04xxeG1krGudN4A5fqoIpXeHqXNkBvfbr_6Q,3292
|
|
12
|
-
awsimple/sqs.py,sha256=ejV9twP15X8-mZ9IHGEUlYWqufEcasYuPf1xlGQt2a8,15506
|
|
13
|
-
awsimple-3.6.1.dist-info/licenses/LICENSE,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
|
|
14
|
-
awsimple-3.6.1.dist-info/licenses/LICENSE.txt,sha256=d956YAgtDaxgxQmccyUk__EfhORZyBjvmJ8pq6zYTxk,1093
|
|
15
|
-
awsimple-3.6.1.dist-info/METADATA,sha256=_pQy4RCP8b5GS1iuz3o-wHx_IQWxq0szAlXSrp40aNE,6377
|
|
16
|
-
awsimple-3.6.1.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
17
|
-
awsimple-3.6.1.dist-info/top_level.txt,sha256=mwqCoH_8vAaK6iYioiRbasXmVCQM7AK3grNWOKp2VHE,9
|
|
18
|
-
awsimple-3.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|