dtlpy 1.85.25__py3-none-any.whl → 1.87.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.
- dtlpy/__init__.py +3 -3
- dtlpy/__version__.py +1 -1
- dtlpy/entities/__init__.py +1 -1
- dtlpy/entities/annotation.py +10 -12
- dtlpy/entities/annotation_collection.py +11 -9
- dtlpy/entities/annotation_definitions/__init__.py +2 -1
- dtlpy/entities/annotation_definitions/ref_image.py +86 -0
- dtlpy/entities/command.py +1 -1
- dtlpy/entities/dataset.py +4 -8
- dtlpy/entities/feature_set.py +0 -3
- dtlpy/entities/filters.py +12 -2
- dtlpy/entities/item.py +0 -1
- dtlpy/entities/model.py +51 -2
- dtlpy/entities/node.py +14 -5
- dtlpy/entities/ontology.py +2 -2
- dtlpy/entities/package_function.py +3 -0
- dtlpy/entities/pipeline.py +11 -2
- dtlpy/entities/recipe.py +1 -1
- dtlpy/entities/service.py +33 -16
- dtlpy/entities/task.py +18 -1
- dtlpy/entities/trigger.py +7 -1
- dtlpy/ml/base_model_adapter.py +56 -11
- dtlpy/ml/train_utils.py +0 -1
- dtlpy/new_instance.py +5 -3
- dtlpy/repositories/artifacts.py +9 -15
- dtlpy/repositories/codebases.py +2 -14
- dtlpy/repositories/commands.py +6 -7
- dtlpy/repositories/datasets.py +73 -43
- dtlpy/repositories/downloader.py +1 -1
- dtlpy/repositories/feature_sets.py +1 -6
- dtlpy/repositories/models.py +69 -26
- dtlpy/repositories/packages.py +5 -4
- dtlpy/repositories/pipelines.py +5 -4
- dtlpy/repositories/services.py +32 -5
- dtlpy/repositories/tasks.py +8 -3
- dtlpy/repositories/uploader.py +1 -1
- dtlpy/services/api_client.py +2 -1
- dtlpy/utilities/dataset_generators/dataset_generator.py +2 -2
- dtlpy/utilities/reports/figures.py +215 -48
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/METADATA +1 -2
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/RECORD +49 -48
- tests/features/environment.py +49 -2
- {dtlpy-1.85.25.data → dtlpy-1.87.18.data}/scripts/dlp +0 -0
- {dtlpy-1.85.25.data → dtlpy-1.87.18.data}/scripts/dlp.bat +0 -0
- {dtlpy-1.85.25.data → dtlpy-1.87.18.data}/scripts/dlp.py +0 -0
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/LICENSE +0 -0
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/WHEEL +0 -0
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.85.25.dist-info → dtlpy-1.87.18.dist-info}/top_level.txt +0 -0
dtlpy/entities/service.py
CHANGED
|
@@ -80,8 +80,6 @@ class InstanceCatalog(str, Enum):
|
|
|
80
80
|
- regular pod with medium size
|
|
81
81
|
* - REGULAR_L
|
|
82
82
|
- regular pod with large size
|
|
83
|
-
* - REGULAR_XL
|
|
84
|
-
- regular pod with extra large size
|
|
85
83
|
* - HIGHMEM_XS
|
|
86
84
|
- highmem pod with extra small size
|
|
87
85
|
* - HIGHMEM_S
|
|
@@ -90,25 +88,27 @@ class InstanceCatalog(str, Enum):
|
|
|
90
88
|
- highmem pod with medium size
|
|
91
89
|
* - HIGHMEM_L
|
|
92
90
|
- highmem pod with large size
|
|
93
|
-
* - HIGHMEM_XL
|
|
94
|
-
- highmem pod with extra large size
|
|
95
91
|
* - GPU_K80_S
|
|
96
|
-
- GPU pod with small size
|
|
92
|
+
- GPU NVIDIA K80 pod with small size
|
|
97
93
|
* - GPU_K80_M
|
|
98
|
-
- GPU pod with medium size
|
|
94
|
+
- GPU NVIDIA K80 pod with medium size
|
|
95
|
+
* - GPU_T4_S
|
|
96
|
+
- GPU NVIDIA T4 pod with regular memory
|
|
97
|
+
* - GPU_T4_M
|
|
98
|
+
- GPU NVIDIA T4 pod with highmem
|
|
99
99
|
"""
|
|
100
100
|
REGULAR_XS = "regular-xs"
|
|
101
101
|
REGULAR_S = "regular-s"
|
|
102
102
|
REGULAR_M = "regular-m"
|
|
103
103
|
REGULAR_L = "regular-l"
|
|
104
|
-
REGULAR_XL = "regular-xl"
|
|
105
104
|
HIGHMEM_XS = "highmem-xs"
|
|
106
105
|
HIGHMEM_S = "highmem-s"
|
|
107
106
|
HIGHMEM_M = "highmem-m"
|
|
108
107
|
HIGHMEM_L = "highmem-l"
|
|
109
|
-
HIGHMEM_XL = "highmem-xl"
|
|
110
108
|
GPU_K80_S = "gpu-k80-s"
|
|
111
109
|
GPU_K80_M = "gpu-k80-m"
|
|
110
|
+
GPU_T4_S = "gpu-t4"
|
|
111
|
+
GPU_T4_M = "gpu-t4-m"
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
class RuntimeType(str, Enum):
|
|
@@ -150,7 +150,7 @@ class KubernetesRuntime(ServiceRuntime):
|
|
|
150
150
|
self.concurrency = kwargs.get('concurrency', concurrency)
|
|
151
151
|
self.runner_image = kwargs.get('runnerImage', runner_image)
|
|
152
152
|
self._proxy_image = kwargs.get('proxyImage', None)
|
|
153
|
-
self.single_agent = kwargs.get('singleAgent',
|
|
153
|
+
self.single_agent = kwargs.get('singleAgent', None)
|
|
154
154
|
self.preemptible = kwargs.get('preemptible', None)
|
|
155
155
|
|
|
156
156
|
self.autoscaler = kwargs.get('autoscaler', autoscaler)
|
|
@@ -166,10 +166,12 @@ class KubernetesRuntime(ServiceRuntime):
|
|
|
166
166
|
'podType': self.pod_type,
|
|
167
167
|
'numReplicas': self.num_replicas,
|
|
168
168
|
'concurrency': self.concurrency,
|
|
169
|
-
'singleAgent': self.single_agent,
|
|
170
169
|
'autoscaler': None if self.autoscaler is None else self.autoscaler.to_json()
|
|
171
170
|
}
|
|
172
171
|
|
|
172
|
+
if self.single_agent is not None:
|
|
173
|
+
_json['singleAgent'] = self.single_agent
|
|
174
|
+
|
|
173
175
|
if self.runner_image is not None:
|
|
174
176
|
_json['runnerImage'] = self.runner_image
|
|
175
177
|
|
|
@@ -221,6 +223,7 @@ class Service(entities.BaseEntity):
|
|
|
221
223
|
max_attempts = attr.ib()
|
|
222
224
|
mode = attr.ib(repr=False)
|
|
223
225
|
metadata = attr.ib()
|
|
226
|
+
archive = attr.ib(repr=False)
|
|
224
227
|
|
|
225
228
|
# SDK
|
|
226
229
|
_package = attr.ib(repr=False)
|
|
@@ -229,6 +232,7 @@ class Service(entities.BaseEntity):
|
|
|
229
232
|
# repositories
|
|
230
233
|
_project = attr.ib(default=None, repr=False)
|
|
231
234
|
_repositories = attr.ib(repr=False)
|
|
235
|
+
updated_by = attr.ib(default=None)
|
|
232
236
|
|
|
233
237
|
@property
|
|
234
238
|
def createdAt(self):
|
|
@@ -263,7 +267,7 @@ class Service(entities.BaseEntity):
|
|
|
263
267
|
return status, service
|
|
264
268
|
|
|
265
269
|
@classmethod
|
|
266
|
-
def from_json(cls, _json: dict, client_api: ApiClient=None, package=None, project=None, is_fetched=True):
|
|
270
|
+
def from_json(cls, _json: dict, client_api: ApiClient = None, package=None, project=None, is_fetched=True):
|
|
267
271
|
"""
|
|
268
272
|
Build a service entity object from a json
|
|
269
273
|
|
|
@@ -323,7 +327,9 @@ class Service(entities.BaseEntity):
|
|
|
323
327
|
secrets=_json.get("secrets", None),
|
|
324
328
|
type=_json.get("type", None),
|
|
325
329
|
mode=_json.get('mode', dict()),
|
|
326
|
-
metadata=_json.get('metadata', None)
|
|
330
|
+
metadata=_json.get('metadata', None),
|
|
331
|
+
archive=_json.get('archive', None),
|
|
332
|
+
updated_by=_json.get('updatedBy', None)
|
|
327
333
|
)
|
|
328
334
|
inst.is_fetched = is_fetched
|
|
329
335
|
return inst
|
|
@@ -352,9 +358,13 @@ class Service(entities.BaseEntity):
|
|
|
352
358
|
@property
|
|
353
359
|
def package(self):
|
|
354
360
|
if self._package is None:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
361
|
+
try:
|
|
362
|
+
self._package = repositories.Packages(client_api=self._client_api).get(package_id=self.package_id,
|
|
363
|
+
fetch=None)
|
|
364
|
+
assert isinstance(self._package, entities.Package)
|
|
365
|
+
except:
|
|
366
|
+
self._package = repositories.Dpks(client_api=self._client_api).get(dpk_id=self.package_id)
|
|
367
|
+
assert isinstance(self._package, entities.Dpk)
|
|
358
368
|
return self._package
|
|
359
369
|
|
|
360
370
|
@property
|
|
@@ -443,7 +453,9 @@ class Service(entities.BaseEntity):
|
|
|
443
453
|
attr.fields(Service).secrets,
|
|
444
454
|
attr.fields(Service)._type,
|
|
445
455
|
attr.fields(Service).mode,
|
|
446
|
-
attr.fields(Service).metadata
|
|
456
|
+
attr.fields(Service).metadata,
|
|
457
|
+
attr.fields(Service).archive,
|
|
458
|
+
attr.fields(Service).updated_by,
|
|
447
459
|
)
|
|
448
460
|
)
|
|
449
461
|
|
|
@@ -463,6 +475,9 @@ class Service(entities.BaseEntity):
|
|
|
463
475
|
_json['createdAt'] = self.created_at
|
|
464
476
|
_json['updatedAt'] = self.updated_at
|
|
465
477
|
|
|
478
|
+
if self.updated_by is not None:
|
|
479
|
+
_json['updatedBy'] = self.updated_by
|
|
480
|
+
|
|
466
481
|
if self.max_attempts is not None:
|
|
467
482
|
_json['maxAttempts'] = self.max_attempts
|
|
468
483
|
|
|
@@ -487,6 +502,8 @@ class Service(entities.BaseEntity):
|
|
|
487
502
|
if self.metadata:
|
|
488
503
|
_json['metadata'] = self.metadata
|
|
489
504
|
|
|
505
|
+
if self.archive:
|
|
506
|
+
_json['archive'] = self.archive
|
|
490
507
|
return _json
|
|
491
508
|
|
|
492
509
|
def update(self, force=False):
|
dtlpy/entities/task.py
CHANGED
|
@@ -92,6 +92,7 @@ class Task:
|
|
|
92
92
|
available_actions = attr.ib()
|
|
93
93
|
total_items = attr.ib()
|
|
94
94
|
priority = attr.ib()
|
|
95
|
+
_description = attr.ib()
|
|
95
96
|
|
|
96
97
|
# sdk
|
|
97
98
|
_client_api = attr.ib(repr=False)
|
|
@@ -102,6 +103,18 @@ class Task:
|
|
|
102
103
|
_tasks = attr.ib(default=None, repr=False)
|
|
103
104
|
_settings = attr.ib(default=None, repr=False)
|
|
104
105
|
|
|
106
|
+
@property
|
|
107
|
+
def description(self):
|
|
108
|
+
return self._description
|
|
109
|
+
|
|
110
|
+
@description.setter
|
|
111
|
+
def description(self, description):
|
|
112
|
+
if not isinstance(description, str):
|
|
113
|
+
raise ValueError('description should be a string')
|
|
114
|
+
if self._description is None:
|
|
115
|
+
self._description = {}
|
|
116
|
+
self._description['content'] = description
|
|
117
|
+
|
|
105
118
|
@staticmethod
|
|
106
119
|
def _protected_from_json(_json, client_api, project, dataset):
|
|
107
120
|
"""
|
|
@@ -176,7 +189,8 @@ class Task:
|
|
|
176
189
|
created_at=_json.get('createdAt', None),
|
|
177
190
|
available_actions=actions,
|
|
178
191
|
total_items=_json.get('totalItems', None),
|
|
179
|
-
priority=_json.get('priority', None)
|
|
192
|
+
priority=_json.get('priority', None),
|
|
193
|
+
description=_json.get('description', None)
|
|
180
194
|
)
|
|
181
195
|
|
|
182
196
|
def to_json(self):
|
|
@@ -208,6 +222,7 @@ class Task:
|
|
|
208
222
|
attr.fields(Task).created_at,
|
|
209
223
|
attr.fields(Task).total_items,
|
|
210
224
|
attr.fields(Task)._settings,
|
|
225
|
+
attr.fields(Task)._description
|
|
211
226
|
)
|
|
212
227
|
)
|
|
213
228
|
_json['projectId'] = self.project_id
|
|
@@ -217,6 +232,7 @@ class Task:
|
|
|
217
232
|
_json['dueDate'] = self.due_date
|
|
218
233
|
_json['totalItems'] = self.total_items
|
|
219
234
|
_json['forReview'] = self.for_review
|
|
235
|
+
_json['description'] = self.description
|
|
220
236
|
|
|
221
237
|
if self.available_actions is not None:
|
|
222
238
|
_json['availableActions'] = [action.to_json() for action in self.available_actions]
|
|
@@ -472,3 +488,4 @@ class Task:
|
|
|
472
488
|
:rtype: bool
|
|
473
489
|
"""
|
|
474
490
|
return self.tasks.set_status(status=status, operation=operation, item_ids=item_ids, task_id=self.id)
|
|
491
|
+
|
dtlpy/entities/trigger.py
CHANGED
|
@@ -84,6 +84,8 @@ class BaseTrigger(entities.BaseEntity):
|
|
|
84
84
|
_op_type = attr.ib(default='service')
|
|
85
85
|
_repositories = attr.ib(repr=False)
|
|
86
86
|
|
|
87
|
+
updated_by = attr.ib(default=None)
|
|
88
|
+
|
|
87
89
|
@staticmethod
|
|
88
90
|
def _get_operation(operation):
|
|
89
91
|
op_type = operation.get('type', None)
|
|
@@ -243,6 +245,7 @@ class BaseTrigger(entities.BaseEntity):
|
|
|
243
245
|
attr.fields(BaseTrigger).created_at,
|
|
244
246
|
attr.fields(BaseTrigger).updated_at,
|
|
245
247
|
attr.fields(BaseTrigger).operation,
|
|
248
|
+
attr.fields(BaseTrigger).updated_by,
|
|
246
249
|
))
|
|
247
250
|
|
|
248
251
|
# rename
|
|
@@ -251,6 +254,8 @@ class BaseTrigger(entities.BaseEntity):
|
|
|
251
254
|
_json['updatedAt'] = self.updated_at
|
|
252
255
|
if self.is_global is not None:
|
|
253
256
|
_json['global'] = self.is_global
|
|
257
|
+
if self.updated_by is not None:
|
|
258
|
+
_json['updatedBy'] = self.updated_by
|
|
254
259
|
return _json
|
|
255
260
|
|
|
256
261
|
def delete(self):
|
|
@@ -342,7 +347,8 @@ class Trigger(BaseTrigger):
|
|
|
342
347
|
op_type=operation.get('type', None),
|
|
343
348
|
spec=spec,
|
|
344
349
|
pipeline_id=pipeline_id,
|
|
345
|
-
operation=operation
|
|
350
|
+
operation=operation,
|
|
351
|
+
updated_by=_json.get('updatedBy', None),
|
|
346
352
|
)
|
|
347
353
|
|
|
348
354
|
|
dtlpy/ml/base_model_adapter.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import tempfile
|
|
3
2
|
import datetime
|
|
4
3
|
import logging
|
|
5
4
|
import shutil
|
|
6
5
|
import base64
|
|
7
6
|
import tqdm
|
|
7
|
+
import sys
|
|
8
8
|
import io
|
|
9
9
|
import os
|
|
10
10
|
from PIL import Image
|
|
@@ -12,7 +12,7 @@ from functools import partial
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
from concurrent.futures import ThreadPoolExecutor
|
|
14
14
|
import attr
|
|
15
|
-
from .. import entities, utilities, repositories
|
|
15
|
+
from .. import entities, utilities, repositories, exceptions
|
|
16
16
|
from ..services import service_defaults
|
|
17
17
|
from ..services.api_client import ApiClient
|
|
18
18
|
|
|
@@ -36,6 +36,8 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
36
36
|
'image': self._item_to_image}
|
|
37
37
|
if model_entity is not None:
|
|
38
38
|
self.load_from_model(model_entity=model_entity)
|
|
39
|
+
logger.warning(
|
|
40
|
+
"in case of a mismatch between 'model.name' and 'model_info.name' in the model adapter, model_info.name will be updated to align with 'model.name'.")
|
|
39
41
|
|
|
40
42
|
##################
|
|
41
43
|
# Configurations #
|
|
@@ -307,8 +309,6 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
307
309
|
if cleanup:
|
|
308
310
|
shutil.rmtree(path=local_path, ignore_errors=True)
|
|
309
311
|
self.logger.info("Clean-up. deleting {}".format(local_path))
|
|
310
|
-
self.model_entity.status = 'trained'
|
|
311
|
-
self.model_entity = self.model_entity.update()
|
|
312
312
|
|
|
313
313
|
# ===============
|
|
314
314
|
# SERVICE METHODS
|
|
@@ -337,10 +337,15 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
337
337
|
pool = ThreadPoolExecutor(max_workers=16)
|
|
338
338
|
|
|
339
339
|
annotations = list()
|
|
340
|
-
for i_batch in tqdm.tqdm(range(0, len(items), batch_size), desc='predicting', unit='bt', leave=None):
|
|
340
|
+
for i_batch in tqdm.tqdm(range(0, len(items), batch_size), desc='predicting', unit='bt', leave=None, file=sys.stdout):
|
|
341
341
|
batch_items = items[i_batch: i_batch + batch_size]
|
|
342
342
|
batch = list(pool.map(self.prepare_item_func, batch_items))
|
|
343
343
|
batch_collections = self.predict(batch, **kwargs)
|
|
344
|
+
_futures = list(pool.map(partial(self._update_predictions_metadata),
|
|
345
|
+
batch_items,
|
|
346
|
+
batch_collections))
|
|
347
|
+
# Loop over the futures to make sure they are all done to avoid race conditions
|
|
348
|
+
_ = [_f for _f in _futures]
|
|
344
349
|
if upload_annotations is True:
|
|
345
350
|
self.logger.debug(
|
|
346
351
|
"Uploading items' annotation for model {!r}.".format(self.model_entity.name))
|
|
@@ -408,12 +413,11 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
408
413
|
cleanup=False,
|
|
409
414
|
progress: utilities.Progress = None,
|
|
410
415
|
context: utilities.Context = None):
|
|
411
|
-
# FROM PARENT
|
|
412
416
|
"""
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
+
Train on existing model.
|
|
418
|
+
data will be taken from dl.Model.datasetId
|
|
419
|
+
configuration is as defined in dl.Model.configuration
|
|
420
|
+
upload the output the model's bucket (model.bucket)
|
|
417
421
|
"""
|
|
418
422
|
if isinstance(model, dict):
|
|
419
423
|
model = repositories.Models(client_api=self._client_api).get(model_id=model['id'])
|
|
@@ -462,7 +466,8 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
462
466
|
progress=99)
|
|
463
467
|
|
|
464
468
|
self.save_to_model(local_path=output_path, replace=True)
|
|
465
|
-
|
|
469
|
+
model.status = 'trained'
|
|
470
|
+
model.update()
|
|
466
471
|
###########
|
|
467
472
|
# cleanup #
|
|
468
473
|
###########
|
|
@@ -597,6 +602,46 @@ class BaseModelAdapter(utilities.BaseServiceRunner):
|
|
|
597
602
|
image = np.asarray(Image.open(io.BytesIO(binary)))
|
|
598
603
|
return image
|
|
599
604
|
|
|
605
|
+
def _update_predictions_metadata(self, item: entities.Item, predictions: entities.AnnotationCollection):
|
|
606
|
+
"""
|
|
607
|
+
add model_name and model_id to the metadata of the annotations.
|
|
608
|
+
add model_info to the metadata of the system metadata of the annotation.
|
|
609
|
+
Add item id to all the annotations in the AnnotationCollection
|
|
610
|
+
|
|
611
|
+
:param item: Entity.Item
|
|
612
|
+
:param predictions: item's AnnotationCollection
|
|
613
|
+
:return:
|
|
614
|
+
"""
|
|
615
|
+
for prediction in predictions:
|
|
616
|
+
if prediction.type == entities.AnnotationType.SEGMENTATION:
|
|
617
|
+
try:
|
|
618
|
+
color = self.model_entity.dataset._get_ontology().color_map.get(prediction.label)
|
|
619
|
+
except (exceptions.BadRequest, exceptions.NotFound):
|
|
620
|
+
color = None
|
|
621
|
+
logger.warning("Can't get annotation color from item's dataset, using model's dataset.")
|
|
622
|
+
if color is None:
|
|
623
|
+
try:
|
|
624
|
+
color = self.model_entity.dataset._get_ontology().color_map.get(prediction.label)
|
|
625
|
+
except (exceptions.BadRequest, exceptions.NotFound):
|
|
626
|
+
logger.warning("Can't get annotation color from model's dataset, using default.")
|
|
627
|
+
color = prediction.color
|
|
628
|
+
prediction.color = color
|
|
629
|
+
|
|
630
|
+
prediction.item_id = item.id
|
|
631
|
+
if 'user' in prediction.metadata and 'model' in prediction.metadata['user']:
|
|
632
|
+
prediction.metadata['user']['model']['model_id'] = self.model_entity.id
|
|
633
|
+
prediction.metadata['user']['model']['name'] = self.model_entity.name
|
|
634
|
+
if 'system' not in prediction.metadata:
|
|
635
|
+
prediction.metadata['system'] = dict()
|
|
636
|
+
if 'model' not in prediction.metadata['system']:
|
|
637
|
+
prediction.metadata['system']['model'] = dict()
|
|
638
|
+
confidence = prediction.metadata.get('user', dict()).get('model', dict()).get('confidence', None)
|
|
639
|
+
prediction.metadata['system']['model'] = {
|
|
640
|
+
'model_id': self.model_entity.id,
|
|
641
|
+
'name': self.model_entity.name,
|
|
642
|
+
'confidence': confidence
|
|
643
|
+
}
|
|
644
|
+
|
|
600
645
|
##############################
|
|
601
646
|
# Callback Factory functions #
|
|
602
647
|
##############################
|
dtlpy/ml/train_utils.py
CHANGED
dtlpy/new_instance.py
CHANGED
|
@@ -166,14 +166,16 @@ class Dtlpy:
|
|
|
166
166
|
REGULAR_S = 'regular-s'
|
|
167
167
|
REGULAR_M = 'regular-m'
|
|
168
168
|
REGULAR_L = 'regular-l'
|
|
169
|
-
REGULAR_XL = 'regular-xl'
|
|
170
169
|
HIGHMEM_MICRO = 'highmem-micro'
|
|
171
170
|
HIGHMEM_XS = 'highmem-xs'
|
|
172
171
|
HIGHMEM_S = 'highmem-s'
|
|
173
172
|
HIGHMEM_M = 'highmem-m'
|
|
174
173
|
HIGHMEM_L = 'highmem-l'
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
GPU_K80_S = "gpu-k80-s"
|
|
175
|
+
GPU_K80_M = "gpu-k80-m"
|
|
176
|
+
GPU_T4_S = "gpu-t4-s"
|
|
177
|
+
GPU_T4_M = "gpu-t4-m"
|
|
178
|
+
|
|
177
179
|
|
|
178
180
|
class LoggingLevel:
|
|
179
181
|
DEBUG = 'debug'
|
dtlpy/repositories/artifacts.py
CHANGED
|
@@ -22,7 +22,7 @@ class Artifacts:
|
|
|
22
22
|
project_id: str = None,
|
|
23
23
|
model: entities.Model = None,
|
|
24
24
|
package: entities.Package = None,
|
|
25
|
-
dataset_name=
|
|
25
|
+
dataset_name=None):
|
|
26
26
|
self._client_api = client_api
|
|
27
27
|
self._project = project
|
|
28
28
|
self._dataset = dataset
|
|
@@ -40,21 +40,15 @@ class Artifacts:
|
|
|
40
40
|
if self._dataset is None:
|
|
41
41
|
# get dataset from project
|
|
42
42
|
try:
|
|
43
|
-
self.
|
|
43
|
+
if self.dataset_name is None:
|
|
44
|
+
self.dataset_name = 'Binaries'
|
|
45
|
+
self._dataset = self.project.datasets._get_binaries_dataset()
|
|
46
|
+
else:
|
|
47
|
+
self._dataset = self.project.datasets.get(dataset_name=self.dataset_name)
|
|
44
48
|
except exceptions.NotFound:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'Dataset for artifacts was not found. Creating... dataset name: {ds!r}. project_id={id}'.format(
|
|
49
|
-
ds=self.dataset_name, id=self.project.id))
|
|
50
|
-
self._dataset = self.project.datasets.create(dataset_name=self.dataset_name)
|
|
51
|
-
# add system to metadata
|
|
52
|
-
if 'metadata' not in self._dataset.to_json():
|
|
53
|
-
self._dataset.metadata = dict()
|
|
54
|
-
if 'system' not in self._dataset.metadata:
|
|
55
|
-
self._dataset.metadata['system'] = dict()
|
|
56
|
-
self._dataset.metadata['system']['scope'] = 'system'
|
|
57
|
-
self.project.datasets.update(dataset=self._dataset, system_metadata=True)
|
|
49
|
+
raise ValueError(
|
|
50
|
+
f'Missing "{self.dataset_name}" dataset in the project. Please contact support for help')
|
|
51
|
+
|
|
58
52
|
return self._dataset
|
|
59
53
|
|
|
60
54
|
@property
|
dtlpy/repositories/codebases.py
CHANGED
|
@@ -65,21 +65,9 @@ class Codebases:
|
|
|
65
65
|
if self._dataset is None:
|
|
66
66
|
# get dataset from project
|
|
67
67
|
try:
|
|
68
|
-
self._dataset = self.project.datasets.
|
|
68
|
+
self._dataset = self.project.datasets._get_binaries_dataset()
|
|
69
69
|
except exceptions.NotFound:
|
|
70
|
-
|
|
71
|
-
if self._dataset is None:
|
|
72
|
-
logger.debug(
|
|
73
|
-
'Dataset for codebases was not found. Creating... dataset name: "Binaries". project_id={}'.format(
|
|
74
|
-
self.project.id))
|
|
75
|
-
self._dataset = self.project.datasets.create(dataset_name='Binaries')
|
|
76
|
-
# add system to metadata
|
|
77
|
-
if 'metadata' not in self._dataset.to_json():
|
|
78
|
-
self._dataset.metadata = dict()
|
|
79
|
-
if 'system' not in self._dataset.metadata:
|
|
80
|
-
self._dataset.metadata['system'] = dict()
|
|
81
|
-
self._dataset.metadata['system']['scope'] = 'system'
|
|
82
|
-
self.project.datasets.update(dataset=self._dataset, system_metadata=True)
|
|
70
|
+
raise ValueError('Missing "Binaries" dataset in the project. Please contact support for help')
|
|
83
71
|
assert isinstance(self._dataset, entities.Dataset)
|
|
84
72
|
return self._dataset
|
|
85
73
|
|
dtlpy/repositories/commands.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
import warnings
|
|
3
2
|
import logging
|
|
4
3
|
import time
|
|
5
4
|
import tqdm
|
|
@@ -10,7 +9,7 @@ from ..services.api_client import ApiClient
|
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(name='dtlpy')
|
|
12
11
|
|
|
13
|
-
MAX_SLEEP_TIME =
|
|
12
|
+
MAX_SLEEP_TIME = 30
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class Commands:
|
|
@@ -71,15 +70,15 @@ class Commands:
|
|
|
71
70
|
return entities.Command.from_json(client_api=self._client_api,
|
|
72
71
|
_json=response.json())
|
|
73
72
|
|
|
74
|
-
def wait(self, command_id, timeout=0, step=None, url=None, backoff_factor=
|
|
73
|
+
def wait(self, command_id, timeout=0, step=None, url=None, backoff_factor=1):
|
|
75
74
|
"""
|
|
76
75
|
Wait for command to finish
|
|
77
76
|
|
|
78
77
|
backoff_factor: A backoff factor to apply between attempts after the second try
|
|
79
78
|
{backoff factor} * (2 ** ({number of total retries} - 1))
|
|
80
|
-
seconds. If the backoff_factor is
|
|
81
|
-
for [
|
|
82
|
-
than
|
|
79
|
+
seconds. If the backoff_factor is 1, then :func:`.sleep` will sleep
|
|
80
|
+
for [0s, 2s, 4s, ...] between retries. It will never be longer
|
|
81
|
+
than 30 sec
|
|
83
82
|
|
|
84
83
|
:param str command_id: Command id to wait to
|
|
85
84
|
:param int timeout: int, seconds to wait until TimeoutError is raised. if 0 - wait until done
|
|
@@ -103,7 +102,7 @@ class Commands:
|
|
|
103
102
|
if not command.in_progress():
|
|
104
103
|
break
|
|
105
104
|
elapsed = time.time() - start
|
|
106
|
-
sleep_time = np.min([timeout - elapsed, backoff_factor * (2 **
|
|
105
|
+
sleep_time = np.min([timeout - elapsed, backoff_factor * (2 ** num_tries), MAX_SLEEP_TIME])
|
|
107
106
|
num_tries += 1
|
|
108
107
|
logger.debug("Command {!r} is running for {:.2f}[s] and now Going to sleep {:.2f}[s]".format(command.id,
|
|
109
108
|
elapsed,
|
dtlpy/repositories/datasets.py
CHANGED
|
@@ -96,6 +96,21 @@ class Datasets:
|
|
|
96
96
|
filters.add(field='dir', values=folder_path + '*')
|
|
97
97
|
return filters
|
|
98
98
|
|
|
99
|
+
def _get_binaries_dataset(self):
|
|
100
|
+
filters = entities.Filters(resource=entities.FiltersResource.DATASET)
|
|
101
|
+
filters.add(field='name', values='Binaries')
|
|
102
|
+
filters.system_space = True
|
|
103
|
+
datasets = self.list(filters=filters)
|
|
104
|
+
if len(datasets) == 0:
|
|
105
|
+
# empty list
|
|
106
|
+
raise exceptions.PlatformException('404', 'Dataset not found. Name: "Binaries"')
|
|
107
|
+
# dataset = None
|
|
108
|
+
elif len(datasets) > 1:
|
|
109
|
+
raise exceptions.PlatformException('400', 'More than one dataset with same name.')
|
|
110
|
+
else:
|
|
111
|
+
dataset = datasets[0]
|
|
112
|
+
return dataset
|
|
113
|
+
|
|
99
114
|
@property
|
|
100
115
|
def platform_url(self):
|
|
101
116
|
return self._client_api._get_resource_url("projects/{}/datasets".format(self.project.id))
|
|
@@ -165,58 +180,78 @@ class Datasets:
|
|
|
165
180
|
self._client_api.state_io.put('dataset', dataset.to_json())
|
|
166
181
|
logger.info('Checked out to dataset {}'.format(dataset.name))
|
|
167
182
|
|
|
168
|
-
@_api_reference.add(path='/datasets', method='
|
|
169
|
-
def list(self, name=None, creator=None) -> miscellaneous.List[entities.Dataset]:
|
|
183
|
+
@_api_reference.add(path='/datasets/query', method='post')
|
|
184
|
+
def list(self, name=None, creator=None, filters: entities.Filters = None) -> miscellaneous.List[entities.Dataset]:
|
|
170
185
|
"""
|
|
171
186
|
List all datasets.
|
|
172
187
|
|
|
173
188
|
**Prerequisites**: You must be an *owner* or *developer* to use this method.
|
|
174
189
|
|
|
175
190
|
:param str name: list by name
|
|
176
|
-
:param str creator: list by
|
|
191
|
+
:param str creator: list by
|
|
192
|
+
:param dtlpy.entities.filters.Filters filters: Filters entity containing filters parameters
|
|
177
193
|
:return: List of datasets
|
|
178
194
|
:rtype: list
|
|
179
195
|
|
|
180
196
|
**Example**:
|
|
181
197
|
|
|
182
198
|
.. code-block:: python
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
filters = dl.Filters(resource='datasets')
|
|
200
|
+
filters.add(field='readonly', values=False)
|
|
201
|
+
datasets = project.datasets.list(filters=filters)
|
|
185
202
|
"""
|
|
186
|
-
|
|
203
|
+
if filters is None:
|
|
204
|
+
filters = entities.Filters(resource=entities.FiltersResource.DATASET)
|
|
205
|
+
# assert type filters
|
|
206
|
+
elif not isinstance(filters, entities.Filters):
|
|
207
|
+
raise exceptions.PlatformException(error='400',
|
|
208
|
+
message='Unknown filters type: {!r}'.format(type(filters)))
|
|
209
|
+
if filters.resource != entities.FiltersResource.DATASET:
|
|
210
|
+
raise exceptions.PlatformException(
|
|
211
|
+
error='400',
|
|
212
|
+
message='Filters resource must to be FiltersResource.DATASET. Got: {!r}'.format(filters.resource))
|
|
187
213
|
|
|
188
|
-
|
|
189
|
-
'name': name,
|
|
190
|
-
'creator': creator
|
|
191
|
-
}
|
|
214
|
+
url = '/datasets/query'
|
|
192
215
|
|
|
216
|
+
if name is not None:
|
|
217
|
+
filters.add(field='name', values=name)
|
|
218
|
+
if creator is not None:
|
|
219
|
+
filters.add(field='creator', values=creator)
|
|
193
220
|
if self._project is not None:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
jobs
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
filters.context = {"projects": [self._project.id]}
|
|
222
|
+
filters.page_size = 1000
|
|
223
|
+
filters.page = 0
|
|
224
|
+
datasets = list()
|
|
225
|
+
while True:
|
|
226
|
+
success, response = self._client_api.gen_request(req_type='POST',
|
|
227
|
+
json_req=filters.prepare(),
|
|
228
|
+
path=url,
|
|
229
|
+
headers={'user_query': filters._user_query})
|
|
230
|
+
if success:
|
|
231
|
+
pool = self._client_api.thread_pools('entity.create')
|
|
232
|
+
datasets_json = response.json()['items']
|
|
233
|
+
jobs = [None for _ in range(len(datasets_json))]
|
|
234
|
+
# return triggers list
|
|
235
|
+
for i_dataset, dataset in enumerate(datasets_json):
|
|
236
|
+
jobs[i_dataset] = pool.submit(entities.Dataset._protected_from_json,
|
|
237
|
+
**{'client_api': self._client_api,
|
|
238
|
+
'_json': dataset,
|
|
239
|
+
'datasets': self,
|
|
240
|
+
'project': self.project})
|
|
241
|
+
|
|
242
|
+
# get all results
|
|
243
|
+
results = [j.result() for j in jobs]
|
|
244
|
+
# log errors
|
|
245
|
+
_ = [logger.warning(r[1]) for r in results if r[0] is False]
|
|
246
|
+
# return good jobs
|
|
247
|
+
datasets.extend([r[1] for r in results if r[0] is True])
|
|
248
|
+
if response.json()['hasNextPage'] is True:
|
|
249
|
+
filters.page += 1
|
|
250
|
+
else:
|
|
251
|
+
break
|
|
252
|
+
else:
|
|
253
|
+
raise exceptions.PlatformException(response)
|
|
254
|
+
datasets = miscellaneous.List(datasets)
|
|
220
255
|
return datasets
|
|
221
256
|
|
|
222
257
|
@_api_reference.add(path='/datasets/{id}', method='get')
|
|
@@ -953,10 +988,5 @@ class Datasets:
|
|
|
953
988
|
|
|
954
989
|
project.datasets.set_readonly(dataset='dataset_entity', state=True)
|
|
955
990
|
"""
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
self.update(dataset=dataset,
|
|
959
|
-
patch=patch)
|
|
960
|
-
dataset._readonly = state
|
|
961
|
-
else:
|
|
962
|
-
logger.warning('Dataset is already "readonly={}". Nothing was done'.format(state))
|
|
991
|
+
import warnings
|
|
992
|
+
warnings.warn("`readonly` flag on dataset is deprecated, doing nothing.", DeprecationWarning)
|