rtls-sdk 0.6.3__tar.gz → 0.7.0__tar.gz

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.
Files changed (103) hide show
  1. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/CHANGELOG.md +14 -1
  2. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/PKG-INFO +2 -2
  3. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/pyproject.toml +12 -2
  4. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/__init__.py +4 -0
  5. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/__init__.py +3 -0
  6. rtls_sdk-0.7.0/src/rtls_sdk/models/zone_download_status.py +51 -0
  7. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/zones.py +47 -1
  8. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/.gitignore +0 -0
  9. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/LICENSE +0 -0
  10. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/README.md +0 -0
  11. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_auth.py +0 -0
  12. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_client.py +0 -0
  13. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_envelope.py +0 -0
  14. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_http.py +0 -0
  15. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_logging.py +0 -0
  16. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_pagination.py +0 -0
  17. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_query.py +0 -0
  18. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_time.py +0 -0
  19. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/_ws.py +0 -0
  20. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/__init__.py +0 -0
  21. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/auth.py +0 -0
  22. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/context.py +0 -0
  23. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/groups.py +0 -0
  24. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/nodes.py +0 -0
  25. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/reports.py +0 -0
  26. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/system.py +0 -0
  27. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/tags.py +0 -0
  28. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/users.py +0 -0
  29. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/compounds/zones.py +0 -0
  30. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/errors.py +0 -0
  31. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/_base.py +0 -0
  32. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/alarm.py +0 -0
  33. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/anchor.py +0 -0
  34. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/anchor_association.py +0 -0
  35. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/area.py +0 -0
  36. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/bulk.py +0 -0
  37. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/company.py +0 -0
  38. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/csv_blob.py +0 -0
  39. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/floorplan.py +0 -0
  40. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/group.py +0 -0
  41. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/heatmap.py +0 -0
  42. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/import_result.py +0 -0
  43. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/lsb_settings.py +0 -0
  44. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/network.py +0 -0
  45. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/node.py +0 -0
  46. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/node_params.py +0 -0
  47. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/notification.py +0 -0
  48. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/position.py +0 -0
  49. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/project.py +0 -0
  50. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/pws.py +0 -0
  51. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/report.py +0 -0
  52. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/schedule.py +0 -0
  53. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/session_context.py +0 -0
  54. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/site.py +0 -0
  55. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/subscriber.py +0 -0
  56. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/system.py +0 -0
  57. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/system_health.py +0 -0
  58. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/tag.py +0 -0
  59. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/tag_association.py +0 -0
  60. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/tag_template.py +0 -0
  61. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/user.py +0 -0
  62. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/__init__.py +0 -0
  63. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/_base.py +0 -0
  64. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/alarm.py +0 -0
  65. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/notify.py +0 -0
  66. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/ota.py +0 -0
  67. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/param_p.py +0 -0
  68. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/position.py +0 -0
  69. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/sensor.py +0 -0
  70. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/user_msg.py +0 -0
  71. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/ws/zone_event.py +0 -0
  72. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/zone.py +0 -0
  73. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/models/zone_event.py +0 -0
  74. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/py.typed +0 -0
  75. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/__init__.py +0 -0
  76. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/_base.py +0 -0
  77. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/alarms.py +0 -0
  78. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/anchor_associations.py +0 -0
  79. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/anchors.py +0 -0
  80. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/areas.py +0 -0
  81. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/auth.py +0 -0
  82. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/companies.py +0 -0
  83. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/context.py +0 -0
  84. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/events.py +0 -0
  85. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/floorplans.py +0 -0
  86. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/groups.py +0 -0
  87. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/logger.py +0 -0
  88. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/lsb.py +0 -0
  89. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/messaging.py +0 -0
  90. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/network.py +0 -0
  91. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/nodes.py +0 -0
  92. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/notifications.py +0 -0
  93. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/projects.py +0 -0
  94. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/reports.py +0 -0
  95. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/schedules.py +0 -0
  96. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/sites.py +0 -0
  97. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/subscribers.py +0 -0
  98. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/system.py +0 -0
  99. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/tag_associations.py +0 -0
  100. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/tags.py +0 -0
  101. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/user_messages.py +0 -0
  102. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/users.py +0 -0
  103. {rtls_sdk-0.6.3 → rtls_sdk-0.7.0}/src/rtls_sdk/resources/ws.py +0 -0
@@ -5,7 +5,20 @@ All notable changes to the RTLS SDK will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.7.0] — 2026-10-20
9
+
10
+ ### Added
11
+
12
+ - `zones.download_request(tag_uids, *, action="UPDATE")` — trigger (or
13
+ verify) the SZM→tag zone-download wire push (`POST /api/zone_download_request`).
14
+ - `zones.download_status(tag_uid=None)` — per-tag zone-download sync status
15
+ (`GET /api/zone_download_status[/:uid]`), returned as a list of the new
16
+ `ZoneDownloadStatus` model (`download_status` one of `OUTDATED`,
17
+ `IN_PROCESS`, `UPDATED`, `OOSYNC`; `tag_uid` is the SDK-canonical name
18
+ for the server's `trackable_uid`, mapped on read per the `0.6.2`
19
+ trackable→tag convention). Both endpoints already existed server-side
20
+ (`rtls-frontend`'s `zone_manager` addon) — the SDK simply had no client
21
+ for them.
9
22
 
10
23
  ## [0.6.3] — 2026-09-10
11
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rtls-sdk
3
- Version: 0.6.3
3
+ Version: 0.7.0
4
4
  Summary: Python SDK for the RTLS REST API
5
5
  Project-URL: Documentation, https://rtls-sdk.pages.dev
6
6
  Author: RTLS SDK Maintainers
@@ -26,7 +26,7 @@ Requires-Dist: pytest-httpserver>=1.0; extra == 'dev'
26
26
  Requires-Dist: pytest>=7.4; extra == 'dev'
27
27
  Requires-Dist: python-dotenv>=1.0; extra == 'dev'
28
28
  Requires-Dist: respx>=0.20; extra == 'dev'
29
- Requires-Dist: ruff>=0.4; extra == 'dev'
29
+ Requires-Dist: ruff<0.13,>=0.12.7; extra == 'dev'
30
30
  Provides-Extra: docs
31
31
  Requires-Dist: mkdocs-material>=9; extra == 'docs'
32
32
  Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rtls-sdk"
7
- version = "0.6.3"
7
+ version = "0.7.0"
8
8
  description = "Python SDK for the RTLS REST API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -33,7 +33,12 @@ dev = [
33
33
  "pytest-cov>=4.1",
34
34
  "respx>=0.20",
35
35
  "pytest-httpserver>=1.0",
36
- "ruff>=0.4",
36
+ # Pinned deliberately, not floored. CI installs `[dev]` fresh on every run, so an
37
+ # unpinned linter makes the gate a function of the release calendar: ruff 0.16
38
+ # turned on rules this code has never satisfied and changed the formatter, and
39
+ # CI went red on a commit nobody had touched since July. Bump this on purpose,
40
+ # in its own PR, with the resulting fixes.
41
+ "ruff>=0.12.7,<0.13",
37
42
  "mypy>=1.8",
38
43
  "python-dotenv>=1.0",
39
44
  ]
@@ -67,6 +72,11 @@ extend-select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
67
72
  ignore = [
68
73
  "B008", # function call in default argument — pydantic uses this idiom
69
74
  "SIM108", # ternary not always more readable
75
+ # `isinstance(x, (A, B))` over `isinstance(x, A | B)`. Ruff removed this rule
76
+ # after 0.12 — the tuple form is faster at runtime and reads no worse — so this
77
+ # ignore matches what the rule's own authors concluded, and keeps the pinned
78
+ # gate green without rewriting three call sites to be slower.
79
+ "UP038",
70
80
  ]
71
81
 
72
82
  [tool.ruff.lint.per-file-ignores]
@@ -75,6 +75,8 @@ from .models import (
75
75
  Version,
76
76
  WsHost,
77
77
  Zone,
78
+ ZoneDownloadState,
79
+ ZoneDownloadStatus,
78
80
  ZoneEvent,
79
81
  )
80
82
  from .models.ws import (
@@ -173,5 +175,7 @@ __all__ = [ # noqa: RUF022 — grouped logically, not alphabetically
173
175
  "Version",
174
176
  "WsHost",
175
177
  "Zone",
178
+ "ZoneDownloadState",
179
+ "ZoneDownloadStatus",
176
180
  "ZoneEvent",
177
181
  ]
@@ -31,6 +31,7 @@ from .tag_association import TagAssociation
31
31
  from .tag_template import TagTemplate
32
32
  from .user import Role, User, UserFlags
33
33
  from .zone import Zone
34
+ from .zone_download_status import ZoneDownloadState, ZoneDownloadStatus
34
35
  from .zone_event import ZoneEvent
35
36
 
36
37
  __all__ = [
@@ -81,5 +82,7 @@ __all__ = [
81
82
  "Version",
82
83
  "WsHost",
83
84
  "Zone",
85
+ "ZoneDownloadState",
86
+ "ZoneDownloadStatus",
84
87
  "ZoneEvent",
85
88
  ]
@@ -0,0 +1,51 @@
1
+ """Zone-download sync-status model.
2
+
3
+ Tracks whether a tag's zone-download push (SZM → tag LZM) has synced.
4
+ The server's own status vocabulary (``server/addons/zone_manager/constants.js``)
5
+ is kept verbatim rather than translated to an SDK-canonical spelling,
6
+ since callers compare against it directly (e.g. polling until
7
+ ``"UPDATED"``).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from datetime import datetime
13
+ from typing import Any, Literal
14
+
15
+ from pydantic import model_validator
16
+
17
+ from ._base import _BaseModel
18
+
19
+ ZoneDownloadState = Literal["OUTDATED", "IN_PROCESS", "UPDATED", "OOSYNC"]
20
+
21
+
22
+ class ZoneDownloadStatus(_BaseModel):
23
+ """A tag's current zone-download sync status.
24
+
25
+ ``tag_uid`` is the SDK-canonical name for the server's
26
+ ``trackable_uid`` wire field (mirroring :class:`Alarm` / :class:`Zone`);
27
+ the raw wire payload remains available via ``.raw``. ``download_status``
28
+ is one of the server's literal ``ZONE_DOWNLOAD_STATUS`` values.
29
+ ``reason`` and ``sync_requested`` are present on some rows and absent
30
+ on others (server returns whatever the underlying row has); both
31
+ default to ``None``.
32
+ """
33
+
34
+ tag_uid: str
35
+ download_status: ZoneDownloadState
36
+ ts: datetime | None = None
37
+ reason: str | None = None
38
+ sync_requested: bool | None = None
39
+
40
+ @model_validator(mode="before")
41
+ @classmethod
42
+ def _map_wire_aliases(cls, data: Any) -> Any:
43
+ if not isinstance(data, dict):
44
+ return data
45
+ out = dict(data)
46
+ if out.get("tag_uid") is None and out.get("trackable_uid") is not None:
47
+ out["tag_uid"] = out["trackable_uid"]
48
+ return out
49
+
50
+
51
+ __all__ = ["ZoneDownloadState", "ZoneDownloadStatus"]
@@ -10,7 +10,7 @@ from .._time import to_epoch_ms
10
10
  from ..compounds import zones as _compound
11
11
  from ..compounds.tags import UNSET, _Unset
12
12
  from ..errors import NotFound
13
- from ..models import Zone
13
+ from ..models import Zone, ZoneDownloadStatus
14
14
  from ._base import ResourceAPI
15
15
 
16
16
 
@@ -209,6 +209,52 @@ class ZonesAPI(ResourceAPI):
209
209
  """
210
210
  _compound.delete_zone(self._client, uid)
211
211
 
212
+ def download_request(self, tag_uids: list[str], *, action: str = "UPDATE") -> str:
213
+ """Trigger (or verify) a zone-download push to one or more tags.
214
+
215
+ Wraps ``POST /api/zone_download_request`` — the SZM→tag LZM
216
+ wire push, same action the SiteWise "start zone download"
217
+ button performs. ``action`` is ``"UPDATE"`` (the default, push
218
+ the current zone set) or ``"VERIFY"`` (ask the tag to report
219
+ back without pushing). The server contract is batch-shaped
220
+ (one or more tags per request) — pass a single-element list
221
+ for one tag.
222
+
223
+ Returns the server's own human-readable ``message`` string
224
+ (which entities were processed / rejected); poll
225
+ :meth:`download_status` for the actual per-tag sync outcome
226
+ rather than parsing this message.
227
+ """
228
+ body = {
229
+ "zone_download_request": {
230
+ "action": action,
231
+ "trackables": [{"trackable_uid": uid} for uid in tag_uids],
232
+ }
233
+ }
234
+ response = self._post("/api/zone_download_request", json=body)
235
+ payload = response.json()
236
+ return payload.get("message", "") if isinstance(payload, dict) else ""
237
+
238
+ def download_status(self, tag_uid: str | None = None) -> list[ZoneDownloadStatus]:
239
+ """Return per-tag zone-download sync status.
240
+
241
+ Wraps ``GET /api/zone_download_status`` (all tags) or
242
+ ``GET /api/zone_download_status/{uid}`` (one tag, still
243
+ returned as a list — the server's own query is filtered, not
244
+ reshaped). Poll this after :meth:`download_request` until the
245
+ relevant row's ``download_status`` reads ``"UPDATED"`` (or
246
+ times out) — the request call itself does not block on the
247
+ wire push completing.
248
+ """
249
+ path = (
250
+ "/api/zone_download_status"
251
+ if tag_uid is None
252
+ else f"/api/zone_download_status/{tag_uid}"
253
+ )
254
+ response = self._get(path)
255
+ items = unwrap_resource(response.json(), "zone_download_status")
256
+ return [ZoneDownloadStatus.from_wire(item) for item in items]
257
+
212
258
  def list(self) -> list[Zone]:
213
259
  """Return zones and buffers for the current project.
214
260
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes