python-terminusgps 35.0.0__py3-none-any.whl → 35.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-terminusgps
3
- Version: 35.0.0
3
+ Version: 35.4.0
4
4
  Summary: Provides abstractions/utilities for working with Wialon API, Authorize.NET API, AWS API, and more.
5
5
  Project-URL: Documentation, https://docs.terminusgps.com
6
6
  Project-URL: Repository, https://github.com/terminusgps/python-terminusgps
@@ -15,14 +15,11 @@ terminusgps/authorizenet/tests/test_auth.py,sha256=sdByYrcv8RKjcJckOHbyHH1z_M4qr
15
15
  terminusgps/authorizenet/tests/test_profiles.py,sha256=tvLBY0iSAC33RlWiBP_NSnPskUQBjfrf0YHMpR-m22k,7566
16
16
  terminusgps/authorizenet/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  terminusgps/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- terminusgps/aws/notifications.py,sha256=HQDsxvFh_8vpAvoOo0K_3nteV0ArM-kgSeMhNzBx3es,9618
19
- terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
20
- terminusgps/aws/speech.py,sha256=8d4kLzc1obzW7uAhlSBmPWh_hw92d1eydcIiQROTj7A,4563
21
- terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
18
+ terminusgps/aws/notifications.py,sha256=76YnACuVIMs5EujEATqcBf8HcCFxs2649fnLx9nSauc,11103
19
+ terminusgps/aws/secrets.py,sha256=-9gNTaagJbWpeqTNXdxXsMVKUF2_mwzx5XvqTOFcqYU,670
22
20
  terminusgps/aws/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
21
  terminusgps/aws/tests/test_notifications.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
22
  terminusgps/aws/tests/test_secrets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- terminusgps/aws/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
23
  terminusgps/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
24
  terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
28
25
  terminusgps/django/settings.py,sha256=yJIcBE0xjY60AXF_gonY5hS3ofseqPW2k9uSoELgoho,639
@@ -41,21 +38,21 @@ terminusgps/wialon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuF
41
38
  terminusgps/wialon/constants.py,sha256=Ju2XL2A4DPFD-w8NjYqEfnnqregl1luPT0XGhGSUnO8,6316
42
39
  terminusgps/wialon/flags.py,sha256=M50EdhxQ8IMnJnU0mrHK7-h8Asc6tvNiTOOfd1dBW6A,12815
43
40
  terminusgps/wialon/session.py,sha256=09WYWvON_H844IFfdt7_x1we6tHhQSkt9K_BEedBjv0,10396
44
- terminusgps/wialon/utils.py,sha256=JWHZ7VEeo8VNBH67gjOAQk5ITKuPllWDlJ4dVSQ5blA,11002
41
+ terminusgps/wialon/utils.py,sha256=SwpcxLPBLy0u3AT84G-Co4wxmhRrPFyW3jo_9O6ICvk,11008
45
42
  terminusgps/wialon/validators.py,sha256=j5vrtcfClAzRdRkWTvRRAmrne1t0R6cKkmYdE7fAkSs,4247
46
43
  terminusgps/wialon/items/__init__.py,sha256=3BVthghekMvhMQSzQgBMlD_phxmKSmp3Zvmo-z0O8Bs,211
47
- terminusgps/wialon/items/base.py,sha256=7J5B-EPj997GJV7TbZP7pAfjl2G4nTcKmP-RMxmuzoY,10959
44
+ terminusgps/wialon/items/base.py,sha256=YpOPxghLYDZmWQZRb64CjwCq_0rTmm83cIYZm2oN5LY,9344
48
45
  terminusgps/wialon/items/resource.py,sha256=DDnyL_Y5P3DxiirUkQb9y4cokMXZVq3xnfn16Vx8sxk,16992
49
46
  terminusgps/wialon/items/retranslator.py,sha256=sVEWrp8q1I-tO_rMTCH7TeQDDbZryIq8d9KVnhYyhH0,3795
50
47
  terminusgps/wialon/items/route.py,sha256=amNPKZqgl8pEzKA_XJudnvBPzEsfHTmrC7uF15sZVI4,1216
51
- terminusgps/wialon/items/unit.py,sha256=0XPLfpFh7djiVOcuZE-lUdB_-_LRSO8C2PvfzBFh3HQ,10744
48
+ terminusgps/wialon/items/unit.py,sha256=G72CSpu5pcilGjXQDz8yTZ-xwWXm1rCYZUkG98lY-8w,10504
52
49
  terminusgps/wialon/items/unit_group.py,sha256=QV2ZZgICaN3n6W1pFIyMLyNKB6KYBsLyKG64i-qXoOg,4113
53
50
  terminusgps/wialon/items/user.py,sha256=5LgwalGO8KS_7psmAVmAlnC0S6SPBDZMOuT5ZqPKRmo,7758
54
51
  terminusgps/wialon/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
52
  terminusgps/wialon/tests/test_items.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
53
  terminusgps/wialon/tests/test_session.py,sha256=9mBlYchMo9NeqRIZsmxYzrM1zBHorqu4ooxqSNppLpI,1336
57
54
  terminusgps/wialon/tests/test_utils.py,sha256=SK4PxJQGECFnzx_EQeRAQfsQ5_3FLaVcis2W9u_ibuI,1730
58
- python_terminusgps-35.0.0.dist-info/METADATA,sha256=FJGCV_uc4f3OcIqAv0Lyl2w9jSFw2X6a-0Ev1wNU3xc,1616
59
- python_terminusgps-35.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
60
- python_terminusgps-35.0.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
61
- python_terminusgps-35.0.0.dist-info/RECORD,,
55
+ python_terminusgps-35.4.0.dist-info/METADATA,sha256=yyOAC6wbzHngb7UQQuqNchUZeeikcsH43qdyJLwBQTg,1616
56
+ python_terminusgps-35.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
57
+ python_terminusgps-35.4.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
58
+ python_terminusgps-35.4.0.dist-info/RECORD,,
@@ -1,60 +1,95 @@
1
1
  import asyncio
2
2
  import os
3
- import uuid
4
3
  from collections.abc import Sequence
5
4
  from contextlib import AsyncExitStack
6
5
 
7
6
  from aiobotocore.session import AioSession
7
+ from loguru import logger
8
8
 
9
- from terminusgps.django.validators import validate_e164_phone_number
10
9
 
10
+ class AsyncNotificationManager:
11
+ """Asyncronously delivers notifications using `AWS End User Messaging <https://docs.aws.amazon.com/end-user-messaging/>`_."""
11
12
 
12
- class AsyncPollyManager:
13
13
  def __init__(
14
- self, bucket_name: str | None = None, region_name: str = "us-east-1"
14
+ self,
15
+ origin_pool_arn: str | None = None,
16
+ configuration_set: str | None = None,
17
+ max_sms_price: str = "0.20",
18
+ max_voice_price: str = "0.20",
19
+ region_name: str = "us-east-1",
20
+ debug_enabled: bool = False,
21
+ logger_level: int = 10,
22
+ logger_days: int = 10,
15
23
  ) -> None:
16
- self._exit_stack = AsyncExitStack()
17
- self._polly_client = None
18
- self._region_name = region_name
19
- self._bucket_name = bucket_name or os.getenv("AWS_POLLY_BUCKET_NAME", "")
20
-
21
- @property
22
- def region_name(self) -> str:
23
24
  """
24
- An AWS region name.
25
-
26
- :type: :py:obj:`str`
27
-
28
- """
29
- return self._region_name
25
+ Sets attributes on the notification manager.
26
+
27
+ :param origin_pool_arn: A phone pool ARN for notification dispatch. Default is :envvar:`AWS_MESSAGING_ORIGIN_POOL`.
28
+ :type origin_pool_arn: :py:obj:`str` | :py:obj:`None`
29
+ :param configuration_set: An end-user messaging configuration set ARN. Default is :envvar:`AWS_MESSAGING_CONFIGURATION`.
30
+ :type configuration_set: :py:obj:`str` | :py:obj:`None`
31
+ :param max_sms_price: Max price to spend on a single SMS message. Default is :py:obj:`"0.20"`.
32
+ :type max_sms_price: :py:obj:`str`
33
+ :param max_voice_price: Max price to spend per minute on a single voice message. Default is :py:obj:`"0.20"`.
34
+ :type max_voice_price: :py:obj:`str`
35
+ :param region_name: An AWS region name used to open an AWS client. Default is :py:obj:`"us-east-1"`.
36
+ :type region_name: :py:obj:`str`
37
+ :param debug_enabled: Whether or not to enable debug mode. Default is :py:obj:`False`
38
+ :type debug_enabled: :py:obj:`False`
39
+ :param logger_level: A logger level. Default is :py:obj:`int`
40
+ :type logger_level: :py:obj:`int`
41
+ :param logger_days: How long in days logging data should be saved.
42
+ :type logger_days: :py:obj:`int`
43
+ :raises ValueError: If ``origin_pool_arn`` wasn't provided and :envvar:`AWS_MESSAGING_ORIGIN_POOL` wasn't set.
44
+ :raises ValueError: If ``configuration_set`` wasn't provided and :envvar:`AWS_MESSAGING_CONFIGURATION` wasn't set.
45
+ :returns: Nothing.
46
+ :rtype: :py:obj:`None`
30
47
 
31
- @property
32
- def bucket_name(self) -> str:
33
48
  """
34
- The AWS bucket that will host audio files.
49
+ if not origin_pool_arn and not os.getenv("AWS_MESSAGING_ORIGIN_POOL"):
50
+ raise ValueError(f"'origin_pool_arn' is required, got '{origin_pool_arn}'.")
51
+ if not configuration_set and not os.getenv("AWS_MESSAGING_CONFIGURATION"):
52
+ raise ValueError(
53
+ f"'configuration_set' is required, got '{configuration_set}'."
54
+ )
35
55
 
36
- :type: :py:obj:`str`
56
+ self._origin_pool_arn = origin_pool_arn or os.getenv(
57
+ "AWS_MESSAGING_ORIGIN_POOL", ""
58
+ )
59
+ self._configuration_set = configuration_set or os.getenv(
60
+ "AWS_MESSAGING_CONFIGURATION", ""
61
+ )
37
62
 
38
- """
39
- return self._bucket_name
63
+ self._exit_stack = AsyncExitStack()
64
+ self._pinpoint_client = None
65
+ self._region_name = region_name
66
+ self._max_sms_price = max_sms_price
67
+ self._max_voice_price = max_voice_price
68
+ self._debug = debug_enabled
69
+ logger.add(
70
+ f"logs/{self.__class__.__name__}.log",
71
+ level=logger_level,
72
+ retention=f"{logger_days} days",
73
+ diagnose=debug_enabled,
74
+ )
40
75
 
41
- async def __aenter__(self) -> "AsyncPollyManager":
76
+ async def __aenter__(self) -> "AsyncNotificationManager":
42
77
  """
43
- Creates an asyncronous session and client.
78
+ Creates asyncronous clients.
44
79
 
45
- :returns: The polly manager.
46
- :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncPollyManager`
80
+ :returns: The notification manager.
81
+ :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
47
82
 
48
83
  """
49
84
  session = AioSession()
50
- self._polly_client = await self._exit_stack.enter_async_context(
51
- session.create_client("polly", region_name=self.region_name)
85
+ self._pinpoint_client = await self._exit_stack.enter_async_context(
86
+ session.create_client("pinpoint-sms-voice-v2", region_name=self.region_name)
52
87
  )
53
88
  return self
54
89
 
55
90
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
56
91
  """
57
- Destroys the asyncronous session.
92
+ Destroys asyncronous clients.
58
93
 
59
94
  :param exc_type: Exception type.
60
95
  :param exc_val: Exception value.
@@ -65,247 +100,206 @@ class AsyncPollyManager:
65
100
  """
66
101
  await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
67
102
 
68
- async def synthesize_speech(
69
- self,
70
- message: str,
71
- format: str = "ogg_vorbis",
72
- sample_rate: int = 22050,
73
- language_code: str = "en-US",
74
- ) -> None:
75
- """
76
- Synthesizes the message into an audio file and saves it to the S3 bucket.
77
-
78
- :param message: A message to be read aloud.
79
- :type message: :py:obj:`str`
80
- :param format: A file format. Default is ``"ogg_vorbis"``.
81
- :type format: :py:obj:`str`
82
- :param sample_rate: The audio frequency in Hz. Default is ``22050``.
83
- :type sample_rate: :py:obj:`int`
84
- :param language_code: The language to use in the synthesis. Default is ``"en-US"``.
85
- :type language_code: :py:obj:`str`
86
- :returns: Nothing.
87
- :rtype: :py:obj:`None`
88
-
89
- """
90
- await self._polly_client.start_speech_synthesis_task(
91
- **{
92
- "Engine": "standard",
93
- "LanguageCode": language_code,
94
- "OutputFormat": format,
95
- "OutputS3BucketName": self.bucket_name,
96
- "SampleRate": str(sample_rate),
97
- "Text": f"<speak>{message}</speak>",
98
- "TextType": "ssml",
99
- "VoiceId": "Joanna",
100
- }
101
- )
102
-
103
- async def synthesize_speech_batch(
104
- self,
105
- messages: Sequence[str],
106
- format: str = "ogg_vorbis",
107
- sample_rate: int = 22050,
108
- language_code: str = "en-US",
109
- ) -> None:
110
- """
111
- Synthesizes a sequence of messages into audio files and saves them to the S3 bucket.
112
-
113
- :param messages: A sequence of messages to be read aloud.
114
- :type messages: :py:obj:`Sequence`
115
- :param format: A file format. Default is ``"ogg_vorbis"``.
116
- :type format: :py:obj:`str`
117
- :param sample_rate: The audio frequency in Hz. Default is ``22050``.
118
- :type sample_rate: :py:obj:`int`
119
- :param language_code: The language to use in the synthesis. Default is ``"en-US"``.
120
- :type language_code: :py:obj:`str`
121
- :returns: Nothing.
122
- :rtype: :py:obj:`None`
123
-
124
- """
125
- await asyncio.gather(
126
- *[
127
- self.synthesize_speech(
128
- message=message,
129
- format=format,
130
- sample_rate=sample_rate,
131
- language_code=language_code,
132
- )
133
- for message in messages
134
- ]
135
- )
136
-
137
-
138
- class AsyncNotificationManager:
139
- def __init__(
140
- self, group_id: str | None = None, region_name: str = "us-east-1"
141
- ) -> None:
103
+ @property
104
+ def region_name(self) -> str:
142
105
  """
143
- Sets :py:attr:`group_id` and :py:attr:`region_name`.
106
+ An AWS region name.
144
107
 
145
- :returns: Nothing.
146
- :rtype: :py:obj:`None`
108
+ :type: :py:obj:`str`
147
109
 
148
110
  """
149
- self._exit_stack = AsyncExitStack()
150
- self._sns_client = None
151
- self._region_name = region_name
152
- self.group_id = group_id
111
+ return self._region_name
153
112
 
154
- async def __aenter__(self) -> "AsyncNotificationManager":
113
+ @property
114
+ def configuration_set(self) -> str:
155
115
  """
156
- Creates an asyncronous session and client.
116
+ The configuration set to use for messaging.
157
117
 
158
- :returns: The notification manager.
159
- :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
118
+ :type: :py:obj:`str`
160
119
 
161
120
  """
162
- session = AioSession()
163
- self._sns_client = await self._exit_stack.enter_async_context(
164
- session.create_client("sns", region_name=self.region_name)
165
- )
166
- return self
121
+ return self._configuration_set
167
122
 
168
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
123
+ @property
124
+ def origin_pool(self) -> str:
169
125
  """
170
- Destroys the asyncronous session.
126
+ The origination pool to use for messaging.
171
127
 
172
- :param exc_type: Exception type.
173
- :param exc_val: Exception value.
174
- :param exc_tb: Exception traceback.
175
- :returns: Nothing.
176
- :rtype: :py:obj:`None`
128
+ :type: :py:obj:`str`
177
129
 
178
130
  """
179
- await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
131
+ return self._origin_pool_arn
180
132
 
181
133
  @property
182
- def region_name(self) -> str:
134
+ def max_sms_price(self) -> str:
183
135
  """
184
- An AWS region name.
136
+ The max allowed price per sms message.
185
137
 
186
138
  :type: :py:obj:`str`
139
+ :value: :py:obj:`"0.20"`
187
140
 
188
141
  """
189
- return self._region_name
142
+ return self._max_sms_price
190
143
 
191
144
  @property
192
- def group_id(self) -> str:
145
+ def max_voice_price(self) -> str:
193
146
  """
194
- A message group id.
147
+ The max allowed price per voice message.
195
148
 
196
149
  :type: :py:obj:`str`
150
+ :value: :py:obj:`"0.20"`
197
151
 
198
152
  """
199
- return str(self._group_id)
153
+ return self._max_voice_price
200
154
 
201
- @group_id.setter
202
- def group_id(self, other: str | None) -> None:
155
+ @property
156
+ def debug(self) -> bool:
203
157
  """
204
- Sets :py:attr:`group_id` to ``other``.
205
-
206
- If ``other`` isn't provided, instead set :py:attr:`group_id` to :py:func:`~uuid.uuid4`.
158
+ Whether or not debug mode is enabled.
207
159
 
208
- :param other: A message group id.
209
- :type other: :py:obj:`str` | :py:obj:`None`
210
- :returns: Nothing.
211
- :rtype: :py:obj:`None`
160
+ :type: :py:obj:`bool`
161
+ :value: :py:obj:`False`
212
162
 
213
163
  """
214
- if not other:
215
- other = str(uuid.uuid4())
216
- self._group_id = other
164
+ return self._debug
217
165
 
218
166
  async def send_sms(
219
- self, to_number: str, message: str, message_id: str | None = None
220
- ) -> None:
167
+ self,
168
+ phone: str,
169
+ message: str,
170
+ ttl: int = 300,
171
+ dry_run: bool = False,
172
+ feedback: bool = False,
173
+ ) -> dict[str, str]:
221
174
  """
222
- Sends an sms to ``to_number``.
175
+ Texts ``message`` to ``phone`` via sms.
223
176
 
224
- :param to_number: A destination phone number.
225
- :type to_number: :py:obj:`str`
177
+ :param phone: A destination phone number.
178
+ :type phone: :py:obj:`str`
226
179
  :param message: A message body.
227
180
  :type message: :py:obj:`str`
228
- :param message_id: A message deduplication id. :py:func:`~uuid.uuid4` is used if ``message_id`` was not provided.
229
- :type message_id: :py:obj:`str` | :py:obj:`None`
230
- :raises ValidationError: If ``to_number`` wasn't a valid E.164 formatted phone number.
231
- :returns: Nothing.
232
- :rtype: :py:obj:`None`
181
+ :param ttl: Time to live in ms. Default is :py:obj:`300`.
182
+ :type ttl: :py:obj:`int`
183
+ :param dry_run: Whether or not to execute the message as a dry run. Default is :py:obj:`False`.
184
+ :type dry_run: :py:obj:`bool`
185
+ :param feedback: Whether or not to include message feedback in the response. Default is :py:obj:`False`.
186
+ :type feedback: :py:obj:`bool`
187
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
188
+ :returns: An sms message response.
189
+ :rtype: :py:obj:`dict`
233
190
 
234
191
  """
235
- validate_e164_phone_number(to_number)
192
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
236
193
 
237
- if not message_id:
238
- message_id = str(uuid.uuid4())
239
-
240
- await self._sns_client.publish(
194
+ logger.debug(f"Texting '{message}' to '{phone}'...")
195
+ return await self._pinpoint_client.send_text_message(
241
196
  **{
242
- "PhoneNumber": to_number,
243
- "Message": message,
244
- "MessageDeduplicationId": message_id,
245
- "MessageGroupId": self.group_id,
197
+ "DestinationPhoneNumber": phone,
198
+ "OriginationIdentity": self.origin_pool,
199
+ "MessageBody": message,
200
+ "MessageType": "TRANSACTIONAL",
201
+ "ConfigurationSetName": self.configuration_set,
202
+ "MaxPrice": self.max_sms_price,
203
+ "TimeToLive": ttl,
204
+ "DryRun": dry_run or self.debug,
205
+ "MessageFeedbackEnabled": feedback,
246
206
  }
247
207
  )
248
208
 
249
- async def send_push(
250
- self, target_arn: str, message: str, message_id: str | None = None
251
- ) -> None:
209
+ async def send_voice(
210
+ self,
211
+ phone: str,
212
+ message: str,
213
+ ttl: int = 300,
214
+ voice_id: str = "Joanna",
215
+ dry_run: bool = False,
216
+ feedback: bool = False,
217
+ ) -> dict[str, str]:
252
218
  """
253
- Sends a push notification to ``target_arn``.
219
+ Calls ``phone`` and reads ``message`` aloud.
254
220
 
255
- :param target_arn: An AWS resource ARN.
256
- :type target_arn: :py:obj:`str`
221
+ :param phone: A destination phone number.
222
+ :type phone: :py:obj:`str`
257
223
  :param message: A message body.
258
224
  :type message: :py:obj:`str`
259
- :param message_id: A message deduplication id. :py:func:`~uuid.uuid4` is used if ``message_id`` was not provided.
260
- :type message_id: :py:obj:`str` | :py:obj:`None`
261
- :returns: Nothing.
262
- :rtype: :py:obj:`None`
263
-
264
- """
265
- if not message_id:
266
- message_id = str(uuid.uuid4())
267
-
268
- await self._sns_client.publish(
225
+ :param ttl: Time to live in ms. Default is :py:obj:`300`.
226
+ :type ttl: :py:obj:`int`
227
+ :param voice_id: A voice id to use for speech synthesis. Default is :py:obj:`"Joanna"`.
228
+ :type voice_id: :py:obj:`str`
229
+ :param dry_run: Whether or not to execute the message as a dry run. Default is :py:obj:`False`.
230
+ :type dry_run: :py:obj:`bool`
231
+ :param feedback: Whether or not to include message feedback in the response. Default is :py:obj:`False`.
232
+ :type feedback: :py:obj:`bool`
233
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
234
+ :returns: A voice message response.
235
+ :rtype: :py:obj:`dict`
236
+
237
+ """
238
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
239
+
240
+ logger.debug(f"Reading '{message}' aloud to '{phone}'...")
241
+ return await self._pinpoint_client.send_voice_message(
269
242
  **{
270
- "TargetArn": target_arn,
271
- "Message": message,
272
- "MessageDeduplicationId": message_id,
273
- "MessageGroupId": self.group_id,
243
+ "DestinationPhoneNumber": phone,
244
+ "OriginationIdentity": self.origin_pool,
245
+ "MessageBody": message,
246
+ "MessageBodyTextType": "TEXT",
247
+ "VoiceId": voice_id.upper(),
248
+ "ConfigurationSetName": self.configuration_set,
249
+ "MaxPricePerMinute": self.max_voice_price,
250
+ "TimeToLive": ttl,
251
+ "DryRun": dry_run or self.debug,
252
+ "MessageFeedbackEnabled": feedback,
274
253
  }
275
254
  )
276
255
 
277
- async def send_sms_batch(self, to_numbers: Sequence[str], message: str) -> None:
256
+ async def batch_send_sms(
257
+ self, phones: Sequence[str], message: str, **kwargs
258
+ ) -> list[dict[str, str]]:
278
259
  """
279
- Sends an sms message to each phone number in ``to_numbers``.
260
+ Sends ``message`` to all phone numbers in ``phones`` via sms.
280
261
 
281
- :param to_numbers: A sequence of phone numbers.
282
- :type to_numbers: :py:obj:`~collections.abc.Sequence`
262
+ :param phones: A sequence of phone numbers.
263
+ :type phones: :py:obj:`~collections.abc.Sequence`
283
264
  :param message: A message body.
284
265
  :type message: :py:obj:`str`
285
- :raises ValidationError: If a ``to_number`` wasn't a valid E.164 formatted phone number.
286
- :returns: Nothing.
287
- :rtype: :py:obj:`None`
266
+ :param kwargs: Additional keyword arguments passed to :py:meth:`~terminusgps.aws.notifications.AsyncNotificationManager.send_sms`.
267
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
268
+ :returns: A list of sms message responses.
269
+ :rtype: :py:obj:`dict`
288
270
 
289
- """
290
- for to_number in to_numbers:
291
- validate_e164_phone_number(to_number)
271
+ .. seealso::
272
+ :py:meth:`~terminusgps.aws.notifications.AsyncNotificationManager.send_sms` for details on available keyword arguments.
292
273
 
293
- await asyncio.gather(
294
- *[self.send_sms(to_number=num, message=message) for num in to_numbers]
274
+ """
275
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
276
+ return await asyncio.gather(
277
+ *[self.send_sms(phone=phone, message=message, **kwargs) for phone in phones]
295
278
  )
296
279
 
297
- async def send_push_batch(self, target_arns: Sequence[str], message: str) -> None:
280
+ async def batch_send_voice(
281
+ self, phones: Sequence[str], message: str, **kwargs
282
+ ) -> list[dict[str, str]]:
298
283
  """
299
- Sends a push notification to each AWS resource by ARN in ``target_arns``.
284
+ Calls each number in ``phones`` and reads ``message`` aloud.
300
285
 
301
- :param target_arns: A sequence of AWS resource ARNs.
302
- :type target_arns: :py:obj:`~collections.abc.Sequence`
286
+ :param phone: A sequence of phone numbers.
287
+ :type phone: :py:obj:`~collections.abc.Sequence`
303
288
  :param message: A message body.
304
289
  :type message: :py:obj:`str`
305
- :returns: Nothing.
306
- :rtype: :py:obj:`None`
290
+ :param kwargs: Additional keyword arguments passed to :py:meth:`~terminusgps.aws.notifications.AsyncNotificationManager.send_voice`.
291
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
292
+ :returns: A voice message response.
293
+ :rtype: :py:obj:`dict`
294
+
295
+ .. seealso::
296
+ :py:meth:`~terminusgps.aws.notifications.AsyncNotificationManager.send_voice` for details on available keyword arguments.
307
297
 
308
298
  """
309
- await asyncio.gather(
310
- *[self.send_push(target_arn=arn, message=message) for arn in target_arns]
299
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
300
+ return await asyncio.gather(
301
+ *[
302
+ self.send_voice(phone=phone, message=message, **kwargs)
303
+ for phone in phones
304
+ ]
311
305
  )
@@ -4,7 +4,17 @@ import boto3
4
4
 
5
5
 
6
6
  def get_secret(name: str, region: str = "us-east-1") -> dict[str, str]:
7
- """Logs into the default AWS CLI session and returns a secret value."""
7
+ """
8
+ Logs into the default AWS CLI session and returns a secret value.
9
+
10
+ :param name: An AWS `secretsmanager <https://docs.aws.amazon.com/secretsmanager/>`_ name.
11
+ :type name: :py:obj:`str`
12
+ :param region: An AWS region name. Default is :py:obj:`"us-east-1"`.
13
+ :type region: :py:obj:`str`
14
+ :returns: A secret value dictionary.
15
+ :rtype: :py:obj:`dict`
16
+
17
+ """
8
18
  client = boto3.Session().client(service_name="secretsmanager", region_name=region)
9
19
  secret = client.get_secret_value(SecretId=name)["SecretString"]
10
20
  return json.loads(secret)
@@ -155,13 +155,24 @@ class WialonBase:
155
155
  )
156
156
  self._name = new_name
157
157
 
158
- def get_cfield_id(self, key: str) -> int | None:
158
+ def delete(self) -> None:
159
+ """
160
+ Deletes the Wialon object.
161
+
162
+ :raises WialonError: If something goes wrong with Wialon.
163
+ :returns: Nothing.
164
+ :rtype: :py:obj:`None`
165
+
159
166
  """
160
- Returns a custom field id by its key, if it exists.
167
+ self.session.wialon_api.item_delete_item(**{"itemId": self.id})
161
168
 
162
- :param key: A custom field key.
169
+ def _get_cfield_id(self, key: str) -> int | None:
170
+ """
171
+ Returns a custom field id by key.
172
+
173
+ :param key: A custom field key (name).
163
174
  :type key: :py:obj:`str`
164
- :returns: A custom field id, if any.
175
+ :returns: The custom field's id as an integer, if found.
165
176
  :rtype: :py:obj:`int` | :py:obj:`None`
166
177
 
167
178
  """
@@ -173,17 +184,18 @@ class WialonBase:
173
184
  .get("flds", {})
174
185
  .values()
175
186
  )
187
+
176
188
  for field in cfields:
177
189
  if field["n"] == key:
178
190
  return int(field["id"])
179
191
 
180
- def get_afield_id(self, key: str) -> int | None:
192
+ def _get_afield_id(self, key: str) -> int | None:
181
193
  """
182
- Returns an admin field id by its key, if it exists.
194
+ Returns an admin field id by key.
183
195
 
184
- :param key: An admin field key.
196
+ :param key: An admin field key (name).
185
197
  :type key: :py:obj:`str`
186
- :returns: An admin field id, if any.
198
+ :returns: The admin field's id as an integer, if found.
187
199
  :rtype: :py:obj:`int` | :py:obj:`None`
188
200
 
189
201
  """
@@ -195,130 +207,79 @@ class WialonBase:
195
207
  .get("aflds", {})
196
208
  .values()
197
209
  )
210
+
198
211
  for field in afields:
199
212
  if field["n"] == key:
200
213
  return int(field["id"])
201
214
 
202
- def add_afield(self, key: str, value: str) -> None:
215
+ def update_afield(self, key: str, value: str) -> None:
203
216
  """
204
- Adds an admin field to the Wialon object.
217
+ Updates an admin field by key.
205
218
 
206
- :param key: A key (name) for the admin field.
219
+ :param key: An admin field key (name).
207
220
  :type key: :py:obj:`str`
208
- :param value: A value for the admin field.
221
+ :param value: A string.
209
222
  :type value: :py:obj:`str`
210
223
  :raises WialonError: If something goes wrong with Wialon.
211
224
  :returns: Nothing.
212
225
  :rtype: :py:obj:`None`
213
226
 
214
227
  """
215
- self.session.wialon_api.item_update_admin_field(
216
- **{"itemId": self.id, "id": 0, "callMode": "create", "n": key, "v": value}
217
- )
228
+ field_id: int | None = self._get_afield_id(key)
218
229
 
219
- def update_afield(self, id: int, key: str, value: str) -> None:
220
- """
221
- Updates an admin field by id.
222
-
223
- :param id: The admin field id.
224
- :type id: :py:obj:`int`
225
- :param key: A key (name) for the admin field.
226
- :type key: :py:obj:`str`
227
- :param value: A value for the admin field.
228
- :type value: :py:obj:`str`
229
- :raises WialonError: If something goes wrong with Wialon.
230
- :returns: Nothing.
231
- :rtype: :py:obj:`None`
232
-
233
- """
234
- self.session.wialon_api.item_update_admin_field(
235
- **{"itemId": self.id, "id": id, "callMode": "update", "n": key, "v": value}
236
- )
237
-
238
- def add_cfield(self, key: str, value: str) -> None:
239
- """
240
- Adds a custom field to the Wialon object.
241
-
242
- :param key: A key (name) for the custom field.
243
- :type key: :py:obj:`str`
244
- :param value: A value for the custom field.
245
- :type value: :py:obj:`str`
246
- :raises WialonError: If something goes wrong with Wialon.
247
- :returns: Nothing.
248
- :rtype: :py:obj:`None`
249
-
250
- """
251
- self.session.wialon_api.item_update_custom_field(
252
- **{"itemId": self.id, "id": 0, "callMode": "create", "n": key, "v": value}
253
- )
230
+ if field_id is not None:
231
+ self.session.wialon_api.item_update_admin_field(
232
+ **{
233
+ "itemId": self.id,
234
+ "id": field_id,
235
+ "callMode": "update",
236
+ "n": key,
237
+ "v": value,
238
+ }
239
+ )
240
+ else:
241
+ self.session.wialon_api.item_update_admin_field(
242
+ **{"itemId": self.id, "callMode": "create", "n": key, "v": value}
243
+ )
254
244
 
255
- def update_cfield(self, id: int, key: str, value: str) -> None:
245
+ def update_cfield(self, key: str, value: str) -> None:
256
246
  """
257
247
  Updates a custom field by id.
258
248
 
259
- :param id: The admin field id.
260
- :type id: :py:obj:`int`
261
- :param field: A key (name) for the custom field.
262
- :type field: :py:obj:`str`
263
- :param field: A value for the custom field.
264
- :type field: :py:obj:`str`
265
- :raises WialonError: If something goes wrong with Wialon.
266
- :returns: Nothing.
267
- :rtype: :py:obj:`None`
268
-
269
- """
270
- self.session.wialon_api.item_update_custom_field(
271
- **{"itemId": self.id, "id": id, "callMode": "update", "n": key, "v": value}
272
- )
273
-
274
- def add_cproperty(self, key: str, value: str) -> None:
275
- """
276
- Adds a custom property to the Wialon object.
277
-
278
- :param key: A key (name) for the custom property.
249
+ :param key: A custom field key (name).
279
250
  :type key: :py:obj:`str`
280
- :param value: A value for the custom property.
251
+ :param value: A string.
281
252
  :type value: :py:obj:`str`
282
253
  :raises WialonError: If something goes wrong with Wialon.
283
254
  :returns: Nothing.
284
255
  :rtype: :py:obj:`None`
285
256
 
286
257
  """
287
- self.session.wialon_api.item_update_custom_property(
288
- **{"itemId": self.id, "name": key, "value": value}
289
- )
258
+ field_id: int | None = self._get_cfield_id(key)
290
259
 
291
- def add_profile_field(self, key: str, value: str) -> None:
292
- """
293
- Adds a profile field to the Wialon object.
294
-
295
- :param key: A key (name) for the profile field.
296
- :type key: :py:obj:`str`
297
- :param value: A value for the profile field.
298
- :type value: :py:obj:`str`
299
- :raises WialonError: If something goes wrong with Wialon.
300
- :returns: Nothing.
301
- :rtype: :py:obj:`None`
302
-
303
- """
304
- self.session.wialon_api.item_update_profile_field(
305
- **{"itemId": self.id, "n": key, "v": value}
306
- )
260
+ if field_id is not None:
261
+ self.session.wialon_api.item_update_custom_field(
262
+ **{
263
+ "itemId": self.id,
264
+ "id": field_id,
265
+ "callMode": "update",
266
+ "n": key,
267
+ "v": value,
268
+ }
269
+ )
270
+ else:
271
+ self.session.wialon_api.item_update_custom_field(
272
+ **{"itemId": self.id, "callMode": "create", "n": key, "v": value}
273
+ )
307
274
 
308
- def delete(self) -> None:
275
+ @property
276
+ def cfields(self) -> dict[str, str]:
309
277
  """
310
- Deletes the Wialon object.
278
+ Custom fields for the Wialon object.
311
279
 
312
- :raises WialonError: If something goes wrong with Wialon.
313
- :returns: Nothing.
314
- :rtype: :py:obj:`None`
280
+ :type: :py:obj:`dict`
315
281
 
316
282
  """
317
- self.session.wialon_api.item_delete_item(**{"itemId": self.id})
318
-
319
- @property
320
- def cfields(self) -> dict[str, str]:
321
- """Custom fields for the Wialon object."""
322
283
  response = self.session.wialon_api.core_search_item(
323
284
  **{"id": self.id, "flags": flags.DataFlag.UNIT_CUSTOM_FIELDS}
324
285
  )
@@ -327,7 +288,12 @@ class WialonBase:
327
288
 
328
289
  @property
329
290
  def afields(self) -> dict[str, str]:
330
- """Admin fields for the Wialon object."""
291
+ """
292
+ Admin fields for the Wialon object.
293
+
294
+ :type: :py:obj:`dict`
295
+
296
+ """
331
297
  response = self.session.wialon_api.core_search_item(
332
298
  **{"id": self.id, "flags": flags.DataFlag.UNIT_ADMIN_FIELDS}
333
299
  )
@@ -249,29 +249,27 @@ class WialonUnit(WialonBase):
249
249
  **{"itemId": self.id, "phoneNumber": quote_plus(phone)}
250
250
  )
251
251
 
252
- def get_phone_numbers(self) -> list[str]:
252
+ def get_phone_numbers(
253
+ self, cfield_key: str = "to_number", afield_key: str = "to_number"
254
+ ) -> list[str]:
253
255
  """
254
256
  Retrieves all phone numbers assigned to the unit.
255
257
 
256
258
  This includes any attached drivers, custom/admin fields or otherwise assigned phone numbers.
257
259
 
260
+ :param cfield_key: A custom field key used to retrieve phone numbers. Default is :py:obj:`"to_number"`.
261
+ :type cfield_key: :py:obj:`str`
262
+ :param afield_key: An admin field key used to retrieve phone numbers. Default is :py:obj:`"to_number"`.
263
+ :type afield_key: :py:obj:`str`
258
264
  :raises WialonError: If something goes wrong with Wialon.
259
265
  :returns: A list of phone numbers.
260
266
  :rtype: :py:obj:`list`
261
267
 
262
268
  """
263
- phone_numbers: list[str] = []
264
- phones_0: list[str] | None = self._get_driver_phone_numbers()
265
- phones_1: list[str] | None = self._get_cfield_phone_numbers()
266
- phones_2: list[str] | None = self._get_afield_phone_numbers()
267
-
268
- if phones_0 is not None:
269
- phone_numbers.extend(phones_0)
270
- if phones_1 is not None:
271
- phone_numbers.extend(phones_1)
272
- if phones_2 is not None:
273
- phone_numbers.extend(phones_2)
274
- return list(dict.fromkeys(phone_numbers)) # Removes duplicate phone numbers
269
+ phones_0: list[str] = self._get_driver_phone_numbers()
270
+ phones_1: list[str] = self._get_cfield_phone_numbers(key=cfield_key)
271
+ phones_2: list[str] = self._get_afield_phone_numbers(key=afield_key)
272
+ return list(frozenset(phones_0 + phones_1 + phones_2))
275
273
 
276
274
  def clean_phone_numbers(self, phones: list[str]) -> list[str]:
277
275
  """Takes a list of phone numbers and returns a list of clean phone numbers."""
@@ -281,43 +279,43 @@ class WialonUnit(WialonBase):
281
279
  for clean_num in (num.split(",") if "," in num else [num])
282
280
  ]
283
281
 
284
- def _get_afield_phone_numbers(self, key: str = "to_number") -> list[str] | None:
282
+ def _get_afield_phone_numbers(self, key: str) -> list[str]:
285
283
  """
286
- Retrives any phone numbers saved in an admin field by key.
284
+ Retrives phone numbers saved in an admin field by key.
287
285
 
288
- :param key: An admin field key. Default is ``"to_number"``.
286
+ :param key: An admin field key.
289
287
  :type key: :py:obj:`str`
290
288
  :raises WialonError: If something goes wrong with Wialon.
291
- :returns: A list of phone numbers, if the unit has admin phone number fields.
292
- :rtype: :py:obj:`list` | :py:obj:`None`
289
+ :returns: A list of phone numbers.
290
+ :rtype: :py:obj:`list`
293
291
 
294
292
  """
295
- for field_name, field_val in self.afields.items():
296
- if field_name == key:
297
- return self.clean_phone_numbers([field_val])
293
+ if key not in self.afields.keys():
294
+ return []
295
+ return self.clean_phone_numbers([self.afields[key]])
298
296
 
299
- def _get_cfield_phone_numbers(self, key: str = "to_number") -> list[str] | None:
297
+ def _get_cfield_phone_numbers(self, key: str) -> list[str]:
300
298
  """
301
- Retrives any phone numbers saved in a custom field by key.
299
+ Retrives phone numbers saved in a custom field by key.
302
300
 
303
- :param key: A custom field key. Default is ``"to_number"``.
301
+ :param key: A custom field key.
304
302
  :type key: :py:obj:`str`
305
303
  :raises WialonError: If something goes wrong with Wialon.
306
- :returns: A list of phone numbers, if the unit has custom phone number fields.
307
- :rtype: :py:obj:`list` | :py:obj:`None`
304
+ :returns: A list of phone numbers.
305
+ :rtype: :py:obj:`list`
308
306
 
309
307
  """
310
- for field_name, field_val in self.cfields.items():
311
- if field_name == key:
312
- return self.clean_phone_numbers([field_val])
308
+ if key not in self.cfields.keys():
309
+ return []
310
+ return self.clean_phone_numbers([self.cfields[key]])
313
311
 
314
- def _get_driver_phone_numbers(self) -> list[str] | None:
312
+ def _get_driver_phone_numbers(self) -> list[str]:
315
313
  """
316
- Retrieves any phone numbers assigned to drivers attached to the unit.
314
+ Returns a list of phone numbers assigned to drivers attached to the unit.
317
315
 
318
316
  :raises WialonError: If something goes wrong with Wialon.
319
- :returns: A list of phone numbers, if the unit has attached drivers with phone numbers.
320
- :rtype: :py:obj:`list` | :py:obj:`None`
317
+ :returns: A list of phone numbers.
318
+ :rtype: :py:obj:`list`
321
319
 
322
320
  """
323
321
  response = self.session.wialon_api.resource_get_unit_drivers(
@@ -326,3 +324,4 @@ class WialonUnit(WialonBase):
326
324
  if response:
327
325
  dirty_phones = [driver[0].get("ph") for driver in response.values()]
328
326
  return self.clean_phone_numbers(dirty_phones)
327
+ return []
@@ -294,9 +294,9 @@ def generate_wialon_password(length: int = 32) -> str:
294
294
  - At least one special symbol.
295
295
  - At least three digits.
296
296
 
297
- :param length: Length of the generated password. Default is ``32``.
297
+ :param length: Length of the generated password. Default is :py:obj:`32`.
298
298
  :type length: :py:obj:`int`
299
- :raises ValueError: If :py:length is less than ``8`` or greater than ``64``.
299
+ :raises ValueError: If ``length`` is less than ``8`` or greater than ``64``.
300
300
  :returns: A Wialon compliant password.
301
301
  :rtype: :py:obj:`str`
302
302
 
terminusgps/aws/speech.py DELETED
@@ -1,146 +0,0 @@
1
- import asyncio
2
- import os
3
- from collections.abc import Sequence
4
- from contextlib import AsyncExitStack
5
-
6
- from aiobotocore.session import AioSession
7
-
8
-
9
- class AsyncSpeechSynthesisManager:
10
- def __init__(
11
- self, bucket_name: str | None = None, region_name: str = "us-east-1"
12
- ) -> None:
13
- self._exit_stack = AsyncExitStack()
14
- self._polly_client = None
15
- self._region_name = region_name
16
- self._bucket_name = bucket_name or os.getenv("AWS_POLLY_BUCKET_NAME", "")
17
-
18
- @property
19
- def region_name(self) -> str:
20
- """
21
- An AWS region name.
22
-
23
- :type: :py:obj:`str`
24
-
25
- """
26
- return self._region_name
27
-
28
- @property
29
- def bucket_name(self) -> str:
30
- """
31
- The AWS bucket that will host audio files.
32
-
33
- :type: :py:obj:`str`
34
-
35
- """
36
- return self._bucket_name
37
-
38
- async def __aenter__(self) -> "AsyncSpeechSynthesisManager":
39
- """
40
- Creates an asyncronous session and client.
41
-
42
- :returns: The polly manager.
43
- :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncPollyManager`
44
-
45
- """
46
- session = AioSession()
47
- self._polly_client = await self._exit_stack.enter_async_context(
48
- session.create_client("polly", region_name=self.region_name)
49
- )
50
- return self
51
-
52
- async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
53
- """
54
- Destroys the asyncronous session.
55
-
56
- :param exc_type: Exception type.
57
- :param exc_val: Exception value.
58
- :param exc_tb: Exception traceback.
59
- :returns: Nothing.
60
- :rtype: :py:obj:`None`
61
-
62
- """
63
- await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
64
-
65
- async def synthesize_speech(
66
- self,
67
- message: str,
68
- format: str = "ogg_vorbis",
69
- sample_rate: int = 22050,
70
- language_code: str = "en-US",
71
- ) -> None:
72
- """
73
- Synthesizes the message into an audio file and saves it to the S3 bucket.
74
-
75
- :param message: A message to be read aloud.
76
- :type message: :py:obj:`str`
77
- :param format: A file format. Default is ``"ogg_vorbis"``.
78
- :type format: :py:obj:`str`
79
- :param sample_rate: The audio frequency in Hz. Default is ``22050``.
80
- :type sample_rate: :py:obj:`int`
81
- :param language_code: The language to use in the synthesis. Default is ``"en-US"``.
82
- :type language_code: :py:obj:`str`
83
- :returns: The audio file URI.
84
- :rtype: :py:obj:`str`
85
-
86
- """
87
- response = await self._polly_client.start_speech_synthesis_task(
88
- **{
89
- "Engine": "standard",
90
- "LanguageCode": language_code,
91
- "OutputFormat": format,
92
- "OutputS3BucketName": self.bucket_name,
93
- "SampleRate": str(sample_rate),
94
- "Text": f"<speak>{message}</speak>",
95
- "TextType": "ssml",
96
- "VoiceId": "Joanna",
97
- }
98
- )
99
- print(f"{response = }")
100
- return response.get("SynthesisTask", {}).get("OutputUri", "")
101
-
102
- async def synthesize_speech_batch(
103
- self,
104
- messages: Sequence[str],
105
- format: str = "ogg_vorbis",
106
- sample_rate: int = 22050,
107
- language_code: str = "en-US",
108
- ) -> None:
109
- """
110
- Synthesizes a sequence of messages into audio files and saves them to the S3 bucket.
111
-
112
- :param messages: A sequence of messages to be read aloud.
113
- :type messages: :py:obj:`Sequence`
114
- :param format: A file format. Default is ``"ogg_vorbis"``.
115
- :type format: :py:obj:`str`
116
- :param sample_rate: The audio frequency in Hz. Default is ``22050``.
117
- :type sample_rate: :py:obj:`int`
118
- :param language_code: The language to use in the synthesis. Default is ``"en-US"``.
119
- :type language_code: :py:obj:`str`
120
- :returns: A list of audio file URIs.
121
- :rtype: :py:obj:`list`
122
-
123
- """
124
- await asyncio.gather(
125
- *[
126
- self.synthesize_speech(
127
- message=message,
128
- format=format,
129
- sample_rate=sample_rate,
130
- language_code=language_code,
131
- )
132
- for message in messages
133
- ]
134
- )
135
-
136
-
137
- async def main() -> None:
138
- async with AsyncSpeechSynthesisManager() as manager:
139
- uri = await manager.synthesize_speech(
140
- "Oh yeah, I'm creating another test audio file."
141
- )
142
- print(f"{uri = }")
143
-
144
-
145
- if __name__ == "__main__":
146
- asyncio.run(main())
File without changes
terminusgps/aws/utils.py DELETED
@@ -1,5 +0,0 @@
1
- import requests
2
-
3
-
4
- def get_public_ip() -> str:
5
- return requests.get("https://checkip.amazonaws.com").text.strip()