python-terminusgps 34.1.0__py3-none-any.whl → 35.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-terminusgps
3
- Version: 34.1.0
3
+ Version: 35.1.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,7 +15,7 @@ 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=i6BR4G-uw5m9i4FaHzjmLZb3ufdqSpeFfg3MwTXILVI,7296
19
19
  terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
20
20
  terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
21
21
  terminusgps/aws/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -54,7 +54,7 @@ terminusgps/wialon/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
54
54
  terminusgps/wialon/tests/test_items.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  terminusgps/wialon/tests/test_session.py,sha256=9mBlYchMo9NeqRIZsmxYzrM1zBHorqu4ooxqSNppLpI,1336
56
56
  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,,
57
+ python_terminusgps-35.1.0.dist-info/METADATA,sha256=uIku-n8_UI-SA-M-ojn5NpxbNoP9Yjmn6G-bKMJ9WO8,1616
58
+ python_terminusgps-35.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
+ python_terminusgps-35.1.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
+ python_terminusgps-35.1.0.dist-info/RECORD,,
@@ -1,46 +1,75 @@
1
- import asyncio
2
- import uuid
3
- from collections.abc import Sequence
1
+ import os
4
2
  from contextlib import AsyncExitStack
5
3
 
6
4
  from aiobotocore.session import AioSession
7
5
 
8
- from terminusgps.django.validators import validate_e164_phone_number
9
-
10
6
 
11
7
  class AsyncNotificationManager:
8
+ """Asyncronously delivers notifications using `AWS End User Messaging <https://docs.aws.amazon.com/end-user-messaging/>`_."""
9
+
12
10
  def __init__(
13
- self, group_id: str | None = None, region_name: str = "us-east-1"
11
+ self,
12
+ origin_pool_arn: str | None = os.getenv("AWS_MESSAGING_ORIGIN_POOL"),
13
+ configuration_set: str | None = os.getenv("AWS_MESSAGING_CONFIGURATION"),
14
+ max_sms_price: str = "0.20",
15
+ max_voice_price: str = "0.20",
16
+ region_name: str = "us-east-1",
17
+ debug_enabled: bool = False,
14
18
  ) -> None:
15
19
  """
16
- Sets :py:attr:`group_id` and :py:attr:`region_name` if provided.
17
-
20
+ Sets attributes on the notification manager.
21
+
22
+ :param origin_pool_arn: A phone pool ARN for notification dispatch. Default is :envvar:`AWS_MESSAGING_ORIGIN_POOL`.
23
+ :type origin_pool_arn: :py:obj:`str` | :py:obj:`None`
24
+ :param configuration_set: An end-user messaging configuration set ARN. Default is :envvar:`AWS_MESSAGING_CONFIGURATION`.
25
+ :type configuration_set: :py:obj:`str` | :py:obj:`None`
26
+ :param max_sms_price: Max price to spend on a single SMS message.
27
+ :type max_sms_price: :py:obj:`str`
28
+ :param max_voice_price: Max price to spend per minute on a single voice message.
29
+ :type max_voice_price: :py:obj:`str`
30
+ :param region_name: An AWS region name used to open an AWS client.
31
+ :type region_name: :py:obj:`str`
32
+ :param debug_enabled: Whether or not to enable debug mode.
33
+ :type debug_enabled: :py:obj:`None`
34
+ :raises ValueError: If ``origin_pool_arn`` wasn't provided and :envvar:`AWS_MESSAGING_ORIGIN_POOL` wasn't set.
35
+ :raises ValueError: If ``configuration_set`` wasn't provided and :envvar:`AWS_MESSAGING_CONFIGURATION` wasn't set.
18
36
  :returns: Nothing.
19
37
  :rtype: :py:obj:`None`
20
38
 
21
39
  """
40
+ if origin_pool_arn is None:
41
+ raise ValueError(f"'origin_pool_arn' is required, got '{origin_pool_arn}'.")
42
+ if configuration_set is None:
43
+ raise ValueError(
44
+ f"'configuration_set' is required, got '{configuration_set}'."
45
+ )
46
+
22
47
  self._exit_stack = AsyncExitStack()
23
- self._sns_client = None
48
+ self._pinpoint_client = None
24
49
  self._region_name = region_name
25
- self.group_id = group_id
50
+ self._origin_pool_arn = origin_pool_arn
51
+ self._configuration_set = configuration_set
52
+ self._max_sms_price = max_sms_price
53
+ self._max_voice_price = max_voice_price
54
+ self._debug = debug_enabled
26
55
 
27
56
  async def __aenter__(self) -> "AsyncNotificationManager":
28
57
  """
29
- Creates an asyncronous session and client.
58
+ Creates asyncronous clients.
30
59
 
31
60
  :returns: The notification manager.
32
61
  :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
33
62
 
34
63
  """
35
64
  session = AioSession()
36
- self._sns_client = await self._exit_stack.enter_async_context(
37
- session.create_client("sns", region_name=self.region_name)
65
+ self._pinpoint_client = await self._exit_stack.enter_async_context(
66
+ session.create_client("pinpoint-sms-voice-v2", region_name=self.region_name)
38
67
  )
39
68
  return self
40
69
 
41
70
  async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
42
71
  """
43
- Destroys the asyncronous session.
72
+ Destroys asyncronous clients.
44
73
 
45
74
  :param exc_type: Exception type.
46
75
  :param exc_val: Exception value.
@@ -62,132 +91,107 @@ class AsyncNotificationManager:
62
91
  return self._region_name
63
92
 
64
93
  @property
65
- def group_id(self) -> str:
66
- """
67
- A message group id.
68
-
69
- :type: :py:obj:`str`
94
+ def configuration_set(self) -> str:
95
+ return self._configuration_set
70
96
 
71
- """
72
- return str(self._group_id)
73
-
74
- @group_id.setter
75
- def group_id(self, other: str | None) -> None:
76
- """
77
- Sets :py:attr:`group_id` to ``other``.
78
-
79
- :param other: A message group id.
80
- :type other: :py:obj:`str` | :py:obj:`None`
81
- :returns: Nothing.
82
- :rtype: :py:obj:`None`
83
-
84
- """
85
- if not other:
86
- other = str(uuid.uuid4())
87
- self._group_id = other
88
-
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``.
97
+ @property
98
+ def origin_pool(self) -> str:
99
+ return self._origin_pool_arn
108
100
 
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`
101
+ @property
102
+ def max_sms_price(self) -> str:
103
+ return self._max_sms_price
116
104
 
117
- """
118
- for to_number in to_numbers:
119
- validate_e164_phone_number(to_number)
105
+ @property
106
+ def max_voice_price(self) -> str:
107
+ return self._max_voice_price
120
108
 
121
- await asyncio.gather(
122
- *[self.send_sms(to_number=num, message=message) for num in to_numbers]
123
- )
109
+ @property
110
+ def debug(self) -> bool:
111
+ return self._debug
124
112
 
125
113
  async def send_sms(
126
- self, to_number: str, message: str, message_id: str | None = None
127
- ) -> None:
128
- """
129
- Sends an sms to ``to_number``.
130
-
131
- :param to_number: A destination phone number.
132
- :type to_number: :py:obj:`str`
114
+ self,
115
+ phone: str,
116
+ message: str,
117
+ ttl: int = 300,
118
+ dry_run: bool = False,
119
+ feedback: bool = False,
120
+ ) -> dict:
121
+ """
122
+ Texts ``message`` to ``phone`` via sms.
123
+
124
+ :param phone: A destination phone number.
125
+ :type phone: :py:obj:`str`
133
126
  :param message: A message body.
134
127
  :type message: :py:obj:`str`
135
- :param message_id: A message deduplication id. :py:func:`~uuid.uuid4` is used if ``message_id`` was not provided.
136
- :type message_id: :py:obj:`str` | :py:obj:`None`
137
- :raises ValidationError: If ``to_number`` wasn't a valid E.164 formatted phone number.
138
- :returns: Nothing.
139
- :rtype: :py:obj:`None`
140
-
141
- """
142
- validate_e164_phone_number(to_number)
143
-
144
- if not message_id:
145
- message_id = str(uuid.uuid4())
146
-
147
- await self._sns_client.publish(
128
+ :param ttl: Time to live in ms. Default is ``300``.
129
+ :type ttl: :py:obj:`int`
130
+ :param dry_run: Whether or not to execute the voice message as a dry run. Default is :py:obj:`False`.
131
+ :type dry_run: :py:obj:`bool`
132
+ :param feedback: Whether or not to include message feedback in the response. Default is :py:obj:`False`.
133
+ :type feedback: :py:obj:`bool`
134
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
135
+ :returns: A voice message response.
136
+ :rtype: :py:obj:`dict`
137
+
138
+ """
139
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
140
+ return await self._pinpoint_client.send_text_message(
148
141
  **{
149
- "PhoneNumber": to_number,
150
- "Message": message,
151
- "MessageDeduplicationId": message_id,
152
- "MessageGroupId": self.group_id,
142
+ "DestinationPhoneNumber": phone,
143
+ "OriginationIdentity": self.origin_pool,
144
+ "MessageBody": message,
145
+ "MessageType": "TRANSACTIONAL",
146
+ "ConfigurationSetName": self.configuration_set,
147
+ "MaxPrice": self.max_sms_price,
148
+ "TimeToLive": ttl,
149
+ "DryRun": dry_run or self.debug,
150
+ "MessageFeedbackEnabled": feedback,
153
151
  }
154
152
  )
155
153
 
156
- async def send_push(
157
- self, target_arn: str, message: str, message_id: str | None = None
158
- ) -> None:
154
+ async def send_voice(
155
+ self,
156
+ phone: str,
157
+ message: str,
158
+ ttl: int = 300,
159
+ voice_id: str = "Joanna",
160
+ dry_run: bool = False,
161
+ feedback: bool = False,
162
+ ) -> dict:
159
163
  """
160
- Sends a push notification to ``target_arn``.
164
+ Calls ``phone`` and reads ``message`` aloud.
161
165
 
162
- :param target_arn: An AWS resource ARN.
163
- :type target_arn: :py:obj:`str`
166
+ :param phone: A destination phone number.
167
+ :type phone: :py:obj:`str`
164
168
  :param message: A message body.
165
169
  :type message: :py:obj:`str`
166
- :param message_id: A message deduplication id. :py:func:`~uuid.uuid4` is used if ``message_id`` was not provided.
167
- :type message_id: :py:obj:`str` | :py:obj:`None`
168
- :returns: Nothing.
169
- :rtype: :py:obj:`None`
170
-
171
- """
172
- if not message_id:
173
- message_id = str(uuid.uuid4())
174
-
175
- await self._sns_client.publish(
170
+ :param ttl: Time to live in ms. Default is ``300``.
171
+ :type ttl: :py:obj:`int`
172
+ :param voice_id: A voice id to use for speech synthesis.
173
+ :type voice_id: :py:obj:`str`
174
+ :param dry_run: Whether or not to execute the voice message as a dry run. Default is :py:obj:`False`.
175
+ :type dry_run: :py:obj:`bool`
176
+ :param feedback: Whether or not to include message feedback in the response. Default is :py:obj:`False`.
177
+ :type feedback: :py:obj:`bool`
178
+ :raises AssertionError: If :py:attr:`_pinpoint_client` wasn't set.
179
+ :returns: A voice message response.
180
+ :rtype: :py:obj:`dict`
181
+
182
+ """
183
+ assert self._pinpoint_client, "Asyncronous client wasn't set."
184
+ return await self._pinpoint_client.send_voice_message(
176
185
  **{
177
- "TargetArn": target_arn,
178
- "Message": message,
179
- "MessageDeduplicationId": message_id,
180
- "MessageGroupId": self.group_id,
186
+ "DestinationPhoneNumber": phone,
187
+ "OriginationIdentity": self.origin_pool,
188
+ "MessageBody": message,
189
+ "MessageBodyTextType": "TEXT",
190
+ "VoiceId": voice_id.upper(),
191
+ "ConfigurationSetName": self.configuration_set,
192
+ "MaxPricePerMinute": self.max_voice_price,
193
+ "TimeToLive": ttl,
194
+ "DryRun": dry_run or self.debug,
195
+ "MessageFeedbackEnabled": feedback,
181
196
  }
182
197
  )
183
-
184
-
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."
189
- )
190
-
191
-
192
- if __name__ == "__main__":
193
- asyncio.run(main())