python-terminusgps 31.0.0__py3-none-any.whl → 31.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.

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: 31.0.0
3
+ Version: 31.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://app.terminusgps.com/docs/apps/python-terminusgps/index.html
6
6
  Project-URL: Repository, https://github.com/terminusgps/python-terminusgps
@@ -22,3 +22,15 @@ Requires-Dist: pandas>=2.2.3
22
22
  Requires-Dist: python-wialon>=1.2.4
23
23
  Requires-Dist: pyzbar>=0.1.9
24
24
  Requires-Dist: twilio>=9.4.5
25
+ Description-Content-Type: text/markdown
26
+
27
+ # python-terminusgps
28
+
29
+ [python-terminusgps](https://docs.terminusgps.com/) is a collection of Python libraries intended for use by Terminus GPS developers.
30
+
31
+ Currently, the following services are streamlined for web development:
32
+
33
+ * [AWS/boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
34
+ * [Authorizenet API](https://developer.authorize.net/api/reference/index.html)
35
+ * [Twilio API](https://www.twilio.com/docs)
36
+ * [Wialon Remote API](https://sdk.wialon.com/wiki/en/sidebar/remoteapi/apiref/apiref)
@@ -13,28 +13,28 @@ terminusgps/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  terminusgps/aws/connect.py,sha256=TWf2bVz6LqSNQfVD7FKRn3K71VuLOwBj05Gs-9BsROg,693
14
14
  terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
15
15
  terminusgps/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- terminusgps/django/mixins.py,sha256=jzs_dH_YDmohH86I3uPnN-66C9OPVXaDPVdRHXA6vdk,940
16
+ terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
17
17
  terminusgps/django/settings.py,sha256=vY5yNjyMu-7Ya0bIPWUvrrSLERtxHGdkfeoY4Ul6cP0,613
18
18
  terminusgps/django/utils.py,sha256=SYDQyHA5tTuVwdpZGsCtf0LpTTD0ilRKoxa8StfSTpQ,156
19
19
  terminusgps/twilio/__init__.py,sha256=dYo41F2jft_eHDWSUtSpKGSRG1bewq_qClqilUJZkYM,33
20
- terminusgps/twilio/caller.py,sha256=TiQ4f0TDDC35he1M1iSsBM2vutdGriwtV4pc17T--6I,3552
21
- terminusgps/twilio/validators.py,sha256=nn7CCBTktNjcln6M4MR0ACxEsClgioSu89LRLgIiBZk,1112
20
+ terminusgps/twilio/caller.py,sha256=Q0wcJ5aaNDGvKfht7ARwPTFdqpFoB81DmZ_K2oZixyA,5077
21
+ terminusgps/twilio/validators.py,sha256=HWCpzgDXTedofty4Zhrm2OenbgtkzrDUC08B1l15gqc,1112
22
22
  terminusgps/wialon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  terminusgps/wialon/constants.py,sha256=-fRrvtQuSAJ6hpBJzp_Xa4GDI4LuQbqZ1z1L_QFzZiE,5609
24
24
  terminusgps/wialon/flags.py,sha256=M50EdhxQ8IMnJnU0mrHK7-h8Asc6tvNiTOOfd1dBW6A,12815
25
25
  terminusgps/wialon/logger.py,sha256=nUBsaLMaffSaAHGHAM6mFXDzix9hbNecVtA3INVtii8,968
26
26
  terminusgps/wialon/session.py,sha256=emB0XHJjFObtb6WbttsXZXTeRaPztcbjAV212B_Ssek,9781
27
- terminusgps/wialon/utils.py,sha256=iRZi4uLk-qqNUXdEIbL-4ibfXh9xvEjXWmdoML9CubQ,10051
28
- terminusgps/wialon/validators.py,sha256=NNqMhoXn1M_f4Edm6oD8I7lzzXKZSqyZ6n7fR3obWj8,4241
27
+ terminusgps/wialon/utils.py,sha256=eRo-NHWEi8zq8mMWFnCs8__iDbkpPvSkCqcsdr13ltI,9953
28
+ terminusgps/wialon/validators.py,sha256=CVEpFsxqplEcpv4U2ipA6v9y5cSrMKFUXLvze-3vs4U,4223
29
29
  terminusgps/wialon/items/__init__.py,sha256=3BVthghekMvhMQSzQgBMlD_phxmKSmp3Zvmo-z0O8Bs,211
30
- terminusgps/wialon/items/base.py,sha256=4TAsmSQ-pw4mqiuL4_clN_Kvplcze4lZwL9oO1p7gkk,8001
31
- terminusgps/wialon/items/resource.py,sha256=wfUxACO4OdzT2IdCF9oBmU_7nYiH3Xvs6BripPD5IMI,14622
30
+ terminusgps/wialon/items/base.py,sha256=y0z3eW_Gz7dlusKHj3T3TH3_nPsx5yGUKuImEJSK10M,10215
31
+ terminusgps/wialon/items/resource.py,sha256=FmuY0thel8aIThSWKJXur72Sptful3MuwBfIn9bdeaY,14616
32
32
  terminusgps/wialon/items/retranslator.py,sha256=sVEWrp8q1I-tO_rMTCH7TeQDDbZryIq8d9KVnhYyhH0,3795
33
33
  terminusgps/wialon/items/route.py,sha256=amNPKZqgl8pEzKA_XJudnvBPzEsfHTmrC7uF15sZVI4,1216
34
- terminusgps/wialon/items/unit.py,sha256=ChONjm3wlFSlbNz-yEXziNbbs4LJ70F9-MI2c-p905k,10638
34
+ terminusgps/wialon/items/unit.py,sha256=jq4S8EV_yukRTTE8rr2oXpu_9AdXDFYA6-GCjkR6shw,12282
35
35
  terminusgps/wialon/items/unit_group.py,sha256=QV2ZZgICaN3n6W1pFIyMLyNKB6KYBsLyKG64i-qXoOg,4113
36
36
  terminusgps/wialon/items/user.py,sha256=N30wdejdG7LXrgafdKZH74YArGkX3xpZfTucn3m3aqM,7155
37
- python_terminusgps-31.0.0.dist-info/METADATA,sha256=y4fXM6pCMkZ0znIWvVqoAOzitHrjZDbrhSDPIy06qcE,1061
38
- python_terminusgps-31.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
- python_terminusgps-31.0.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
- python_terminusgps-31.0.0.dist-info/RECORD,,
37
+ python_terminusgps-31.1.0.dist-info/METADATA,sha256=MIfipx-9aKDFbozGWwy-i6MXtywcZMOk8O968CjS2TQ,1630
38
+ python_terminusgps-31.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ python_terminusgps-31.1.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
+ python_terminusgps-31.1.0.dist-info/RECORD,,
@@ -24,6 +24,7 @@ class HtmxTemplateResponseMixin(TemplateResponseMixin):
24
24
  def render_to_response(
25
25
  self, context: dict[str, typing.Any], **response_kwargs: typing.Any
26
26
  ) -> HttpResponse:
27
+ """Sets :py:attr:`template_name` to :py:attr:`partial_template_name` if necessary."""
27
28
  htmx_request = self.request.headers.get("HX-Request", False)
28
29
  boosted = self.request.headers.get("HX-Boosted", False)
29
30
 
@@ -17,30 +17,53 @@ if not hasattr(settings, "TWILIO_MESSAGING_SID"):
17
17
 
18
18
 
19
19
  class TwilioCaller:
20
- def __init__(self) -> None:
21
- """Sets Twilio messaging session variables."""
22
- self.client_sid = settings.TWILIO_SID
23
- self.client_token = settings.TWILIO_TOKEN
24
- self.from_number = settings.TWILIO_FROM_NUMBER
25
- self.messaging_sid = settings.TWILIO_MESSAGING_SID
20
+ def __init__(
21
+ self,
22
+ client_sid: str | None = None,
23
+ client_token: str | None = None,
24
+ from_number: str | None = None,
25
+ messaging_sid: str | None = None,
26
+ ) -> None:
27
+ """
28
+ Sets Twilio client session variables.
29
+
30
+ All parameters are optional, default values are pulled from a Django settings module.
31
+
32
+ :param client_sid: A Twilio client session id.
33
+ :type client_sid: :py:obj:`str` | :py:obj:`None`
34
+ :param client_token: A Twilio client API token.
35
+ :type client_token: :py:obj:`str` | :py:obj:`None`
36
+ :param from_number: Phone number used to send notifications.
37
+ :type from_number: :py:obj:`str` | :py:obj:`None`
38
+ :param messaging_sid: A Twilio client messaging service session id.
39
+ :type messaging_sid: :py:obj:`str` | :py:obj:`None`
40
+ :returns: Nothing.
41
+ :rtype: :py:obj:`None`
42
+
43
+ """
44
+ self._client_sid = client_sid or settings.TWILIO_SID
45
+ self._client_token = client_token or settings.TWILIO_TOKEN
46
+ self._from_number = from_number or settings.TWILIO_FROM_NUMBER
47
+ self._messaging_sid = messaging_sid or settings.TWILIO_MESSAGING_SID
26
48
 
27
49
  def __enter__(self) -> "TwilioCaller":
28
- """Creates an asyncronous Twilio client."""
50
+ """Opens a context manager and creates an asyncronous Twilio client."""
29
51
  self.client = twilio.rest.Client(
30
- self.client_sid, self.client_token, http_client=AsyncTwilioHttpClient()
52
+ self.client_sid, self._client_token, http_client=AsyncTwilioHttpClient()
31
53
  )
32
54
  return self
33
55
 
34
56
  def __exit__(self, exc_type, exc_value, exc_tb) -> None:
57
+ """Closes the context manager."""
35
58
  return None
36
59
 
37
- async def create_notification(
60
+ def create_notification(
38
61
  self, to_number: str, message: str, method: str = "sms"
39
62
  ) -> asyncio.Task[Any]:
40
63
  """
41
64
  Returns an awaitable notification task.
42
65
 
43
- Available methods are ``"sms"``, ``"call"`` and ``"phone"``.
66
+ Valid methods are ``"sms"``, ``"call"`` and ``"phone"``.
44
67
 
45
68
  :param to_number: A phone number to notify.
46
69
  :type to_number: :py:obj:`str`
@@ -48,6 +71,7 @@ class TwilioCaller:
48
71
  :type message: :py:obj:`str`
49
72
  :param method: A notification method. Default is ``"sms"``.
50
73
  :type method: :py:obj:`str`
74
+ :raises ValueError: If ``method`` is invalid.
51
75
  :returns: An awaitable task.
52
76
  :rtype: :py:obj:`~asyncio.Task`
53
77
 
@@ -100,3 +124,33 @@ class TwilioCaller:
100
124
  body=message,
101
125
  messaging_service_sid=self.messaging_sid,
102
126
  )
127
+
128
+ @property
129
+ def client_sid(self) -> str:
130
+ """
131
+ Client session id.
132
+
133
+ :type: :py:obj:`str`
134
+
135
+ """
136
+ return self._client_sid
137
+
138
+ @property
139
+ def from_number(self) -> str:
140
+ """
141
+ Origin phone number.
142
+
143
+ :type: :py:obj:`str`
144
+
145
+ """
146
+ return self._from_number
147
+
148
+ @property
149
+ def messaging_sid(self) -> str:
150
+ """
151
+ Messaging service session id.
152
+
153
+ :type: :py:obj:`str`
154
+
155
+ """
156
+ return self._messaging_sid
@@ -27,5 +27,5 @@ def validate_phone_number(value: str) -> None:
27
27
  raise ValidationError(
28
28
  _("Phone number cannot be greater than %(max)s characters, got %(len)s."),
29
29
  code="invalid",
30
- params={"max": min_length, "len": len(value)},
30
+ params={"max": max_length, "len": len(value)},
31
31
  )
@@ -9,6 +9,20 @@ class WialonBase:
9
9
  def __init__(
10
10
  self, id: str | int | None, session: WialonSession, *args, **kwargs
11
11
  ) -> None:
12
+ """
13
+ Sets the Wialon session and id for the Wialon object.
14
+
15
+ If no id was provided, :py:meth:`create` is executed to get one.
16
+
17
+ :param id: An optional Wialon object id.
18
+ :type id: :py:obj:`str` | :py:obj:`int` | :py:obj:`None`
19
+ :param session: A valid Wialon API session.
20
+ :type session: :py:obj:`~terminusgps.wialon.session.WialonSession`
21
+ :raises ValueError: If ``id`` was a string that contained non-digit values.
22
+ :returns: Nothing.
23
+ :rtype: :py:obj:`None`
24
+
25
+ """
12
26
  if isinstance(id, str) and not id.isdigit():
13
27
  raise ValueError(f"'id' must be a digit. Got '{id}'.")
14
28
 
@@ -19,13 +33,23 @@ class WialonBase:
19
33
  self._access_lvl = None
20
34
 
21
35
  def __str__(self) -> str:
36
+ """Returns the Wialon object id as a string."""
22
37
  return str(self.id)
23
38
 
24
39
  def __repr__(self) -> str:
40
+ """Returns the Wialon object type and parameters used to initialize it."""
25
41
  return f"{type(self).__name__}(id={self.id}, session={self.session.id})"
26
42
 
27
43
  def populate(self) -> None:
28
- """Retrieves and saves the latest data for the item from Wialon."""
44
+ """
45
+ Retrieves and saves the latest data for the item from Wialon.
46
+
47
+ :raises AssertionError: If :py:attr:`id` wasn't set.
48
+ :returns: Nothing.
49
+ :rtype: :py:obj:`None`
50
+
51
+ """
52
+ assert self.id, "No Wialon object id set."
29
53
  response = self.session.wialon_api.core_search_item(
30
54
  **{"id": str(self.id), "flags": flags.DataFlag.UNIT_BASE}
31
55
  )
@@ -111,6 +135,50 @@ class WialonBase:
111
135
  )
112
136
  self._name = new_name
113
137
 
138
+ def get_cfield_id(self, key: str) -> int | None:
139
+ """
140
+ Returns a custom field id by its key, if it exists.
141
+
142
+ :param key: A custom field key.
143
+ :type key: :py:obj:`str`
144
+ :returns: A custom field id, if any.
145
+ :rtype: :py:obj:`int` | :py:obj:`None`
146
+
147
+ """
148
+ cfields = (
149
+ self.session.wialon_api.core_search_item(
150
+ **{"id": str(self.id), "flags": flags.DataFlag.UNIT_CUSTOM_FIELDS}
151
+ )
152
+ .get("item", {})
153
+ .get("flds", {})
154
+ .values()
155
+ )
156
+ for field in cfields:
157
+ if field["n"] == key:
158
+ return int(field["id"])
159
+
160
+ def get_afield_id(self, key: str) -> int | None:
161
+ """
162
+ Returns an admin field id by its key, if it exists.
163
+
164
+ :param key: An admin field key.
165
+ :type key: :py:obj:`str`
166
+ :returns: An admin field id, if any.
167
+ :rtype: :py:obj:`int` | :py:obj:`None`
168
+
169
+ """
170
+ afields = (
171
+ self.session.wialon_api.core_search_item(
172
+ **{"id": str(self.id), "flags": flags.DataFlag.UNIT_ADMIN_FIELDS}
173
+ )
174
+ .get("item", {})
175
+ .get("aflds", {})
176
+ .values()
177
+ )
178
+ for field in afields:
179
+ if field["n"] == key:
180
+ return int(field["id"])
181
+
114
182
  def add_afield(self, key: str, value: str) -> None:
115
183
  """
116
184
  Adds an admin field to the Wialon object.
@@ -30,7 +30,7 @@ class WialonResource(WialonBase):
30
30
  **{
31
31
  "creatorId": creator_id,
32
32
  "name": name,
33
- "dataFlags": flags.DataFlag.UNIT_BASE.value,
33
+ "dataFlags": flags.DataFlag.UNIT_BASE,
34
34
  "skipCreatorCheck": int(skip_creator_check),
35
35
  }
36
36
  )
@@ -70,7 +70,7 @@ class WialonUnit(WialonBase):
70
70
 
71
71
  def get_position(self) -> dict | None:
72
72
  return self.session.wialon_api.core_search_item(
73
- **{"id": self.id, "flags": flags.DataFlag.UNIT_POSITION.value}
73
+ **{"id": self.id, "flags": flags.DataFlag.UNIT_POSITION}
74
74
  )
75
75
 
76
76
  @property
@@ -152,10 +152,62 @@ class WialonUnit(WialonBase):
152
152
 
153
153
  @property
154
154
  def image_url(self) -> str | None:
155
+ """Returns an absolute url to the unit's icon in Wialon."""
155
156
  if hasattr(settings, "WIALON_HOST"):
156
157
  return urljoin(settings.WIALON_HOST, self._image_uri)
157
158
  return urljoin("https://hst-api.wialon.com", self._image_uri)
158
159
 
160
+ def assign_to_number(self, value: str) -> None:
161
+ """
162
+ Sets ``value`` to the unit's ``to_number`` custom field.
163
+
164
+ :param value: A new value for the unit's ``to_number`` custom field.
165
+ :type value: :py:obj:`str`
166
+ :returns: Nothing.
167
+ :rtype: :py:obj:`None`
168
+
169
+ """
170
+ cfield_key: str = "to_number"
171
+ cfield_val: str = value
172
+ cfield_id: int | None = self.get_cfield_id(cfield_key)
173
+
174
+ if cfield_id:
175
+ self.update_cfield(cfield_id, cfield_key, cfield_val)
176
+
177
+ def assign_iccid(self, value: str) -> None:
178
+ """
179
+ Sets ``value`` to the unit's ``iccid`` admin field.
180
+
181
+ :param value: A new value for the unit's ``iccid`` custom field.
182
+ :type value: :py:obj:`str`
183
+ :returns: Nothing.
184
+ :rtype: :py:obj:`None`
185
+
186
+ """
187
+ afield_key: str = "iccid"
188
+ afield_val: str = value
189
+ afield_id: int | None = self.get_afield_id(afield_key)
190
+
191
+ if afield_id:
192
+ self.update_afield(afield_id, afield_key, afield_val)
193
+
194
+ def assign_carrier(self, value: str) -> None:
195
+ """
196
+ Sets ``value`` to the unit's ``carrier`` admin field.
197
+
198
+ :param value: A new value for the unit's ``carrier`` custom field.
199
+ :type value: :py:obj:`str`
200
+ :returns: Nothing.
201
+ :rtype: :py:obj:`None`
202
+
203
+ """
204
+ afield_key: str = "carrier"
205
+ afield_val: str = value
206
+ afield_id: int | None = self.get_afield_id(afield_key)
207
+
208
+ if afield_id:
209
+ self.update_afield(afield_id, afield_key, afield_val)
210
+
159
211
  def execute_command(
160
212
  self,
161
213
  name: str,
@@ -2,10 +2,9 @@ import secrets
2
2
  import string
3
3
  import typing
4
4
 
5
- from terminusgps.wialon import constants, flags
6
- from terminusgps.wialon.items import WialonUnit
7
- from terminusgps.wialon.items.resource import WialonResource
8
- from terminusgps.wialon.session import WialonSession
5
+ from . import constants, flags
6
+ from .items import WialonResource, WialonUnit
7
+ from .session import WialonSession
9
8
 
10
9
 
11
10
  def get_carrier_names(session: WialonSession) -> list[str]:
@@ -4,7 +4,7 @@ from django.conf import settings
4
4
  from django.core.exceptions import ImproperlyConfigured, ValidationError
5
5
  from django.utils.translation import gettext_lazy as _
6
6
 
7
- from terminusgps.wialon.session import WialonSession
7
+ from .session import WialonSession
8
8
 
9
9
  if not hasattr(settings, "WIALON_TOKEN"):
10
10
  raise ImproperlyConfigured("'WIALON_TOKEN' setting is required.")