python-terminusgps 31.0.1__py3-none-any.whl → 31.2.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: 31.0.1
3
+ Version: 31.2.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,18 @@ 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* is a collection of Python libraries intended for use by Terminus GPS developers.
30
+
31
+ [Documentation](https://docs.terminusgps.com/)
32
+
33
+ Currently, the following services are streamlined for web development:
34
+
35
+ * [AWS/boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)
36
+ * [Authorizenet API](https://developer.authorize.net/api/reference/index.html)
37
+ * [Twilio API](https://www.twilio.com/docs)
38
+ * [Wialon Remote API](https://sdk.wialon.com/wiki/en/sidebar/remoteapi/apiref/apiref)
39
+
@@ -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
17
- terminusgps/django/settings.py,sha256=vY5yNjyMu-7Ya0bIPWUvrrSLERtxHGdkfeoY4Ul6cP0,613
16
+ terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
17
+ terminusgps/django/settings.py,sha256=cKUpFXH-uYWStvT6vzclczJAKwCGNx_2wY9-fqsbmIs,624
18
18
  terminusgps/django/utils.py,sha256=SYDQyHA5tTuVwdpZGsCtf0LpTTD0ilRKoxa8StfSTpQ,156
19
19
  terminusgps/twilio/__init__.py,sha256=dYo41F2jft_eHDWSUtSpKGSRG1bewq_qClqilUJZkYM,33
20
20
  terminusgps/twilio/caller.py,sha256=Q0wcJ5aaNDGvKfht7ARwPTFdqpFoB81DmZ_K2oZixyA,5077
21
- terminusgps/twilio/validators.py,sha256=nn7CCBTktNjcln6M4MR0ACxEsClgioSu89LRLgIiBZk,1112
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
- 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
26
+ terminusgps/wialon/session.py,sha256=21ZRBUnsuK5zhkUHeccQW1lR39A4kQ_0qkLlRT1hsS8,10044
27
+ terminusgps/wialon/utils.py,sha256=JWHZ7VEeo8VNBH67gjOAQk5ITKuPllWDlJ4dVSQ5blA,11002
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.1.dist-info/METADATA,sha256=nrk9c775z_OXX5PVMLZgRkd4x_Butw1pspqwR_XyF48,1061
38
- python_terminusgps-31.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
- python_terminusgps-31.0.1.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
- python_terminusgps-31.0.1.dist-info/RECORD,,
37
+ python_terminusgps-31.2.0.dist-info/METADATA,sha256=EwPAAmEbYoZnSd8MUlqJErruNb1nS8UZ-iyKBrmE2x8,1648
38
+ python_terminusgps-31.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ python_terminusgps-31.2.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
+ python_terminusgps-31.2.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
 
@@ -1,15 +1,17 @@
1
1
  import os
2
2
  import pathlib
3
3
 
4
- DEBUG = True
5
4
  BASE_DIR = pathlib.Path(__file__).resolve().parent
5
+ DEBUG = True
6
+ SECRET_KEY = "xem*6no%8d9%^qzt2f3x3ar-uq4_+7h9myc$t0!+4%bj5us6f)"
7
+
8
+ # Secrets
9
+ CONNECT_SECRET = os.getenv("CONNECT_SECRET")
6
10
  MERCHANT_AUTH_LOGIN_ID = os.getenv("MERCHANT_AUTH_LOGIN_ID")
7
11
  MERCHANT_AUTH_TRANSACTION_KEY = os.getenv("MERCHANT_AUTH_TRANSACTION_KEY")
8
- SECRET_KEY = "xem*6no%8d9%^qzt2f3x3ar-uq4_+7h9myc$t0!+4%bj5us6f)"
9
12
  TWILIO_FROM_NUMBER = os.getenv("TWILIO_FROM_NUMBER")
10
13
  TWILIO_MESSAGING_SID = os.getenv("TWILIO_MESSAGING_SID")
11
14
  TWILIO_SID = os.getenv("TWILIO_SID")
12
15
  TWILIO_TOKEN = os.getenv("TWILIO_TOKEN")
13
16
  WIALON_ADMIN_ID = os.getenv("WIALON_ADMIN_ID")
14
17
  WIALON_TOKEN = os.getenv("WIALON_TOKEN")
15
- CONNECT_SECRET = os.getenv("CONNECT_SECRET")
@@ -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,
@@ -105,13 +105,26 @@ class WialonSession:
105
105
  return f"{self.__class__}(sid={self.id})"
106
106
 
107
107
  def __enter__(self) -> "WialonSession":
108
- """Logs into the Wialon API using a token set in :py:meth:`__init__`."""
108
+ """
109
+ Logs into the Wialon API using :py:meth:`login`.
110
+
111
+ :raises AssertionError: If the Wialon API token was not set.
112
+ :returns: A valid Wialon API session.
113
+ :rtype: :py:obj:`~terminusgps.wialon.session.WialonSession`
114
+
115
+ """
109
116
  assert self.token, "Wialon API token was not set"
110
117
  self.login(self.token)
111
118
  return self
112
119
 
113
120
  def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
114
- """Logs out of the session by calling :py:meth:`logout`."""
121
+ """
122
+ Logs out of the session by calling :py:meth:`logout`.
123
+
124
+ :returns: Nothing.
125
+ :rtype: :py:obj:`None`
126
+
127
+ """
115
128
  self.logout()
116
129
 
117
130
  @property
@@ -2,10 +2,39 @@ 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
8
+
9
+
10
+ def get_user_by_name(name: str, session: WialonSession) -> WialonUnit | None:
11
+ """
12
+ Returns a Wialon user by name, if it exists.
13
+
14
+ :param name: A Wialon user name.
15
+ :type name: :py:obj:`str`
16
+ :param session: A valid Wialon API session.
17
+ :type session: :py:obj:`~terminusgps.wialon.session.WialonSession`
18
+ :returns: A Wialon unit, if any.
19
+ :rtype: :py:obj:`~terminusgps.wialon.items.unit.WialonUnit` | :py:obj:`None`
20
+
21
+ """
22
+ results: dict = session.wialon_api.core_search_items(
23
+ **{
24
+ "spec": {
25
+ "itemsType": constants.WialonItemsType.USER,
26
+ "propName": constants.WialonItemProperty.NAME,
27
+ "propValueMask": name,
28
+ "sortType": constants.WialonItemProperty.NAME,
29
+ },
30
+ "force": 0,
31
+ "flags": flags.DataFlag.USER_BASE,
32
+ "from": 0,
33
+ "to": 0,
34
+ }
35
+ )
36
+ if results and results.get("items", [{}]):
37
+ return WialonUnit(id=results.get("items", [{}])[0].get("id"), session=session)
9
38
 
10
39
 
11
40
  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.")