rtls-sdk 0.2.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 (81) hide show
  1. rtls_sdk-0.2.0/.gitignore +16 -0
  2. rtls_sdk-0.2.0/CHANGELOG.md +242 -0
  3. rtls_sdk-0.2.0/LICENSE +21 -0
  4. rtls_sdk-0.2.0/PKG-INFO +141 -0
  5. rtls_sdk-0.2.0/README.md +105 -0
  6. rtls_sdk-0.2.0/prompts/README.md +126 -0
  7. rtls_sdk-0.2.0/pyproject.toml +92 -0
  8. rtls_sdk-0.2.0/src/rtls_sdk/__init__.py +123 -0
  9. rtls_sdk-0.2.0/src/rtls_sdk/_auth.py +266 -0
  10. rtls_sdk-0.2.0/src/rtls_sdk/_client.py +419 -0
  11. rtls_sdk-0.2.0/src/rtls_sdk/_envelope.py +74 -0
  12. rtls_sdk-0.2.0/src/rtls_sdk/_http.py +145 -0
  13. rtls_sdk-0.2.0/src/rtls_sdk/_logging.py +143 -0
  14. rtls_sdk-0.2.0/src/rtls_sdk/_pagination.py +235 -0
  15. rtls_sdk-0.2.0/src/rtls_sdk/_query.py +114 -0
  16. rtls_sdk-0.2.0/src/rtls_sdk/_time.py +84 -0
  17. rtls_sdk-0.2.0/src/rtls_sdk/compounds/__init__.py +19 -0
  18. rtls_sdk-0.2.0/src/rtls_sdk/compounds/auth.py +100 -0
  19. rtls_sdk-0.2.0/src/rtls_sdk/compounds/context.py +159 -0
  20. rtls_sdk-0.2.0/src/rtls_sdk/compounds/groups.py +126 -0
  21. rtls_sdk-0.2.0/src/rtls_sdk/compounds/nodes.py +176 -0
  22. rtls_sdk-0.2.0/src/rtls_sdk/compounds/reports.py +339 -0
  23. rtls_sdk-0.2.0/src/rtls_sdk/compounds/system.py +43 -0
  24. rtls_sdk-0.2.0/src/rtls_sdk/compounds/tags.py +404 -0
  25. rtls_sdk-0.2.0/src/rtls_sdk/compounds/users.py +143 -0
  26. rtls_sdk-0.2.0/src/rtls_sdk/compounds/zones.py +203 -0
  27. rtls_sdk-0.2.0/src/rtls_sdk/errors.py +238 -0
  28. rtls_sdk-0.2.0/src/rtls_sdk/models/__init__.py +73 -0
  29. rtls_sdk-0.2.0/src/rtls_sdk/models/_base.py +46 -0
  30. rtls_sdk-0.2.0/src/rtls_sdk/models/alarm.py +23 -0
  31. rtls_sdk-0.2.0/src/rtls_sdk/models/anchor.py +25 -0
  32. rtls_sdk-0.2.0/src/rtls_sdk/models/area.py +28 -0
  33. rtls_sdk-0.2.0/src/rtls_sdk/models/association.py +48 -0
  34. rtls_sdk-0.2.0/src/rtls_sdk/models/bulk.py +80 -0
  35. rtls_sdk-0.2.0/src/rtls_sdk/models/company.py +32 -0
  36. rtls_sdk-0.2.0/src/rtls_sdk/models/csv_blob.py +40 -0
  37. rtls_sdk-0.2.0/src/rtls_sdk/models/floorplan.py +42 -0
  38. rtls_sdk-0.2.0/src/rtls_sdk/models/group.py +20 -0
  39. rtls_sdk-0.2.0/src/rtls_sdk/models/heatmap.py +37 -0
  40. rtls_sdk-0.2.0/src/rtls_sdk/models/import_result.py +42 -0
  41. rtls_sdk-0.2.0/src/rtls_sdk/models/node.py +28 -0
  42. rtls_sdk-0.2.0/src/rtls_sdk/models/notification.py +38 -0
  43. rtls_sdk-0.2.0/src/rtls_sdk/models/position.py +66 -0
  44. rtls_sdk-0.2.0/src/rtls_sdk/models/project.py +26 -0
  45. rtls_sdk-0.2.0/src/rtls_sdk/models/pws.py +38 -0
  46. rtls_sdk-0.2.0/src/rtls_sdk/models/report.py +34 -0
  47. rtls_sdk-0.2.0/src/rtls_sdk/models/session_context.py +81 -0
  48. rtls_sdk-0.2.0/src/rtls_sdk/models/site.py +31 -0
  49. rtls_sdk-0.2.0/src/rtls_sdk/models/subscriber.py +68 -0
  50. rtls_sdk-0.2.0/src/rtls_sdk/models/system.py +97 -0
  51. rtls_sdk-0.2.0/src/rtls_sdk/models/system_health.py +36 -0
  52. rtls_sdk-0.2.0/src/rtls_sdk/models/tag.py +48 -0
  53. rtls_sdk-0.2.0/src/rtls_sdk/models/tag_template.py +24 -0
  54. rtls_sdk-0.2.0/src/rtls_sdk/models/user.py +121 -0
  55. rtls_sdk-0.2.0/src/rtls_sdk/models/zone.py +27 -0
  56. rtls_sdk-0.2.0/src/rtls_sdk/models/zone_event.py +21 -0
  57. rtls_sdk-0.2.0/src/rtls_sdk/py.typed +0 -0
  58. rtls_sdk-0.2.0/src/rtls_sdk/resources/__init__.py +49 -0
  59. rtls_sdk-0.2.0/src/rtls_sdk/resources/_base.py +63 -0
  60. rtls_sdk-0.2.0/src/rtls_sdk/resources/alarms.py +108 -0
  61. rtls_sdk-0.2.0/src/rtls_sdk/resources/anchors.py +147 -0
  62. rtls_sdk-0.2.0/src/rtls_sdk/resources/areas.py +78 -0
  63. rtls_sdk-0.2.0/src/rtls_sdk/resources/associations.py +157 -0
  64. rtls_sdk-0.2.0/src/rtls_sdk/resources/auth.py +63 -0
  65. rtls_sdk-0.2.0/src/rtls_sdk/resources/companies.py +79 -0
  66. rtls_sdk-0.2.0/src/rtls_sdk/resources/context.py +50 -0
  67. rtls_sdk-0.2.0/src/rtls_sdk/resources/events.py +149 -0
  68. rtls_sdk-0.2.0/src/rtls_sdk/resources/floorplans.py +283 -0
  69. rtls_sdk-0.2.0/src/rtls_sdk/resources/groups.py +99 -0
  70. rtls_sdk-0.2.0/src/rtls_sdk/resources/logger.py +40 -0
  71. rtls_sdk-0.2.0/src/rtls_sdk/resources/messaging.py +51 -0
  72. rtls_sdk-0.2.0/src/rtls_sdk/resources/nodes.py +157 -0
  73. rtls_sdk-0.2.0/src/rtls_sdk/resources/notifications.py +55 -0
  74. rtls_sdk-0.2.0/src/rtls_sdk/resources/projects.py +67 -0
  75. rtls_sdk-0.2.0/src/rtls_sdk/resources/reports.py +180 -0
  76. rtls_sdk-0.2.0/src/rtls_sdk/resources/sites.py +115 -0
  77. rtls_sdk-0.2.0/src/rtls_sdk/resources/subscribers.py +110 -0
  78. rtls_sdk-0.2.0/src/rtls_sdk/resources/system.py +125 -0
  79. rtls_sdk-0.2.0/src/rtls_sdk/resources/tags.py +370 -0
  80. rtls_sdk-0.2.0/src/rtls_sdk/resources/users.py +275 -0
  81. rtls_sdk-0.2.0/src/rtls_sdk/resources/zones.py +199 -0
@@ -0,0 +1,16 @@
1
+ .env.local
2
+ .env.*.local
3
+ .idea
4
+ .venv/
5
+ venv/
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ build/
13
+ dist/
14
+ *.egg-info/
15
+ .coverage
16
+ htmlcov/
@@ -0,0 +1,242 @@
1
+ # Changelog
2
+
3
+ All notable changes to the RTLS SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] — 2026-05-12
11
+
12
+ ### Added
13
+
14
+ #### Places CRUD (M9)
15
+
16
+ - `client.sites.create / update / delete` — full lifecycle for the
17
+ wire `locations` resource. `update` uses a conservative `name` /
18
+ `shift` allowlist mirroring the JS reference client; `create`
19
+ accepts the wider server payload via `**fields`. Server-side
20
+ cascade on `delete` (areas / floorplans / anchors / zones go too).
21
+ - `client.areas.create / update / delete` — wire `sublocations`
22
+ resource. `create` requires `name` + `location_uid`; `update`
23
+ passes arbitrary placement fields (`position_x`, `position_y`,
24
+ `width`, `height`, `rotation`) through. Server-side cascade.
25
+ - `client.anchors.create / update / delete` — wire `win_anchors`.
26
+ `create` enforces `name`, `position`, `type` (`ANCHOR` or
27
+ `BRIDGE`, validated locally); optional `locked`, `acc_config`,
28
+ `bt_config`, `state`, `device_config` ride in `**fields`.
29
+ - `normalize_mac()` helper — accepts six input shapes (bare 12-hex
30
+ upper / lower, `:` or `-` separated) and emits the canonical
31
+ bare-12-hex-lowercase the server's `mac_regex` requires. Wired
32
+ through `nodes.create / validate / assign_raw / release_raw /
33
+ release / import_` and `associations.create / bulk_create`.
34
+ - Live fixtures `writable_site_uid` / `writable_area_uid` for
35
+ destructive live tests against the shared test server, with
36
+ `sdk-test-<uuid4>` prefixes and `yield`-style cleanup.
37
+
38
+ #### Floorplans + image lifecycle (M10)
39
+
40
+ - `client.floorplans.create` — base64-in-JSON image upload. Image
41
+ is required; pass `image_bytes` + `original_filename` OR
42
+ `image_path` (either-or, enforced with `ValueError` before any
43
+ HTTP call).
44
+ - `client.floorplans.update` — diff-driven; replaces the stored
45
+ image only when `image_bytes` / `image_path` is provided.
46
+ - `client.floorplans.delete` — removes the row and the stored
47
+ image.
48
+ - `client.floorplans.download_image` — 180-second read timeout
49
+ (overrides the default 30 s) for multi-MB PDFs; optional
50
+ `stream_to=<file>` for incremental writes.
51
+ - Server re-encodes PNGs on storage — documented in the entity
52
+ page; tests compare IHDR dimensions, not byte content.
53
+
54
+ ### Fixed
55
+
56
+ #### Schema audit (M12)
57
+
58
+ - **`tags.create` no longer accepts `mac_address`.** The server's
59
+ Joi `createTrackObject` schema has no `mac_address` field; the
60
+ default `stripUnknown` silently dropped it, so the call appeared
61
+ to succeed but the MAC never bound. Callers who want hardware
62
+ binding now go through `nodes.create` + `associations.create`
63
+ (the migration guide and `docs/entities/tags.md` document the
64
+ three-call replacement).
65
+ - **Canonical MAC format is bare 12-hex lowercase.** The server's
66
+ `mac_regex` rejects any separator; previous SDK responses /
67
+ examples that showed colon-separated MACs
68
+ (`AA:BB:CC:DD:EE:FF`) have been corrected across docs and tests.
69
+ `normalize_mac()` (introduced in M9) is now applied at every MAC
70
+ entry-point as a precondition, not a convenience.
71
+ - Regression gate: a live MAC round-trip test exercises the
72
+ create → list → release cycle to prove the wire format stays
73
+ canonical on both sides.
74
+
75
+ #### Wire-shape mismatches caught by the M14 full-stack live test
76
+
77
+ - **`companies.create` now includes a `role` on the seeded admin user**
78
+ (defaults to `"company_admin"`; override via `admin_role=`). The Joi
79
+ `companyCreate.user.role` field is required; previously the SDK omitted
80
+ it and the server responded `"user.role" is required`.
81
+ - **`projects.create` / `projects.update` now wrap the body under
82
+ `{"project": {...}}`** to match the Joi `createProject` /
83
+ `updateProject` schemas. The previously-flat body was rejected with
84
+ `"project" is required`.
85
+ - **`associations.close` now sends `mac_address` / `obj_uid` instead of
86
+ the association's own `uid`.** The server's close handler reads only
87
+ those two keys from the body; passing `{"uid": "..."}` raised
88
+ `"No mac or trackable object uid specified."`. The signature now
89
+ accepts an `Association` object, an association uid (with an extra
90
+ `list()` lookup), or explicit `mac_address` / `trackable_uid` kwargs.
91
+ `compounds/nodes.release` updated to pass the `Association` object
92
+ directly.
93
+
94
+ Each of these has a matching unit-test regression in
95
+ `tests/unit/test_resources_write_m3.py`.
96
+
97
+ ### Documentation
98
+
99
+ - Reorganized user docs around **entities** instead of workflows
100
+ (M11). Every entity (tags, sites, areas, …) now has a single page
101
+ covering methods, examples, model fields, and cross-references —
102
+ answering "what can I do with X?" in one click from the new
103
+ `docs/entities/` catalog.
104
+ - Deleted `docs/guides/reading-data.md`,
105
+ `docs/guides/writing-data.md`, `docs/guides/session-context.md` —
106
+ superseded by entity pages.
107
+ - Trimmed `docs/guides/compound-workflows.md` to a concept page that
108
+ points at entity pages for examples.
109
+ - Nav restructure: Entities is now the second section after Quickstart;
110
+ cross-cutting guides (error handling, scope switching, pagination, …)
111
+ follow.
112
+ - README rewritten to lead with the entity catalog; previous flat
113
+ link list (which pointed at the now-deleted reading-data /
114
+ writing-data / session-context guides) replaced with a grouped
115
+ Start-here / Cross-cutting / Advanced / Reference structure.
116
+
117
+ ## [0.1.0] — initial release
118
+
119
+ The first release covers the milestones M1–M8 of the original design.
120
+ The SDK provides a sync surface for the RTLS REST API with built-in
121
+ session management, scope handling, and compound workflows that
122
+ collapse multi-call sagas behind a single Python method.
123
+
124
+ ### Added
125
+
126
+ #### Core client (M1)
127
+
128
+ - `RtlsClient(username, password, base_url, ...)` — construct from
129
+ explicit credentials.
130
+ - `RtlsClient(token, base_url, ...)` — bring-your-own token mode.
131
+ - `RtlsClient.from_env()` — construct from `RTLS_USERNAME` /
132
+ `RTLS_PASSWORD` / `RTLS_BASE_URL` (+ optional scope vars).
133
+ - Lazy login: no network I/O at construction; the first method call
134
+ performs `POST /auth/log_in.json`.
135
+ - Transparent 401 re-authentication with `threading.Lock`-protected
136
+ refresh coalescing.
137
+ - Mandatory secret redaction filter for the `rtls_sdk` named logger
138
+ (no opt-out). Headers and body keys redacted.
139
+ - Typed exception hierarchy under `RtlsError`:
140
+ `AuthenticationError`, `PermissionDenied`, `NotFound`,
141
+ `Conflict`, `ValidationError`, `RateLimited`, `ServerError`,
142
+ `ConnectionError`.
143
+
144
+ #### Read surface (M2)
145
+
146
+ - `client.tags.list/get`, `client.sites.list/get`,
147
+ `client.areas.list/get`, `client.floorplans.list/get`,
148
+ `client.zones.list/get`, `client.groups.list/get`,
149
+ `client.users.list/get/me`, `client.nodes.list/get`,
150
+ `client.anchors.list/get`, `client.alarms.list/get`,
151
+ `client.events.list/get`, `client.notifications.list/get`,
152
+ `client.subscribers.list/get`, `client.projects.list/get`,
153
+ `client.companies.list/get`, `client.reports.list/get`.
154
+ - `client.auth.whoami()`, `client.auth.capabilities()`.
155
+ - `client.system.host/uptime/connections/version/beta_version/ws_host/helper_link/monitor_link`.
156
+ - `client.messaging.*`, `client.logger.*`.
157
+ - Typed pydantic v2 models for every returned resource.
158
+
159
+ #### Simple writes (M3)
160
+
161
+ - `create_raw` / `update_raw` / `delete_raw` for all writable
162
+ resources — 1:1 wrappers over the REST surface.
163
+ - `auth.change_password(old, new)` with token invalidation.
164
+ - `users.create_raw / update_raw / change_password_raw` and
165
+ related raw flows.
166
+
167
+ #### Trackable compounds (M4)
168
+
169
+ - `tags.create(name, ..., attached_zone=..., groups=...)` — POST + zone
170
+ attach + groups bind with rollback.
171
+ - `tags.update`, `tags.delete` (incl. bulk-list form returning
172
+ `BulkResult`).
173
+ - `tags.bulk_update(uids, changes)`.
174
+ - `zones.create/update/delete` with dynamic-binding management.
175
+ - `groups.create/update/delete` with membership cleanup.
176
+ - `associations.bulk_create` continue-on-error.
177
+
178
+ #### User/Node compounds (M5)
179
+
180
+ - `users.create(email, password, ..., project_role=...)` — user +
181
+ project-role binding compound.
182
+ - `users.update(uid, ..., project_role=...)` — with project_role diff
183
+ semantics (add/remove/replace).
184
+ - `nodes.release(node_uid, project_uid=...)` — cross-scope release
185
+ with association close and per-thread scope override.
186
+ - `system.health()` — aggregated host + uptime + connections snapshot
187
+ with `partial`/`errors` fan-out tracking.
188
+
189
+ #### Session context (M6)
190
+
191
+ - `client.context.load()` — the dashboard bootstrap. Fans out ~20
192
+ sub-resource reads into one `SessionContext` snapshot with
193
+ `partial` / `errors` partial-failure semantics.
194
+ - Cross-scope nodes fetch via internal per-thread scope override.
195
+
196
+ #### Reports + pagination (M7)
197
+
198
+ - `client.reports.heatmap(...)` — POST + poll until ready, with
199
+ `ReportFailed` / `ReportTimeout` outcomes.
200
+ - `client.reports.pws(...)` — aggregate PWS event report.
201
+ - `client.reports.zone_activity_csv(...)` — chunked CSV download
202
+ following `content-next-page`, with optional streaming and
203
+ duplicate-header dedup.
204
+ - `client.reports.alarms_csv(...)` — alarms CSV with optional
205
+ multi-line banner prefix.
206
+ - `client.events.iter_pws(...)` — lazy iterator surface for PWS
207
+ events; three-style paginator infrastructure
208
+ (`HeaderCursorPaginator`, `BodyUrlPaginator`,
209
+ `PageLimitPaginator`).
210
+ - New models: `Heatmap`, `HeatmapInput`, `PwsReport`, `PwsEvent`,
211
+ `CsvBlob`.
212
+
213
+ #### Polish + release prep (M8)
214
+
215
+ - `client.with_scope(project_uid=..., company_uid=...)` — returns a
216
+ copy of the client bound to a different scope, sharing the same
217
+ authenticated session and connection pool.
218
+ - `client.use_project(uid)` / `client.use_company(uid)` — in-place
219
+ scope rebind.
220
+ - `RtlsClient(http_client=...)` — caller-supplied `httpx.Client`
221
+ for proxies, custom TLS, or other transport tuning.
222
+ - Comprehensive `docs/` site built with `mkdocs-material`
223
+ (guides, advanced topics, API reference auto-generated via
224
+ `mkdocstrings-python`, migration guide).
225
+ - Security audit test (`tests/security/test_secret_leaks.py`)
226
+ walking the public surface to verify redaction.
227
+ - `LICENSE` (MIT). `CHANGELOG.md`. CI workflow.
228
+
229
+ ### Compatibility
230
+
231
+ - Python **3.10**, **3.11**, **3.12**.
232
+ - Wheel ships `py.typed`. Inline type hints work out of the box with
233
+ `mypy --strict` and `pyright`.
234
+
235
+ ### Migration
236
+
237
+ Coming from the JS reference client? Read the migration guide
238
+ (`docs/migration/from-js-client.md`) — a code-by-code translation of
239
+ every common JS idiom into the SDK's equivalent.
240
+
241
+ [0.2.0]: https://github.com/rpplabs/rtls-sdk/releases/tag/v0.2.0
242
+ [0.1.0]: https://github.com/rpplabs/rtls-sdk/releases/tag/v0.1.0
rtls_sdk-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RTLS SDK Maintainers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: rtls-sdk
3
+ Version: 0.2.0
4
+ Summary: Python SDK for the RTLS REST API
5
+ Project-URL: Homepage, https://github.com/rpplabs/rtls-sdk
6
+ Project-URL: Documentation, https://github.com/rpplabs/rtls-sdk/tree/master/docs
7
+ Project-URL: Issues, https://github.com/rpplabs/rtls-sdk/issues
8
+ Project-URL: Changelog, https://github.com/rpplabs/rtls-sdk/blob/master/CHANGELOG.md
9
+ Author: RTLS SDK Maintainers
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: iot,rtls,sdk,tracking
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic>=2.5
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.8; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
27
+ Requires-Dist: pytest-httpserver>=1.0; extra == 'dev'
28
+ Requires-Dist: pytest>=7.4; extra == 'dev'
29
+ Requires-Dist: python-dotenv>=1.0; extra == 'dev'
30
+ Requires-Dist: respx>=0.20; extra == 'dev'
31
+ Requires-Dist: ruff>=0.4; extra == 'dev'
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs-material>=9; extra == 'docs'
34
+ Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # RTLS SDK
38
+
39
+ A Python SDK for the RTLS REST API. Three-step usage:
40
+
41
+ ```python
42
+ from rtls_sdk import RtlsClient
43
+
44
+ client = RtlsClient.from_env()
45
+ tags = client.tags.list()
46
+ ```
47
+
48
+ That's it — token, refresh, scoping headers, 401 re-login are all
49
+ internal. The first method call performs a lazy login; subsequent
50
+ calls reuse the session.
51
+
52
+ ## What you get
53
+
54
+ - **Compound workflows.** One Python call replaces the multi-call
55
+ REST sagas the JS reference client makes you script — e.g.
56
+ `tags.create(name="X", attached_zone="Z", groups=[...])` runs the
57
+ three-step create-and-attach with rollback on partial failure.
58
+ - **One exception hierarchy.** Everything under `RtlsError`:
59
+ `NotFound`, `ValidationError`, `RateLimited`, `ServerError`,
60
+ `PartialFailureError`, `ReportFailed` / `ReportTimeout`, …
61
+ - **Typed models.** Pydantic v2 under the hood; `mypy --strict` clean
62
+ out of the box; ships `py.typed`.
63
+ - **Mandatory redaction.** No password, token, refresh-token, cookie,
64
+ or `Authorization` header ever reaches a log handler. There is no
65
+ opt-out flag.
66
+ - **Sync-only for v1.** Async surface and WebSocket support are on the
67
+ v2 roadmap.
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install rtls-sdk
73
+ ```
74
+
75
+ Python 3.10+ supported. See [`docs/install.md`](docs/install.md) for
76
+ dev install and verification.
77
+
78
+ ## Quickstart
79
+
80
+ ```bash
81
+ export RTLS_USERNAME="qa@example.com"
82
+ export RTLS_PASSWORD="hunter2"
83
+ export RTLS_BASE_URL="https://rtls.example.com"
84
+ ```
85
+
86
+ ```python
87
+ from rtls_sdk import RtlsClient
88
+
89
+ with RtlsClient.from_env() as client:
90
+ for tag in client.tags.list():
91
+ print(tag.uid, tag.name)
92
+ ```
93
+
94
+ For the full 5-minute walkthrough see
95
+ [`docs/quickstart.md`](docs/quickstart.md).
96
+
97
+ ## Documentation
98
+
99
+ The full docs site (`mkdocs build`):
100
+
101
+ **Start here**
102
+
103
+ - **[Entity catalog](docs/entities/index.md)** — one page per entity
104
+ (tags, sites, areas, floorplans, anchors, zones, nodes, users,
105
+ reports, …) with methods, examples, and model fields. The fastest
106
+ path to "what can I do with X?".
107
+ - [Install](docs/install.md) · [Quickstart](docs/quickstart.md)
108
+
109
+ **Cross-cutting guides**
110
+
111
+ - [Compound workflows](docs/guides/compound-workflows.md) · [Reports](docs/guides/reports.md) · [Floorplans](docs/guides/floorplans.md)
112
+ - [Error handling](docs/guides/error-handling.md) · [Scope switching](docs/guides/scope-switching.md) · [Pagination](docs/guides/pagination.md)
113
+ - [Rate limiting](docs/guides/rate-limiting.md) · [Timestamps](docs/guides/timestamps.md) · [Credential rotation](docs/guides/credential-rotation.md)
114
+
115
+ **Advanced**
116
+
117
+ - [BYO token](docs/advanced/byo-token.md) · [Raw REST methods](docs/advanced/raw-rest-methods.md) · [Logging](docs/advanced/logging.md) · [HTTP client override](docs/advanced/http-client-override.md) · [Security](docs/advanced/security.md)
118
+
119
+ **Reference**
120
+
121
+ - [Migrating from the JS client](docs/migration/from-js-client.md)
122
+ - API reference — auto-generated from docstrings via mkdocstrings.
123
+
124
+ Build locally:
125
+
126
+ ```bash
127
+ pip install -e ".[docs]"
128
+ mkdocs serve
129
+ ```
130
+
131
+ ## Status
132
+
133
+ v0.2.0 — adds M9 places CRUD (sites / areas / anchors), M10 floorplans
134
+ + image lifecycle, M11 entity-oriented documentation reorganisation,
135
+ and M12 schema-audit fixes (canonical bare-12-hex MACs;
136
+ `tags.create` no longer accepts the silently-stripped `mac_address`
137
+ argument). See [`CHANGELOG.md`](CHANGELOG.md).
138
+
139
+ ## License
140
+
141
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,105 @@
1
+ # RTLS SDK
2
+
3
+ A Python SDK for the RTLS REST API. Three-step usage:
4
+
5
+ ```python
6
+ from rtls_sdk import RtlsClient
7
+
8
+ client = RtlsClient.from_env()
9
+ tags = client.tags.list()
10
+ ```
11
+
12
+ That's it — token, refresh, scoping headers, 401 re-login are all
13
+ internal. The first method call performs a lazy login; subsequent
14
+ calls reuse the session.
15
+
16
+ ## What you get
17
+
18
+ - **Compound workflows.** One Python call replaces the multi-call
19
+ REST sagas the JS reference client makes you script — e.g.
20
+ `tags.create(name="X", attached_zone="Z", groups=[...])` runs the
21
+ three-step create-and-attach with rollback on partial failure.
22
+ - **One exception hierarchy.** Everything under `RtlsError`:
23
+ `NotFound`, `ValidationError`, `RateLimited`, `ServerError`,
24
+ `PartialFailureError`, `ReportFailed` / `ReportTimeout`, …
25
+ - **Typed models.** Pydantic v2 under the hood; `mypy --strict` clean
26
+ out of the box; ships `py.typed`.
27
+ - **Mandatory redaction.** No password, token, refresh-token, cookie,
28
+ or `Authorization` header ever reaches a log handler. There is no
29
+ opt-out flag.
30
+ - **Sync-only for v1.** Async surface and WebSocket support are on the
31
+ v2 roadmap.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install rtls-sdk
37
+ ```
38
+
39
+ Python 3.10+ supported. See [`docs/install.md`](docs/install.md) for
40
+ dev install and verification.
41
+
42
+ ## Quickstart
43
+
44
+ ```bash
45
+ export RTLS_USERNAME="qa@example.com"
46
+ export RTLS_PASSWORD="hunter2"
47
+ export RTLS_BASE_URL="https://rtls.example.com"
48
+ ```
49
+
50
+ ```python
51
+ from rtls_sdk import RtlsClient
52
+
53
+ with RtlsClient.from_env() as client:
54
+ for tag in client.tags.list():
55
+ print(tag.uid, tag.name)
56
+ ```
57
+
58
+ For the full 5-minute walkthrough see
59
+ [`docs/quickstart.md`](docs/quickstart.md).
60
+
61
+ ## Documentation
62
+
63
+ The full docs site (`mkdocs build`):
64
+
65
+ **Start here**
66
+
67
+ - **[Entity catalog](docs/entities/index.md)** — one page per entity
68
+ (tags, sites, areas, floorplans, anchors, zones, nodes, users,
69
+ reports, …) with methods, examples, and model fields. The fastest
70
+ path to "what can I do with X?".
71
+ - [Install](docs/install.md) · [Quickstart](docs/quickstart.md)
72
+
73
+ **Cross-cutting guides**
74
+
75
+ - [Compound workflows](docs/guides/compound-workflows.md) · [Reports](docs/guides/reports.md) · [Floorplans](docs/guides/floorplans.md)
76
+ - [Error handling](docs/guides/error-handling.md) · [Scope switching](docs/guides/scope-switching.md) · [Pagination](docs/guides/pagination.md)
77
+ - [Rate limiting](docs/guides/rate-limiting.md) · [Timestamps](docs/guides/timestamps.md) · [Credential rotation](docs/guides/credential-rotation.md)
78
+
79
+ **Advanced**
80
+
81
+ - [BYO token](docs/advanced/byo-token.md) · [Raw REST methods](docs/advanced/raw-rest-methods.md) · [Logging](docs/advanced/logging.md) · [HTTP client override](docs/advanced/http-client-override.md) · [Security](docs/advanced/security.md)
82
+
83
+ **Reference**
84
+
85
+ - [Migrating from the JS client](docs/migration/from-js-client.md)
86
+ - API reference — auto-generated from docstrings via mkdocstrings.
87
+
88
+ Build locally:
89
+
90
+ ```bash
91
+ pip install -e ".[docs]"
92
+ mkdocs serve
93
+ ```
94
+
95
+ ## Status
96
+
97
+ v0.2.0 — adds M9 places CRUD (sites / areas / anchors), M10 floorplans
98
+ + image lifecycle, M11 entity-oriented documentation reorganisation,
99
+ and M12 schema-audit fixes (canonical bare-12-hex MACs;
100
+ `tags.create` no longer accepts the silently-stripped `mac_address`
101
+ argument). See [`CHANGELOG.md`](CHANGELOG.md).
102
+
103
+ ## License
104
+
105
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,126 @@
1
+ # SDK implementation prompts
2
+
3
+ These prompts implement the design in `/Users/yzhbankov/rtls/rtls-sdk/DESIGN.md`. Run them sequentially. Each milestone leaves the SDK in a working, tested, lint-clean state. After milestone 8 the SDK is feature-complete for v1 and ready to publish.
4
+
5
+ | # | Prompt | Milestone | Delivers |
6
+ |---|---|---|---|
7
+ | 03 | `03-m1-foundation.md` | M1 | First end-to-end: `RtlsClient.from_env().tags.list()` works against a mocked server. Auth, errors, redaction, packaging, CI scaffolding. |
8
+ | 04 | `04-m2-read-surface.md` | M2 | All `.list()` / `.get()` methods for every resource the JS client reads. All read-side pydantic models. |
9
+ | 05 | `05-m3-simple-writes.md` | M3 | All `.create()` / `.update()` / `.delete()` as thin REST mirrors (also the escape hatches for compound methods). |
10
+ | 06 | `06-m4-trackable-compounds.md` | M4 | Compound workflows: `tags.create(attached_zone=..., groups=...)`, `tags.update`, `tags.delete` cascade, `tags.bulk_update`, dynamic-zone re-binding, `groups.add_tags` / `groups.delete` cascade. `PartialFailureError` wiring. |
11
+ | 07 | `07-m5-user-node-compounds.md` | M5 | Compound user/node workflows: `users.create` / `users.update` with project-role binding, `nodes.release`, `nodes.import_`, `auth.change_password` with re-login. |
12
+ | 08 | `08-m6-context-load.md` | M6 | The `client.context.load()` mega-method — one call hydrates 16+ entities. |
13
+ | 09 | `09-m7-reports.md` | M7 | Reports + pagination: `reports.heatmap` (polling), `reports.pws`, `reports.zone_activity_csv` (chunked), `events.iter_pws` / `events.pages` (three pagination styles unified). |
14
+ | 10 | `10-m8-polish-docs.md` | M8 | Polish, comprehensive docs site (mkdocs-material), API reference, CHANGELOG, release prep. v0.1.0 ships. |
15
+ | 11 | `11-m9-places-crud.md` | M9 | Site / area / anchor full CRUD — closes the v0.1 gap for the `desktop-site_plan` client. MAC normalization, server-cascade contract, new `writable_site_uid` / `writable_area_uid` live fixtures. |
16
+ | 12 | `12-m10-floorplans-images.md` | M10 | Floorplans full CRUD + image upload (base64-in-JSON) + image download (180 s timeout, streaming). Completes the place-entity gap. |
17
+ | 13 | `13-m11-entity-docs.md` | M11 | Docs-only: reorganize user documentation around **entities**. New `docs/entities/` tree (one page per entity + catalog index) replaces the workflow-oriented guides. Answers "what can I do with X?" on one page. |
18
+ | 14 | `14-m12-schema-audit-mac-fix.md` | M12 | Schema-correctness audit. Fix three real bugs caught by reading the server's Joi schemas directly: MAC format is bare 12-hex (not colon-separated); `tags.create` does not accept `mac_address` (server silently strips); add live MAC round-trip test as a regression gate. |
19
+ | 15 | `15-m13-final-audit.md` | M13 | **Check-only.** Full ship-readiness audit across code quality, documentation site, internal consistency, wire-shape correctness, deep doc-quality analysis (with persona-driven proposals), and release artifacts. Produces `AUDIT.md` at the repo root with a verdict (ready / not ready), itemised blockers, repo-wide markdown link validation, and a prioritised list of documentation improvement proposals. Does not modify code or docs — a separate milestone applies any accepted fixes. |
20
+
21
+ ## Inputs each prompt reads
22
+
23
+ - `/Users/yzhbankov/rtls/rtls-sdk/DESIGN.md` — authoritative design.
24
+ - `/Users/yzhbankov/rtls/rtls-sdk/RESEARCH.md` — endpoint inventory, compound workflows, quirks.
25
+ - `/Users/yzhbankov/rtls/rtls-frontend/client/src/modules/api/` — reference JS client. When docs and client disagree, client wins.
26
+ - `/Users/yzhbankov/rtls/python-libraries/` — prior art for test scaffolding (`pytest-httpserver` handlers).
27
+
28
+ ## Standing constraints (apply to every milestone)
29
+
30
+ 1. **The three-step user mental model is sacred.** No idiomatic user-facing code touches tokens, refresh, or scoping headers. If a snippet in Section 1 of DESIGN.md would have to change to use what you built, you built the wrong thing.
31
+ 2. **§8 of RESEARCH.md is binding.** Stack picks (`httpx`, `pydantic`, sync v1, `ruff`, `pytest`), credential UX, lazy login, 401 re-login, in-memory token only, mandatory secret redaction — all decided.
32
+ 3. **Type hints everywhere.** `py.typed` marker shipped; `mypy --strict src/` passes (configured to permit `Any` in pydantic field validators only).
33
+ 4. **Tests on every new method.** Unit (`respx`) for behaviour, integration (`pytest-httpserver`) for end-to-end call sequences in compounds.
34
+ 5. **Docstrings on every public method, class, and module.** Numpy or Google style — pick one in M1 and keep it. Include a minimal usage example in each public method's docstring.
35
+ 6. **Lint clean.** `ruff check` and `ruff format --check` both pass. CI fails on warnings.
36
+ 7. **No backwards-compat carry-over.** Each milestone builds on the last with no shims, no deprecated-but-kept methods, no migration guides. If something is wrong, fix it cleanly.
37
+
38
+ ## How to run a prompt
39
+
40
+ Open the prompt file. It assumes Claude is starting fresh with no memory of the previous prompt — but with access to the files the previous prompt left behind. The prompt is self-contained: it tells Claude what to read, what to build, and how to verify.
41
+
42
+ ---
43
+
44
+ ## Live-server validation protocol (applies to every milestone)
45
+
46
+ A live RTLS test server is available. **Every milestone has a final "Live verification" gate**: the work isn't done until the milestone's behavior has been validated against the real server, not just against mocks. Mocks catch what we anticipated; the live server catches what we didn't.
47
+
48
+ ### One-time credentials onboarding (do this at the start of M1)
49
+
50
+ The first prompt (M1) does this once. Subsequent prompts reuse what M1 set up. The agent must:
51
+
52
+ 1. **Ask the user for** (via `AskUserQuestion`):
53
+ - `RTLS_LIVE_BASE_URL` — e.g. `https://rtls.test.example.com`
54
+ - `RTLS_LIVE_USERNAME` — superuser email
55
+ - `RTLS_LIVE_PASSWORD` — superuser password
56
+ - `RTLS_LIVE_PROJECT_UID` — a project uid the test user has access to (optional but used by M2+)
57
+ - `RTLS_LIVE_COMPANY_UID` — a company uid (optional; needed for company-scope tests in M5–M6)
58
+
59
+ 2. **Write them to `.env.local`** at the repo root in dotenv format. **Do not commit this file.** M1's `.gitignore` must include `.env.local` (and `.env.*.local`) on its first line.
60
+
61
+ 3. **Create `tests/live/conftest.py`** with this shape:
62
+
63
+ ```python
64
+ import os
65
+ import pytest
66
+ from dotenv import load_dotenv
67
+
68
+ load_dotenv(".env.local")
69
+
70
+ def _require(name: str) -> str:
71
+ val = os.getenv(name)
72
+ if not val:
73
+ pytest.skip(f"{name} not set in .env.local — skipping live tests")
74
+ return val
75
+
76
+ @pytest.fixture(scope="session")
77
+ def live_creds():
78
+ return {
79
+ "base_url": _require("RTLS_LIVE_BASE_URL"),
80
+ "username": _require("RTLS_LIVE_USERNAME"),
81
+ "password": _require("RTLS_LIVE_PASSWORD"),
82
+ }
83
+
84
+ @pytest.fixture(scope="session")
85
+ def live_project_uid() -> str:
86
+ return _require("RTLS_LIVE_PROJECT_UID")
87
+
88
+ @pytest.fixture
89
+ def live_client(live_creds, live_project_uid):
90
+ from rtls_sdk import RtlsClient
91
+ with RtlsClient(**live_creds, project_uid=live_project_uid) as client:
92
+ yield client
93
+ ```
94
+
95
+ 4. **Register the `live` pytest marker** in `pyproject.toml`:
96
+
97
+ ```toml
98
+ [tool.pytest.ini_options]
99
+ markers = ["live: tests that hit a real RTLS server (require .env.local)"]
100
+ ```
101
+
102
+ And in each `tests/live/test_*.py` file, mark tests with `pytestmark = pytest.mark.live` at module top.
103
+
104
+ 5. **Add `python-dotenv` to the dev extras** in `pyproject.toml` (no runtime dep).
105
+
106
+ ### Default vs. live test runs
107
+
108
+ - `pytest` (no marker filter) runs unit + mocked integration tests only. Live tests are auto-skipped if `.env.local` is missing. CI runs this.
109
+ - `pytest -m live` runs the live tests. Agent runs this at the end of every milestone as the final verification gate.
110
+ - `pytest -m "not live"` is the explicit safe form for CI / sandboxes that must never reach the network.
111
+
112
+ ### Cleanup discipline (binding from M3 onward)
113
+
114
+ Live tests that **create** server-side state MUST clean it up. Use `yield`-style fixtures or `try/finally`. The live server is shared infrastructure, not scratch space.
115
+
116
+ Naming convention: every test-created resource gets a deterministic `name` or `description` prefix `sdk-test-<uuid4>` so a periodic cleanup script can sweep stale resources if a test crashes hard. Add a `tests/live/_cleanup.py` helper that the M3 prompt sets up.
117
+
118
+ If a live test fails mid-flight, the cleanup fixture still runs (that's why we use `yield` fixtures, not `setup/teardown` returns). Tests must be idempotent: running them twice on a fresh database produces the same outcome as running them once.
119
+
120
+ ### Credentials in logs
121
+
122
+ The redaction filter from M1 strips passwords/tokens from logs. Verify this is working with a live test before treating any subsequent live test output as safe to share. **Never paste raw test logs back to the user without re-checking they don't contain credentials.**
123
+
124
+ ### What a milestone's "Live verification" gate covers
125
+
126
+ The standing rule: **the milestone's primary user-facing happy path must work against the real server, with cleanup.** Each prompt lists the specific live checks for that milestone. The expected pattern is small (3–8 live tests per milestone, each ~10 LoC), focused on shape and integration, not exhaustive coverage — unit tests still own coverage.