python-terminusgps 33.3.0__py3-none-any.whl → 33.5.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-33.3.0.dist-info → python_terminusgps-33.5.0.dist-info}/METADATA +2 -1
- {python_terminusgps-33.3.0.dist-info → python_terminusgps-33.5.0.dist-info}/RECORD +6 -9
- terminusgps/aws/notifier.py +49 -0
- terminusgps/django/validators.py +93 -0
- terminusgps/aws/connect.py +0 -21
- terminusgps/twilio/tests/__init__.py +0 -0
- terminusgps/twilio/tests/test_caller.py +0 -0
- terminusgps/twilio/tests/test_validators.py +0 -40
- terminusgps/twilio/validators.py +0 -31
- {python_terminusgps-33.3.0.dist-info → python_terminusgps-33.5.0.dist-info}/WHEEL +0 -0
- {python_terminusgps-33.3.0.dist-info → python_terminusgps-33.5.0.dist-info}/licenses/COPYING +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-terminusgps
|
|
3
|
-
Version: 33.
|
|
3
|
+
Version: 33.5.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
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Python: >=3.12
|
|
15
|
+
Requires-Dist: aiobotocore>=2.21.1
|
|
15
16
|
Requires-Dist: argparse>=1.4.0
|
|
16
17
|
Requires-Dist: authorizenet>=1.1.5
|
|
17
18
|
Requires-Dist: boto3>=1.34.144
|
|
@@ -15,13 +15,14 @@ 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/
|
|
18
|
+
terminusgps/aws/notifier.py,sha256=0w7tuMJ2tslMXKrLYcHi8kkxPsbvgfyFajkpD4UwlLc,1361
|
|
19
19
|
terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
|
|
20
20
|
terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
|
|
21
21
|
terminusgps/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
|
|
23
23
|
terminusgps/django/settings.py,sha256=yJIcBE0xjY60AXF_gonY5hS3ofseqPW2k9uSoELgoho,639
|
|
24
24
|
terminusgps/django/utils.py,sha256=SYDQyHA5tTuVwdpZGsCtf0LpTTD0ilRKoxa8StfSTpQ,156
|
|
25
|
+
terminusgps/django/validators.py,sha256=dnENfniiHRJjZhQQ76dIcjfwuq-mkjQahKegZYgmryc,3342
|
|
25
26
|
terminusgps/django/forms/__init__.py,sha256=hh8Z9AC_u2m2bu3h6sdmPyndfQneCEVovENPHmoTDC0,21
|
|
26
27
|
terminusgps/django/forms/fields.py,sha256=BfuPzOZyGvWsEzONW2h4i01fhoL0IS0-gDdBKW4jHgI,2225
|
|
27
28
|
terminusgps/django/forms/forms.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -31,10 +32,6 @@ terminusgps/django/tests/test_mixins.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
|
31
32
|
terminusgps/django/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
terminusgps/twilio/__init__.py,sha256=dYo41F2jft_eHDWSUtSpKGSRG1bewq_qClqilUJZkYM,33
|
|
33
34
|
terminusgps/twilio/caller.py,sha256=l53-IxXn0wXc_oDcDxfhlrKPz-F4_KfMPV1dAzZjNo4,5060
|
|
34
|
-
terminusgps/twilio/validators.py,sha256=HWCpzgDXTedofty4Zhrm2OenbgtkzrDUC08B1l15gqc,1112
|
|
35
|
-
terminusgps/twilio/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
terminusgps/twilio/tests/test_caller.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
terminusgps/twilio/tests/test_validators.py,sha256=CETv7Mcr19UMMfh1UaWSV26FiOeh2WCK_VutfKXnf7g,1428
|
|
38
35
|
terminusgps/wialon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
36
|
terminusgps/wialon/constants.py,sha256=Ju2XL2A4DPFD-w8NjYqEfnnqregl1luPT0XGhGSUnO8,6316
|
|
40
37
|
terminusgps/wialon/flags.py,sha256=M50EdhxQ8IMnJnU0mrHK7-h8Asc6tvNiTOOfd1dBW6A,12815
|
|
@@ -53,7 +50,7 @@ terminusgps/wialon/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
53
50
|
terminusgps/wialon/tests/test_items.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
51
|
terminusgps/wialon/tests/test_session.py,sha256=9mBlYchMo9NeqRIZsmxYzrM1zBHorqu4ooxqSNppLpI,1336
|
|
55
52
|
terminusgps/wialon/tests/test_utils.py,sha256=SK4PxJQGECFnzx_EQeRAQfsQ5_3FLaVcis2W9u_ibuI,1730
|
|
56
|
-
python_terminusgps-33.
|
|
57
|
-
python_terminusgps-33.
|
|
58
|
-
python_terminusgps-33.
|
|
59
|
-
python_terminusgps-33.
|
|
53
|
+
python_terminusgps-33.5.0.dist-info/METADATA,sha256=Jd4lLTQC74E24wIMEG6WDYXiYg0e4uY8i-LTxwWkEvs,1616
|
|
54
|
+
python_terminusgps-33.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
55
|
+
python_terminusgps-33.5.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
56
|
+
python_terminusgps-33.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,49 @@
|
|
|
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())
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
|
|
4
|
+
VALID_COUNTRY_CODES = ("+1", "+52")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def validate_e164_phone_number(value: str) -> None:
|
|
8
|
+
"""Raises :py:exec:`~django.core.exceptions.ValidationError` if the value is not a valid `E.164 <https://en.wikipedia.org/wiki/E.164>`_ formatted phone number."""
|
|
9
|
+
if not value.startswith("+"):
|
|
10
|
+
raise ValidationError(
|
|
11
|
+
_("E.164 phone number must begin with a '+', got '%(char)s'."),
|
|
12
|
+
code="invalid",
|
|
13
|
+
params={"char": value[0]},
|
|
14
|
+
)
|
|
15
|
+
if " " in value:
|
|
16
|
+
raise ValidationError(
|
|
17
|
+
_("E.164 phone number cannot contain spaces, got '%(value)s'."),
|
|
18
|
+
code="invalid",
|
|
19
|
+
params={"value": value},
|
|
20
|
+
)
|
|
21
|
+
if "-" in value:
|
|
22
|
+
raise ValidationError(
|
|
23
|
+
_("E.164 phone number cannot contain hyphens, got '%(value)s'."),
|
|
24
|
+
code="invalid",
|
|
25
|
+
params={"value": value},
|
|
26
|
+
)
|
|
27
|
+
if len(value) < 12:
|
|
28
|
+
raise ValidationError(
|
|
29
|
+
_(
|
|
30
|
+
"E.164 phone number cannot be less than 12 characters in length, got %(len)s."
|
|
31
|
+
),
|
|
32
|
+
code="invalid",
|
|
33
|
+
params={"len": len(value)},
|
|
34
|
+
)
|
|
35
|
+
if len(value) > 15:
|
|
36
|
+
raise ValidationError(
|
|
37
|
+
_(
|
|
38
|
+
"E.164 phone number cannot be greater than 15 characters in length, got %(len)s."
|
|
39
|
+
),
|
|
40
|
+
code="invalid",
|
|
41
|
+
params={"len": len(value)},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
country_code: str = value[:-10]
|
|
45
|
+
area_code: str = value[-10:-7]
|
|
46
|
+
subscriber_number: str = value[-7:]
|
|
47
|
+
|
|
48
|
+
if len(country_code) < 2 or len(country_code) > 5:
|
|
49
|
+
raise ValidationError(
|
|
50
|
+
_(
|
|
51
|
+
"E.164 phone number country code must be between 2 and 5 characters in length, got %(len)s."
|
|
52
|
+
),
|
|
53
|
+
code="invalid",
|
|
54
|
+
params={"len": len(country_code)},
|
|
55
|
+
)
|
|
56
|
+
if country_code not in VALID_COUNTRY_CODES:
|
|
57
|
+
raise ValidationError(
|
|
58
|
+
_(
|
|
59
|
+
"E.164 phone number cannot contain an invalid country code, got '%(country_code)s'."
|
|
60
|
+
),
|
|
61
|
+
code="invalid",
|
|
62
|
+
params={"country_code": country_code},
|
|
63
|
+
)
|
|
64
|
+
if not len(area_code) == 3:
|
|
65
|
+
raise ValidationError(
|
|
66
|
+
_(
|
|
67
|
+
"E.164 phone number must contain a 3-digit area code, got '%(area_code)s'."
|
|
68
|
+
),
|
|
69
|
+
code="invalid",
|
|
70
|
+
params={"area_code": area_code},
|
|
71
|
+
)
|
|
72
|
+
if not area_code.isdigit():
|
|
73
|
+
raise ValidationError(
|
|
74
|
+
_("E.164 phone number must have a valid area code, got '%(area_code)s'"),
|
|
75
|
+
code="invalid",
|
|
76
|
+
params={"area_code": area_code},
|
|
77
|
+
)
|
|
78
|
+
if not len(subscriber_number) == 7:
|
|
79
|
+
raise ValidationError(
|
|
80
|
+
_(
|
|
81
|
+
"E.164 phone number must contain a 7-digit subscriber number, got '%(subscriber_number)s'."
|
|
82
|
+
),
|
|
83
|
+
code="invalid",
|
|
84
|
+
params={"subscriber_number": subscriber_number},
|
|
85
|
+
)
|
|
86
|
+
if not subscriber_number.isdigit():
|
|
87
|
+
raise ValidationError(
|
|
88
|
+
_(
|
|
89
|
+
"E.164 phone number must have a valid subscriber number, got '%(subscriber_number)s'"
|
|
90
|
+
),
|
|
91
|
+
code="invalid",
|
|
92
|
+
params={"subscriber_number": subscriber_number},
|
|
93
|
+
)
|
terminusgps/aws/connect.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
|
|
3
|
-
import jwt
|
|
4
|
-
from django.conf import settings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_aws_connect_jwt(widget_id: str, expires_in: int = 500) -> str:
|
|
8
|
-
if expires_in > 600:
|
|
9
|
-
raise ValueError(f"'expires_in' must be less than 600, got '{expires_in}'.")
|
|
10
|
-
|
|
11
|
-
payload = {
|
|
12
|
-
"sub": widget_id,
|
|
13
|
-
"iat": datetime.datetime.utcnow(),
|
|
14
|
-
"exp": datetime.datetime.utcnow() - datetime.timedelta(seconds=expires_in),
|
|
15
|
-
"segmentAttributes": {"connect:Subtype": {"ValueString": "connect:Guide"}},
|
|
16
|
-
}
|
|
17
|
-
header = {"typ": "JWT", "alg": "HS256"}
|
|
18
|
-
encoded_token = jwt.encode(
|
|
19
|
-
payload, settings.CONNECT_SECRET, algorithm=header["alg"], headers=header
|
|
20
|
-
)
|
|
21
|
-
return encoded_token
|
|
File without changes
|
|
File without changes
|
|
@@ -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
|
-
)
|
terminusgps/twilio/validators.py
DELETED
|
@@ -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
|
{python_terminusgps-33.3.0.dist-info → python_terminusgps-33.5.0.dist-info}/licenses/COPYING
RENAMED
|
File without changes
|