wizata-dsapi 2.2.0.dev15__tar.gz → 2.2.0.dev16__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 (71) hide show
  1. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/PKG-INFO +1 -1
  2. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/__init__.py +1 -1
  3. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/edge_config.py +70 -1
  4. wizata_dsapi-2.2.0.dev16/wizata_dsapi/version.py +1 -0
  5. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/wizata_dsapi_client.py +60 -9
  6. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi.egg-info/PKG-INFO +1 -1
  7. wizata_dsapi-2.2.0.dev15/wizata_dsapi/version.py +0 -1
  8. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/LICENSE.txt +0 -0
  9. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/README.rst +0 -0
  10. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/setup.cfg +0 -0
  11. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/setup.py +0 -0
  12. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/api_config.py +0 -0
  13. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/api_dto.py +0 -0
  14. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/api_interface.py +0 -0
  15. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/bucket.py +0 -0
  16. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/business_label.py +0 -0
  17. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/connection.py +0 -0
  18. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/connection_types.py +0 -0
  19. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/context.py +0 -0
  20. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/dashboard.py +0 -0
  21. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/dataframe_toolkit.py +0 -0
  22. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/datapoint.py +0 -0
  23. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/datastore.py +0 -0
  24. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/deployment.py +0 -0
  25. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/ds_dataframe.py +0 -0
  26. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/dsapi_json_encoder.py +0 -0
  27. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/edge_device.py +0 -0
  28. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/edge_module.py +0 -0
  29. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/evaluation.py +0 -0
  30. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/execution.py +0 -0
  31. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/execution_log.py +0 -0
  32. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/experiment.py +0 -0
  33. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/graylog_log.py +0 -0
  34. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/group_system.py +0 -0
  35. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/ilogger.py +0 -0
  36. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/insight.py +0 -0
  37. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/inventory.py +0 -0
  38. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/mlmodel.py +0 -0
  39. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/mobile_asset.py +0 -0
  40. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/model_toolkit.py +0 -0
  41. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/models/__init__.py +0 -0
  42. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/models/common.py +0 -0
  43. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/notification.py +0 -0
  44. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/paged_query_result.py +0 -0
  45. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/pipeline.py +0 -0
  46. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/pipeline_image.py +0 -0
  47. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/plot.py +0 -0
  48. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/plots/__init__.py +0 -0
  49. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/plots/common.py +0 -0
  50. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/plots/theme.py +0 -0
  51. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/product.py +0 -0
  52. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/request.py +0 -0
  53. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/script.py +0 -0
  54. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/scripts/__init__.py +0 -0
  55. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/scripts/common.py +0 -0
  56. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/search.py +0 -0
  57. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/solution_component.py +0 -0
  58. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/streamlit_utils.py +0 -0
  59. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/template.py +0 -0
  60. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/template_config.py +0 -0
  61. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/trigger.py +0 -0
  62. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/twin.py +0 -0
  63. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/twinregistration.py +0 -0
  64. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/user.py +0 -0
  65. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/wizard_function.py +0 -0
  66. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/wizard_request.py +0 -0
  67. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi/words.py +0 -0
  68. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi.egg-info/SOURCES.txt +0 -0
  69. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
  70. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/wizata_dsapi.egg-info/requires.txt +0 -0
  71. {wizata_dsapi-2.2.0.dev15 → wizata_dsapi-2.2.0.dev16}/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.dev16
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
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, \
@@ -50,19 +50,25 @@ class EdgeWriter(Dto):
50
50
  """
51
51
  A data output destination configured on an edge device.
52
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
+
53
57
  :ivar str id: writer identifier.
54
58
  :ivar str type: writer type (e.g. ``"tsdb"``, ``"hub"``, ``"opc-ua"``).
55
59
  :ivar datapoints: ``"*"`` for all, a list of datapoint names, or a dict mapping names to addresses.
56
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).
57
62
  """
58
63
 
59
64
  _KNOWN_KEYS = {"id", "type", "datapoints"}
60
65
 
61
- def __init__(self, id=None, type=None, datapoints=None, extras=None):
66
+ def __init__(self, id=None, type=None, datapoints=None, extras=None, edge_device_id=None):
62
67
  self.id = id
63
68
  self.type = type
64
69
  self.datapoints = datapoints
65
70
  self.extras = extras or {}
71
+ self.edge_device_id = edge_device_id
66
72
 
67
73
  @classmethod
68
74
  def from_dict(cls, data: dict) -> "EdgeWriter":
@@ -137,3 +143,66 @@ class EdgeTrigger(Dto):
137
143
  if include_extras and self.extras:
138
144
  obj.update(self.extras)
139
145
  return obj
146
+
147
+
148
+ class EdgeFile(Dto):
149
+ """
150
+ A file an edge needs besides its rendered configuration, stored in PostgreSQL
151
+ (DEV-6014): today the code of the transform functions MQTT and RabbitMQ mappings
152
+ name by ``filepath``. Served to the edge at the same path under its conf directory.
153
+
154
+ :ivar str edge_device_id: the device id it belongs to.
155
+ :ivar str path: relative path on the edge, e.g. ``functions/parse.py``.
156
+ :ivar str content: the file's text.
157
+ :ivar str sha256: hex digest of the content.
158
+ """
159
+
160
+ def __init__(self, edge_device_id=None, path=None, content=None, sha256=None,
161
+ created_at=None, updated_at=None):
162
+ self.edge_device_id = edge_device_id
163
+ self.path = path
164
+ self.content = content
165
+ self.sha256 = sha256
166
+ self.created_at = created_at
167
+ self.updated_at = updated_at
168
+
169
+ @classmethod
170
+ def from_dict(cls, data: dict) -> "EdgeFile":
171
+ return cls(edge_device_id=data.get("edgeDeviceId"), path=data.get("path"),
172
+ content=data.get("content"), sha256=data.get("sha256"),
173
+ created_at=data.get("createdAt"), updated_at=data.get("updatedAt"))
174
+
175
+ def to_dict(self, include_content=True) -> dict:
176
+ obj = {"edgeDeviceId": self.edge_device_id, "path": self.path, "sha256": self.sha256}
177
+ if include_content:
178
+ obj["content"] = self.content
179
+ if self.created_at is not None:
180
+ obj["createdAt"] = self.created_at
181
+ if self.updated_at is not None:
182
+ obj["updatedAt"] = self.updated_at
183
+ return obj
184
+
185
+
186
+ class EdgeConfig(Dto):
187
+ """
188
+ Everything an edge syncs into its conf directory, rendered from PostgreSQL by the
189
+ platform (DEV-6014): ``consumers.json``, ``writers.json`` and the stored files, by
190
+ path, plus the pipeline images it should hold. ``etag`` changes whenever any of it
191
+ does, so an edge asks again with it and gets nothing back when nothing changed.
192
+
193
+ :ivar dict files: relative path -> text content.
194
+ :ivar list images: pipeline image ids the edge should have under ``images/``.
195
+ :ivar str etag: digest of files and images.
196
+ """
197
+
198
+ def __init__(self, files=None, images=None, etag=None):
199
+ self.files = files or {}
200
+ self.images = images or []
201
+ self.etag = etag
202
+
203
+ @classmethod
204
+ def from_dict(cls, data: dict) -> "EdgeConfig":
205
+ return cls(files=data.get("files"), images=data.get("images"), etag=data.get("etag"))
206
+
207
+ def to_dict(self) -> dict:
208
+ return {"files": dict(self.files), "images": list(self.images), "etag": self.etag}
@@ -0,0 +1 @@
1
+ __version__ = "2.2.0.dev16"
@@ -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.dev16
4
4
  Summary: Wizata Data Science Toolkit
5
5
  Author: Wizata S.A.
6
6
  Author-email: info@wizata.com
@@ -1 +0,0 @@
1
- __version__ = "2.2.0.dev15"