wizata-dsapi 1.2.7__tar.gz → 1.2.10__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 (54) hide show
  1. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/PKG-INFO +1 -1
  2. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/__init__.py +1 -1
  3. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/request.py +159 -172
  4. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/twin.py +14 -1
  5. wizata_dsapi-1.2.10/wizata_dsapi/version.py +1 -0
  6. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi.egg-info/PKG-INFO +1 -1
  7. wizata_dsapi-1.2.7/wizata_dsapi/version.py +0 -1
  8. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/LICENSE.txt +0 -0
  9. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/README.rst +0 -0
  10. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/setup.cfg +0 -0
  11. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/setup.py +0 -0
  12. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/api_config.py +0 -0
  13. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/api_dto.py +0 -0
  14. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/api_interface.py +0 -0
  15. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/bucket.py +0 -0
  16. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/business_label.py +0 -0
  17. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/context.py +0 -0
  18. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/dataframe_toolkit.py +0 -0
  19. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/datapoint.py +0 -0
  20. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/datastore.py +0 -0
  21. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/ds_dataframe.py +0 -0
  22. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/dsapi_json_encoder.py +0 -0
  23. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/evaluation.py +0 -0
  24. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/execution.py +0 -0
  25. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/experiment.py +0 -0
  26. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/group_system.py +0 -0
  27. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/ilogger.py +0 -0
  28. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/insight.py +0 -0
  29. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/mlmodel.py +0 -0
  30. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/model_toolkit.py +0 -0
  31. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/models/__init__.py +0 -0
  32. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/models/common.py +0 -0
  33. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/paged_query_result.py +0 -0
  34. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/pipeline.py +0 -0
  35. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/pipeline_image.py +0 -0
  36. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/plot.py +0 -0
  37. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/plots/__init__.py +0 -0
  38. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/plots/common.py +0 -0
  39. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/script.py +0 -0
  40. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/scripts/__init__.py +0 -0
  41. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/scripts/common.py +0 -0
  42. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/solution_component.py +0 -0
  43. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/streamlit_utils.py +0 -0
  44. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/template.py +0 -0
  45. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/trigger.py +0 -0
  46. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/twinregistration.py +0 -0
  47. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/wizard_function.py +0 -0
  48. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/wizard_request.py +0 -0
  49. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/wizata_dsapi_client.py +0 -0
  50. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi/words.py +0 -0
  51. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi.egg-info/SOURCES.txt +0 -0
  52. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
  53. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi.egg-info/requires.txt +0 -0
  54. {wizata_dsapi-1.2.7 → wizata_dsapi-1.2.10}/wizata_dsapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wizata_dsapi
3
- Version: 1.2.7
3
+ Version: 1.2.10
4
4
  Summary: Wizata Data Science Toolkit
5
5
  Author: Wizata S.A.
6
6
  Author-email: info@wizata.com
@@ -4,7 +4,7 @@ from .api_dto import ApiDto, VarType
4
4
  from .paged_query_result import PagedQueryResult
5
5
  from .plot import Plot
6
6
  from .mlmodel import MLModel, MLModelConfig
7
- from .request import Request, filter_map, RequestGroup, RequestGroupMap
7
+ from .request import Request, filter_map, RequestGroup, RequestGroupMap, DynamicSelector
8
8
  from .execution import Execution, ExecutionStatus, ExecutionStepLog, AbortedException
9
9
  from .experiment import Experiment
10
10
  from .ds_dataframe import DSDataFrame
@@ -15,6 +15,35 @@ filter_map = {
15
15
  '!=': 'neq'
16
16
  }
17
17
 
18
+ class DynamicSelector:
19
+ """
20
+ A dynamic selector defines how datapoint(s) are fetched within a query.
21
+ - it corresponds to a request to fetch datapoints
22
+ - it must be used on a twin base query
23
+ :ivar category: filter datapoints on uuid of a category or str name.
24
+ """
25
+
26
+ def __init__(self, category = None):
27
+ self.category = category
28
+
29
+ @classmethod
30
+ def from_dict(cls, obj):
31
+ dynamic_selector = cls()
32
+ dynamic_selector.from_json(obj)
33
+ return dynamic_selector
34
+
35
+ def from_json(self, obj):
36
+ if not isinstance(obj, dict):
37
+ raise TypeError('dynamic selector to parse is not a dict.')
38
+ if "category" in obj:
39
+ self.category = obj["category"]
40
+
41
+ def to_json(self) -> dict:
42
+ obj = {}
43
+ if self.category:
44
+ obj["category"] = self.category
45
+ return obj
46
+
18
47
  class RequestGroup:
19
48
  """
20
49
  Define a group statement within a structured query.
@@ -140,10 +169,9 @@ class Request(ApiDto):
140
169
  self.request_id = request_id
141
170
  self.function = None
142
171
 
143
- # Equipments & Data Points
144
- self.equipments = []
145
- if datapoints is not None:
146
- self.add_datapoints(datapoints)
172
+ # Datapoints
173
+ self._datapoints = []
174
+ self.datapoints = datapoints
147
175
 
148
176
  # Template & Registration
149
177
  self.template = None
@@ -293,63 +321,6 @@ class Request(ApiDto):
293
321
  else:
294
322
  return
295
323
 
296
- def prepare(self):
297
- """
298
- prepare analyse the query and produce a dict with query information.
299
- :return: dict
300
- """
301
- query = {}
302
- if self.request_id is not None:
303
- query["id"] = str(self.request_id)
304
- if self.equipments is not None:
305
- query["equipments_list"] = self.equipments
306
- else:
307
- raise KeyError("Missing data points inside the query - add_datapoints")
308
- if self.start is not None and self.end is not None:
309
-
310
- if isinstance(self.start, str):
311
- start = self.start
312
- else:
313
- start = self.__format_date(self.start)
314
-
315
- if isinstance(self.end, str):
316
- end = self.end
317
- else:
318
- end = self.__format_date(self.end)
319
-
320
- query["timeframe"] = {
321
- "start": start,
322
- "end": end
323
- }
324
- else:
325
- if self.group is None:
326
- raise KeyError("missing in query start and end date, "
327
- "please use datatime format or try with a group system")
328
- query["aggregations"] = {
329
- "agg_method": self.aggregation
330
- }
331
- if self.interval:
332
- query["aggregations"]["interval"] = self.interval * 1000
333
- if self.null is not None and self.null != 'drop':
334
- query['null'] = self.null
335
- if self.template is not None:
336
- query['template'] = self.template
337
- if isinstance(self.template, dict) and 'template_id' in query['template']:
338
- query['template']['template_id'] = str(query['template']['template_id'])
339
- if self.filters is not None:
340
- query['filters'] = self.filters
341
- if self.group is not None:
342
- query['group'] = self.group
343
- if self.options is not None:
344
- query['options'] = self.options
345
- if self.field is not None:
346
- query['field'] = self.field
347
- if self.bucket is not None:
348
- query['bucket'] = self.bucket
349
- if self.tags is not None:
350
- query['tags'] = self.tags
351
- return query
352
-
353
324
  def __format_date(self, dt_to_format):
354
325
  if isinstance(dt_to_format, datetime):
355
326
  millisec = dt_to_format.timestamp() * 1000
@@ -387,79 +358,42 @@ class Request(ApiDto):
387
358
  else:
388
359
  raise TypeError(f'unsupported end datetime type {self.end.__class__.__name__}')
389
360
 
390
- def add_datapoints(self, datapoints, shift: int = 0):
391
- """
392
- add datapoints to existing list of datapoints in the query.
393
- :param datapoints: datapoints to fetch identified by hardware id or template property name.
394
- :param shift: shift to apply in seconds on timestamp, by default 0.
395
- """
396
- self.equipments.append({
397
- "id": None,
398
- "datapoints": list(datapoints),
399
- "shift": str(shift) + "s"
400
- })
361
+ @property
362
+ def datapoints(self):
363
+ return self._datapoints
364
+
365
+ @datapoints.setter
366
+ def datapoints(self, values):
367
+ self._datapoints = []
368
+ if values:
369
+ if not isinstance(values, list):
370
+ raise ValueError(f'datapoints on a query must be a list of str, dict or DynamicSelector')
371
+ for value in values:
372
+ if isinstance(value, str):
373
+ self._datapoints.append(value)
374
+ elif isinstance(value, dict):
375
+ self._datapoints.append(DynamicSelector.from_dict(value))
376
+ elif isinstance(value, DynamicSelector):
377
+ self._datapoints.append(value)
378
+ else:
379
+ raise TypeError(f'datapoints on a query must be a list of str, dict or DynamicSelector '
380
+ f'but encountered {type(value)} as {value}')
401
381
 
402
- def get_datapoints(self) -> list:
403
- """
404
- get datapoints
405
- :return: list of declared datapoints.
406
- """
382
+ @property
383
+ def datapoints_without_selectors(self):
407
384
  datapoints = []
408
-
409
- if self.equipments is not None:
410
- for equipment in self.equipments:
411
- if "datapoints" not in equipment.keys():
412
- raise KeyError("No 'data points' have been provided for equipment with id '" +
413
- str(equipment["id"]) + "'")
414
- for datapoint in equipment["datapoints"]:
415
- if isinstance(datapoint, str):
416
- datapoints.append(datapoint)
417
- elif "id" in datapoint.keys():
418
- datapoints.append(datapoint["id"])
419
- else:
420
- raise KeyError("Incorrect datapoint declaration : '" + str(datapoint) + "'")
421
-
385
+ for datapoint in self._datapoints:
386
+ if not isinstance(datapoint, DynamicSelector):
387
+ datapoints.append(datapoint)
422
388
  return datapoints
423
389
 
424
- def set_datapoints(self, datapoints: list):
425
- """
426
- replace current datapoints by provided list
427
- """
428
- self.equipments = []
429
- if datapoints is not None:
430
- self.add_datapoints(datapoints)
431
-
432
- def add_equipment(self, equipment_id: uuid.UUID, datapoints, shift=0):
433
- """
434
- add datapoints to fetch with a digital twin id identification.
435
- :param equipment_id: UUID of the Digital Twin ID to which the datapoints are linked.
436
- :param datapoints: List(str) of datapoints to fetch identified by Hardware ID.
437
- :param shift: Shift to apply in seconds on timestamp, by default 0.
438
- """
439
- if not isinstance(equipment_id, uuid.UUID):
440
- raise TypeError("equipment_id must be of type uuid.UUID")
441
- for equipment in self.equipments:
442
- if "id" in equipment.keys() and equipment["id"] == str(equipment_id):
443
- raise ValueError("equipment_id is already in the request please remove it before adding datapoints.")
444
- self.equipments.append({
445
- "id": str(equipment_id),
446
- "datapoints": list(datapoints),
447
- "shift": str(shift) + "s"
448
- })
449
-
450
- def remove_equipment(self, equipment_id: uuid.UUID):
451
- """
452
- remove equipment from the list including all its listed datapoints.
453
- :param equipment_id: UUID of the Digital Twin item.
454
- """
455
- if equipment_id is not None and not isinstance(equipment_id, uuid.UUID):
456
- raise TypeError("equipment_id must be None or of type uuid.UUID")
457
- found = None
458
- for equipment in self.equipments:
459
- if "id" in equipment.keys() and equipment["id"] == str(equipment_id):
460
- found = equipment
461
- if found is not None:
462
- self.equipments.remove(equipment)
390
+ @property
391
+ def datapoints_only_selectors(self):
392
+ datapoints = []
393
+ for datapoint in self._datapoints:
394
+ if isinstance(datapoint, DynamicSelector):
395
+ datapoints.append(datapoint)
396
+ return datapoints
463
397
 
464
398
  def set_aggregation(self, method, interval=None):
465
399
  """
@@ -555,9 +489,87 @@ class Request(ApiDto):
555
489
  raise KeyError(f'parameter {name} not found in request.')
556
490
 
557
491
  def to_json(self, target: str = None):
558
- obj = self.prepare()
559
- self.set_extra_data(obj)
560
- return obj
492
+ query = {}
493
+
494
+ if self.request_id is not None:
495
+ query["id"] = str(self.request_id)
496
+
497
+ datapoints = []
498
+ for datapoint in self.datapoints:
499
+ if isinstance(datapoint, str):
500
+ datapoints.append(datapoint)
501
+ elif isinstance(datapoint, dict):
502
+ datapoints.append(datapoint)
503
+ elif isinstance(datapoint, DynamicSelector):
504
+ datapoints.append(datapoint.to_json())
505
+ else:
506
+ raise TypeError(f'datapoints on a query must be a list of str, dict or DynamicSelector '
507
+ f'but encountered {type(datapoint)} as {datapoint}')
508
+ query["datapoints"] = datapoints
509
+
510
+ if self.start is not None and self.end is not None:
511
+
512
+ if isinstance(self.start, str):
513
+ start = self.start
514
+ else:
515
+ start = self.__format_date(self.start)
516
+
517
+ if isinstance(self.end, str):
518
+ end = self.end
519
+ else:
520
+ end = self.__format_date(self.end)
521
+
522
+ query["timeframe"] = {
523
+ "start": start,
524
+ "end": end
525
+ }
526
+ else:
527
+ if self.group is None:
528
+ raise KeyError("missing in query start and end date, "
529
+ "please use datatime format or try with a group system")
530
+ query["aggregations"] = {
531
+ "agg_method": self.aggregation
532
+ }
533
+ if self.interval:
534
+ query["aggregations"]["interval"] = self.interval * 1000
535
+ if self.null is not None and self.null != 'drop':
536
+ query['null'] = self.null
537
+ if self.template is not None:
538
+ query['template'] = self.template
539
+ if isinstance(self.template, dict) and 'template_id' in query['template']:
540
+ query['template']['template_id'] = str(query['template']['template_id'])
541
+ if self.filters is not None:
542
+ query['filters'] = self.filters
543
+ if self.group is not None:
544
+ query['group'] = self.group
545
+ if self.options is not None:
546
+ query['options'] = self.options
547
+ if self.field is not None:
548
+ query['field'] = self.field
549
+ if self.bucket is not None:
550
+ query['bucket'] = self.bucket
551
+ if self.tags is not None:
552
+ query['tags'] = self.tags
553
+
554
+ if self.target_feat is not None:
555
+ query["target_feat"] = {
556
+ "sensor": self.target_feat["sensor"],
557
+ "operator": self.target_feat["operator"],
558
+ "threshold": self.target_feat["threshold"]
559
+ }
560
+ if self.on_off_sensor is not None and self.restart_time is not None:
561
+ query["restart_filter"] = {
562
+ "on_off_sensor": self.on_off_sensor,
563
+ "stop_restart_time": self.restart_time
564
+ }
565
+
566
+ if self.sensitivity is not None:
567
+ query["sensitivity"] = self.sensitivity
568
+
569
+ if self.extra_data is not None:
570
+ query["extra_data"] = self.extra_data
571
+
572
+ return query
561
573
 
562
574
  def from_json(self, json_data):
563
575
  if "id" in json_data.keys():
@@ -566,28 +578,27 @@ class Request(ApiDto):
566
578
  if "name" in json_data.keys():
567
579
  self.name = json_data["name"]
568
580
 
569
- found_dps = False
581
+ datapoints = []
570
582
  if "equipments_list" in json_data.keys():
571
- self.equipments = json_data["equipments_list"]
572
- for equipment in self.equipments:
583
+ for equipment in json_data["equipments_list"]:
573
584
  if "datapoints" not in equipment.keys():
574
- raise KeyError("No 'data points' have been provided for equipment with id '" +
585
+ raise KeyError("no 'datapoints' have been provided for equipment with id '" +
575
586
  str(equipment["id"]) + "'")
576
- else:
577
- found_dps = True
578
- elif "datapoints" in json_data.keys():
579
- equipment = {"datapoints": []}
580
- for datapoint in json_data["datapoints"]:
581
- equipment["datapoints"].append(datapoint)
582
- self.equipments.append(equipment)
583
- found_dps = True
587
+ datapoints.extend(equipment["datapoints"])
588
+
589
+ if "datapoints" in json_data.keys():
590
+ datapoints.extend(["datapoints"])
584
591
 
585
592
  if "template" in json_data.keys():
586
593
  self.template = json_data["template"]
587
- found_dps = True
588
594
 
589
- if not found_dps:
590
- raise KeyError('no equipments_list datapoints, nor template specified inside the request.')
595
+ if len(datapoints) == 0 and self.template is None:
596
+ raise KeyError('at least one datapoint or template is required within a request')
597
+
598
+ if len(datapoints) >= 0:
599
+ self.datapoints = datapoints
600
+ else:
601
+ self._datapoints = []
591
602
 
592
603
  if "timeframe" in json_data.keys():
593
604
  if "start" not in json_data["timeframe"].keys():
@@ -682,28 +693,4 @@ class Request(ApiDto):
682
693
  if "extra_data" in json_data.keys():
683
694
  self.extra_data = json_data["extra_data"]
684
695
 
685
- def set_extra_data(self, obj) -> dict:
686
- """
687
- deprecated - set additional data to query used as post-processing
688
- """
689
- if obj is None:
690
- obj = {}
691
- if self.target_feat is not None:
692
- obj["target_feat"] = {
693
- "sensor": self.target_feat["sensor"],
694
- "operator": self.target_feat["operator"],
695
- "threshold": self.target_feat["threshold"]
696
- }
697
- if self.on_off_sensor is not None and self.restart_time is not None:
698
- obj["restart_filter"] = {
699
- "on_off_sensor": self.on_off_sensor,
700
- "stop_restart_time": self.restart_time
701
- }
702
-
703
- if self.sensitivity is not None:
704
- obj["sensitivity"] = self.sensitivity
705
-
706
- if self.extra_data is not None:
707
- obj["extra_data"] = self.extra_data
708
- return obj
709
696
 
@@ -2,6 +2,7 @@ import uuid
2
2
  from .api_dto import ApiDto
3
3
  from enum import Enum
4
4
  import re
5
+ import json
5
6
 
6
7
 
7
8
  def is_valid_hex_color(color):
@@ -149,7 +150,9 @@ class Twin(ApiDto):
149
150
  def __init__(self, twin_id=None, hardware_id=None, name=None, parent_id=None, ttype=TwinBlockType.MACHINE,
150
151
  type_id:uuid.UUID = None,
151
152
  latitude_id: uuid.UUID = None, longitude_id: uuid.UUID = None, color: str = None, icon: str = None,
152
- latitude: float = None, longitude: float = None, description: str = None):
153
+ latitude: float = None, longitude: float = None,
154
+ extra_properties: dict = None,
155
+ description: str = None):
153
156
  if twin_id is None:
154
157
  self.twin_id = uuid.uuid4()
155
158
  else:
@@ -168,6 +171,7 @@ class Twin(ApiDto):
168
171
  is_valid_hex_color(color)
169
172
  self.color = color
170
173
  self.icon = icon
174
+ self.extra_properties = extra_properties
171
175
  self.createdById = None
172
176
  self.createdDate = None
173
177
  self.updatedById = None
@@ -207,6 +211,11 @@ class Twin(ApiDto):
207
211
  self.description = str(obj["description"])
208
212
  if "type" in obj.keys():
209
213
  self.type = TwinBlockType(str(obj["type"]))
214
+ if "extraProperties" in obj.keys():
215
+ if isinstance(obj["extraProperties"], str):
216
+ self.extra_properties = json.loads(obj["extraProperties"])
217
+ else:
218
+ self.extra_properties = obj["extraProperties"]
210
219
  if "createdById" in obj.keys() and obj["createdById"] is not None:
211
220
  self.createdById = obj["createdById"]
212
221
  if "createdDate" in obj.keys() and obj["createdDate"] is not None:
@@ -244,6 +253,10 @@ class Twin(ApiDto):
244
253
  obj["longitudeSensorId"] = str(self.longitude_id)
245
254
  if self.icon is not None:
246
255
  obj["icon"] = str(self.icon)
256
+ if self.extra_properties is not None:
257
+ if not isinstance(self.extra_properties, dict):
258
+ raise ValueError('on twin extra properties must be None or a JSON serializable dict')
259
+ obj["extraProperties"] = json.dumps(self.extra_properties)
247
260
  if self.createdById is not None:
248
261
  obj["createdById"] = str(self.createdById)
249
262
  if self.createdDate is not None:
@@ -0,0 +1 @@
1
+ __version__ = "1.2.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wizata-dsapi
3
- Version: 1.2.7
3
+ Version: 1.2.10
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__ = "1.2.7"
File without changes
File without changes
File without changes
File without changes