dtlpy 1.113.10__py3-none-any.whl → 1.114.13__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 (243) hide show
  1. dtlpy/__init__.py +488 -488
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/__init__.py +26 -26
  4. dtlpy/assets/__pycache__/__init__.cpython-38.pyc +0 -0
  5. dtlpy/assets/code_server/config.yaml +2 -2
  6. dtlpy/assets/code_server/installation.sh +24 -24
  7. dtlpy/assets/code_server/launch.json +13 -13
  8. dtlpy/assets/code_server/settings.json +2 -2
  9. dtlpy/assets/main.py +53 -53
  10. dtlpy/assets/main_partial.py +18 -18
  11. dtlpy/assets/mock.json +11 -11
  12. dtlpy/assets/model_adapter.py +83 -83
  13. dtlpy/assets/package.json +61 -61
  14. dtlpy/assets/package_catalog.json +29 -29
  15. dtlpy/assets/package_gitignore +307 -307
  16. dtlpy/assets/service_runners/__init__.py +33 -33
  17. dtlpy/assets/service_runners/converter.py +96 -96
  18. dtlpy/assets/service_runners/multi_method.py +49 -49
  19. dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
  20. dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
  21. dtlpy/assets/service_runners/multi_method_item.py +52 -52
  22. dtlpy/assets/service_runners/multi_method_json.py +52 -52
  23. dtlpy/assets/service_runners/single_method.py +37 -37
  24. dtlpy/assets/service_runners/single_method_annotation.py +43 -43
  25. dtlpy/assets/service_runners/single_method_dataset.py +43 -43
  26. dtlpy/assets/service_runners/single_method_item.py +41 -41
  27. dtlpy/assets/service_runners/single_method_json.py +42 -42
  28. dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
  29. dtlpy/assets/voc_annotation_template.xml +23 -23
  30. dtlpy/caches/base_cache.py +32 -32
  31. dtlpy/caches/cache.py +473 -473
  32. dtlpy/caches/dl_cache.py +201 -201
  33. dtlpy/caches/filesystem_cache.py +89 -89
  34. dtlpy/caches/redis_cache.py +84 -84
  35. dtlpy/dlp/__init__.py +20 -20
  36. dtlpy/dlp/cli_utilities.py +367 -367
  37. dtlpy/dlp/command_executor.py +764 -764
  38. dtlpy/dlp/dlp +1 -1
  39. dtlpy/dlp/dlp.bat +1 -1
  40. dtlpy/dlp/dlp.py +128 -128
  41. dtlpy/dlp/parser.py +651 -651
  42. dtlpy/entities/__init__.py +83 -83
  43. dtlpy/entities/analytic.py +311 -311
  44. dtlpy/entities/annotation.py +1879 -1879
  45. dtlpy/entities/annotation_collection.py +699 -699
  46. dtlpy/entities/annotation_definitions/__init__.py +20 -20
  47. dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
  48. dtlpy/entities/annotation_definitions/box.py +195 -195
  49. dtlpy/entities/annotation_definitions/classification.py +67 -67
  50. dtlpy/entities/annotation_definitions/comparison.py +72 -72
  51. dtlpy/entities/annotation_definitions/cube.py +204 -204
  52. dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
  53. dtlpy/entities/annotation_definitions/description.py +32 -32
  54. dtlpy/entities/annotation_definitions/ellipse.py +124 -124
  55. dtlpy/entities/annotation_definitions/free_text.py +62 -62
  56. dtlpy/entities/annotation_definitions/gis.py +69 -69
  57. dtlpy/entities/annotation_definitions/note.py +139 -139
  58. dtlpy/entities/annotation_definitions/point.py +117 -117
  59. dtlpy/entities/annotation_definitions/polygon.py +182 -182
  60. dtlpy/entities/annotation_definitions/polyline.py +111 -111
  61. dtlpy/entities/annotation_definitions/pose.py +92 -92
  62. dtlpy/entities/annotation_definitions/ref_image.py +86 -86
  63. dtlpy/entities/annotation_definitions/segmentation.py +240 -240
  64. dtlpy/entities/annotation_definitions/subtitle.py +34 -34
  65. dtlpy/entities/annotation_definitions/text.py +85 -85
  66. dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
  67. dtlpy/entities/app.py +220 -220
  68. dtlpy/entities/app_module.py +107 -107
  69. dtlpy/entities/artifact.py +174 -174
  70. dtlpy/entities/assignment.py +399 -399
  71. dtlpy/entities/base_entity.py +214 -214
  72. dtlpy/entities/bot.py +113 -113
  73. dtlpy/entities/codebase.py +296 -296
  74. dtlpy/entities/collection.py +38 -38
  75. dtlpy/entities/command.py +169 -169
  76. dtlpy/entities/compute.py +442 -442
  77. dtlpy/entities/dataset.py +1285 -1285
  78. dtlpy/entities/directory_tree.py +44 -44
  79. dtlpy/entities/dpk.py +470 -470
  80. dtlpy/entities/driver.py +222 -222
  81. dtlpy/entities/execution.py +397 -397
  82. dtlpy/entities/feature.py +124 -124
  83. dtlpy/entities/feature_set.py +145 -145
  84. dtlpy/entities/filters.py +641 -641
  85. dtlpy/entities/gis_item.py +107 -107
  86. dtlpy/entities/integration.py +184 -184
  87. dtlpy/entities/item.py +953 -953
  88. dtlpy/entities/label.py +123 -123
  89. dtlpy/entities/links.py +85 -85
  90. dtlpy/entities/message.py +175 -175
  91. dtlpy/entities/model.py +694 -691
  92. dtlpy/entities/node.py +1005 -1005
  93. dtlpy/entities/ontology.py +803 -803
  94. dtlpy/entities/organization.py +287 -287
  95. dtlpy/entities/package.py +657 -657
  96. dtlpy/entities/package_defaults.py +5 -5
  97. dtlpy/entities/package_function.py +185 -185
  98. dtlpy/entities/package_module.py +113 -113
  99. dtlpy/entities/package_slot.py +118 -118
  100. dtlpy/entities/paged_entities.py +290 -267
  101. dtlpy/entities/pipeline.py +593 -593
  102. dtlpy/entities/pipeline_execution.py +279 -279
  103. dtlpy/entities/project.py +394 -394
  104. dtlpy/entities/prompt_item.py +499 -499
  105. dtlpy/entities/recipe.py +301 -301
  106. dtlpy/entities/reflect_dict.py +102 -102
  107. dtlpy/entities/resource_execution.py +138 -138
  108. dtlpy/entities/service.py +958 -958
  109. dtlpy/entities/service_driver.py +117 -117
  110. dtlpy/entities/setting.py +294 -294
  111. dtlpy/entities/task.py +491 -491
  112. dtlpy/entities/time_series.py +143 -143
  113. dtlpy/entities/trigger.py +426 -426
  114. dtlpy/entities/user.py +118 -118
  115. dtlpy/entities/webhook.py +124 -124
  116. dtlpy/examples/__init__.py +19 -19
  117. dtlpy/examples/add_labels.py +135 -135
  118. dtlpy/examples/add_metadata_to_item.py +21 -21
  119. dtlpy/examples/annotate_items_using_model.py +65 -65
  120. dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
  121. dtlpy/examples/annotations_convert_to_voc.py +9 -9
  122. dtlpy/examples/annotations_convert_to_yolo.py +9 -9
  123. dtlpy/examples/convert_annotation_types.py +51 -51
  124. dtlpy/examples/converter.py +143 -143
  125. dtlpy/examples/copy_annotations.py +22 -22
  126. dtlpy/examples/copy_folder.py +31 -31
  127. dtlpy/examples/create_annotations.py +51 -51
  128. dtlpy/examples/create_video_annotations.py +83 -83
  129. dtlpy/examples/delete_annotations.py +26 -26
  130. dtlpy/examples/filters.py +113 -113
  131. dtlpy/examples/move_item.py +23 -23
  132. dtlpy/examples/play_video_annotation.py +13 -13
  133. dtlpy/examples/show_item_and_mask.py +53 -53
  134. dtlpy/examples/triggers.py +49 -49
  135. dtlpy/examples/upload_batch_of_items.py +20 -20
  136. dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
  137. dtlpy/examples/upload_items_with_modalities.py +43 -43
  138. dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
  139. dtlpy/examples/upload_yolo_format_annotations.py +70 -70
  140. dtlpy/exceptions.py +125 -125
  141. dtlpy/miscellaneous/__init__.py +20 -20
  142. dtlpy/miscellaneous/dict_differ.py +95 -95
  143. dtlpy/miscellaneous/git_utils.py +217 -217
  144. dtlpy/miscellaneous/json_utils.py +14 -14
  145. dtlpy/miscellaneous/list_print.py +105 -105
  146. dtlpy/miscellaneous/zipping.py +130 -130
  147. dtlpy/ml/__init__.py +20 -20
  148. dtlpy/ml/base_feature_extractor_adapter.py +27 -27
  149. dtlpy/ml/base_model_adapter.py +945 -940
  150. dtlpy/ml/metrics.py +461 -461
  151. dtlpy/ml/predictions_utils.py +274 -274
  152. dtlpy/ml/summary_writer.py +57 -57
  153. dtlpy/ml/train_utils.py +60 -60
  154. dtlpy/new_instance.py +252 -252
  155. dtlpy/repositories/__init__.py +56 -56
  156. dtlpy/repositories/analytics.py +85 -85
  157. dtlpy/repositories/annotations.py +916 -916
  158. dtlpy/repositories/apps.py +383 -383
  159. dtlpy/repositories/artifacts.py +452 -452
  160. dtlpy/repositories/assignments.py +599 -599
  161. dtlpy/repositories/bots.py +213 -213
  162. dtlpy/repositories/codebases.py +559 -559
  163. dtlpy/repositories/collections.py +332 -348
  164. dtlpy/repositories/commands.py +158 -158
  165. dtlpy/repositories/compositions.py +61 -61
  166. dtlpy/repositories/computes.py +434 -406
  167. dtlpy/repositories/datasets.py +1291 -1291
  168. dtlpy/repositories/downloader.py +895 -895
  169. dtlpy/repositories/dpks.py +433 -433
  170. dtlpy/repositories/drivers.py +266 -266
  171. dtlpy/repositories/executions.py +817 -817
  172. dtlpy/repositories/feature_sets.py +226 -226
  173. dtlpy/repositories/features.py +238 -238
  174. dtlpy/repositories/integrations.py +484 -484
  175. dtlpy/repositories/items.py +909 -915
  176. dtlpy/repositories/messages.py +94 -94
  177. dtlpy/repositories/models.py +877 -867
  178. dtlpy/repositories/nodes.py +80 -80
  179. dtlpy/repositories/ontologies.py +511 -511
  180. dtlpy/repositories/organizations.py +525 -525
  181. dtlpy/repositories/packages.py +1941 -1941
  182. dtlpy/repositories/pipeline_executions.py +448 -448
  183. dtlpy/repositories/pipelines.py +642 -642
  184. dtlpy/repositories/projects.py +539 -539
  185. dtlpy/repositories/recipes.py +399 -399
  186. dtlpy/repositories/resource_executions.py +137 -137
  187. dtlpy/repositories/schema.py +120 -120
  188. dtlpy/repositories/service_drivers.py +213 -213
  189. dtlpy/repositories/services.py +1704 -1704
  190. dtlpy/repositories/settings.py +339 -339
  191. dtlpy/repositories/tasks.py +1124 -1124
  192. dtlpy/repositories/times_series.py +278 -278
  193. dtlpy/repositories/triggers.py +536 -536
  194. dtlpy/repositories/upload_element.py +257 -257
  195. dtlpy/repositories/uploader.py +651 -651
  196. dtlpy/repositories/webhooks.py +249 -249
  197. dtlpy/services/__init__.py +22 -22
  198. dtlpy/services/aihttp_retry.py +131 -131
  199. dtlpy/services/api_client.py +1782 -1782
  200. dtlpy/services/api_reference.py +40 -40
  201. dtlpy/services/async_utils.py +133 -133
  202. dtlpy/services/calls_counter.py +44 -44
  203. dtlpy/services/check_sdk.py +68 -68
  204. dtlpy/services/cookie.py +115 -115
  205. dtlpy/services/create_logger.py +156 -156
  206. dtlpy/services/events.py +84 -84
  207. dtlpy/services/logins.py +235 -235
  208. dtlpy/services/reporter.py +256 -256
  209. dtlpy/services/service_defaults.py +91 -91
  210. dtlpy/utilities/__init__.py +20 -20
  211. dtlpy/utilities/annotations/__init__.py +16 -16
  212. dtlpy/utilities/annotations/annotation_converters.py +269 -269
  213. dtlpy/utilities/base_package_runner.py +264 -264
  214. dtlpy/utilities/converter.py +1650 -1650
  215. dtlpy/utilities/dataset_generators/__init__.py +1 -1
  216. dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
  217. dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
  218. dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
  219. dtlpy/utilities/local_development/__init__.py +1 -1
  220. dtlpy/utilities/local_development/local_session.py +179 -179
  221. dtlpy/utilities/reports/__init__.py +2 -2
  222. dtlpy/utilities/reports/figures.py +343 -343
  223. dtlpy/utilities/reports/report.py +71 -71
  224. dtlpy/utilities/videos/__init__.py +17 -17
  225. dtlpy/utilities/videos/video_player.py +598 -598
  226. dtlpy/utilities/videos/videos.py +470 -470
  227. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp +1 -1
  228. dtlpy-1.114.13.data/scripts/dlp.bat +2 -0
  229. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp.py +128 -128
  230. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/LICENSE +200 -200
  231. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/METADATA +172 -172
  232. dtlpy-1.114.13.dist-info/RECORD +240 -0
  233. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/WHEEL +1 -1
  234. tests/features/environment.py +551 -550
  235. dtlpy-1.113.10.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.113.10.dist-info/RECORD +0 -244
  237. tests/assets/__init__.py +0 -0
  238. tests/assets/models_flow/__init__.py +0 -0
  239. tests/assets/models_flow/failedmain.py +0 -52
  240. tests/assets/models_flow/main.py +0 -62
  241. tests/assets/models_flow/main_model.py +0 -54
  242. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/entry_points.txt +0 -0
  243. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/top_level.txt +0 -0
@@ -1,867 +1,877 @@
1
- import time
2
- from typing import List
3
- import logging
4
-
5
- from .. import entities, repositories, exceptions, miscellaneous
6
- from ..services.api_client import ApiClient
7
-
8
- logger = logging.getLogger(name='dtlpy')
9
-
10
- MIN_INTERVAL = 1
11
- BACKOFF_FACTOR = 1.2
12
- MAX_INTERVAL = 12
13
-
14
-
15
- class Models:
16
- """
17
- Models Repository
18
- """
19
-
20
- def __init__(self,
21
- client_api: ApiClient,
22
- package: entities.Package = None,
23
- project: entities.Project = None,
24
- project_id: str = None):
25
- self._client_api = client_api
26
- self._project = project
27
- self._package = package
28
- self._project_id = project_id
29
-
30
- if self._project is not None:
31
- self._project_id = self._project.id
32
-
33
- ############
34
- # entities #
35
- ############
36
- @property
37
- def project(self) -> entities.Project:
38
- if self._project is None:
39
- if self._project_id is not None:
40
- projects = repositories.Projects(client_api=self._client_api)
41
- self._project = projects.get(project_id=self._project_id)
42
- if self._project is None:
43
- if self._package is not None:
44
- if self._package._project is not None:
45
- self._project = self._package._project
46
- if self._project is None:
47
- raise exceptions.PlatformException(
48
- error='2001',
49
- message='Missing "project". need to set a Project entity or use project.models repository')
50
- assert isinstance(self._project, entities.Project)
51
- return self._project
52
-
53
- @project.setter
54
- def project(self, project: entities.Project):
55
- if not isinstance(project, entities.Project):
56
- raise ValueError('Must input a valid Project entity')
57
- self._project = project
58
-
59
- @property
60
- def package(self) -> entities.Package:
61
- if self._package is None:
62
- raise exceptions.PlatformException(
63
- error='2001',
64
- message='Cannot perform action WITHOUT Package entity in {} repository.'.format(
65
- self.__class__.__name__) +
66
- ' Please use package.models or set a model')
67
- assert isinstance(self._package, entities.Package)
68
- return self._package
69
-
70
- ###########
71
- # methods #
72
- ###########
73
- def get(self, model_name=None, model_id=None) -> entities.Model:
74
- """
75
- Get model object
76
- :param model_name:
77
- :param model_id:
78
- :return: dl.Model object
79
- """
80
-
81
- if model_id is not None:
82
- success, response = self._client_api.gen_request(req_type="get",
83
- path="/ml/models/{}".format(model_id))
84
- if not success:
85
- raise exceptions.PlatformException(response)
86
- model = entities.Model.from_json(client_api=self._client_api,
87
- _json=response.json(),
88
- project=self._project,
89
- package=self._package)
90
- # verify input model name is same as the given id
91
- if model_name is not None and model.name != model_name:
92
- logger.warning(
93
- "Mismatch found in models.get: model_name is different then model.name:"
94
- " {!r} != {!r}".format(
95
- model_name,
96
- model.name))
97
- elif model_name is not None:
98
-
99
- filters = entities.Filters(
100
- resource=entities.FiltersResource.MODEL,
101
- field='name',
102
- values=model_name
103
- )
104
-
105
- project_id = None
106
-
107
- if self._project is not None:
108
- project_id = self._project.id
109
- elif self._project_id is not None:
110
- project_id = self._project_id
111
-
112
- if project_id is not None:
113
- filters.add(field='projectId', values=project_id)
114
-
115
- if self._package is not None:
116
- filters.add(field='packageId', values=self._package.id)
117
-
118
- models = self.list(filters=filters)
119
-
120
- if models.items_count == 0:
121
- raise exceptions.PlatformException(
122
- error='404',
123
- message='Model not found. Name: {}'.format(model_name))
124
- elif models.items_count > 1:
125
- raise exceptions.PlatformException(
126
- error='400',
127
- message='More than one Model found by the name of: {}. Try "get" by id or "list()".'.format(
128
- model_name))
129
- model = models.items[0]
130
- else:
131
- raise exceptions.PlatformException(
132
- error='400',
133
- message='No checked-out Model was found, must checkout or provide an identifier in inputs')
134
-
135
- return model
136
-
137
- def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Model]:
138
- jobs = [None for _ in range(len(response_items))]
139
- pool = self._client_api.thread_pools(pool_name='entity.create')
140
-
141
- # return triggers list
142
- for i_service, service in enumerate(response_items):
143
- jobs[i_service] = pool.submit(entities.Model._protected_from_json,
144
- **{'client_api': self._client_api,
145
- '_json': service,
146
- 'package': self._package,
147
- 'project': self._project})
148
-
149
- # get all results
150
- results = [j.result() for j in jobs]
151
- # log errors
152
- _ = [logger.warning(r[1]) for r in results if r[0] is False]
153
- # return good jobs
154
- return miscellaneous.List([r[1] for r in results if r[0] is True])
155
-
156
- def _list(self, filters: entities.Filters):
157
- # request
158
- success, response = self._client_api.gen_request(req_type='POST',
159
- path='/ml/models/query',
160
- json_req=filters.prepare())
161
- if not success:
162
- raise exceptions.PlatformException(response)
163
- return response.json()
164
-
165
- def list(self, filters: entities.Filters = None) -> entities.PagedEntities:
166
- """
167
- List project model
168
-
169
- :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
170
- :return: Paged entity
171
- :rtype: dtlpy.entities.paged_entities.PagedEntities
172
- """
173
- # default filters
174
- if filters is None:
175
- filters = entities.Filters(resource=entities.FiltersResource.MODEL)
176
- if self._project is not None:
177
- filters.add(field='projectId', values=self._project.id)
178
- if self._package is not None:
179
- filters.add(field='packageId', values=self._package.id)
180
-
181
- # assert type filters
182
- if not isinstance(filters, entities.Filters):
183
- raise exceptions.PlatformException(error='400',
184
- message='Unknown filters type: {!r}'.format(type(filters)))
185
-
186
- if filters.resource != entities.FiltersResource.MODEL:
187
- raise exceptions.PlatformException(
188
- error='400',
189
- message='Filters resource must to be FiltersResource.MODEL. Got: {!r}'.format(filters.resource))
190
-
191
- paged = entities.PagedEntities(items_repository=self,
192
- filters=filters,
193
- page_offset=filters.page,
194
- page_size=filters.page_size,
195
- client_api=self._client_api)
196
- paged.get_page()
197
- return paged
198
-
199
- def _set_model_filter(self,
200
- metadata: dict,
201
- train_filter: entities.Filters = None,
202
- validation_filter: entities.Filters = None):
203
- if metadata is None:
204
- metadata = {}
205
- if 'system' not in metadata:
206
- metadata['system'] = {}
207
- if 'subsets' not in metadata['system']:
208
- metadata['system']['subsets'] = {}
209
- if train_filter is not None:
210
- metadata['system']['subsets']['train'] = train_filter.prepare() if isinstance(train_filter,
211
- entities.Filters) else train_filter
212
- if validation_filter is not None:
213
- metadata['system']['subsets']['validation'] = validation_filter.prepare() if isinstance(validation_filter,
214
- entities.Filters) else validation_filter
215
- return metadata
216
-
217
- @staticmethod
218
- def add_subset(model: entities.Model, subset_name: str, subset_filter: entities.Filters):
219
- """
220
- Adds a subset for a model, specifying a subset of the model's dataset that could be used for training or
221
- validation.
222
-
223
- :param dtlpy.entities.Model model: the model to which the subset should be added
224
- :param str subset_name: the name of the subset
225
- :param dtlpy.entities.Filters subset_filter: the filtering operation that this subset performs in the dataset.
226
-
227
- **Example**
228
-
229
- .. code-block:: python
230
-
231
- project.models.add_subset(model=model_entity, subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
232
- model_entity.metadata['system']['subsets']
233
- {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
234
-
235
- """
236
- if 'system' not in model.metadata:
237
- model.metadata['system'] = dict()
238
- if 'subsets' not in model.metadata['system']:
239
- model.metadata['system']['subsets'] = dict()
240
- model.metadata['system']['subsets'][subset_name] = subset_filter.prepare()
241
- model.update(system_metadata=True)
242
-
243
- @staticmethod
244
- def delete_subset(model: entities.Model, subset_name: str):
245
- """
246
- Removes a subset from a model's metadata.
247
-
248
- :param dtlpy.entities.Model model: the model to which the subset should be added
249
- :param str subset_name: the name of the subset
250
-
251
- **Example**
252
-
253
- .. code-block:: python
254
-
255
- project.models.add_subset(model=model_entity, subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
256
- model_entity.metadata['system']['subsets']
257
- {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
258
- project.models.delete_subset(model=model_entity, subset_name='train')
259
- model_entity.metadata['system']['subsets']
260
- {}
261
-
262
- """
263
- if model.metadata.get("system", dict()).get("subsets", dict()).get(subset_name) is None:
264
- logger.error(f"Model system metadata incomplete, could not delete subset {subset_name}.")
265
- else:
266
- _ = model.metadata['system']['subsets'].pop(subset_name)
267
- model.update(system_metadata=True)
268
-
269
- def create(
270
- self,
271
- model_name: str,
272
- dataset_id: str = None,
273
- labels: list = None,
274
- ontology_id: str = None,
275
- description: str = None,
276
- model_artifacts: List[entities.Artifact] = None,
277
- project_id=None,
278
- tags: List[str] = None,
279
- package: entities.Package = None,
280
- configuration: dict = None,
281
- status: str = None,
282
- scope: entities.EntityScopeLevel = entities.EntityScopeLevel.PROJECT,
283
- version: str = '1.0.0',
284
- input_type=None,
285
- output_type=None,
286
- train_filter: entities.Filters = None,
287
- validation_filter: entities.Filters = None,
288
- app: entities.App = None
289
- ) -> entities.Model:
290
- """
291
- Create a Model entity
292
-
293
- :param str model_name: name of the model
294
- :param str dataset_id: dataset id
295
- :param list labels: list of labels from ontology (must mach ontology id) can be a subset
296
- :param str ontology_id: ontology to connect to the model
297
- :param str description: description
298
- :param model_artifacts: optional list of dl.Artifact. Can be ItemArtifact, LocaArtifact or LinkArtifact
299
- :param str project_id: project that owns the model
300
- :param list tags: list of string tags
301
- :param package: optional - Package object
302
- :param dict configuration: optional - model configuration - dict
303
- :param str status: `str` of the optional values of
304
- :param str scope: the scope level of the model dl.EntityScopeLevel
305
- :param str version: version of the model
306
- :param str input_type: the file type the model expect as input (image, video, txt, etc)
307
- :param str output_type: dl.AnnotationType - the type of annotations the model produces (class, box segment, text, etc)
308
- :param dtlpy.entities.filters.Filters train_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model train
309
- :param dtlpy.entities.filters.Filters validation_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model validation
310
- :param dtlpy.entities.App app: App entity to connect the model to
311
- :return: Model Entity
312
-
313
- **Example**:
314
-
315
- .. code-block:: python
316
-
317
- project.models.create(model_name='model_name', dataset_id='dataset_id', labels=['label1', 'label2'], train_filter={filter: {$and: [{dir: "/10K short videos"}]},page: 0,pageSize: 1000,resource: "items"}})
318
-
319
- """
320
-
321
- if ontology_id is not None:
322
- # take labels from ontology
323
- ontologies = repositories.Ontologies(client_api=self._client_api)
324
- labels = [label.tag for label in ontologies.get(ontology_id=ontology_id).labels]
325
-
326
- if labels is None:
327
- # dont have to have labels. can use an empty list
328
- labels = list()
329
-
330
- if input_type is None:
331
- input_type = 'image'
332
-
333
- if output_type is None:
334
- output_type = entities.AnnotationType.CLASSIFICATION
335
-
336
- if package is None and self._package is None:
337
- raise exceptions.PlatformException('Must provide a Package or create from package.models')
338
- elif package is None:
339
- package = self._package
340
-
341
- # TODO need to remove the entire project id user interface - need to take it from dataset id (in BE)
342
- if project_id is None:
343
- if self._project is None:
344
- raise exceptions.PlatformException('Please provide project_id')
345
- project_id = self._project.id
346
- else:
347
- if project_id != self._project_id:
348
- if (isinstance(package, entities.Package) and not package.is_global) or \
349
- (isinstance(package, entities.Dpk) and not package.scope != 'public'):
350
- logger.warning(
351
- "Note! you are specified project_id {!r} which is different from repository context: {!r}".format(
352
- project_id, self._project_id))
353
-
354
- if model_artifacts is None:
355
- model_artifacts = []
356
-
357
- if not isinstance(model_artifacts, list):
358
- raise ValueError('`model_artifacts` must be a list of dl.Artifact entities')
359
-
360
- # create payload for request
361
- payload = {
362
- 'packageId': package.id,
363
- 'name': model_name,
364
- 'projectId': project_id,
365
- 'datasetId': dataset_id,
366
- 'labels': labels,
367
- 'artifacts': [artifact.to_json(as_artifact=True) for artifact in model_artifacts],
368
- 'scope': scope,
369
- 'version': version,
370
- 'inputType': input_type,
371
- 'outputType': output_type,
372
- }
373
-
374
- if app is not None:
375
- if not isinstance(package, entities.Dpk):
376
- raise ValueError('package must be a Dpk entity')
377
- if app.dpk_name != package.name or app.dpk_version != package.version:
378
- raise ValueError('App and package must be the same')
379
- component_name = None
380
- compute_config = None
381
- for model in package.components.models:
382
- if model['name'] == model_name:
383
- component_name = model['name']
384
- compute_config = model.get('computeConfigs', None)
385
- break
386
- if component_name is None:
387
- raise ValueError('Model name not found in package')
388
- payload['app'] = {
389
- "id": app.id,
390
- "componentName": component_name,
391
- "dpkName": package.name,
392
- "dpkVersion": package.version
393
- }
394
- if compute_config is not None:
395
- payload['app']['computeConfig'] = compute_config
396
-
397
- if configuration is not None:
398
- payload['configuration'] = configuration
399
-
400
- if tags is not None:
401
- payload['tags'] = tags
402
-
403
- if description is not None:
404
- payload['description'] = description
405
-
406
- if status is not None:
407
- payload['status'] = status
408
-
409
- if train_filter or validation_filter:
410
- metadata = self._set_model_filter(metadata={},
411
- train_filter=train_filter,
412
- validation_filter=validation_filter)
413
- payload['metadata'] = metadata
414
-
415
- # request
416
- success, response = self._client_api.gen_request(req_type='post',
417
- path='/ml/models',
418
- json_req=payload)
419
-
420
- # exception handling
421
- if not success:
422
- raise exceptions.PlatformException(response)
423
-
424
- model = entities.Model.from_json(_json=response.json(),
425
- client_api=self._client_api,
426
- project=self._project,
427
- package=package)
428
-
429
- return model
430
-
431
- def clone(self,
432
- from_model: entities.Model,
433
- model_name: str,
434
- dataset: entities.Dataset = None,
435
- configuration: dict = None,
436
- status=None,
437
- scope=None,
438
- project_id: str = None,
439
- labels: list = None,
440
- description: str = None,
441
- tags: list = None,
442
- train_filter: entities.Filters = None,
443
- validation_filter: entities.Filters = None,
444
- wait=True,
445
- ) -> entities.Model:
446
- """
447
- Clones and creates a new model out of existing one
448
-
449
- :param from_model: existing model to clone from
450
- :param str model_name: `str` new model name
451
- :param str dataset: dataset object for the cloned model
452
- :param dict configuration: `dict` (optional) if passed replaces the current configuration
453
- :param str status: `str` (optional) set the new status
454
- :param str scope: `str` (optional) set the new scope. default is "project"
455
- :param str project_id: `str` specify the project id to create the new model on (if other than the source model)
456
- :param list labels: `list` of `str` - label of the model
457
- :param str description: `str` description of the new model
458
- :param list tags: `list` of `str` - label of the model
459
- :param dtlpy.entities.filters.Filters train_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model train
460
- :param dtlpy.entities.filters.Filters validation_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model validation
461
- :param bool wait: `bool` wait for model to be ready
462
- :return: dl.Model which is a clone version of the existing model
463
- """
464
- from_json = {"name": model_name,
465
- "packageId": from_model.package_id,
466
- "configuration": from_model.configuration,
467
- "outputType": from_model.output_type,
468
- "inputType": from_model.input_type}
469
- if project_id is None:
470
- if dataset is not None:
471
- # take dataset project
472
- project_id = dataset.project.id
473
- else:
474
- # take model's project
475
- project_id = self.project.id
476
- from_json['projectId'] = project_id
477
- if dataset is not None:
478
- if labels is None:
479
- labels = list(dataset.labels_flat_dict.keys())
480
- from_json['datasetId'] = dataset.id
481
- if labels is not None:
482
- from_json['labels'] = labels
483
- # if there are new labels - pop the mapping from the original
484
- _ = from_json['configuration'].pop('id_to_label_map', None)
485
- _ = from_json['configuration'].pop('label_to_id_map', None)
486
- if configuration is not None:
487
- from_json['configuration'].update(configuration)
488
- if description is not None:
489
- from_json['description'] = description
490
- if tags is not None:
491
- from_json['tags'] = tags
492
- if scope is not None:
493
- from_json['scope'] = scope
494
- if status is not None:
495
- from_json['status'] = status
496
-
497
- metadata = self._set_model_filter(metadata={},
498
- train_filter=train_filter if train_filter is not None else from_model.metadata.get(
499
- 'system', {}).get('subsets', {}).get('train', None),
500
- validation_filter=validation_filter if validation_filter is not None else from_model.metadata.get(
501
- 'system', {}).get('subsets', {}).get('validation', None))
502
- if metadata:
503
- from_json['metadata'] = metadata
504
- success, response = self._client_api.gen_request(req_type='post',
505
- path='/ml/models/{}/clone'.format(from_model.id),
506
- json_req=from_json)
507
- if not success:
508
- raise exceptions.PlatformException(response)
509
- new_model = entities.Model.from_json(_json=response.json(),
510
- client_api=self._client_api,
511
- project=self._project,
512
- package=from_model._package)
513
- if wait:
514
- new_model = self.wait_for_model_ready(model=new_model)
515
- return new_model
516
-
517
- def wait_for_model_ready(self, model: entities.Model):
518
- """
519
- Wait for model to be ready
520
-
521
- :param model: Model entity
522
- """
523
- sleep_time = MIN_INTERVAL
524
- while model.status == entities.ModelStatus.CLONING:
525
- model = self.get(model_id=model.id)
526
- time.sleep(sleep_time)
527
- sleep_time = min(sleep_time * BACKOFF_FACTOR, MAX_INTERVAL)
528
- time.sleep(sleep_time)
529
- return model
530
-
531
- @property
532
- def platform_url(self):
533
- return self._client_api._get_resource_url("projects/{}/models".format(self.project.id))
534
-
535
- def open_in_web(self, model=None, model_id=None):
536
- """
537
- Open the model in web platform
538
-
539
- :param model: model entity
540
- :param str model_id: model id
541
- """
542
- if model is not None:
543
- model.open_in_web()
544
- elif model_id is not None:
545
- self._client_api._open_in_web(url=self.platform_url + '/' + str(model_id) + '/main')
546
- else:
547
- self._client_api._open_in_web(url=self.platform_url)
548
-
549
- def delete(self, model: entities.Model = None, model_name=None, model_id=None):
550
- """
551
- Delete Model object
552
-
553
- :param model: Model entity to delete
554
- :param str model_name: delete by model name
555
- :param str model_id: delete by model id
556
- :return: True
557
- :rtype: bool
558
- """
559
- # get id and name
560
- if model_id is None:
561
- if model is not None:
562
- model_id = model.id
563
- elif model_name is not None:
564
- model = self.get(model_name=model_name)
565
- model_id = model.id
566
- else:
567
- raise exceptions.PlatformException(error='400',
568
- message='Must input at least one parameter to models.delete')
569
-
570
- # request
571
- success, response = self._client_api.gen_request(
572
- req_type="delete",
573
- path="/ml/models/{}".format(model_id)
574
- )
575
-
576
- # exception handling
577
- if not success:
578
- raise exceptions.PlatformException(response)
579
-
580
- # return results
581
- return True
582
-
583
- def update(self,
584
- model: entities.Model,
585
- system_metadata: bool = False) -> entities.Model:
586
- """
587
- Update Model changes to platform
588
-
589
- :param model: Model entity
590
- :param bool system_metadata: True, if you want to change metadata system
591
- :return: Model entity
592
- """
593
- # payload
594
- payload = model.to_json()
595
-
596
- # url
597
- url_path = '/ml/models/{}'.format(model.id)
598
- if system_metadata:
599
- url_path += '?system=true'
600
-
601
- # request
602
- success, response = self._client_api.gen_request(req_type='patch',
603
- path=url_path,
604
- json_req=payload)
605
-
606
- # exception handling
607
- if not success:
608
- raise exceptions.PlatformException(response)
609
-
610
- # return entity
611
- return entities.Model.from_json(_json=response.json(),
612
- client_api=self._client_api,
613
- project=self._project,
614
- package=model._package)
615
-
616
- def train(self, model_id: str, service_config=None):
617
- """
618
- Train the model in the cloud. This will create a service and will run the adapter's train function as an execution
619
-
620
- :param model_id: id of the model to train
621
- :param dict service_config : Service object as dict. Contains the spec of the default service to create.
622
- :return:
623
- """
624
- payload = dict()
625
- if service_config is not None:
626
- payload['serviceConfig'] = service_config
627
- success, response = self._client_api.gen_request(req_type="post",
628
- path=f"/ml/models/{model_id}/train",
629
- json_req=payload)
630
- if not success:
631
- raise exceptions.PlatformException(response)
632
- return entities.Execution.from_json(_json=response.json(),
633
- client_api=self._client_api,
634
- project=self._project)
635
-
636
- def evaluate(self, model_id: str, dataset_id: str, filters: entities.Filters = None, service_config=None):
637
- """
638
- Evaluate Model, provide data to evaluate the model on You can also provide specific config for the deployed service
639
-
640
- :param str model_id: Model id to predict
641
- :param dict service_config : Service object as dict. Contains the spec of the default service to create.
642
- :param str dataset_id: ID of the dataset to evaluate
643
- :param entities.Filters filters: dl.Filter entity to run the predictions on
644
- :return:
645
- """
646
-
647
- payload = {'input': {'datasetId': dataset_id}}
648
- if service_config is not None:
649
- payload['config'] = {'serviceConfig': service_config}
650
- if filters is None:
651
- filters = entities.Filters()
652
- if filters is not None:
653
- payload['input']['datasetQuery'] = filters.prepare()
654
- success, response = self._client_api.gen_request(req_type="post",
655
- path=f"/ml/models/{model_id}/evaluate",
656
- json_req=payload)
657
- if not success:
658
- raise exceptions.PlatformException(response)
659
- return entities.Execution.from_json(_json=response.json(),
660
- client_api=self._client_api,
661
- project=self._project)
662
-
663
- def predict(self, model, item_ids, dataset_id=None):
664
- """
665
- Run model prediction with items
666
-
667
- :param model: dl.Model entity to run the prediction.
668
- :param item_ids: a list of item id to run the prediction.
669
- :param dataset_id: a dataset id to run the prediction.
670
- :return:
671
- """
672
- if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
673
- # no services for model
674
- raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
675
- if item_ids is None and dataset_id is None:
676
- raise ValueError("Need to provide either item_ids or dataset_id")
677
- payload_input = {}
678
- if item_ids is not None:
679
- payload_input['itemIds'] = item_ids
680
- if dataset_id is not None:
681
- payload_input['datasetId'] = dataset_id
682
- payload = {'input': payload_input,
683
- 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]}}
684
-
685
- success, response = self._client_api.gen_request(req_type="post",
686
- path=f"/ml/models/{model.id}/predict",
687
- json_req=payload)
688
- if not success:
689
- raise exceptions.PlatformException(response)
690
- return entities.Execution.from_json(_json=response.json(),
691
- client_api=self._client_api,
692
- project=self._project)
693
-
694
- def embed(self, model, item_ids=None, dataset_id=None):
695
- """
696
- Run model embed with items
697
-
698
- :param model: dl.Model entity to run the prediction.
699
- :param item_ids: a list of item id to run the embed.
700
- :param dataset_id: a dataset id to run the embed.
701
- :return: Execution
702
- :rtype: dtlpy.entities.execution.Execution
703
- """
704
- if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
705
- # no services for model
706
- raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
707
- if item_ids is None and dataset_id is None:
708
- raise ValueError("Need to provide either item_ids or dataset_id")
709
- payload_input = {}
710
- if item_ids is not None:
711
- payload_input['itemIds'] = item_ids
712
- if dataset_id is not None:
713
- payload_input['datasetId'] = dataset_id
714
- payload = {'input': payload_input,
715
- 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]}}
716
-
717
- success, response = self._client_api.gen_request(req_type="post",
718
- path=f"/ml/models/{model.id}/embed",
719
- json_req=payload)
720
- if not success:
721
- raise exceptions.PlatformException(response)
722
- return entities.Execution.from_json(_json=response.json(),
723
- client_api=self._client_api,
724
- project=self._project)
725
-
726
- def embed_datasets(self, model, dataset_ids, attach_trigger=False):
727
- """
728
- Run model embed with datasets
729
-
730
- :param model: dl.Model entity to run the prediction.
731
- :param dataset_ids: a list of dataset id to run the embed.
732
- :param attach_trigger: bool, if True will activate the trigger
733
- :return:
734
- """
735
- if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
736
- # no services for model
737
- raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
738
- if dataset_ids is None:
739
- raise ValueError("Need to provide either dataset_id")
740
- payload = {'datasetIds': dataset_ids,
741
- 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]},
742
- 'attachTrigger': attach_trigger
743
- }
744
-
745
- success, response = self._client_api.gen_request(req_type="post",
746
- path=f"/ml/models/{model.id}/embed/datasets",
747
- json_req=payload)
748
- if not success:
749
- raise exceptions.PlatformException(response)
750
- command = entities.Command.from_json(_json=response.json(),
751
- client_api=self._client_api)
752
- command = command.wait()
753
- return command
754
-
755
- def deploy(self, model_id: str, service_config=None) -> entities.Service:
756
- """
757
- Deploy a trained model. This will create a service that will execute predictions
758
-
759
- :param model_id: id of the model to deploy
760
- :param dict service_config : Service object as dict. Contains the spec of the default service to create.
761
- :return: dl.Service: the deployed service
762
- """
763
- payload = dict()
764
- if service_config is not None:
765
- payload['serviceConfig'] = service_config if not service_config.get("serviceConfig") else service_config.get("serviceConfig")
766
- success, response = self._client_api.gen_request(req_type="post",
767
- path=f"/ml/models/{model_id}/deploy",
768
- json_req=payload)
769
- if not success:
770
- raise exceptions.PlatformException(response)
771
-
772
- return entities.Service.from_json(_json=response.json(),
773
- client_api=self._client_api,
774
- project=self._project,
775
- package=self._package)
776
-
777
-
778
- class Metrics:
779
- def __init__(self, client_api, model=None, model_id=None):
780
- self._client_api = client_api
781
- self._model_id = model_id
782
- self._model = model
783
-
784
- @property
785
- def model(self):
786
- return self._model
787
-
788
- def create(self, samples, dataset_id) -> bool:
789
- """
790
- Add Samples for model analytics and metrics
791
-
792
- :param samples: list of dl.PlotSample - must contain: model_id, figure, legend, x, y
793
- :param model_id: model id to save samples on
794
- :param dataset_id:
795
- :return: bool: True if success
796
- """
797
- if not isinstance(samples, list):
798
- samples = [samples]
799
-
800
- payload = list()
801
- for sample in samples:
802
- _json = sample.to_json()
803
- _json['modelId'] = self.model.id
804
- _json['datasetId'] = dataset_id
805
- payload.append(_json)
806
- # request
807
- success, response = self._client_api.gen_request(req_type='post',
808
- path='/ml/metrics/publish',
809
- json_req=payload)
810
-
811
- # exception handling
812
- if not success:
813
- raise exceptions.PlatformException(response)
814
-
815
- # return entity
816
- return True
817
-
818
- def _list(self, filters: entities.Filters):
819
- # request
820
- success, response = self._client_api.gen_request(req_type='POST',
821
- path='/ml/metrics/query',
822
- json_req=filters.prepare())
823
- if not success:
824
- raise exceptions.PlatformException(response)
825
- return response.json()
826
-
827
- def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Model]:
828
- jobs = [None for _ in range(len(response_items))]
829
- pool = self._client_api.thread_pools(pool_name='entity.create')
830
-
831
- # return triggers list
832
- for i_service, sample in enumerate(response_items):
833
- jobs[i_service] = pool.submit(entities.PlotSample,
834
- **{'x': sample.get('data', dict()).get('x', None),
835
- 'y': sample.get('data', dict()).get('y', None),
836
- 'legend': sample.get('legend', ''),
837
- 'figure': sample.get('figure', '')})
838
-
839
- # get all results
840
- results = [j.result() for j in jobs]
841
- # return good jobs
842
- return miscellaneous.List(results)
843
-
844
- def list(self, filters=None) -> entities.PagedEntities:
845
- """
846
- List Samples for model analytics and metrics
847
-
848
- :param filters: dl.Filter query entity
849
- """
850
- if filters is None:
851
- filters = entities.Filters(resource=entities.FiltersResource.METRICS)
852
- if not isinstance(filters, entities.Filters):
853
- raise exceptions.PlatformException(error='400',
854
- message='Unknown filters type: {!r}'.format(type(filters)))
855
- if filters.resource != entities.FiltersResource.METRICS:
856
- raise exceptions.PlatformException(
857
- error='400',
858
- message='Filters resource must to be FiltersResource.METRICS. Got: {!r}'.format(filters.resource))
859
- if self._model is not None:
860
- filters.add(field='modelId', values=self._model.id)
861
- paged = entities.PagedEntities(items_repository=self,
862
- filters=filters,
863
- page_offset=filters.page,
864
- page_size=filters.page_size,
865
- client_api=self._client_api)
866
- paged.get_page()
867
- return paged
1
+ import time
2
+ from typing import List
3
+ import logging
4
+ from urllib.parse import urlencode
5
+
6
+ from .. import entities, repositories, exceptions, miscellaneous
7
+ from ..services.api_client import ApiClient
8
+
9
+ logger = logging.getLogger(name='dtlpy')
10
+
11
+ MIN_INTERVAL = 1
12
+ BACKOFF_FACTOR = 1.2
13
+ MAX_INTERVAL = 12
14
+
15
+
16
+ class Models:
17
+ """
18
+ Models Repository
19
+ """
20
+
21
+ def __init__(self,
22
+ client_api: ApiClient,
23
+ package: entities.Package = None,
24
+ project: entities.Project = None,
25
+ project_id: str = None):
26
+ self._client_api = client_api
27
+ self._project = project
28
+ self._package = package
29
+ self._project_id = project_id
30
+
31
+ if self._project is not None:
32
+ self._project_id = self._project.id
33
+
34
+ ############
35
+ # entities #
36
+ ############
37
+ @property
38
+ def project(self) -> entities.Project:
39
+ if self._project is None:
40
+ if self._project_id is not None:
41
+ projects = repositories.Projects(client_api=self._client_api)
42
+ self._project = projects.get(project_id=self._project_id)
43
+ if self._project is None:
44
+ if self._package is not None:
45
+ if self._package._project is not None:
46
+ self._project = self._package._project
47
+ if self._project is None:
48
+ raise exceptions.PlatformException(
49
+ error='2001',
50
+ message='Missing "project". need to set a Project entity or use project.models repository')
51
+ assert isinstance(self._project, entities.Project)
52
+ return self._project
53
+
54
+ @project.setter
55
+ def project(self, project: entities.Project):
56
+ if not isinstance(project, entities.Project):
57
+ raise ValueError('Must input a valid Project entity')
58
+ self._project = project
59
+
60
+ @property
61
+ def package(self) -> entities.Package:
62
+ if self._package is None:
63
+ raise exceptions.PlatformException(
64
+ error='2001',
65
+ message='Cannot perform action WITHOUT Package entity in {} repository.'.format(
66
+ self.__class__.__name__) +
67
+ ' Please use package.models or set a model')
68
+ assert isinstance(self._package, entities.Package)
69
+ return self._package
70
+
71
+ ###########
72
+ # methods #
73
+ ###########
74
+ def get(self, model_name=None, model_id=None) -> entities.Model:
75
+ """
76
+ Get model object
77
+ :param model_name:
78
+ :param model_id:
79
+ :return: dl.Model object
80
+ """
81
+
82
+ if model_id is not None:
83
+ success, response = self._client_api.gen_request(req_type="get",
84
+ path="/ml/models/{}".format(model_id))
85
+ if not success:
86
+ raise exceptions.PlatformException(response)
87
+ model = entities.Model.from_json(client_api=self._client_api,
88
+ _json=response.json(),
89
+ project=self._project,
90
+ package=self._package)
91
+ # verify input model name is same as the given id
92
+ if model_name is not None and model.name != model_name:
93
+ logger.warning(
94
+ "Mismatch found in models.get: model_name is different then model.name:"
95
+ " {!r} != {!r}".format(
96
+ model_name,
97
+ model.name))
98
+ elif model_name is not None:
99
+
100
+ filters = entities.Filters(
101
+ resource=entities.FiltersResource.MODEL,
102
+ field='name',
103
+ values=model_name
104
+ )
105
+
106
+ project_id = None
107
+
108
+ if self._project is not None:
109
+ project_id = self._project.id
110
+ elif self._project_id is not None:
111
+ project_id = self._project_id
112
+
113
+ if project_id is not None:
114
+ filters.add(field='projectId', values=project_id)
115
+
116
+ if self._package is not None:
117
+ filters.add(field='packageId', values=self._package.id)
118
+
119
+ models = self.list(filters=filters)
120
+
121
+ if models.items_count == 0:
122
+ raise exceptions.PlatformException(
123
+ error='404',
124
+ message='Model not found. Name: {}'.format(model_name))
125
+ elif models.items_count > 1:
126
+ raise exceptions.PlatformException(
127
+ error='400',
128
+ message='More than one Model found by the name of: {}. Try "get" by id or "list()".'.format(
129
+ model_name))
130
+ model = models.items[0]
131
+ else:
132
+ raise exceptions.PlatformException(
133
+ error='400',
134
+ message='No checked-out Model was found, must checkout or provide an identifier in inputs')
135
+
136
+ return model
137
+
138
+ def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Model]:
139
+ jobs = [None for _ in range(len(response_items))]
140
+ pool = self._client_api.thread_pools(pool_name='entity.create')
141
+
142
+ # return triggers list
143
+ for i_service, service in enumerate(response_items):
144
+ jobs[i_service] = pool.submit(entities.Model._protected_from_json,
145
+ **{'client_api': self._client_api,
146
+ '_json': service,
147
+ 'package': self._package,
148
+ 'project': self._project})
149
+
150
+ # get all results
151
+ results = [j.result() for j in jobs]
152
+ # log errors
153
+ _ = [logger.warning(r[1]) for r in results if r[0] is False]
154
+ # return good jobs
155
+ return miscellaneous.List([r[1] for r in results if r[0] is True])
156
+
157
+ def _list(self, filters: entities.Filters):
158
+ # request
159
+ success, response = self._client_api.gen_request(req_type='POST',
160
+ path='/ml/models/query',
161
+ json_req=filters.prepare())
162
+ if not success:
163
+ raise exceptions.PlatformException(response)
164
+ return response.json()
165
+
166
+ def list(self, filters: entities.Filters = None) -> entities.PagedEntities:
167
+ """
168
+ List project model
169
+
170
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
171
+ :return: Paged entity
172
+ :rtype: dtlpy.entities.paged_entities.PagedEntities
173
+ """
174
+ # default filters
175
+ if filters is None:
176
+ filters = entities.Filters(resource=entities.FiltersResource.MODEL)
177
+ if self._project is not None:
178
+ filters.add(field='projectId', values=self._project.id)
179
+ if self._package is not None:
180
+ filters.add(field='packageId', values=self._package.id)
181
+
182
+ # assert type filters
183
+ if not isinstance(filters, entities.Filters):
184
+ raise exceptions.PlatformException(error='400',
185
+ message='Unknown filters type: {!r}'.format(type(filters)))
186
+
187
+ if filters.resource != entities.FiltersResource.MODEL:
188
+ raise exceptions.PlatformException(
189
+ error='400',
190
+ message='Filters resource must to be FiltersResource.MODEL. Got: {!r}'.format(filters.resource))
191
+
192
+ paged = entities.PagedEntities(items_repository=self,
193
+ filters=filters,
194
+ page_offset=filters.page,
195
+ page_size=filters.page_size,
196
+ client_api=self._client_api)
197
+ paged.get_page()
198
+ return paged
199
+
200
+ def _set_model_filter(self,
201
+ metadata: dict,
202
+ train_filter: entities.Filters = None,
203
+ validation_filter: entities.Filters = None):
204
+ if metadata is None:
205
+ metadata = {}
206
+ if 'system' not in metadata:
207
+ metadata['system'] = {}
208
+ if 'subsets' not in metadata['system']:
209
+ metadata['system']['subsets'] = {}
210
+ if train_filter is not None:
211
+ metadata['system']['subsets']['train'] = train_filter.prepare() if isinstance(train_filter,
212
+ entities.Filters) else train_filter
213
+ if validation_filter is not None:
214
+ metadata['system']['subsets']['validation'] = validation_filter.prepare() if isinstance(validation_filter,
215
+ entities.Filters) else validation_filter
216
+ return metadata
217
+
218
+ @staticmethod
219
+ def add_subset(model: entities.Model, subset_name: str, subset_filter: entities.Filters):
220
+ """
221
+ Adds a subset for a model, specifying a subset of the model's dataset that could be used for training or
222
+ validation.
223
+
224
+ :param dtlpy.entities.Model model: the model to which the subset should be added
225
+ :param str subset_name: the name of the subset
226
+ :param dtlpy.entities.Filters subset_filter: the filtering operation that this subset performs in the dataset.
227
+
228
+ **Example**
229
+
230
+ .. code-block:: python
231
+
232
+ project.models.add_subset(model=model_entity, subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
233
+ model_entity.metadata['system']['subsets']
234
+ {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
235
+
236
+ """
237
+ if 'system' not in model.metadata:
238
+ model.metadata['system'] = dict()
239
+ if 'subsets' not in model.metadata['system']:
240
+ model.metadata['system']['subsets'] = dict()
241
+ model.metadata['system']['subsets'][subset_name] = subset_filter.prepare()
242
+ model.update(system_metadata=True)
243
+
244
+ @staticmethod
245
+ def delete_subset(model: entities.Model, subset_name: str):
246
+ """
247
+ Removes a subset from a model's metadata.
248
+
249
+ :param dtlpy.entities.Model model: the model to which the subset should be added
250
+ :param str subset_name: the name of the subset
251
+
252
+ **Example**
253
+
254
+ .. code-block:: python
255
+
256
+ project.models.add_subset(model=model_entity, subset_name='train', subset_filter=dtlpy.Filters(field='dir', values='/train'))
257
+ model_entity.metadata['system']['subsets']
258
+ {'train': <dtlpy.entities.filters.Filters object at 0x1501dfe20>}
259
+ project.models.delete_subset(model=model_entity, subset_name='train')
260
+ model_entity.metadata['system']['subsets']
261
+ {}
262
+
263
+ """
264
+ if model.metadata.get("system", dict()).get("subsets", dict()).get(subset_name) is None:
265
+ logger.error(f"Model system metadata incomplete, could not delete subset {subset_name}.")
266
+ else:
267
+ _ = model.metadata['system']['subsets'].pop(subset_name)
268
+ model.update(system_metadata=True)
269
+
270
+ def create(
271
+ self,
272
+ model_name: str,
273
+ dataset_id: str = None,
274
+ labels: list = None,
275
+ ontology_id: str = None,
276
+ description: str = None,
277
+ model_artifacts: List[entities.Artifact] = None,
278
+ project_id=None,
279
+ tags: List[str] = None,
280
+ package: entities.Package = None,
281
+ configuration: dict = None,
282
+ status: str = None,
283
+ scope: entities.EntityScopeLevel = entities.EntityScopeLevel.PROJECT,
284
+ version: str = '1.0.0',
285
+ input_type=None,
286
+ output_type=None,
287
+ train_filter: entities.Filters = None,
288
+ validation_filter: entities.Filters = None,
289
+ app: entities.App = None
290
+ ) -> entities.Model:
291
+ """
292
+ Create a Model entity
293
+
294
+ :param str model_name: name of the model
295
+ :param str dataset_id: dataset id
296
+ :param list labels: list of labels from ontology (must mach ontology id) can be a subset
297
+ :param str ontology_id: ontology to connect to the model
298
+ :param str description: description
299
+ :param model_artifacts: optional list of dl.Artifact. Can be ItemArtifact, LocaArtifact or LinkArtifact
300
+ :param str project_id: project that owns the model
301
+ :param list tags: list of string tags
302
+ :param package: optional - Package object
303
+ :param dict configuration: optional - model configuration - dict
304
+ :param str status: `str` of the optional values of
305
+ :param str scope: the scope level of the model dl.EntityScopeLevel
306
+ :param str version: version of the model
307
+ :param str input_type: the file type the model expect as input (image, video, txt, etc)
308
+ :param str output_type: dl.AnnotationType - the type of annotations the model produces (class, box segment, text, etc)
309
+ :param dtlpy.entities.filters.Filters train_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model train
310
+ :param dtlpy.entities.filters.Filters validation_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model validation
311
+ :param dtlpy.entities.App app: App entity to connect the model to
312
+ :return: Model Entity
313
+
314
+ **Example**:
315
+
316
+ .. code-block:: python
317
+
318
+ project.models.create(model_name='model_name', dataset_id='dataset_id', labels=['label1', 'label2'], train_filter={filter: {$and: [{dir: "/10K short videos"}]},page: 0,pageSize: 1000,resource: "items"}})
319
+
320
+ """
321
+
322
+ if ontology_id is not None:
323
+ # take labels from ontology
324
+ ontologies = repositories.Ontologies(client_api=self._client_api)
325
+ labels = [label.tag for label in ontologies.get(ontology_id=ontology_id).labels]
326
+
327
+ if labels is None:
328
+ # dont have to have labels. can use an empty list
329
+ labels = list()
330
+
331
+ if input_type is None:
332
+ input_type = 'image'
333
+
334
+ if output_type is None:
335
+ output_type = entities.AnnotationType.CLASSIFICATION
336
+
337
+ if package is None and self._package is None:
338
+ raise exceptions.PlatformException('Must provide a Package or create from package.models')
339
+ elif package is None:
340
+ package = self._package
341
+
342
+ # TODO need to remove the entire project id user interface - need to take it from dataset id (in BE)
343
+ if project_id is None:
344
+ if self._project is None:
345
+ raise exceptions.PlatformException('Please provide project_id')
346
+ project_id = self._project.id
347
+ else:
348
+ if project_id != self._project_id:
349
+ if (isinstance(package, entities.Package) and not package.is_global) or \
350
+ (isinstance(package, entities.Dpk) and not package.scope != 'public'):
351
+ logger.warning(
352
+ "Note! you are specified project_id {!r} which is different from repository context: {!r}".format(
353
+ project_id, self._project_id))
354
+
355
+ if model_artifacts is None:
356
+ model_artifacts = []
357
+
358
+ if not isinstance(model_artifacts, list):
359
+ raise ValueError('`model_artifacts` must be a list of dl.Artifact entities')
360
+
361
+ # create payload for request
362
+ payload = {
363
+ 'packageId': package.id,
364
+ 'name': model_name,
365
+ 'projectId': project_id,
366
+ 'datasetId': dataset_id,
367
+ 'labels': labels,
368
+ 'artifacts': [artifact.to_json(as_artifact=True) for artifact in model_artifacts],
369
+ 'scope': scope,
370
+ 'version': version,
371
+ 'inputType': input_type,
372
+ 'outputType': output_type,
373
+ }
374
+
375
+ if app is not None:
376
+ if not isinstance(package, entities.Dpk):
377
+ raise ValueError('package must be a Dpk entity')
378
+ if app.dpk_name != package.name or app.dpk_version != package.version:
379
+ raise ValueError('App and package must be the same')
380
+ component_name = None
381
+ compute_config = None
382
+ for model in package.components.models:
383
+ if model['name'] == model_name:
384
+ component_name = model['name']
385
+ compute_config = model.get('computeConfigs', None)
386
+ break
387
+ if component_name is None:
388
+ raise ValueError('Model name not found in package')
389
+ payload['app'] = {
390
+ "id": app.id,
391
+ "componentName": component_name,
392
+ "dpkName": package.name,
393
+ "dpkVersion": package.version
394
+ }
395
+ if compute_config is not None:
396
+ payload['app']['computeConfig'] = compute_config
397
+
398
+ if configuration is not None:
399
+ payload['configuration'] = configuration
400
+
401
+ if tags is not None:
402
+ payload['tags'] = tags
403
+
404
+ if description is not None:
405
+ payload['description'] = description
406
+
407
+ if status is not None:
408
+ payload['status'] = status
409
+
410
+ if train_filter or validation_filter:
411
+ metadata = self._set_model_filter(metadata={},
412
+ train_filter=train_filter,
413
+ validation_filter=validation_filter)
414
+ payload['metadata'] = metadata
415
+
416
+ # request
417
+ success, response = self._client_api.gen_request(req_type='post',
418
+ path='/ml/models',
419
+ json_req=payload)
420
+
421
+ # exception handling
422
+ if not success:
423
+ raise exceptions.PlatformException(response)
424
+
425
+ model = entities.Model.from_json(_json=response.json(),
426
+ client_api=self._client_api,
427
+ project=self._project,
428
+ package=package)
429
+
430
+ return model
431
+
432
+ def clone(self,
433
+ from_model: entities.Model,
434
+ model_name: str,
435
+ dataset: entities.Dataset = None,
436
+ configuration: dict = None,
437
+ status=None,
438
+ scope=None,
439
+ project_id: str = None,
440
+ labels: list = None,
441
+ description: str = None,
442
+ tags: list = None,
443
+ train_filter: entities.Filters = None,
444
+ validation_filter: entities.Filters = None,
445
+ wait=True,
446
+ ) -> entities.Model:
447
+ """
448
+ Clones and creates a new model out of existing one
449
+
450
+ :param from_model: existing model to clone from
451
+ :param str model_name: `str` new model name
452
+ :param str dataset: dataset object for the cloned model
453
+ :param dict configuration: `dict` (optional) if passed replaces the current configuration
454
+ :param str status: `str` (optional) set the new status
455
+ :param str scope: `str` (optional) set the new scope. default is "project"
456
+ :param str project_id: `str` specify the project id to create the new model on (if other than the source model)
457
+ :param list labels: `list` of `str` - label of the model
458
+ :param str description: `str` description of the new model
459
+ :param list tags: `list` of `str` - label of the model
460
+ :param dtlpy.entities.filters.Filters train_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model train
461
+ :param dtlpy.entities.filters.Filters validation_filter: Filters entity or a dictionary to define the items' scope in the specified dataset_id for the model validation
462
+ :param bool wait: `bool` wait for model to be ready
463
+ :return: dl.Model which is a clone version of the existing model
464
+ """
465
+ from_json = {"name": model_name,
466
+ "packageId": from_model.package_id,
467
+ "configuration": from_model.configuration,
468
+ "outputType": from_model.output_type,
469
+ "inputType": from_model.input_type}
470
+ if project_id is None:
471
+ if dataset is not None:
472
+ # take dataset project
473
+ project_id = dataset.project.id
474
+ else:
475
+ # take model's project
476
+ project_id = self.project.id
477
+ from_json['projectId'] = project_id
478
+ if dataset is not None:
479
+ if labels is None:
480
+ labels = list(dataset.labels_flat_dict.keys())
481
+ from_json['datasetId'] = dataset.id
482
+ if labels is not None:
483
+ from_json['labels'] = labels
484
+ # if there are new labels - pop the mapping from the original
485
+ _ = from_json['configuration'].pop('id_to_label_map', None)
486
+ _ = from_json['configuration'].pop('label_to_id_map', None)
487
+ if configuration is not None:
488
+ from_json['configuration'].update(configuration)
489
+ if description is not None:
490
+ from_json['description'] = description
491
+ if tags is not None:
492
+ from_json['tags'] = tags
493
+ if scope is not None:
494
+ from_json['scope'] = scope
495
+ if status is not None:
496
+ from_json['status'] = status
497
+
498
+ metadata = self._set_model_filter(metadata={},
499
+ train_filter=train_filter if train_filter is not None else from_model.metadata.get(
500
+ 'system', {}).get('subsets', {}).get('train', None),
501
+ validation_filter=validation_filter if validation_filter is not None else from_model.metadata.get(
502
+ 'system', {}).get('subsets', {}).get('validation', None))
503
+ if metadata:
504
+ from_json['metadata'] = metadata
505
+ success, response = self._client_api.gen_request(req_type='post',
506
+ path='/ml/models/{}/clone'.format(from_model.id),
507
+ json_req=from_json)
508
+ if not success:
509
+ raise exceptions.PlatformException(response)
510
+ new_model = entities.Model.from_json(_json=response.json(),
511
+ client_api=self._client_api,
512
+ project=self._project,
513
+ package=from_model._package)
514
+ if wait:
515
+ new_model = self.wait_for_model_ready(model=new_model)
516
+ return new_model
517
+
518
+ def wait_for_model_ready(self, model: entities.Model):
519
+ """
520
+ Wait for model to be ready
521
+
522
+ :param model: Model entity
523
+ """
524
+ sleep_time = MIN_INTERVAL
525
+ while model.status == entities.ModelStatus.CLONING:
526
+ model = self.get(model_id=model.id)
527
+ time.sleep(sleep_time)
528
+ sleep_time = min(sleep_time * BACKOFF_FACTOR, MAX_INTERVAL)
529
+ time.sleep(sleep_time)
530
+ return model
531
+
532
+ @property
533
+ def platform_url(self):
534
+ return self._client_api._get_resource_url("projects/{}/models".format(self.project.id))
535
+
536
+ def open_in_web(self, model=None, model_id=None):
537
+ """
538
+ Open the model in web platform
539
+
540
+ :param model: model entity
541
+ :param str model_id: model id
542
+ """
543
+ if model is not None:
544
+ model.open_in_web()
545
+ elif model_id is not None:
546
+ self._client_api._open_in_web(url=self.platform_url + '/' + str(model_id) + '/main')
547
+ else:
548
+ self._client_api._open_in_web(url=self.platform_url)
549
+
550
+ def delete(self, model: entities.Model = None, model_name=None, model_id=None):
551
+ """
552
+ Delete Model object
553
+
554
+ :param model: Model entity to delete
555
+ :param str model_name: delete by model name
556
+ :param str model_id: delete by model id
557
+ :return: True
558
+ :rtype: bool
559
+ """
560
+ # get id and name
561
+ if model_id is None:
562
+ if model is not None:
563
+ model_id = model.id
564
+ elif model_name is not None:
565
+ model = self.get(model_name=model_name)
566
+ model_id = model.id
567
+ else:
568
+ raise exceptions.PlatformException(error='400',
569
+ message='Must input at least one parameter to models.delete')
570
+
571
+ # request
572
+ success, response = self._client_api.gen_request(
573
+ req_type="delete",
574
+ path="/ml/models/{}".format(model_id)
575
+ )
576
+
577
+ # exception handling
578
+ if not success:
579
+ raise exceptions.PlatformException(response)
580
+
581
+ # return results
582
+ return True
583
+
584
+ def update(self,
585
+ model: entities.Model,
586
+ system_metadata: bool = False,
587
+ reload_services: bool = True
588
+ ) -> entities.Model:
589
+ """
590
+ Update Model changes to platform
591
+
592
+ :param model: Model entity
593
+ :param bool system_metadata: True, if you want to change metadata system
594
+ :param bool reload_services: True, if you want to update services
595
+ :return: Model entity
596
+ """
597
+ # payload
598
+ payload = model.to_json()
599
+
600
+ # url
601
+ url_path = '/ml/models/{}'.format(model.id)
602
+ query_params = {}
603
+ if system_metadata:
604
+ query_params['system'] = 'true'
605
+ if reload_services is not None:
606
+ query_params['reloadServices'] = 'true' if reload_services else 'false'
607
+
608
+ if query_params:
609
+ url_path += '?' + urlencode(query_params)
610
+
611
+ # request
612
+ success, response = self._client_api.gen_request(req_type='patch',
613
+ path=url_path,
614
+ json_req=payload)
615
+
616
+ # exception handling
617
+ if not success:
618
+ raise exceptions.PlatformException(response)
619
+
620
+ # return entity
621
+ return entities.Model.from_json(_json=response.json(),
622
+ client_api=self._client_api,
623
+ project=self._project,
624
+ package=model._package)
625
+
626
+ def train(self, model_id: str, service_config=None):
627
+ """
628
+ Train the model in the cloud. This will create a service and will run the adapter's train function as an execution
629
+
630
+ :param model_id: id of the model to train
631
+ :param dict service_config : Service object as dict. Contains the spec of the default service to create.
632
+ :return:
633
+ """
634
+ payload = dict()
635
+ if service_config is not None:
636
+ payload['serviceConfig'] = service_config
637
+ success, response = self._client_api.gen_request(req_type="post",
638
+ path=f"/ml/models/{model_id}/train",
639
+ json_req=payload)
640
+ if not success:
641
+ raise exceptions.PlatformException(response)
642
+ return entities.Execution.from_json(_json=response.json(),
643
+ client_api=self._client_api,
644
+ project=self._project)
645
+
646
+ def evaluate(self, model_id: str, dataset_id: str, filters: entities.Filters = None, service_config=None):
647
+ """
648
+ Evaluate Model, provide data to evaluate the model on You can also provide specific config for the deployed service
649
+
650
+ :param str model_id: Model id to predict
651
+ :param dict service_config : Service object as dict. Contains the spec of the default service to create.
652
+ :param str dataset_id: ID of the dataset to evaluate
653
+ :param entities.Filters filters: dl.Filter entity to run the predictions on
654
+ :return:
655
+ """
656
+
657
+ payload = {'input': {'datasetId': dataset_id}}
658
+ if service_config is not None:
659
+ payload['config'] = {'serviceConfig': service_config}
660
+ if filters is None:
661
+ filters = entities.Filters()
662
+ if filters is not None:
663
+ payload['input']['datasetQuery'] = filters.prepare()
664
+ success, response = self._client_api.gen_request(req_type="post",
665
+ path=f"/ml/models/{model_id}/evaluate",
666
+ json_req=payload)
667
+ if not success:
668
+ raise exceptions.PlatformException(response)
669
+ return entities.Execution.from_json(_json=response.json(),
670
+ client_api=self._client_api,
671
+ project=self._project)
672
+
673
+ def predict(self, model, item_ids, dataset_id=None):
674
+ """
675
+ Run model prediction with items
676
+
677
+ :param model: dl.Model entity to run the prediction.
678
+ :param item_ids: a list of item id to run the prediction.
679
+ :param dataset_id: a dataset id to run the prediction.
680
+ :return:
681
+ """
682
+ if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
683
+ # no services for model
684
+ raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
685
+ if item_ids is None and dataset_id is None:
686
+ raise ValueError("Need to provide either item_ids or dataset_id")
687
+ payload_input = {}
688
+ if item_ids is not None:
689
+ payload_input['itemIds'] = item_ids
690
+ if dataset_id is not None:
691
+ payload_input['datasetId'] = dataset_id
692
+ payload = {'input': payload_input,
693
+ 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]}}
694
+
695
+ success, response = self._client_api.gen_request(req_type="post",
696
+ path=f"/ml/models/{model.id}/predict",
697
+ json_req=payload)
698
+ if not success:
699
+ raise exceptions.PlatformException(response)
700
+ return entities.Execution.from_json(_json=response.json(),
701
+ client_api=self._client_api,
702
+ project=self._project)
703
+
704
+ def embed(self, model, item_ids=None, dataset_id=None):
705
+ """
706
+ Run model embed with items
707
+
708
+ :param model: dl.Model entity to run the prediction.
709
+ :param item_ids: a list of item id to run the embed.
710
+ :param dataset_id: a dataset id to run the embed.
711
+ :return: Execution
712
+ :rtype: dtlpy.entities.execution.Execution
713
+ """
714
+ if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
715
+ # no services for model
716
+ raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
717
+ if item_ids is None and dataset_id is None:
718
+ raise ValueError("Need to provide either item_ids or dataset_id")
719
+ payload_input = {}
720
+ if item_ids is not None:
721
+ payload_input['itemIds'] = item_ids
722
+ if dataset_id is not None:
723
+ payload_input['datasetId'] = dataset_id
724
+ payload = {'input': payload_input,
725
+ 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]}}
726
+
727
+ success, response = self._client_api.gen_request(req_type="post",
728
+ path=f"/ml/models/{model.id}/embed",
729
+ json_req=payload)
730
+ if not success:
731
+ raise exceptions.PlatformException(response)
732
+ return entities.Execution.from_json(_json=response.json(),
733
+ client_api=self._client_api,
734
+ project=self._project)
735
+
736
+ def embed_datasets(self, model, dataset_ids, attach_trigger=False):
737
+ """
738
+ Run model embed with datasets
739
+
740
+ :param model: dl.Model entity to run the prediction.
741
+ :param dataset_ids: a list of dataset id to run the embed.
742
+ :param attach_trigger: bool, if True will activate the trigger
743
+ :return:
744
+ """
745
+ if len(model.metadata['system'].get('deploy', {}).get('services', [])) == 0:
746
+ # no services for model
747
+ raise ValueError("Model doesnt have any associated services. Need to deploy before predicting")
748
+ if dataset_ids is None:
749
+ raise ValueError("Need to provide either dataset_id")
750
+ payload = {'datasetIds': dataset_ids,
751
+ 'config': {'serviceId': model.metadata['system']['deploy']['services'][0]},
752
+ 'attachTrigger': attach_trigger
753
+ }
754
+
755
+ success, response = self._client_api.gen_request(req_type="post",
756
+ path=f"/ml/models/{model.id}/embed/datasets",
757
+ json_req=payload)
758
+ if not success:
759
+ raise exceptions.PlatformException(response)
760
+ command = entities.Command.from_json(_json=response.json(),
761
+ client_api=self._client_api)
762
+ command = command.wait()
763
+ return command
764
+
765
+ def deploy(self, model_id: str, service_config=None) -> entities.Service:
766
+ """
767
+ Deploy a trained model. This will create a service that will execute predictions
768
+
769
+ :param model_id: id of the model to deploy
770
+ :param dict service_config : Service object as dict. Contains the spec of the default service to create.
771
+ :return: dl.Service: the deployed service
772
+ """
773
+ payload = dict()
774
+ if service_config is not None:
775
+ payload['serviceConfig'] = service_config if not service_config.get("serviceConfig") else service_config.get("serviceConfig")
776
+ success, response = self._client_api.gen_request(req_type="post",
777
+ path=f"/ml/models/{model_id}/deploy",
778
+ json_req=payload)
779
+ if not success:
780
+ raise exceptions.PlatformException(response)
781
+
782
+ return entities.Service.from_json(_json=response.json(),
783
+ client_api=self._client_api,
784
+ project=self._project,
785
+ package=self._package)
786
+
787
+
788
+ class Metrics:
789
+ def __init__(self, client_api, model=None, model_id=None):
790
+ self._client_api = client_api
791
+ self._model_id = model_id
792
+ self._model = model
793
+
794
+ @property
795
+ def model(self):
796
+ return self._model
797
+
798
+ def create(self, samples, dataset_id) -> bool:
799
+ """
800
+ Add Samples for model analytics and metrics
801
+
802
+ :param samples: list of dl.PlotSample - must contain: model_id, figure, legend, x, y
803
+ :param model_id: model id to save samples on
804
+ :param dataset_id:
805
+ :return: bool: True if success
806
+ """
807
+ if not isinstance(samples, list):
808
+ samples = [samples]
809
+
810
+ payload = list()
811
+ for sample in samples:
812
+ _json = sample.to_json()
813
+ _json['modelId'] = self.model.id
814
+ _json['datasetId'] = dataset_id
815
+ payload.append(_json)
816
+ # request
817
+ success, response = self._client_api.gen_request(req_type='post',
818
+ path='/ml/metrics/publish',
819
+ json_req=payload)
820
+
821
+ # exception handling
822
+ if not success:
823
+ raise exceptions.PlatformException(response)
824
+
825
+ # return entity
826
+ return True
827
+
828
+ def _list(self, filters: entities.Filters):
829
+ # request
830
+ success, response = self._client_api.gen_request(req_type='POST',
831
+ path='/ml/metrics/query',
832
+ json_req=filters.prepare())
833
+ if not success:
834
+ raise exceptions.PlatformException(response)
835
+ return response.json()
836
+
837
+ def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Model]:
838
+ jobs = [None for _ in range(len(response_items))]
839
+ pool = self._client_api.thread_pools(pool_name='entity.create')
840
+
841
+ # return triggers list
842
+ for i_service, sample in enumerate(response_items):
843
+ jobs[i_service] = pool.submit(entities.PlotSample,
844
+ **{'x': sample.get('data', dict()).get('x', None),
845
+ 'y': sample.get('data', dict()).get('y', None),
846
+ 'legend': sample.get('legend', ''),
847
+ 'figure': sample.get('figure', '')})
848
+
849
+ # get all results
850
+ results = [j.result() for j in jobs]
851
+ # return good jobs
852
+ return miscellaneous.List(results)
853
+
854
+ def list(self, filters=None) -> entities.PagedEntities:
855
+ """
856
+ List Samples for model analytics and metrics
857
+
858
+ :param filters: dl.Filter query entity
859
+ """
860
+ if filters is None:
861
+ filters = entities.Filters(resource=entities.FiltersResource.METRICS)
862
+ if not isinstance(filters, entities.Filters):
863
+ raise exceptions.PlatformException(error='400',
864
+ message='Unknown filters type: {!r}'.format(type(filters)))
865
+ if filters.resource != entities.FiltersResource.METRICS:
866
+ raise exceptions.PlatformException(
867
+ error='400',
868
+ message='Filters resource must to be FiltersResource.METRICS. Got: {!r}'.format(filters.resource))
869
+ if self._model is not None:
870
+ filters.add(field='modelId', values=self._model.id)
871
+ paged = entities.PagedEntities(items_repository=self,
872
+ filters=filters,
873
+ page_offset=filters.page,
874
+ page_size=filters.page_size,
875
+ client_api=self._client_api)
876
+ paged.get_page()
877
+ return paged