dtlpy 1.90.39__py3-none-any.whl → 1.92.18__py3-none-any.whl

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 (57) hide show
  1. dtlpy/__init__.py +5 -2
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/lock_open.png +0 -0
  4. dtlpy/entities/__init__.py +1 -1
  5. dtlpy/entities/analytic.py +118 -98
  6. dtlpy/entities/annotation.py +22 -31
  7. dtlpy/entities/annotation_collection.py +19 -21
  8. dtlpy/entities/app.py +13 -3
  9. dtlpy/entities/assignment.py +6 -0
  10. dtlpy/entities/base_entity.py +0 -23
  11. dtlpy/entities/command.py +3 -2
  12. dtlpy/entities/dataset.py +53 -3
  13. dtlpy/entities/dpk.py +15 -0
  14. dtlpy/entities/execution.py +13 -1
  15. dtlpy/entities/feature_set.py +3 -0
  16. dtlpy/entities/filters.py +87 -8
  17. dtlpy/entities/integration.py +1 -1
  18. dtlpy/entities/item.py +41 -1
  19. dtlpy/entities/node.py +49 -3
  20. dtlpy/entities/ontology.py +62 -5
  21. dtlpy/entities/package_function.py +2 -0
  22. dtlpy/entities/package_module.py +13 -0
  23. dtlpy/entities/pipeline.py +20 -1
  24. dtlpy/entities/pipeline_execution.py +37 -6
  25. dtlpy/entities/prompt_item.py +240 -27
  26. dtlpy/entities/recipe.py +37 -0
  27. dtlpy/entities/service.py +33 -4
  28. dtlpy/ml/base_model_adapter.py +166 -18
  29. dtlpy/new_instance.py +80 -9
  30. dtlpy/repositories/apps.py +68 -22
  31. dtlpy/repositories/assignments.py +1 -1
  32. dtlpy/repositories/commands.py +10 -2
  33. dtlpy/repositories/datasets.py +143 -13
  34. dtlpy/repositories/dpks.py +34 -1
  35. dtlpy/repositories/executions.py +27 -30
  36. dtlpy/repositories/feature_sets.py +23 -3
  37. dtlpy/repositories/features.py +4 -1
  38. dtlpy/repositories/models.py +1 -1
  39. dtlpy/repositories/packages.py +6 -3
  40. dtlpy/repositories/pipeline_executions.py +58 -5
  41. dtlpy/repositories/services.py +28 -7
  42. dtlpy/repositories/tasks.py +8 -2
  43. dtlpy/repositories/uploader.py +5 -2
  44. dtlpy/services/api_client.py +74 -12
  45. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/METADATA +2 -2
  46. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/RECORD +54 -57
  47. tests/features/environment.py +67 -1
  48. dtlpy/callbacks/__init__.py +0 -16
  49. dtlpy/callbacks/piper_progress_reporter.py +0 -29
  50. dtlpy/callbacks/progress_viewer.py +0 -54
  51. {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp +0 -0
  52. {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp.bat +0 -0
  53. {dtlpy-1.90.39.data → dtlpy-1.92.18.data}/scripts/dlp.py +0 -0
  54. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/LICENSE +0 -0
  55. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/WHEEL +0 -0
  56. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/entry_points.txt +0 -0
  57. {dtlpy-1.90.39.dist-info → dtlpy-1.92.18.dist-info}/top_level.txt +0 -0
@@ -75,7 +75,20 @@ class PackageModule(entities.DlEntity):
75
75
  for cls_name, cls_inst in inspect.getmembers(file_module, predicate=inspect.isclass):
76
76
  spec = getattr(cls_inst, '__dtlpy__', None)
77
77
  if spec is not None:
78
+ functions = spec['functions']
79
+ available_methods = [name for name in ['train', 'predict']
80
+ if 'BaseModelAdapter' not in getattr(cls_inst, name).__qualname__]
81
+ if "train" not in available_methods:
82
+ # remove train_model from functions list if train is not available
83
+ functions[:] = [d for d in functions if d.get('name') != "train_model"]
84
+ if "predict" not in available_methods:
85
+ # remove predict_items from functions list if predict is not available
86
+ functions[:] = [d for d in functions if d.get('name') != "predict_items"]
87
+ if "extract_features" not in available_methods:
88
+ # remove extract_item_features from functions list if extract_features is not available
89
+ functions[:] = [d for d in functions if d.get('name') != "extract_item_features"]
78
90
  spec['entryPoint'] = entry_point
91
+ spec['functions'] = functions
79
92
  module = cls.from_json(spec)
80
93
  break
81
94
  if module is None:
@@ -456,7 +456,6 @@ class Pipeline(entities.BaseEntity):
456
456
  """
457
457
  return self.pipelines.update(pipeline=self)
458
458
 
459
-
460
459
  def delete(self):
461
460
  """
462
461
  Delete pipeline object
@@ -560,3 +559,23 @@ class Pipeline(entities.BaseEntity):
560
559
  else:
561
560
  self.start_nodes = [{"nodeId": node.node_id,
562
561
  "type": "root", }]
562
+
563
+ def update_variables_values(self, **kwargs):
564
+ """
565
+ Update pipeline variables values for the given keyword arguments.
566
+
567
+ **Example**:
568
+
569
+ .. code-block:: python
570
+ pipeline.update_variables_values(
571
+ dataset=dataset.id,
572
+ model=model.id,
573
+ threshold=0.9
574
+ )
575
+ pipeline.update()
576
+ """
577
+ keys = kwargs.keys()
578
+ for variable in self.variables:
579
+ if variable.name in keys:
580
+ variable.value = kwargs[variable.name]
581
+
@@ -11,6 +11,16 @@ from ..services.api_client import ApiClient
11
11
  logger = logging.getLogger(name='dtlpy')
12
12
 
13
13
 
14
+ class PipelineExecutionStatus(str, Enum):
15
+ PENDING = "pending"
16
+ IN_PROGRESS = "in-progress"
17
+ FAILED = "failed"
18
+ SUCCESS = "success"
19
+ QUEUE = "queue"
20
+ TERMINATED = "terminated"
21
+ RERUN = "rerun"
22
+
23
+
14
24
  class CycleRerunMethod(str, Enum):
15
25
  START_FROM_NODES = 'startFromNodes',
16
26
  START_FROM_FAILED_EXECUTIONS = 'startFromFailedExecutions',
@@ -76,6 +86,7 @@ class PipelineExecution(entities.BaseEntity):
76
86
 
77
87
  # sdk
78
88
  _pipeline = attr.ib(repr=False)
89
+ _project = attr.ib(repr=False)
79
90
  _client_api = attr.ib(type=ApiClient, repr=False)
80
91
  _repositories = attr.ib(repr=False)
81
92
 
@@ -103,7 +114,7 @@ class PipelineExecution(entities.BaseEntity):
103
114
  return status, pipeline
104
115
 
105
116
  @classmethod
106
- def from_json(cls, _json, client_api, pipeline, is_fetched=True):
117
+ def from_json(cls, _json, client_api, pipeline, is_fetched=True) -> 'PipelineExecution':
107
118
  """
108
119
  Turn platform representation of pipeline_execution into a pipeline_execution entity
109
120
 
@@ -112,9 +123,11 @@ class PipelineExecution(entities.BaseEntity):
112
123
  :param dtlpy.entities.pipeline.Pipeline pipeline: Pipeline entity
113
124
  :param bool is_fetched: is Entity fetched from Platform
114
125
  :return: Pipeline entity
115
- :rtype: dtlpy.entities.pipeline.Pipeline
126
+ :rtype: dtlpy.entities.PipelineExecution
116
127
  """
128
+ project = None
117
129
  if pipeline is not None:
130
+ project = pipeline._project
118
131
  if pipeline.id != _json.get('pipelineId', None):
119
132
  logger.warning('Pipeline has been fetched from a project that is not belong to it')
120
133
  pipeline = None
@@ -132,6 +145,7 @@ class PipelineExecution(entities.BaseEntity):
132
145
  nodes=nodes,
133
146
  executions=_json.get('executions', dict()),
134
147
  pipeline=pipeline,
148
+ project=project,
135
149
  client_api=client_api,
136
150
  )
137
151
 
@@ -183,7 +197,10 @@ class PipelineExecution(entities.BaseEntity):
183
197
 
184
198
  @property
185
199
  def project(self):
186
- return self.pipeline.project
200
+ if self._project is None:
201
+ self._project = self.pipeline.project
202
+ assert isinstance(self._pipeline.project, entities.Project)
203
+ return self._pipeline.project
187
204
 
188
205
  ################
189
206
  # repositories #
@@ -195,9 +212,10 @@ class PipelineExecution(entities.BaseEntity):
195
212
 
196
213
  r = reps(
197
214
  projects=repositories.Projects(client_api=self._client_api),
198
- pipelines=repositories.Pipelines(client_api=self._client_api, project=self.project),
199
- pipeline_executions=repositories.PipelineExecutions(client_api=self._client_api, project=self.project,
200
- pipeline=self.pipeline)
215
+ pipelines=repositories.Pipelines(client_api=self._client_api, project=self._project),
216
+ pipeline_executions=repositories.PipelineExecutions(client_api=self._client_api,
217
+ project=self._project,
218
+ pipeline=self._pipeline)
201
219
  )
202
220
  return r
203
221
 
@@ -246,3 +264,16 @@ class PipelineExecution(entities.BaseEntity):
246
264
  filters=filters,
247
265
  wait=wait
248
266
  )
267
+
268
+ def wait(self):
269
+ """
270
+ Wait for pipeline execution
271
+
272
+ :return: Pipeline execution object
273
+ """
274
+ return self.pipeline_executions.wait(pipeline_execution_id=self.id)
275
+
276
+ def in_progress(self):
277
+ return self.status not in [PipelineExecutionStatus.FAILED,
278
+ PipelineExecutionStatus.SUCCESS,
279
+ PipelineExecutionStatus.TERMINATED]
@@ -1,7 +1,11 @@
1
1
  import logging
2
- import io
3
2
  import enum
4
3
  import json
4
+ import os.path
5
+ from dtlpy import entities, repositories
6
+ from dtlpy.services.api_client import client as client_api
7
+ import base64
8
+ import requests
5
9
 
6
10
  logger = logging.getLogger(name='dtlpy')
7
11
 
@@ -11,27 +15,37 @@ class PromptType(str, enum.Enum):
11
15
  IMAGE = 'image/*'
12
16
  AUDIO = 'audio/*'
13
17
  VIDEO = 'video/*'
18
+ METADATA = 'metadata'
14
19
 
15
20
 
16
21
  class Prompt:
17
- def __init__(self, key):
22
+ def __init__(self, key, role='user'):
18
23
  """
19
24
  Create a single Prompt. Prompt can contain multiple mimetype elements, e.g. text sentence and an image.
20
-
21
25
  :param key: unique identifier of the prompt in the item
22
26
  """
23
27
  self.key = key
24
28
  self.elements = list()
29
+ self._items = repositories.Items(client_api=client_api)
30
+ self.metadata = {'role': role}
25
31
 
26
- def add(self, value, mimetype='text'):
32
+ def add_element(self, value, mimetype='application/text'):
27
33
  """
28
34
 
29
35
  :param value: url or string of the input
30
36
  :param mimetype: mimetype of the input. options: `text`, `image/*`, `video/*`, `audio/*`
31
37
  :return:
32
38
  """
33
- self.elements.append({'mimetype': mimetype,
34
- 'value': value})
39
+ allowed_prompt_types = [prompt_type for prompt_type in PromptType]
40
+ if mimetype not in allowed_prompt_types:
41
+ raise ValueError(f'Invalid mimetype: {mimetype}. Allowed values: {allowed_prompt_types}')
42
+ if not isinstance(value, str) and mimetype != PromptType.METADATA:
43
+ raise ValueError(f'Expected str for Prompt element value, got {type(value)} instead')
44
+ if mimetype == PromptType.METADATA and isinstance(value, dict):
45
+ self.metadata.update(value)
46
+ else:
47
+ self.elements.append({'mimetype': mimetype,
48
+ 'value': value})
35
49
 
36
50
  def to_json(self):
37
51
  """
@@ -39,26 +53,169 @@ class Prompt:
39
53
 
40
54
  :return:
41
55
  """
56
+ elements_json = [
57
+ {
58
+ "mimetype": e['mimetype'],
59
+ "value": e['value'],
60
+ } for e in self.elements
61
+ ]
62
+ elements_json.append({
63
+ "mimetype": PromptType.METADATA,
64
+ "value": self.metadata
65
+ })
42
66
  return {
43
- self.key: [
44
- {
45
- "mimetype": e['mimetype'],
46
- "value": e['value']
47
- } for e in self.elements
48
- ]
67
+ self.key: elements_json
49
68
  }
50
69
 
51
-
52
- class PromptItem:
53
- def __init__(self, name):
70
+ def _convert_stream_to_binary(self, image_url: str):
54
71
  """
55
- Create a new Prompt Item. Single item can have multiple prompt, e.g. a conversation.
72
+ Convert a stream to binary
73
+ :param image_url: dataloop image stream url
74
+ :return: binary object
75
+ """
76
+ image_buffer = None
77
+ if '.' in image_url and 'dataloop.ai' not in image_url:
78
+ # URL and not DL item stream
79
+ try:
80
+ response = requests.get(image_url, stream=True)
81
+ response.raise_for_status() # Raise an exception for bad status codes
82
+
83
+ # Check for valid image content type
84
+ if response.headers["Content-Type"].startswith("image/"):
85
+ # Read the image data in chunks to avoid loading large images in memory
86
+ image_buffer = b"".join(chunk for chunk in response.iter_content(1024))
87
+ except requests.exceptions.RequestException as e:
88
+ logger.error(f"Failed to download image from URL: {image_url}, error: {e}")
56
89
 
57
- :param name: name of the item (filename)
90
+ elif '.' in image_url and 'stream' in image_url:
91
+ # DL Stream URL
92
+ item_id = image_url.split("/stream")[0].split("/items/")[-1]
93
+ image_buffer = self._items.get(item_id=item_id).download(save_locally=False).getvalue()
94
+ else:
95
+ # DL item ID
96
+ image_buffer = self._items.get(item_id=image_url).download(save_locally=False).getvalue()
97
+
98
+ if image_buffer is not None:
99
+ encoded_image = base64.b64encode(image_buffer).decode()
100
+ else:
101
+ logger.error(f'Invalid image url: {image_url}')
102
+ return None
103
+
104
+ return f'data:image/jpeg;base64,{encoded_image}'
105
+
106
+ def messages(self):
107
+ """
108
+ return a list of messages in the prompt item,
109
+ messages are returned following the openai SDK format https://platform.openai.com/docs/guides/vision
58
110
  """
111
+ messages = []
112
+ for element in self.elements:
113
+ if element['mimetype'] == PromptType.TEXT:
114
+ data = {
115
+ "type": "text",
116
+ "text": element['value']
117
+ }
118
+ messages.append(data)
119
+ elif element['mimetype'] == PromptType.IMAGE:
120
+ image_url = self._convert_stream_to_binary(element['value'])
121
+ data = {
122
+ "type": "image_url",
123
+ "image_url": {
124
+ "url": image_url
125
+ }
126
+ }
127
+ messages.append(data)
128
+ elif element['mimetype'] == PromptType.AUDIO:
129
+ raise NotImplementedError('Audio prompt is not supported yet')
130
+ elif element['mimetype'] == PromptType.VIDEO:
131
+ raise NotImplementedError('Video prompt is not supported yet')
132
+ else:
133
+ raise ValueError(f'Invalid mimetype: {element["mimetype"]}')
134
+ return messages, self.key
135
+
136
+
137
+ class PromptItem:
138
+ def __init__(self, name, item: entities.Item = None):
139
+ # prompt item name
59
140
  self.name = name
60
- self.type = "prompt"
141
+ # list of user prompts in the prompt item
61
142
  self.prompts = list()
143
+ # list of assistant (annotations) prompts in the prompt item
144
+ self.assistant_prompts = dict()
145
+ # Dataloop Item
146
+ self._item = None
147
+
148
+ @classmethod
149
+ def from_item(cls, item: entities.Item):
150
+ """
151
+ Load a prompt item from the platform
152
+ :param item : Item object
153
+ :return: PromptItem object
154
+ """
155
+ if 'json' not in item.mimetype or item.system.get('shebang', dict()).get('dltype') != 'prompt':
156
+ raise ValueError('Expecting a json item with system.shebang.dltype = prompt')
157
+ # Not using `save_locally=False` to use the from_local_file method
158
+ item_file_path = item.download()
159
+ prompt_item = cls.from_local_file(file_path=item_file_path)
160
+ if os.path.exists(item_file_path):
161
+ os.remove(item_file_path)
162
+ prompt_item._item = item
163
+ return prompt_item
164
+
165
+ @classmethod
166
+ def from_local_file(cls, file_path):
167
+ """
168
+ Create a new prompt item from a file
169
+ :param file_path: path to the file
170
+ :return: PromptItem object
171
+ """
172
+ if os.path.exists(file_path) is False:
173
+ raise FileNotFoundError(f'File does not exists: {file_path}')
174
+ if 'json' not in os.path.splitext(file_path)[-1]:
175
+ raise ValueError(f'Expected path to json item, got {os.path.splitext(file_path)[-1]}')
176
+ prompt_item = cls(name=file_path)
177
+ with open(file_path, 'r') as f:
178
+ data = json.load(f)
179
+ for prompt_key, prompt_values in data.get('prompts', dict()).items():
180
+ prompt = Prompt(key=prompt_key)
181
+ for val in prompt_values:
182
+ if val['mimetype'] == PromptType.METADATA:
183
+ _ = val.pop('mimetype')
184
+ prompt.add_element(value=val, mimetype=PromptType.METADATA)
185
+ else:
186
+ prompt.add_element(mimetype=val['mimetype'], value=val['value'])
187
+ prompt_item.add_prompt(prompt=prompt, update_item=False)
188
+ return prompt_item
189
+
190
+ def get_assistant_messages(self, annotations: entities.AnnotationCollection):
191
+ """
192
+ Get all the annotations in the item for the assistant messages
193
+ """
194
+ # clearing the assistant prompts from previous annotations that might not belong
195
+ self.assistant_prompts = dict()
196
+ for annotation in annotations:
197
+ prompt_id = annotation.metadata.get('system', dict()).get('promptId', None)
198
+ if annotation.type == 'ref_image':
199
+ prompt = Prompt(key=prompt_id)
200
+ prompt.add_element(value=annotation.coordinates.get('ref'), mimetype=PromptType.IMAGE)
201
+ self.assistant_prompts[annotation.id] = prompt
202
+ elif annotation.type == 'text':
203
+ prompt = Prompt(key=prompt_id)
204
+ prompt.add_element(value=annotation.coordinates, mimetype=PromptType.TEXT)
205
+ self.assistant_prompts[annotation.id] = prompt
206
+
207
+ def get_assistant_prompts(self, model_name):
208
+ """
209
+ Get assistant prompts
210
+ :return:
211
+ """
212
+ if self._item is None:
213
+ logger.warning('Item is not loaded, skipping annotations context')
214
+ return
215
+ filters = entities.Filters(resource=entities.FiltersResource.ANNOTATION)
216
+ filters.add(field='metadata.user.model.name', values=model_name)
217
+ annotations = self._item.annotations.list(filters=filters)
218
+ self.get_assistant_messages(annotations=annotations)
62
219
 
63
220
  def to_json(self):
64
221
  """
@@ -69,26 +226,82 @@ class PromptItem:
69
226
  prompts_json = {
70
227
  "shebang": "dataloop",
71
228
  "metadata": {
72
- "dltype": self.type
229
+ "dltype": 'prompt'
73
230
  },
74
231
  "prompts": {}
75
232
  }
76
233
  for prompt in self.prompts:
77
234
  for prompt_key, prompt_values in prompt.to_json().items():
78
235
  prompts_json["prompts"][prompt_key] = prompt_values
79
- return prompts_json
236
+ prompts_json["prompts"][prompt_key].append({'metadata'})
80
237
 
81
- def to_bytes_io(self):
82
- byte_io = io.BytesIO()
83
- byte_io.name = self.name
84
- byte_io.write(json.dumps(self.to_json()).encode())
85
- byte_io.seek(0)
86
- return byte_io
238
+ return prompts_json
87
239
 
88
- def add(self, prompt):
240
+ def add_prompt(self, prompt: Prompt, update_item=True):
89
241
  """
90
242
  add a prompt to the prompt item
91
243
  prompt: a dictionary. keys are prompt message id, values are prompt messages
92
244
  responses: a list of annotations representing responses to the prompt
93
245
  """
94
246
  self.prompts.append(prompt)
247
+ if update_item is True:
248
+ if self._item is not None:
249
+ self._item._Item__update_item_binary(_json=self.to_json())
250
+ else:
251
+ logger.warning('Item is not loaded, skipping upload')
252
+
253
+ def messages(self, model_name=None):
254
+ """
255
+ return a list of messages in the prompt item
256
+ messages are returned following the openai SDK format
257
+ """
258
+ if model_name is not None:
259
+ self.get_assistant_prompts(model_name=model_name)
260
+ else:
261
+ logger.warning('Model name is not provided, skipping assistant prompts')
262
+
263
+ all_prompts_messages = dict()
264
+ for prompt in self.prompts:
265
+ if prompt.key not in all_prompts_messages:
266
+ all_prompts_messages[prompt.key] = list()
267
+ prompt_messages, prompt_key = prompt.messages()
268
+ messages = {
269
+ 'role': prompt.metadata.get('role', 'user'),
270
+ 'content': prompt_messages
271
+ }
272
+ all_prompts_messages[prompt.key].append(messages)
273
+
274
+ for ann_id, prompt in self.assistant_prompts.items():
275
+ if prompt.key not in all_prompts_messages:
276
+ logger.warning(f'Prompt key {prompt.key} is not found in the user prompts, skipping Assistant prompt')
277
+ continue
278
+ prompt_messages, prompt_key = prompt.messages()
279
+ assistant_messages = {
280
+ 'role': 'assistant',
281
+ 'content': prompt_messages
282
+ }
283
+ all_prompts_messages[prompt.key].append(assistant_messages)
284
+ res = list()
285
+ for prompts in all_prompts_messages.values():
286
+ for prompt in prompts:
287
+ res.append(prompt)
288
+ return res
289
+
290
+ def add_responses(self, annotation: entities.BaseAnnotationDefinition, model: entities.Model):
291
+ """
292
+ Add an annotation to the prompt item
293
+ :param annotation: Annotation object
294
+ :param model: Model object
295
+ """
296
+ if self._item is None:
297
+ raise ValueError('Item is not loaded, cannot add annotation')
298
+ annotation_collection = entities.AnnotationCollection()
299
+ annotation_collection.add(annotation_definition=annotation,
300
+ prompt_id=self.prompts[-1].key,
301
+ model_info={
302
+ 'name': model.name,
303
+ 'model_id': model.id,
304
+ 'confidence': 1.0
305
+ })
306
+ annotations = self._item.annotations.upload(annotations=annotation_collection)
307
+ self.get_assistant_messages(annotations=annotations)
dtlpy/entities/recipe.py CHANGED
@@ -262,3 +262,40 @@ class Recipe(entities.BaseEntity):
262
262
  'datasetId': dataset.id,
263
263
  'name': instruction_item.name}
264
264
  self.update(True)
265
+
266
+ def upload_annotations_verification_file(self, local_path: str, overwrite: bool = False) -> entities.Item:
267
+ """
268
+ Add Annotations Verification js file to the recipe.
269
+
270
+ :param str local_path: file path of the annotations verification js file.
271
+ :param bool overwrite: overwrite exiting file if the local and the remote names are matching
272
+ :return: annotations verification js item.
273
+ """
274
+
275
+ validation_file_metadata = self.metadata.get("system", dict()).get("validationFile", None)
276
+ if validation_file_metadata is None:
277
+ validation_file_metadata = dict()
278
+
279
+ remote_name = validation_file_metadata.get("name", None)
280
+ local_name = os.path.basename(local_path)
281
+ binaries_dataset = self._project.datasets._get_binaries_dataset()
282
+ remote_path = f"/.dataloop/recipes/{self.id}/verification/"
283
+
284
+ if remote_name is None or overwrite or remote_name != local_name:
285
+ validation_item = binaries_dataset.items.upload(
286
+ local_path=local_path,
287
+ remote_path=remote_path,
288
+ remote_name=local_name,
289
+ overwrite=True
290
+ )
291
+ self.metadata["system"]["validationFile"] = {
292
+ "itemId": validation_item.id,
293
+ "datasetId": binaries_dataset.id,
294
+ "name": local_name
295
+ }
296
+ self.update(system_metadata=True)
297
+ else:
298
+ logger.debug(f"Existing Annotations Validation Script was found.")
299
+ validation_item_id = self.metadata["system"]["validationFile"]["itemId"]
300
+ validation_item = binaries_dataset.items.get(item_id=validation_item_id)
301
+ return validation_item
dtlpy/entities/service.py CHANGED
@@ -1,3 +1,4 @@
1
+ import warnings
1
2
  from collections import namedtuple
2
3
  from enum import Enum
3
4
  import traceback
@@ -219,6 +220,7 @@ class Service(entities.BaseEntity):
219
220
  on_reset = attr.ib(type=OnResetAction)
220
221
  _type = attr.ib(type=ServiceType)
221
222
  project_id = attr.ib()
223
+ org_id = attr.ib()
222
224
  is_global = attr.ib()
223
225
  max_attempts = attr.ib()
224
226
  mode = attr.ib(repr=False)
@@ -235,6 +237,8 @@ class Service(entities.BaseEntity):
235
237
  _project = attr.ib(default=None, repr=False)
236
238
  _repositories = attr.ib(repr=False)
237
239
  updated_by = attr.ib(default=None)
240
+ app = attr.ib(default=None)
241
+ integrations = attr.ib(default=None)
238
242
 
239
243
  @property
240
244
  def createdAt(self):
@@ -334,6 +338,9 @@ class Service(entities.BaseEntity):
334
338
  updated_by=_json.get('updatedBy', None),
335
339
  config=_json.get('config', None),
336
340
  settings=_json.get('settings', None),
341
+ app=_json.get('app', None),
342
+ integrations=_json.get('integrations', None),
343
+ org_id=_json.get('orgId', None)
337
344
  )
338
345
  inst.is_fetched = is_fetched
339
346
  return inst
@@ -363,13 +370,25 @@ class Service(entities.BaseEntity):
363
370
  def package(self):
364
371
  if self._package is None:
365
372
  try:
373
+ dpk_id = None
374
+ dpk_version = None
375
+ if self.app and isinstance(self.app, dict):
376
+ dpk_id = self.app.get('dpkId', None)
377
+ dpk_version = self.app.get('dpkVersion', None)
378
+ if dpk_id is None:
379
+ self._package = repositories.Dpks(client_api=self._client_api, project=self.project).get(
380
+ dpk_id=self.package_id)
381
+ else:
382
+ self._package = repositories.Dpks(client_api=self._client_api, project=self.project).get_revisions(
383
+ dpk_id=dpk_id,
384
+ version=dpk_version)
385
+
386
+ assert isinstance(self._package, entities.Dpk)
387
+ except:
366
388
  self._package = repositories.Packages(client_api=self._client_api).get(package_id=self.package_id,
367
389
  fetch=None,
368
390
  log_error=False)
369
391
  assert isinstance(self._package, entities.Package)
370
- except:
371
- self._package = repositories.Dpks(client_api=self._client_api).get(dpk_id=self.package_id)
372
- assert isinstance(self._package, entities.Dpk)
373
392
  return self._package
374
393
 
375
394
  @property
@@ -462,11 +481,15 @@ class Service(entities.BaseEntity):
462
481
  attr.fields(Service).archive,
463
482
  attr.fields(Service).updated_by,
464
483
  attr.fields(Service).config,
465
- attr.fields(Service).settings
484
+ attr.fields(Service).settings,
485
+ attr.fields(Service).app,
486
+ attr.fields(Service).integrations,
487
+ attr.fields(Service).org_id
466
488
  )
467
489
  )
468
490
 
469
491
  _json['projectId'] = self.project_id
492
+ _json['orgId'] = self.org_id
470
493
  _json['packageId'] = self.package_id
471
494
  _json['initParams'] = self.init_input
472
495
  _json['moduleName'] = self.module_name
@@ -518,6 +541,12 @@ class Service(entities.BaseEntity):
518
541
  if self.settings is not None:
519
542
  _json['settings'] = self.settings
520
543
 
544
+ if self.app is not None:
545
+ _json['app'] = self.app
546
+
547
+ if self.integrations is not None:
548
+ _json['integrations'] = self.integrations
549
+
521
550
  return _json
522
551
 
523
552
  def update(self, force=False):