wizata-dsapi 2.2.0.dev15__tar.gz → 2.2.0.dev17__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 (72) hide show
  1. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/PKG-INFO +1 -1
  2. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/__init__.py +1 -1
  3. wizata_dsapi-2.2.0.dev17/wizata_dsapi/edge_config.py +240 -0
  4. wizata_dsapi-2.2.0.dev17/wizata_dsapi/version.py +1 -0
  5. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/wizata_dsapi_client.py +60 -9
  6. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi.egg-info/PKG-INFO +1 -1
  7. wizata_dsapi-2.2.0.dev15/wizata_dsapi/edge_config.py +0 -139
  8. wizata_dsapi-2.2.0.dev15/wizata_dsapi/version.py +0 -1
  9. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/LICENSE.txt +0 -0
  10. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/README.rst +0 -0
  11. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/setup.cfg +0 -0
  12. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/setup.py +0 -0
  13. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/api_config.py +0 -0
  14. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/api_dto.py +0 -0
  15. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/api_interface.py +0 -0
  16. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/bucket.py +0 -0
  17. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/business_label.py +0 -0
  18. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/connection.py +0 -0
  19. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/connection_types.py +0 -0
  20. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/context.py +0 -0
  21. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/dashboard.py +0 -0
  22. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/dataframe_toolkit.py +0 -0
  23. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/datapoint.py +0 -0
  24. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/datastore.py +0 -0
  25. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/deployment.py +0 -0
  26. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/ds_dataframe.py +0 -0
  27. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/dsapi_json_encoder.py +0 -0
  28. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/edge_device.py +0 -0
  29. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/edge_module.py +0 -0
  30. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/evaluation.py +0 -0
  31. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/execution.py +0 -0
  32. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/execution_log.py +0 -0
  33. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/experiment.py +0 -0
  34. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/graylog_log.py +0 -0
  35. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/group_system.py +0 -0
  36. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/ilogger.py +0 -0
  37. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/insight.py +0 -0
  38. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/inventory.py +0 -0
  39. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/mlmodel.py +0 -0
  40. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/mobile_asset.py +0 -0
  41. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/model_toolkit.py +0 -0
  42. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/models/__init__.py +0 -0
  43. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/models/common.py +0 -0
  44. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/notification.py +0 -0
  45. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/paged_query_result.py +0 -0
  46. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/pipeline.py +0 -0
  47. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/pipeline_image.py +0 -0
  48. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/plot.py +0 -0
  49. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/plots/__init__.py +0 -0
  50. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/plots/common.py +0 -0
  51. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/plots/theme.py +0 -0
  52. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/product.py +0 -0
  53. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/request.py +0 -0
  54. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/script.py +0 -0
  55. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/scripts/__init__.py +0 -0
  56. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/scripts/common.py +0 -0
  57. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/search.py +0 -0
  58. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/solution_component.py +0 -0
  59. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/streamlit_utils.py +0 -0
  60. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/template.py +0 -0
  61. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/template_config.py +0 -0
  62. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/trigger.py +0 -0
  63. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/twin.py +0 -0
  64. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/twinregistration.py +0 -0
  65. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/user.py +0 -0
  66. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/wizard_function.py +0 -0
  67. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/wizard_request.py +0 -0
  68. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi/words.py +0 -0
  69. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi.egg-info/SOURCES.txt +0 -0
  70. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
  71. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi.egg-info/requires.txt +0 -0
  72. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev17}/wizata_dsapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wizata_dsapi
3
- Version: 2.2.0.dev15
3
+ Version: 2.2.0.dev17
4
4
  Summary: Wizata Data Science Toolkit
5
5
  Author: Wizata S.A.
6
6
  Author-email: info@wizata.com
@@ -60,7 +60,7 @@ from .notification import NotificationScope, DeviceToken, AlertRule, AlertActive
60
60
  MonitoredPipeline, NotificationPreference, RecipientList, Recipient, RecipientType
61
61
  from .edge_device import EdgeDevice, EdgeDeviceStatus, EdgeDeviceType, EdgeDeviceProvisioning
62
62
  from .edge_module import EdgeModule, EdgeModuleType, WIZATA_MODULES
63
- from .edge_config import EdgeConsumer, EdgeWriter, EdgeTrigger
63
+ from .edge_config import EdgeConsumer, EdgeWriter, EdgeTrigger, EdgeFile, EdgeConfig, EdgeCommand
64
64
  from .connection import Connection, ConnectionMapping, ConnectionDirection, TransformKind, \
65
65
  EdgeDeviceKey, EdgeDeviceKeyStatus
66
66
  from .connection_types import ConnectionType, CONNECTION_TYPES, TRANSFORM_SCHEMAS, get_connection_type, \
@@ -0,0 +1,240 @@
1
+ from .api_dto import Dto
2
+
3
+
4
+ class EdgeConsumer(Dto):
5
+ """
6
+ A data source connector configured on an edge device.
7
+
8
+ Known fields are extracted as attributes; everything else lands in ``extras``.
9
+ Consumer types vary (opc-ua, mqtt, modbus, …) so type-specific fields are
10
+ intentionally left unstructured.
11
+
12
+ :ivar str id: connector identifier.
13
+ :ivar str type: connector type (e.g. ``"opc-ua"``, ``"mqtt"``).
14
+ :ivar str prefix: datapoint prefix applied by this consumer.
15
+ :ivar dict extras: all other fields from the JSON config.
16
+ """
17
+
18
+ _KNOWN_KEYS = {"id", "type", "prefix"}
19
+
20
+ def __init__(self, id=None, type=None, prefix=None, extras=None):
21
+ self.id = id
22
+ self.type = type
23
+ self.prefix = prefix
24
+ self.extras = extras or {}
25
+
26
+ @classmethod
27
+ def from_dict(cls, data: dict) -> "EdgeConsumer":
28
+ extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
29
+ return cls(
30
+ id=data.get("id"),
31
+ type=data.get("type"),
32
+ prefix=data.get("prefix"),
33
+ extras=extras,
34
+ )
35
+
36
+ def to_dict(self, include_extras=True) -> dict:
37
+ obj = {}
38
+ if self.id is not None:
39
+ obj["id"] = self.id
40
+ if self.type is not None:
41
+ obj["type"] = self.type
42
+ if self.prefix is not None:
43
+ obj["prefix"] = self.prefix
44
+ if include_extras and self.extras:
45
+ obj.update(self.extras)
46
+ return obj
47
+
48
+
49
+ class EdgeWriter(Dto):
50
+ """
51
+ A data output destination configured on an edge device.
52
+
53
+ Also the stored entity for an edge's ``cloud`` (hub) and ``edge`` (tsdb) writers
54
+ (DEV-6014, ``"EdgeWriter"`` table): which datapoints each one passes. Their connection
55
+ settings stay in the edge's environment, so a stored writer has no ``extras``.
56
+
57
+ :ivar str id: writer identifier.
58
+ :ivar str type: writer type (e.g. ``"tsdb"``, ``"hub"``, ``"opc-ua"``).
59
+ :ivar datapoints: ``"*"`` for all, a list of datapoint names, or a dict mapping names to addresses.
60
+ :ivar dict extras: all other fields from the JSON config.
61
+ :ivar str edge_device_id: the device it belongs to, when stored (not part of writers.json).
62
+ """
63
+
64
+ _KNOWN_KEYS = {"id", "type", "datapoints"}
65
+
66
+ def __init__(self, id=None, type=None, datapoints=None, extras=None, edge_device_id=None):
67
+ self.id = id
68
+ self.type = type
69
+ self.datapoints = datapoints
70
+ self.extras = extras or {}
71
+ self.edge_device_id = edge_device_id
72
+
73
+ @classmethod
74
+ def from_dict(cls, data: dict) -> "EdgeWriter":
75
+ extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
76
+ return cls(
77
+ id=data.get("id"),
78
+ type=data.get("type"),
79
+ datapoints=data.get("datapoints"),
80
+ extras=extras,
81
+ )
82
+
83
+ def to_dict(self, include_extras=True) -> dict:
84
+ obj = {}
85
+ if self.id is not None:
86
+ obj["id"] = self.id
87
+ if self.type is not None:
88
+ obj["type"] = self.type
89
+ if self.datapoints is not None:
90
+ obj["datapoints"] = self.datapoints
91
+ if include_extras and self.extras:
92
+ obj.update(self.extras)
93
+ return obj
94
+
95
+
96
+ class EdgeTrigger(Dto):
97
+ """
98
+ A pipeline trigger configured on an edge device.
99
+
100
+ Also the stored entity for an edge's hand-made triggers (DEV-6095, ``"EdgeTrigger"``
101
+ table): a standalone trigger, or an overlay naming a ``deploymentId`` whose extra
102
+ keys (``exceptions``...) are merged into that deployment's rendered entry.
103
+
104
+ :ivar str id: trigger identifier (the stored key).
105
+ :ivar int interval: execution interval in milliseconds.
106
+ :ivar str pipeline_image_id: pipeline image reference (build tag + pipeline key).
107
+ :ivar str twin: twin key this trigger targets.
108
+ :ivar str template: template key this trigger targets.
109
+ :ivar dict extras: all other fields from the JSON config.
110
+ :ivar str edge_device_id: the device it belongs to, when stored (not part of triggers.json).
111
+ """
112
+
113
+ _KNOWN_KEYS = {"id", "interval", "pipelineImageId", "twin", "template"}
114
+
115
+ def __init__(self, id=None, interval=None, pipeline_image_id=None,
116
+ twin=None, template=None, extras=None, edge_device_id=None):
117
+ self.id = id
118
+ self.interval = interval
119
+ self.pipeline_image_id = pipeline_image_id
120
+ self.twin = twin
121
+ self.template = template
122
+ self.extras = extras or {}
123
+ self.edge_device_id = edge_device_id
124
+
125
+ @classmethod
126
+ def from_dict(cls, data: dict) -> "EdgeTrigger":
127
+ extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
128
+ return cls(
129
+ id=data.get("id"),
130
+ interval=data.get("interval"),
131
+ pipeline_image_id=data.get("pipelineImageId"),
132
+ twin=data.get("twin"),
133
+ template=data.get("template"),
134
+ extras=extras,
135
+ )
136
+
137
+ def to_dict(self, include_extras=True) -> dict:
138
+ obj = {}
139
+ if self.id is not None:
140
+ obj["id"] = self.id
141
+ if self.interval is not None:
142
+ obj["interval"] = self.interval
143
+ if self.pipeline_image_id is not None:
144
+ obj["pipelineImageId"] = self.pipeline_image_id
145
+ if self.twin is not None:
146
+ obj["twin"] = self.twin
147
+ if self.template is not None:
148
+ obj["template"] = self.template
149
+ if include_extras and self.extras:
150
+ obj.update(self.extras)
151
+ return obj
152
+
153
+
154
+ class EdgeFile(Dto):
155
+ """
156
+ A file an edge needs besides its rendered configuration, stored in PostgreSQL
157
+ (DEV-6014): today the code of the transform functions MQTT and RabbitMQ mappings
158
+ name by ``filepath``. Served to the edge at the same path under its conf directory.
159
+
160
+ :ivar str edge_device_id: the device id it belongs to.
161
+ :ivar str path: relative path on the edge, e.g. ``functions/parse.py``.
162
+ :ivar str content: the file's text.
163
+ :ivar str sha256: hex digest of the content.
164
+ """
165
+
166
+ def __init__(self, edge_device_id=None, path=None, content=None, sha256=None,
167
+ created_at=None, updated_at=None):
168
+ self.edge_device_id = edge_device_id
169
+ self.path = path
170
+ self.content = content
171
+ self.sha256 = sha256
172
+ self.created_at = created_at
173
+ self.updated_at = updated_at
174
+
175
+ @classmethod
176
+ def from_dict(cls, data: dict) -> "EdgeFile":
177
+ return cls(edge_device_id=data.get("edgeDeviceId"), path=data.get("path"),
178
+ content=data.get("content"), sha256=data.get("sha256"),
179
+ created_at=data.get("createdAt"), updated_at=data.get("updatedAt"))
180
+
181
+ def to_dict(self, include_content=True) -> dict:
182
+ obj = {"edgeDeviceId": self.edge_device_id, "path": self.path, "sha256": self.sha256}
183
+ if include_content:
184
+ obj["content"] = self.content
185
+ if self.created_at is not None:
186
+ obj["createdAt"] = self.created_at
187
+ if self.updated_at is not None:
188
+ obj["updatedAt"] = self.updated_at
189
+ return obj
190
+
191
+
192
+ class EdgeConfig(Dto):
193
+ """
194
+ Everything an edge syncs into its conf directory, rendered from PostgreSQL by the
195
+ platform (DEV-6014): ``consumers.json``, ``writers.json`` and the stored files, by
196
+ path, plus the pipeline images it should hold. ``etag`` changes whenever any of it
197
+ does, so an edge asks again with it and gets nothing back when nothing changed.
198
+
199
+ :ivar dict files: relative path -> text content.
200
+ :ivar list images: pipeline image ids the edge should have under ``images/``.
201
+ :ivar str etag: digest of files and images.
202
+ """
203
+
204
+ def __init__(self, files=None, images=None, etag=None):
205
+ self.files = files or {}
206
+ self.images = images or []
207
+ self.etag = etag
208
+
209
+ @classmethod
210
+ def from_dict(cls, data: dict) -> "EdgeConfig":
211
+ return cls(files=data.get("files"), images=data.get("images"), etag=data.get("etag"))
212
+
213
+ def to_dict(self) -> dict:
214
+ return {"files": dict(self.files), "images": list(self.images), "etag": self.etag}
215
+
216
+
217
+ class EdgeCommand(Dto):
218
+ """
219
+ A one-shot operator command for an edge (DEV-6095), served as ``command.json`` until
220
+ it expires. The edge's command handler runs it once and refuses it when older than
221
+ 15 minutes, so the platform only stores what was asked, and when.
222
+
223
+ :ivar str id: command id (UUID); the edge remembers which ids it ran.
224
+ :ivar str edge_device_id: the device it is for.
225
+ :ivar str action: e.g. ``purge_queue``.
226
+ :ivar str queue: e.g. ``pipeline``.
227
+ :ivar str issued_at: ISO-8601 UTC, ``Z`` suffix.
228
+ """
229
+
230
+ def __init__(self, id=None, edge_device_id=None, action=None, queue=None, issued_at=None):
231
+ self.id = id
232
+ self.edge_device_id = edge_device_id
233
+ self.action = action
234
+ self.queue = queue
235
+ self.issued_at = issued_at
236
+
237
+ def to_dict(self) -> dict:
238
+ """The ``command.json`` shape the edge's command handler reads."""
239
+ return {"id": self.id, "action": self.action, "queue": self.queue,
240
+ "issuedAt": self.issued_at}
@@ -0,0 +1 @@
1
+ __version__ = "2.2.0.dev17"
@@ -57,6 +57,7 @@ from .deployment import Deployment, DeploymentStatus
57
57
  from .product import ProductKind, Product, ProductComponent
58
58
  from .inventory import InventoryBalance, InventoryStorage, InventoryStock
59
59
  from .connection import EdgeDeviceKey
60
+ from .edge_config import EdgeConfig
60
61
  from .api_config import _registry
61
62
 
62
63
  from .streamlit_utils import get_streamlit_token, get_streamlit_domain
@@ -2990,13 +2991,28 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
2990
2991
  self._planner_request("DELETE", f"planner/inventory/balances/{self._balance_id(balance)}/",
2991
2992
  expected=(204,))
2992
2993
 
2993
- # ------------------------------------------------------------ edge device keys
2994
+ # ------------------------------------------------------------ edge (DEV-6013/6014)
2994
2995
 
2995
2996
  @staticmethod
2996
- def _edge_device_keys_route(edge_device_id) -> str:
2997
+ def _edge_route(edge_device_id, *parts) -> str:
2998
+ """``edge/<device id>/...``: the routes an edge itself calls."""
2997
2999
  if not edge_device_id:
2998
3000
  raise ValueError("please provide the edge device id")
2999
- return f"edgedevices/{urllib.parse.quote(str(edge_device_id), safe='')}/keys"
3001
+ quoted = [urllib.parse.quote(str(p), safe='') for p in (edge_device_id,) + parts]
3002
+ return "edge/" + "/".join(quoted)
3003
+
3004
+ def _edge_request(self, method: str, route: str, body=None, headers=None,
3005
+ expected=(200,), stream=False):
3006
+ response = requests.request(
3007
+ method,
3008
+ self.__url() + route,
3009
+ headers={**self.__header(), **(headers or {})},
3010
+ data=json.dumps(body) if body is not None else None,
3011
+ stream=stream,
3012
+ )
3013
+ if response.status_code not in expected:
3014
+ raise self.__raise_error(response)
3015
+ return response
3000
3016
 
3001
3017
  def register_edge_device_key(self, edge_device_id: str, public_key: str) -> EdgeDeviceKey:
3002
3018
  """
@@ -3012,9 +3028,8 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
3012
3028
  :param public_key: PEM of the edge's X25519 public key.
3013
3029
  :return: the edge's active key.
3014
3030
  """
3015
- response = self._process_request(method="POST",
3016
- route=self._edge_device_keys_route(edge_device_id),
3017
- data=json.dumps({"publicKey": public_key}))
3031
+ response = self._edge_request("POST", self._edge_route(edge_device_id, "keys"),
3032
+ body={"publicKey": public_key}, expected=(200, 201))
3018
3033
  return EdgeDeviceKey.from_dict(response.json())
3019
3034
 
3020
3035
  def get_edge_device_keys(self, edge_device_id: str) -> list:
@@ -3023,9 +3038,45 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
3023
3038
  :return: every EdgeDeviceKey the edge registered, newest first. With no active
3024
3039
  one, the edge has not registered yet and no secret can be entered for it.
3025
3040
  """
3026
- return self._process_request(method="GET",
3027
- route=self._edge_device_keys_route(edge_device_id),
3028
- dto_class=EdgeDeviceKey, result_type="list")
3041
+ response = self._edge_request("GET", self._edge_route(edge_device_id, "keys"))
3042
+ return [EdgeDeviceKey.from_dict(k) for k in response.json()]
3043
+
3044
+ def get_edge_config(self, edge_device_id: str, etag: str = None):
3045
+ """
3046
+ What the edge syncs into its conf directory, rendered from PostgreSQL (DEV-6014).
3047
+
3048
+ :param edge_device_id: the device id the edge runs as.
3049
+ :param etag: the etag of the configuration the edge already holds.
3050
+ :return: EdgeConfig, or None when it has not changed since ``etag``.
3051
+ """
3052
+ headers = {"If-None-Match": f'"{etag}"'} if etag else None
3053
+ response = self._edge_request("GET", self._edge_route(edge_device_id, "config"),
3054
+ headers=headers, expected=(200, 304))
3055
+ if response.status_code == 304:
3056
+ return None
3057
+ return EdgeConfig.from_dict(response.json())
3058
+
3059
+ def download_edge_image(self, edge_device_id: str, image_id: str, path: str) -> str:
3060
+ """
3061
+ Stream a pipeline image to ``path``, written whole or not at all: it lands in a
3062
+ temporary file next to ``path`` and is renamed once complete, so a runner never
3063
+ loads half an image.
3064
+
3065
+ :return: ``path``.
3066
+ """
3067
+ response = self._edge_request("GET", self._edge_route(edge_device_id, "images", image_id),
3068
+ stream=True)
3069
+ temporary = path + ".part"
3070
+ try:
3071
+ with open(temporary, "wb") as f:
3072
+ for chunk in response.iter_content(chunk_size=1024 * 1024):
3073
+ f.write(chunk)
3074
+ os.replace(temporary, path)
3075
+ finally:
3076
+ response.close()
3077
+ if os.path.exists(temporary):
3078
+ os.remove(temporary)
3079
+ return path
3029
3080
 
3030
3081
 
3031
3082
  def api() -> WizataDSAPIClient:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wizata_dsapi
3
- Version: 2.2.0.dev15
3
+ Version: 2.2.0.dev17
4
4
  Summary: Wizata Data Science Toolkit
5
5
  Author: Wizata S.A.
6
6
  Author-email: info@wizata.com
@@ -1,139 +0,0 @@
1
- from .api_dto import Dto
2
-
3
-
4
- class EdgeConsumer(Dto):
5
- """
6
- A data source connector configured on an edge device.
7
-
8
- Known fields are extracted as attributes; everything else lands in ``extras``.
9
- Consumer types vary (opc-ua, mqtt, modbus, …) so type-specific fields are
10
- intentionally left unstructured.
11
-
12
- :ivar str id: connector identifier.
13
- :ivar str type: connector type (e.g. ``"opc-ua"``, ``"mqtt"``).
14
- :ivar str prefix: datapoint prefix applied by this consumer.
15
- :ivar dict extras: all other fields from the JSON config.
16
- """
17
-
18
- _KNOWN_KEYS = {"id", "type", "prefix"}
19
-
20
- def __init__(self, id=None, type=None, prefix=None, extras=None):
21
- self.id = id
22
- self.type = type
23
- self.prefix = prefix
24
- self.extras = extras or {}
25
-
26
- @classmethod
27
- def from_dict(cls, data: dict) -> "EdgeConsumer":
28
- extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
29
- return cls(
30
- id=data.get("id"),
31
- type=data.get("type"),
32
- prefix=data.get("prefix"),
33
- extras=extras,
34
- )
35
-
36
- def to_dict(self, include_extras=True) -> dict:
37
- obj = {}
38
- if self.id is not None:
39
- obj["id"] = self.id
40
- if self.type is not None:
41
- obj["type"] = self.type
42
- if self.prefix is not None:
43
- obj["prefix"] = self.prefix
44
- if include_extras and self.extras:
45
- obj.update(self.extras)
46
- return obj
47
-
48
-
49
- class EdgeWriter(Dto):
50
- """
51
- A data output destination configured on an edge device.
52
-
53
- :ivar str id: writer identifier.
54
- :ivar str type: writer type (e.g. ``"tsdb"``, ``"hub"``, ``"opc-ua"``).
55
- :ivar datapoints: ``"*"`` for all, a list of datapoint names, or a dict mapping names to addresses.
56
- :ivar dict extras: all other fields from the JSON config.
57
- """
58
-
59
- _KNOWN_KEYS = {"id", "type", "datapoints"}
60
-
61
- def __init__(self, id=None, type=None, datapoints=None, extras=None):
62
- self.id = id
63
- self.type = type
64
- self.datapoints = datapoints
65
- self.extras = extras or {}
66
-
67
- @classmethod
68
- def from_dict(cls, data: dict) -> "EdgeWriter":
69
- extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
70
- return cls(
71
- id=data.get("id"),
72
- type=data.get("type"),
73
- datapoints=data.get("datapoints"),
74
- extras=extras,
75
- )
76
-
77
- def to_dict(self, include_extras=True) -> dict:
78
- obj = {}
79
- if self.id is not None:
80
- obj["id"] = self.id
81
- if self.type is not None:
82
- obj["type"] = self.type
83
- if self.datapoints is not None:
84
- obj["datapoints"] = self.datapoints
85
- if include_extras and self.extras:
86
- obj.update(self.extras)
87
- return obj
88
-
89
-
90
- class EdgeTrigger(Dto):
91
- """
92
- A pipeline trigger configured on an edge device.
93
-
94
- :ivar str id: trigger identifier.
95
- :ivar int interval: execution interval in milliseconds.
96
- :ivar str pipeline_image_id: pipeline image reference (build tag + pipeline key).
97
- :ivar str twin: twin key this trigger targets.
98
- :ivar str template: template key this trigger targets.
99
- :ivar dict extras: all other fields from the JSON config.
100
- """
101
-
102
- _KNOWN_KEYS = {"id", "interval", "pipelineImageId", "twin", "template"}
103
-
104
- def __init__(self, id=None, interval=None, pipeline_image_id=None,
105
- twin=None, template=None, extras=None):
106
- self.id = id
107
- self.interval = interval
108
- self.pipeline_image_id = pipeline_image_id
109
- self.twin = twin
110
- self.template = template
111
- self.extras = extras or {}
112
-
113
- @classmethod
114
- def from_dict(cls, data: dict) -> "EdgeTrigger":
115
- extras = {k: v for k, v in data.items() if k not in cls._KNOWN_KEYS}
116
- return cls(
117
- id=data.get("id"),
118
- interval=data.get("interval"),
119
- pipeline_image_id=data.get("pipelineImageId"),
120
- twin=data.get("twin"),
121
- template=data.get("template"),
122
- extras=extras,
123
- )
124
-
125
- def to_dict(self, include_extras=True) -> dict:
126
- obj = {}
127
- if self.id is not None:
128
- obj["id"] = self.id
129
- if self.interval is not None:
130
- obj["interval"] = self.interval
131
- if self.pipeline_image_id is not None:
132
- obj["pipelineImageId"] = self.pipeline_image_id
133
- if self.twin is not None:
134
- obj["twin"] = self.twin
135
- if self.template is not None:
136
- obj["template"] = self.template
137
- if include_extras and self.extras:
138
- obj.update(self.extras)
139
- return obj
@@ -1 +0,0 @@
1
- __version__ = "2.2.0.dev15"