python-terminusgps 33.3.1__py3-none-any.whl → 34.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: 33.3.1
3
+ Version: 34.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
@@ -1,23 +1,27 @@
1
1
  terminusgps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  terminusgps/authorizenet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- terminusgps/authorizenet/auth.py,sha256=umNpOXObDrIt2K40padWBedQl7tVAmEg2alBWhGk-qQ,1238
3
+ terminusgps/authorizenet/auth.py,sha256=g2tJPxP9V1nXnUcKzhHSRSs6ruSIHej5_hcf8P7bod8,1262
4
4
  terminusgps/authorizenet/constants.py,sha256=xmulPmiBLhFfZoc7Il3l-DZA9Nq5HSaJmJKU-v-5X-Y,420
5
5
  terminusgps/authorizenet/controllers.py,sha256=9t62qdGpmAkP4tNyPefd7vBRutXvMWsxql6z_pg52Bg,2146
6
6
  terminusgps/authorizenet/utils.py,sha256=FrKqXWrplcgiET1rDTmUaqmN_ze7j93jWgfFeJR8rlg,7073
7
7
  terminusgps/authorizenet/profiles/__init__.py,sha256=IG4XCEEYtHKqXOatlZlob6J3ukAgF7cFR-RXxOSMS1c,161
8
8
  terminusgps/authorizenet/profiles/addresses.py,sha256=xDlnFlIjuR7NVXwfj5aJT9E_GolnYFuYASkfenvWfm8,6292
9
- terminusgps/authorizenet/profiles/base.py,sha256=lPc8Pugp5EcXtZmeBpeqicij7l0ROGvMYTV8J0fLLmQ,3768
9
+ terminusgps/authorizenet/profiles/base.py,sha256=Pu1NGrvC5Ox2W1oZuVcPqUw3aUoSK191Evexw1U6_3k,3940
10
10
  terminusgps/authorizenet/profiles/customers.py,sha256=ArXOHPgyBu01tgBPHHYyezb4PIeXh70IWjYCy-lHpWg,11734
11
- terminusgps/authorizenet/profiles/payments.py,sha256=MmjvE4ZSfgyGPRUzeREB_dX_n6QfpC3zGIlqFnIC8BQ,10956
11
+ terminusgps/authorizenet/profiles/payments.py,sha256=nhudGuJ6RzmK0MrhDVuYL2BxvyzKNpliBIfIpFU5Y-4,11038
12
12
  terminusgps/authorizenet/profiles/subscriptions.py,sha256=m_jb9GqeD1tLmeqSnaGwJrzPcQOulEkF8K1wCaqKR24,7924
13
13
  terminusgps/authorizenet/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  terminusgps/authorizenet/tests/test_auth.py,sha256=sdByYrcv8RKjcJckOHbyHH1z_M4qrATFI3qj_XDQXbk,1624
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/notifier.py,sha256=0w7tuMJ2tslMXKrLYcHi8kkxPsbvgfyFajkpD4UwlLc,1361
18
+ terminusgps/aws/notifications.py,sha256=N5p9EqN4wqBRJAMEFOjyseVatpwsxlB0QrHyqFDK5SI,5698
19
19
  terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
20
20
  terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
21
+ terminusgps/aws/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ terminusgps/aws/tests/test_notifications.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ terminusgps/aws/tests/test_secrets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ terminusgps/aws/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
25
  terminusgps/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
26
  terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
23
27
  terminusgps/django/settings.py,sha256=yJIcBE0xjY60AXF_gonY5hS3ofseqPW2k9uSoELgoho,639
@@ -32,10 +36,6 @@ terminusgps/django/tests/test_mixins.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
32
36
  terminusgps/django/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
37
  terminusgps/twilio/__init__.py,sha256=dYo41F2jft_eHDWSUtSpKGSRG1bewq_qClqilUJZkYM,33
34
38
  terminusgps/twilio/caller.py,sha256=l53-IxXn0wXc_oDcDxfhlrKPz-F4_KfMPV1dAzZjNo4,5060
35
- terminusgps/twilio/validators.py,sha256=HWCpzgDXTedofty4Zhrm2OenbgtkzrDUC08B1l15gqc,1112
36
- terminusgps/twilio/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- terminusgps/twilio/tests/test_caller.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- terminusgps/twilio/tests/test_validators.py,sha256=CETv7Mcr19UMMfh1UaWSV26FiOeh2WCK_VutfKXnf7g,1428
39
39
  terminusgps/wialon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  terminusgps/wialon/constants.py,sha256=Ju2XL2A4DPFD-w8NjYqEfnnqregl1luPT0XGhGSUnO8,6316
41
41
  terminusgps/wialon/flags.py,sha256=M50EdhxQ8IMnJnU0mrHK7-h8Asc6tvNiTOOfd1dBW6A,12815
@@ -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-33.3.1.dist-info/METADATA,sha256=BoV05Kh_54bDSH1P82Pe7MhHm3j8OTqBMqwZiEMrC0w,1616
58
- python_terminusgps-33.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
- python_terminusgps-33.3.1.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
- python_terminusgps-33.3.1.dist-info/RECORD,,
57
+ python_terminusgps-34.0.0.dist-info/METADATA,sha256=OLumu5xwbGDw-7yR3niZdmytYWiKwS80xQMazYTcy5g,1616
58
+ python_terminusgps-34.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
+ python_terminusgps-34.0.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
60
+ python_terminusgps-34.0.0.dist-info/RECORD,,
@@ -10,7 +10,7 @@ if not settings.configured:
10
10
 
11
11
  def get_merchant_auth() -> merchantAuthenticationType:
12
12
  """
13
- Returns the merchant authentication information for Authorizenet API requests.
13
+ Returns the current merchant authentication information for Authorizenet API requests.
14
14
 
15
15
  :returns: A merchant authentication object.
16
16
  :rtype: :py:obj:`~authorizenet.apicontractsv1.merchantAuthenticationType`
@@ -24,7 +24,7 @@ def get_merchant_auth() -> merchantAuthenticationType:
24
24
 
25
25
  def get_environment() -> str:
26
26
  """
27
- Returns the environment for Authorizenet API requests.
27
+ Returns the current environment for Authorizenet API requests.
28
28
 
29
29
  :returns: An Authorizenet API environment string.
30
30
  :rtype: :py:obj:`str`
@@ -35,7 +35,7 @@ def get_environment() -> str:
35
35
 
36
36
  def get_validation_mode() -> str:
37
37
  """
38
- Returns the validation mode for Authorizenet API requests.
38
+ Returns the current validation mode for Authorizenet API requests.
39
39
 
40
40
  :returns: An Authorizenet API validation string.
41
41
  :rtype: :py:obj:`str`
@@ -62,6 +62,17 @@ class AuthorizenetProfileBase(AuthorizenetControllerExecutor):
62
62
  """
63
63
  return get_merchant_auth()
64
64
 
65
+ @property
66
+ def validationMode(self) -> str:
67
+ """
68
+ The current Authorizenet API validation mode.
69
+
70
+ :returns: An Authorizenet API validation mode string.
71
+ :rtype: :py:obj:`str`
72
+
73
+ """
74
+ return get_validation_mode()
75
+
65
76
 
66
77
  class AuthorizenetSubProfileBase(AuthorizenetProfileBase):
67
78
  """Base class for Authorizenet 'sub-profiles', profiles that have an associated customer profile."""
@@ -83,10 +94,6 @@ class AuthorizenetSubProfileBase(AuthorizenetProfileBase):
83
94
  self.customerProfileId = customer_profile_id
84
95
  self.default = default
85
96
 
86
- @property
87
- def validationMode(self) -> str:
88
- return get_validation_mode()
89
-
90
97
  @property
91
98
  def customerProfileId(self) -> str:
92
99
  """
@@ -8,7 +8,12 @@ class PaymentProfile(AuthorizenetSubProfileBase):
8
8
 
9
9
  @property
10
10
  def last_4(self) -> str | None:
11
- """Last 4 digits of the payment profile credit card."""
11
+ """
12
+ Last 4 digits of the payment profile credit card.
13
+
14
+ :type: :py:obj:`str` | :py:obj:`None`
15
+
16
+ """
12
17
  if self.id:
13
18
  response = self._authorizenet_get_payment_profile()
14
19
  return str(response.paymentProfile.payment.creditCard.cardNumber)[-4:]
@@ -31,7 +36,7 @@ class PaymentProfile(AuthorizenetSubProfileBase):
31
36
  """
32
37
  return int(
33
38
  self._authorizenet_create_payment_profile(
34
- address, payment
39
+ address=address, payment=payment
35
40
  ).customerPaymentProfileId
36
41
  )
37
42
 
@@ -0,0 +1,193 @@
1
+ import asyncio
2
+ import uuid
3
+ from collections.abc import Sequence
4
+ from contextlib import AsyncExitStack
5
+
6
+ from aiobotocore.session import AioSession
7
+
8
+ from terminusgps.django.validators import validate_e164_phone_number
9
+
10
+
11
+ class AsyncNotificationManager:
12
+ def __init__(
13
+ self, group_id: str | None = None, region_name: str = "us-east-1"
14
+ ) -> None:
15
+ """
16
+ Sets :py:attr:`group_id` and :py:attr:`region_name` if provided.
17
+
18
+ :returns: Nothing.
19
+ :rtype: :py:obj:`None`
20
+
21
+ """
22
+ self._exit_stack = AsyncExitStack()
23
+ self._sns_client = None
24
+ self._region_name = region_name
25
+ self.group_id = group_id
26
+
27
+ async def __aenter__(self) -> "AsyncNotificationManager":
28
+ """
29
+ Creates an asyncronous session and client.
30
+
31
+ :returns: The notification manager.
32
+ :rtype: :py:obj:`~terminusgps.aws.notifications.AsyncNotificationManager`
33
+
34
+ """
35
+ session = AioSession()
36
+ self._sns_client = await self._exit_stack.enter_async_context(
37
+ session.create_client("sns", region_name=self.region_name)
38
+ )
39
+ return self
40
+
41
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
42
+ """
43
+ Destroys the asyncronous session.
44
+
45
+ :param exc_type: Exception type.
46
+ :param exc_val: Exception value.
47
+ :param exc_tb: Exception traceback.
48
+ :returns: Nothing.
49
+ :rtype: :py:obj:`None`
50
+
51
+ """
52
+ await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
53
+
54
+ @property
55
+ def region_name(self) -> str:
56
+ """
57
+ An AWS region name.
58
+
59
+ :type: :py:obj:`str`
60
+
61
+ """
62
+ return self._region_name
63
+
64
+ @property
65
+ def group_id(self) -> str:
66
+ """
67
+ A message group id.
68
+
69
+ :type: :py:obj:`str`
70
+
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
+ *[
103
+ self.send_push(
104
+ target_arn=arn, message=message, message_id=str(uuid.uuid4())
105
+ )
106
+ for arn in target_arns
107
+ ]
108
+ )
109
+
110
+ async def send_sms_batch(self, to_numbers: Sequence[str], message: str) -> None:
111
+ """
112
+ Sends an sms message to each phone number in ``to_numbers``.
113
+
114
+ :param to_numbers: A sequence of phone numbers.
115
+ :type to_numbers: :py:obj:`~collections.abc.Sequence`
116
+ :param message: A message body.
117
+ :type message: :py:obj:`str`
118
+ :raises ValidationError: If a ``to_number`` wasn't a valid E.164 formatted phone number.
119
+ :returns: Nothing.
120
+ :rtype: :py:obj:`None`
121
+
122
+ """
123
+ for to_number in to_numbers:
124
+ validate_e164_phone_number(to_number)
125
+
126
+ await asyncio.gather(
127
+ *[
128
+ self.send_sms(
129
+ to_number=num, message=message, message_id=str(uuid.uuid4())
130
+ )
131
+ for num in to_numbers
132
+ ]
133
+ )
134
+
135
+ async def send_sms(self, to_number: str, message: str, message_id: str) -> None:
136
+ """
137
+ Sends an sms to ``to_number``.
138
+
139
+ :param to_number: A destination phone number.
140
+ :type to_number: :py:obj:`str`
141
+ :param message: A message body.
142
+ :type message: :py:obj:`str`
143
+ :param message_id: A message deduplication id.
144
+ :type message_id: :py:obj:`str`
145
+ :raises ValidationError: If ``to_number`` wasn't a valid E.164 formatted phone number.
146
+ :returns: Nothing.
147
+ :rtype: :py:obj:`None`
148
+
149
+ """
150
+ validate_e164_phone_number(to_number)
151
+
152
+ await self._sns_client.publish(
153
+ **{
154
+ "PhoneNumber": to_number,
155
+ "Message": message,
156
+ "MessageDeduplicationId": message_id,
157
+ "MessageGroupId": self.group_id,
158
+ }
159
+ )
160
+
161
+ async def send_push(self, target_arn: str, message: str, message_id: str) -> None:
162
+ """
163
+ Sends a push notification to ``target_arn``.
164
+
165
+ :param target_arn: An AWS resource ARN.
166
+ :type target_arn: :py:obj:`str`
167
+ :param message: A message body.
168
+ :type message: :py:obj:`str`
169
+ :param message_id: A message deduplication id.
170
+ :type message_id: :py:obj:`str`
171
+ :returns: Nothing.
172
+ :rtype: :py:obj:`None`
173
+
174
+ """
175
+ await self._sns_client.publish(
176
+ **{
177
+ "TargetArn": target_arn,
178
+ "Message": message,
179
+ "MessageDeduplicationId": message_id,
180
+ "MessageGroupId": self.group_id,
181
+ }
182
+ )
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())
File without changes
File without changes
@@ -1,49 +0,0 @@
1
- import asyncio
2
- import uuid
3
- from contextlib import AsyncExitStack
4
-
5
- from aiobotocore.session import AioSession
6
-
7
- from terminusgps.django.validators import validate_e164_phone_number
8
-
9
-
10
- class AWSNotifier:
11
- def __init__(self, region: str = "us-east-1") -> None:
12
- self._client = None
13
- self._region = region
14
- self._exit_stack = AsyncExitStack()
15
-
16
- async def __aenter__(self):
17
- session = AioSession()
18
- self._client = await self._exit_stack.enter_async_context(
19
- session.create_client("sns")
20
- )
21
-
22
- async def __aexit__(self, exc_type, exc_val, exc_tb):
23
- await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
24
-
25
- async def send_sms(
26
- self, phone_number: str, message: str, message_id: str, group_id: str
27
- ) -> dict:
28
- validate_e164_phone_number(phone_number)
29
- await self._client.publish(
30
- **{
31
- "PhoneNumber": phone_number,
32
- "Message": message,
33
- "MessageDeduplicationId": message_id,
34
- "MessageGroupId": group_id,
35
- }
36
- )
37
-
38
-
39
- async def main() -> None:
40
- notifier = AWSNotifier()
41
- message_id = str(uuid.uuid4())
42
- await notifier.send_sms(
43
- "+17133049421", "This is a test message", message_id, "message-group"
44
- )
45
- return
46
-
47
-
48
- if __name__ == "__main__":
49
- asyncio.run(main())
@@ -1,40 +0,0 @@
1
- from unittest import TestCase
2
-
3
- import django
4
- from django.core.exceptions import ValidationError
5
-
6
- from terminusgps.twilio import validators
7
-
8
- django.setup()
9
-
10
-
11
- class PhoneNumberValidatorTestCase(TestCase):
12
- def setUp(self) -> None:
13
- self.validator = validators.validate_phone_number
14
-
15
- def test_phone_without_plus(self) -> None:
16
- """Fails if a phone number without a '+' leading character passes validation."""
17
- with self.assertRaises(ValidationError) as context:
18
- self.validator("15555555555")
19
- self.assertTrue(
20
- "Phone number must begin with a '+', got '15555555555'."
21
- in str(context.exception)
22
- )
23
-
24
- def test_phone_min_length(self) -> None:
25
- """Fails if a phone number of 8 characters in length passes validation."""
26
- with self.assertRaises(ValidationError) as context:
27
- self.validator("+1555555")
28
- self.assertTrue(
29
- "Phone number cannot be less than 12 characters, got 8."
30
- in str(context.exception)
31
- )
32
-
33
- def test_phone_max_length(self) -> None:
34
- """Fails if a phone number of 20 characters in length passes validation."""
35
- with self.assertRaises(ValidationError) as context:
36
- self.validator("+1555555555555555555")
37
- self.assertTrue(
38
- "Phone number cannot be greater than 19 characters, got 20."
39
- in str(context.exception)
40
- )
@@ -1,31 +0,0 @@
1
- from django.core.exceptions import ValidationError
2
- from django.utils.translation import gettext_lazy as _
3
-
4
-
5
- def validate_phone_number(value: str) -> None:
6
- min_length, max_length = 12, 19
7
-
8
- if not value.startswith("+"):
9
- raise ValidationError(
10
- _("Phone number must begin with a '+', got '%(value)s'."),
11
- code="invalid",
12
- params={"value": value},
13
- )
14
- if not value[1:].isdigit():
15
- raise ValidationError(
16
- _("Phone number must be '+' followed by a digit, got '%(value)s'"),
17
- code="invalid",
18
- params={"value": value},
19
- )
20
- if len(value) < min_length:
21
- raise ValidationError(
22
- _("Phone number cannot be less than %(min)s characters, got %(len)s."),
23
- code="invalid",
24
- params={"min": min_length, "len": len(value)},
25
- )
26
- if len(value) > max_length:
27
- raise ValidationError(
28
- _("Phone number cannot be greater than %(max)s characters, got %(len)s."),
29
- code="invalid",
30
- params={"max": max_length, "len": len(value)},
31
- )
File without changes