python-terminusgps 34.1.0__py3-none-any.whl → 35.0.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 python-terminusgps might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-terminusgps
3
- Version: 34.1.0
3
+ Version: 35.0.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,8 +15,9 @@ 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=tYU1VMmkq9Bg0CBCirVtUcNBLNkl5XnCBwzQgYIlDQU,5836
18
+ terminusgps/aws/notifications.py,sha256=HQDsxvFh_8vpAvoOo0K_3nteV0ArM-kgSeMhNzBx3es,9618
19
19
  terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
20
+ terminusgps/aws/speech.py,sha256=8d4kLzc1obzW7uAhlSBmPWh_hw92d1eydcIiQROTj7A,4563
20
21
  terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
21
22
  terminusgps/aws/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
23
  terminusgps/aws/tests/test_notifications.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,7 +55,7 @@ terminusgps/wialon/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
54
55
  terminusgps/wialon/tests/test_items.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
56
  terminusgps/wialon/tests/test_session.py,sha256=9mBlYchMo9NeqRIZsmxYzrM1zBHorqu4ooxqSNppLpI,1336
56
57
  terminusgps/wialon/tests/test_utils.py,sha256=SK4PxJQGECFnzx_EQeRAQfsQ5_3FLaVcis2W9u_ibuI,1730
57
- python_terminusgps-34.1.0.dist-info/METADATA,sha256=rSD4YC5vTMvT3ML8Yi9sM5zVFSQU5NJI_Y9kR8nIgyA,1616
58
- python_terminusgps-34.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
- python_terminusgps-34.1.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
- python_terminusgps-34.1.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import os
2
3
  import uuid
3
4
  from collections.abc import Sequence
4
5
  from contextlib import AsyncExitStack
@@ -8,12 +9,138 @@ from aiobotocore.session import AioSession
8
9
  from terminusgps.django.validators import validate_e164_phone_number
9
10
 
10
11
 
12
+ class AsyncPollyManager:
13
+ def __init__(
14
+ self, bucket_name: str | None = None, region_name: str = "us-east-1"
15
+ ) -> 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
+ An AWS region name.
25
+
26
+ :type: :py:obj:`str`
27
+
28
+ """
29
+ return self._region_name
30
+
31
+ @property
32
+ def bucket_name(self) -> str:
33
+ """
34
+ The AWS bucket that will host audio files.
35
+
36
+ :type: :py:obj:`str`
37
+
38
+ """
39
+ return self._bucket_name
40
+
41
+ async def __aenter__(self) -> "AsyncPollyManager":
42
+ """
43
+ Creates an asyncronous session and client.
44
+
45
+ :returns: The polly manager.
46
+ :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncPollyManager`
47
+
48
+ """
49
+ session = AioSession()
50
+ self._polly_client = await self._exit_stack.enter_async_context(
51
+ session.create_client("polly", region_name=self.region_name)
52
+ )
53
+ return self
54
+
55
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
56
+ """
57
+ Destroys the asyncronous session.
58
+
59
+ :param exc_type: Exception type.
60
+ :param exc_val: Exception value.
61
+ :param exc_tb: Exception traceback.
62
+ :returns: Nothing.
63
+ :rtype: :py:obj:`None`
64
+
65
+ """
66
+ await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
67
+
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
+
11
138
  class AsyncNotificationManager:
12
139
  def __init__(
13
140
  self, group_id: str | None = None, region_name: str = "us-east-1"
14
141
  ) -> None:
15
142
  """
16
- Sets :py:attr:`group_id` and :py:attr:`region_name` if provided.
143
+ Sets :py:attr:`group_id` and :py:attr:`region_name`.
17
144
 
18
145
  :returns: Nothing.
19
146
  :rtype: :py:obj:`None`
@@ -76,6 +203,8 @@ class AsyncNotificationManager:
76
203
  """
77
204
  Sets :py:attr:`group_id` to ``other``.
78
205
 
206
+ If ``other`` isn't provided, instead set :py:attr:`group_id` to :py:func:`~uuid.uuid4`.
207
+
79
208
  :param other: A message group id.
80
209
  :type other: :py:obj:`str` | :py:obj:`None`
81
210
  :returns: Nothing.
@@ -86,42 +215,6 @@ class AsyncNotificationManager:
86
215
  other = str(uuid.uuid4())
87
216
  self._group_id = other
88
217
 
89
- async def send_push_batch(self, target_arns: Sequence[str], message: str) -> None:
90
- """
91
- Sends a push notification to each AWS resource by ARN in ``target_arns``.
92
-
93
- :param target_arns: A sequence of AWS resource ARNs.
94
- :type target_arns: :py:obj:`~collections.abc.Sequence`
95
- :param message: A message body.
96
- :type message: :py:obj:`str`
97
- :returns: Nothing.
98
- :rtype: :py:obj:`None`
99
-
100
- """
101
- await asyncio.gather(
102
- *[self.send_push(target_arn=arn, message=message) for arn in target_arns]
103
- )
104
-
105
- async def send_sms_batch(self, to_numbers: Sequence[str], message: str) -> None:
106
- """
107
- Sends an sms message to each phone number in ``to_numbers``.
108
-
109
- :param to_numbers: A sequence of phone numbers.
110
- :type to_numbers: :py:obj:`~collections.abc.Sequence`
111
- :param message: A message body.
112
- :type message: :py:obj:`str`
113
- :raises ValidationError: If a ``to_number`` wasn't a valid E.164 formatted phone number.
114
- :returns: Nothing.
115
- :rtype: :py:obj:`None`
116
-
117
- """
118
- for to_number in to_numbers:
119
- validate_e164_phone_number(to_number)
120
-
121
- await asyncio.gather(
122
- *[self.send_sms(to_number=num, message=message) for num in to_numbers]
123
- )
124
-
125
218
  async def send_sms(
126
219
  self, to_number: str, message: str, message_id: str | None = None
127
220
  ) -> None:
@@ -181,13 +274,38 @@ class AsyncNotificationManager:
181
274
  }
182
275
  )
183
276
 
277
+ async def send_sms_batch(self, to_numbers: Sequence[str], message: str) -> None:
278
+ """
279
+ Sends an sms message to each phone number in ``to_numbers``.
280
+
281
+ :param to_numbers: A sequence of phone numbers.
282
+ :type to_numbers: :py:obj:`~collections.abc.Sequence`
283
+ :param message: A message body.
284
+ :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`
288
+
289
+ """
290
+ for to_number in to_numbers:
291
+ validate_e164_phone_number(to_number)
184
292
 
185
- async def main() -> None:
186
- async with AsyncNotificationManager(str(uuid.uuid4())) as manager:
187
- await manager.send_sms_batch(
188
- ["+17133049421", "+18324667085", "+18322835634"], "This is a test message."
293
+ await asyncio.gather(
294
+ *[self.send_sms(to_number=num, message=message) for num in to_numbers]
189
295
  )
190
296
 
297
+ async def send_push_batch(self, target_arns: Sequence[str], message: str) -> None:
298
+ """
299
+ Sends a push notification to each AWS resource by ARN in ``target_arns``.
300
+
301
+ :param target_arns: A sequence of AWS resource ARNs.
302
+ :type target_arns: :py:obj:`~collections.abc.Sequence`
303
+ :param message: A message body.
304
+ :type message: :py:obj:`str`
305
+ :returns: Nothing.
306
+ :rtype: :py:obj:`None`
191
307
 
192
- if __name__ == "__main__":
193
- asyncio.run(main())
308
+ """
309
+ await asyncio.gather(
310
+ *[self.send_push(target_arn=arn, message=message) for arn in target_arns]
311
+ )
@@ -0,0 +1,146 @@
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())