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.
- {python_terminusgps-35.0.0.dist-info → python_terminusgps-35.4.0.dist-info}/METADATA +1 -1
- {python_terminusgps-35.0.0.dist-info → python_terminusgps-35.4.0.dist-info}/RECORD +9 -12
- terminusgps/aws/notifications.py +199 -205
- terminusgps/aws/secrets.py +11 -1
- terminusgps/wialon/items/base.py +68 -102
- terminusgps/wialon/items/unit.py +32 -33
- terminusgps/wialon/utils.py +2 -2
- terminusgps/aws/speech.py +0 -146
- terminusgps/aws/tests/test_utils.py +0 -0
- terminusgps/aws/utils.py +0 -5
- {python_terminusgps-35.0.0.dist-info → python_terminusgps-35.4.0.dist-info}/WHEEL +0 -0
- {python_terminusgps-35.0.0.dist-info → python_terminusgps-35.4.0.dist-info}/licenses/COPYING +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-terminusgps
|
|
3
|
-
Version: 35.
|
|
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=
|
|
19
|
-
terminusgps/aws/secrets.py,sha256
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
59
|
-
python_terminusgps-35.
|
|
60
|
-
python_terminusgps-35.
|
|
61
|
-
python_terminusgps-35.
|
|
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,,
|
terminusgps/aws/notifications.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) -> "
|
|
76
|
+
async def __aenter__(self) -> "AsyncNotificationManager":
|
|
42
77
|
"""
|
|
43
|
-
Creates
|
|
78
|
+
Creates asyncronous clients.
|
|
44
79
|
|
|
45
|
-
:returns: The
|
|
46
|
-
:rtype: :py:obj:`~terminusgps.aws.notifications.
|
|
80
|
+
:returns: The notification manager.
|
|
81
|
+
:rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
|
|
47
82
|
|
|
48
83
|
"""
|
|
49
84
|
session = AioSession()
|
|
50
|
-
self.
|
|
51
|
-
session.create_client("
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
106
|
+
An AWS region name.
|
|
144
107
|
|
|
145
|
-
:
|
|
146
|
-
:rtype: :py:obj:`None`
|
|
108
|
+
:type: :py:obj:`str`
|
|
147
109
|
|
|
148
110
|
"""
|
|
149
|
-
self.
|
|
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
|
-
|
|
113
|
+
@property
|
|
114
|
+
def configuration_set(self) -> str:
|
|
155
115
|
"""
|
|
156
|
-
|
|
116
|
+
The configuration set to use for messaging.
|
|
157
117
|
|
|
158
|
-
:
|
|
159
|
-
:rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
|
|
118
|
+
:type: :py:obj:`str`
|
|
160
119
|
|
|
161
120
|
"""
|
|
162
|
-
|
|
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
|
-
|
|
123
|
+
@property
|
|
124
|
+
def origin_pool(self) -> str:
|
|
169
125
|
"""
|
|
170
|
-
|
|
126
|
+
The origination pool to use for messaging.
|
|
171
127
|
|
|
172
|
-
:
|
|
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
|
-
|
|
131
|
+
return self._origin_pool_arn
|
|
180
132
|
|
|
181
133
|
@property
|
|
182
|
-
def
|
|
134
|
+
def max_sms_price(self) -> str:
|
|
183
135
|
"""
|
|
184
|
-
|
|
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.
|
|
142
|
+
return self._max_sms_price
|
|
190
143
|
|
|
191
144
|
@property
|
|
192
|
-
def
|
|
145
|
+
def max_voice_price(self) -> str:
|
|
193
146
|
"""
|
|
194
|
-
|
|
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
|
|
153
|
+
return self._max_voice_price
|
|
200
154
|
|
|
201
|
-
@
|
|
202
|
-
def
|
|
155
|
+
@property
|
|
156
|
+
def debug(self) -> bool:
|
|
203
157
|
"""
|
|
204
|
-
|
|
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
|
-
:
|
|
209
|
-
:
|
|
210
|
-
:returns: Nothing.
|
|
211
|
-
:rtype: :py:obj:`None`
|
|
160
|
+
:type: :py:obj:`bool`
|
|
161
|
+
:value: :py:obj:`False`
|
|
212
162
|
|
|
213
163
|
"""
|
|
214
|
-
|
|
215
|
-
other = str(uuid.uuid4())
|
|
216
|
-
self._group_id = other
|
|
164
|
+
return self._debug
|
|
217
165
|
|
|
218
166
|
async def send_sms(
|
|
219
|
-
self,
|
|
220
|
-
|
|
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
|
-
|
|
175
|
+
Texts ``message`` to ``phone`` via sms.
|
|
223
176
|
|
|
224
|
-
:param
|
|
225
|
-
:type
|
|
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
|
|
229
|
-
:type
|
|
230
|
-
:
|
|
231
|
-
:
|
|
232
|
-
:
|
|
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
|
-
|
|
192
|
+
assert self._pinpoint_client, "Asyncronous client wasn't set."
|
|
236
193
|
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
"
|
|
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
|
|
250
|
-
self,
|
|
251
|
-
|
|
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
|
-
|
|
219
|
+
Calls ``phone`` and reads ``message`` aloud.
|
|
254
220
|
|
|
255
|
-
:param
|
|
256
|
-
:type
|
|
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
|
|
260
|
-
:type
|
|
261
|
-
:
|
|
262
|
-
:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
"
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
"
|
|
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
|
|
256
|
+
async def batch_send_sms(
|
|
257
|
+
self, phones: Sequence[str], message: str, **kwargs
|
|
258
|
+
) -> list[dict[str, str]]:
|
|
278
259
|
"""
|
|
279
|
-
Sends
|
|
260
|
+
Sends ``message`` to all phone numbers in ``phones`` via sms.
|
|
280
261
|
|
|
281
|
-
:param
|
|
282
|
-
:type
|
|
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
|
-
:
|
|
286
|
-
:
|
|
287
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
|
280
|
+
async def batch_send_voice(
|
|
281
|
+
self, phones: Sequence[str], message: str, **kwargs
|
|
282
|
+
) -> list[dict[str, str]]:
|
|
298
283
|
"""
|
|
299
|
-
|
|
284
|
+
Calls each number in ``phones`` and reads ``message`` aloud.
|
|
300
285
|
|
|
301
|
-
:param
|
|
302
|
-
:type
|
|
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
|
-
:
|
|
306
|
-
:
|
|
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
|
-
|
|
310
|
-
|
|
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
|
)
|
terminusgps/aws/secrets.py
CHANGED
|
@@ -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
|
-
"""
|
|
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)
|
terminusgps/wialon/items/base.py
CHANGED
|
@@ -155,13 +155,24 @@ class WialonBase:
|
|
|
155
155
|
)
|
|
156
156
|
self._name = new_name
|
|
157
157
|
|
|
158
|
-
def
|
|
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
|
-
|
|
167
|
+
self.session.wialon_api.item_delete_item(**{"itemId": self.id})
|
|
161
168
|
|
|
162
|
-
|
|
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:
|
|
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
|
|
192
|
+
def _get_afield_id(self, key: str) -> int | None:
|
|
181
193
|
"""
|
|
182
|
-
Returns an admin field id by
|
|
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:
|
|
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
|
|
215
|
+
def update_afield(self, key: str, value: str) -> None:
|
|
203
216
|
"""
|
|
204
|
-
|
|
217
|
+
Updates an admin field by key.
|
|
205
218
|
|
|
206
|
-
:param key:
|
|
219
|
+
:param key: An admin field key (name).
|
|
207
220
|
:type key: :py:obj:`str`
|
|
208
|
-
:param value: A
|
|
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.
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
:
|
|
230
|
-
|
|
231
|
-
|
|
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,
|
|
245
|
+
def update_cfield(self, key: str, value: str) -> None:
|
|
256
246
|
"""
|
|
257
247
|
Updates a custom field by id.
|
|
258
248
|
|
|
259
|
-
:param
|
|
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
|
|
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.
|
|
288
|
-
**{"itemId": self.id, "name": key, "value": value}
|
|
289
|
-
)
|
|
258
|
+
field_id: int | None = self._get_cfield_id(key)
|
|
290
259
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
275
|
+
@property
|
|
276
|
+
def cfields(self) -> dict[str, str]:
|
|
309
277
|
"""
|
|
310
|
-
|
|
278
|
+
Custom fields for the Wialon object.
|
|
311
279
|
|
|
312
|
-
:
|
|
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
|
-
"""
|
|
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
|
)
|
terminusgps/wialon/items/unit.py
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
282
|
+
def _get_afield_phone_numbers(self, key: str) -> list[str]:
|
|
285
283
|
"""
|
|
286
|
-
Retrives
|
|
284
|
+
Retrives phone numbers saved in an admin field by key.
|
|
287
285
|
|
|
288
|
-
:param key: An admin field key.
|
|
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
|
|
292
|
-
:rtype: :py:obj:`list`
|
|
289
|
+
:returns: A list of phone numbers.
|
|
290
|
+
:rtype: :py:obj:`list`
|
|
293
291
|
|
|
294
292
|
"""
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
297
|
+
def _get_cfield_phone_numbers(self, key: str) -> list[str]:
|
|
300
298
|
"""
|
|
301
|
-
Retrives
|
|
299
|
+
Retrives phone numbers saved in a custom field by key.
|
|
302
300
|
|
|
303
|
-
:param key: A custom field key.
|
|
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
|
|
307
|
-
:rtype: :py:obj:`list`
|
|
304
|
+
:returns: A list of phone numbers.
|
|
305
|
+
:rtype: :py:obj:`list`
|
|
308
306
|
|
|
309
307
|
"""
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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]
|
|
312
|
+
def _get_driver_phone_numbers(self) -> list[str]:
|
|
315
313
|
"""
|
|
316
|
-
|
|
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
|
|
320
|
-
:rtype: :py:obj:`list`
|
|
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 []
|
terminusgps/wialon/utils.py
CHANGED
|
@@ -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
|
|
297
|
+
:param length: Length of the generated password. Default is :py:obj:`32`.
|
|
298
298
|
:type length: :py:obj:`int`
|
|
299
|
-
:raises ValueError: If
|
|
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
|
File without changes
|
{python_terminusgps-35.0.0.dist-info → python_terminusgps-35.4.0.dist-info}/licenses/COPYING
RENAMED
|
File without changes
|