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,1125 +1,1125 @@
1
- import datetime
2
- import logging
3
- import json
4
- from typing import Union, List
5
- import warnings
6
-
7
- from .. import exceptions, miscellaneous, entities, repositories, _api_reference
8
- from ..services.api_client import ApiClient
9
-
10
- logger = logging.getLogger(name='dtlpy')
11
- URL_PATH = '/annotationtasks'
12
-
13
-
14
- class Tasks:
15
- """
16
- Tasks Repository
17
-
18
- The Tasks class allows the user to manage tasks and their properties.
19
- For more information, read in our developers' documentation about `Creating Tasks <https://developers.dataloop.ai/tutorials/task_workflows/create_a_task/chapter/>`_, and `Redistributing and Reassigning Tasks <https://developers.dataloop.ai/tutorials/task_workflows/redistributing_and_reassigning_a_task/chapter/>`_.
20
- """
21
-
22
- def __init__(self,
23
- client_api: ApiClient,
24
- project: entities.Project = None,
25
- dataset: entities.Dataset = None,
26
- project_id: str = None):
27
- self._client_api = client_api
28
- self._project = project
29
- self._dataset = dataset
30
- self._assignments = None
31
- if project_id is None:
32
- if self._project is not None:
33
- project_id = self._project.id
34
- elif self._dataset is not None:
35
- if self._dataset._project is not None:
36
- project_id = self._dataset._project.id
37
- elif isinstance(self._dataset.projects, list) and len(self._dataset.projects) > 0:
38
- project_id = self._dataset.projects[0]
39
- self._project_id = project_id
40
-
41
- ############
42
- # entities #
43
- ############
44
- @property
45
- def project(self) -> entities.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.tasks 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 dataset(self) -> entities.Dataset:
61
- if self._dataset is None:
62
- raise exceptions.PlatformException(
63
- error='2001',
64
- message='Missing "dataset". need to set a Dataset entity or use dataset.tasks repository')
65
- assert isinstance(self._dataset, entities.Dataset)
66
- return self._dataset
67
-
68
- @dataset.setter
69
- def dataset(self, dataset: entities.Dataset):
70
- if not isinstance(dataset, entities.Dataset):
71
- raise ValueError('Must input a valid Dataset entity')
72
- self._dataset = dataset
73
-
74
- @property
75
- def assignments(self) -> repositories.Assignments:
76
- if self._assignments is None:
77
- self._assignments = repositories.Assignments(client_api=self._client_api, project=self._project)
78
- assert isinstance(self._assignments, repositories.Assignments)
79
- return self._assignments
80
-
81
- def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Task]:
82
- pool = self._client_api.thread_pools(pool_name='entity.create')
83
- jobs = [None for _ in range(len(response_items))]
84
-
85
- for i_task, task in enumerate(response_items):
86
- jobs[i_task] = pool.submit(
87
- entities.Task._protected_from_json,
88
- **{
89
- 'client_api': self._client_api,
90
- '_json': task,
91
- 'project': self._project,
92
- 'dataset': self._dataset
93
- }
94
- )
95
-
96
- # get all results
97
- results = [j.result() for j in jobs]
98
- # log errors
99
- _ = [logger.warning(r[1]) for r in results if r[0] is False]
100
- # return good jobs
101
- tasks = miscellaneous.List([r[1] for r in results if r[0] is True])
102
- return tasks
103
-
104
- def _list(self, filters: entities.Filters):
105
- url = '{}/query'.format(URL_PATH)
106
- query = filters.prepare()
107
- query['context'] = dict(projectIds=[self._project_id])
108
- success, response = self._client_api.gen_request(
109
- req_type='post',
110
- path=url,
111
- json_req=filters.prepare()
112
- )
113
-
114
- if not success:
115
- raise exceptions.PlatformException(response)
116
- return response.json()
117
-
118
- def query(self, filters=None, project_ids=None):
119
- """
120
- List all tasks by filter.
121
-
122
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
123
-
124
- :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
125
- :param list project_ids: list of project ids of the required tasks
126
- :return: Paged entity - task pages generator
127
- :rtype: dtlpy.entities.paged_entities.PagedEntities
128
-
129
- **Example**:
130
-
131
- .. code-block:: python
132
-
133
- dataset.tasks.query(project_ids='project_ids')
134
- """
135
- if project_ids is None:
136
- if self._project_id is not None:
137
- project_ids = self._project_id
138
- else:
139
- raise exceptions.PlatformException('400', 'Please provide param project_ids')
140
-
141
- if not isinstance(project_ids, list):
142
- project_ids = [project_ids]
143
-
144
- if filters is None:
145
- filters = entities.Filters(resource=entities.FiltersResource.TASK)
146
- else:
147
- if not isinstance(filters, entities.Filters):
148
- raise exceptions.PlatformException('400', 'Unknown filters type')
149
- if filters.resource != entities.FiltersResource.TASK:
150
- raise exceptions.PlatformException('400', 'Filter resource must be task')
151
-
152
- if filters.context is None:
153
- filters.context = {'projectIds': project_ids}
154
-
155
- if self._project_id is not None:
156
- filters.add(field='projectId', values=self._project_id)
157
-
158
- if self._dataset is not None:
159
- filters.add(field='datasetId', values=self._dataset.id)
160
-
161
- paged = entities.PagedEntities(items_repository=self,
162
- filters=filters,
163
- page_offset=filters.page,
164
- page_size=filters.page_size,
165
- project_id=self._project_id,
166
- client_api=self._client_api)
167
- paged.get_page()
168
- return paged
169
-
170
- ###########
171
- # methods #
172
- ###########
173
- @_api_reference.add(path='/annotationtasks/query', method='post')
174
- def list(
175
- self,
176
- project_ids=None,
177
- status=None,
178
- task_name=None,
179
- pages_size=None,
180
- page_offset=None,
181
- recipe=None,
182
- creator=None,
183
- assignments=None,
184
- min_date=None,
185
- max_date=None,
186
- filters: entities.Filters = None
187
- ) -> Union[miscellaneous.List[entities.Task], entities.PagedEntities]:
188
- """
189
- List all tasks.
190
-
191
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
192
-
193
- :param project_ids: search tasks by given list of project ids
194
- :param str status: search tasks by a given task status
195
- :param str task_name: search tasks by a given task name
196
- :param int pages_size: pages size of the output generator
197
- :param int page_offset: page offset of the output generator
198
- :param dtlpy.entities.recipe.Recipe recipe: Search tasks that use a given recipe. Provide the required recipe object
199
- :param str creator: search tasks created by a given creator (user email)
200
- :param dtlpy.entities.assignment.Assignment recipe assignments: assignments object
201
- :param double min_date: search all tasks created AFTER a given date, use a milliseconds format. For example: 1661780622008
202
- :param double max_date: search all tasks created BEFORE a given date, use a milliseconds format. For example: 1661780622008
203
- :param dtlpy.entities.filters.Filters filters: dl.Filters entity to filters tasks using DQL
204
- :return: List of Task objects
205
-
206
- **Example**:
207
-
208
- .. code-block:: python
209
-
210
- dataset.tasks.list(project_ids='project_ids',pages_size=100, page_offset=0)
211
- """
212
- # url
213
- url = URL_PATH + '/query'
214
-
215
- if filters is None:
216
- filters = entities.Filters(use_defaults=False, resource=entities.FiltersResource.TASK)
217
- else:
218
- return self.query(filters=filters, project_ids=project_ids)
219
-
220
- if self._dataset is not None:
221
- filters.add(field='datasetId', values=self._dataset.id)
222
-
223
- if project_ids is not None:
224
- if not isinstance(project_ids, list):
225
- project_ids = [project_ids]
226
- elif self._project_id is not None:
227
- project_ids = [self._project_id]
228
- else:
229
- raise ('400', 'Must provide project')
230
- filters.context = {"projectIds": project_ids}
231
-
232
- if assignments is not None:
233
- if not isinstance(assignments, list):
234
- assignments = [assignments]
235
- assignments = [
236
- assignments_entity.id if isinstance(assignments_entity, entities.Assignment) else assignments_entity
237
- for assignments_entity in assignments]
238
- filters.add(field='assignmentIds', values=assignments, operator=entities.FiltersOperations.IN)
239
- if status is not None:
240
- filters.add(field='status', values=status)
241
- if task_name is not None:
242
- filters.add(field='name', values=task_name)
243
- if pages_size is not None:
244
- filters.page_size = pages_size
245
- if pages_size is None:
246
- filters.page_size = 500
247
- if page_offset is not None:
248
- filters.page = page_offset
249
- if recipe is not None:
250
- if not isinstance(recipe, list):
251
- recipe = [recipe]
252
- recipe = [recipe_entity.id if isinstance(recipe_entity, entities.Recipe) else recipe_entity
253
- for recipe_entity in recipe]
254
- filters.add(field='recipeId', values=recipe, operator=entities.FiltersOperations.IN)
255
- if creator is not None:
256
- filters.add(field='creator', values=creator)
257
- if min_date is not None:
258
- filters.add(field='dueDate', values=min_date, operator=entities.FiltersOperations.GREATER_THAN)
259
- if max_date is not None:
260
- filters.add(field='dueDate', values=max_date, operator=entities.FiltersOperations.LESS_THAN)
261
-
262
- success, response = self._client_api.gen_request(req_type='post',
263
- path=url,
264
- json_req=filters.prepare())
265
- if success:
266
- tasks = miscellaneous.List(
267
- [entities.Task.from_json(client_api=self._client_api,
268
- _json=_json, project=self._project, dataset=self._dataset)
269
- for _json in response.json()['items']])
270
- else:
271
- logger.error('Platform error getting annotation task')
272
- raise exceptions.PlatformException(response)
273
-
274
- return tasks
275
-
276
- @_api_reference.add(path='/annotationtasks/{id}', method='get')
277
- def get(self, task_name=None, task_id=None) -> entities.Task:
278
- """
279
- Get a Task object to use in your code.
280
-
281
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
282
-
283
- :param str task_name: optional - search by name
284
- :param str task_id: optional - search by id
285
- :return: task object
286
- :rtype: dtlpy.entities.task.Task
287
-
288
- **Example**:
289
-
290
- .. code-block:: python
291
-
292
- dataset.tasks.get(task_id='task_id')
293
- """
294
-
295
- # url
296
- url = URL_PATH
297
-
298
- if task_id is not None:
299
- url = '{}/{}'.format(url, task_id)
300
- success, response = self._client_api.gen_request(req_type='get',
301
- path=url)
302
- if not success:
303
- raise exceptions.PlatformException(response)
304
- else:
305
- task = entities.Task.from_json(_json=response.json(),
306
- client_api=self._client_api, project=self._project,
307
- dataset=self._dataset)
308
- # verify input task name is same as the given id
309
- if task_name is not None and task.name != task_name:
310
- logger.warning(
311
- "Mismatch found in tasks.get: task_name is different then task.name:"
312
- " {!r} != {!r}".format(
313
- task_name,
314
- task.name))
315
- elif task_name is not None:
316
- tasks = self.list(filters=entities.Filters(field='name',
317
- values=task_name,
318
- resource=entities.FiltersResource.TASK))
319
- if tasks.items_count == 0:
320
- raise exceptions.PlatformException('404', 'Annotation task not found')
321
- elif tasks.items_count > 1:
322
- raise exceptions.PlatformException('404',
323
- 'More than one Annotation task exist with the same name: {}'.format(
324
- task_name))
325
- else:
326
- task = tasks[0][0]
327
- else:
328
- raise exceptions.PlatformException('400', 'Must provide either Annotation task name or Annotation task id')
329
-
330
- assert isinstance(task, entities.Task)
331
- return task
332
-
333
- @property
334
- def platform_url(self):
335
- return self._client_api._get_resource_url("projects/{}/tasks".format(self.project.id))
336
-
337
- def open_in_web(self,
338
- task_name: str = None,
339
- task_id: str = None,
340
- task: entities.Task = None):
341
- """
342
- Open the task in the web platform.
343
-
344
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
345
-
346
- :param str task_name: the name of the task
347
- :param str task_id: the Id of the task
348
- :param dtlpy.entities.task.Task task: the task object
349
-
350
- **Example**:
351
-
352
- .. code-block:: python
353
-
354
- dataset.tasks.open_in_web(task_id='task_id')
355
- """
356
- if task_name is not None:
357
- task = self.get(task_name=task_name)
358
- if task is not None:
359
- task.open_in_web()
360
- elif task_id is not None:
361
- self._client_api._open_in_web(url=self.platform_url + '/' + str(task_id))
362
- else:
363
- self._client_api._open_in_web(url=self.platform_url)
364
-
365
- @_api_reference.add(path='/annotationtasks/{id}', method='delete')
366
- def delete(self,
367
- task: entities.Task = None,
368
- task_name: str = None,
369
- task_id: str = None,
370
- wait: bool = True):
371
- """
372
- Delete the Task.
373
-
374
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who created that task.
375
-
376
- :param dtlpy.entities.task.Task task: the task object
377
- :param str task_name: the name of the task
378
- :param str task_id: the Id of the task
379
- :param bool wait: wait until delete task finish
380
- :return: True is success
381
- :rtype: bool
382
-
383
- **Example**:
384
-
385
- .. code-block:: python
386
-
387
- dataset.tasks.delete(task_id='task_id')
388
- """
389
- if task_id is None:
390
- if task is None:
391
- if task_name is None:
392
- raise exceptions.PlatformException('400',
393
- 'Must provide either annotation task, '
394
- 'annotation task name or annotation task id')
395
- else:
396
- task = self.get(task_name=task_name)
397
- task_id = task.id
398
-
399
- url = URL_PATH
400
- url = '{}/{}'.format(url, task_id)
401
- success, response = self._client_api.gen_request(req_type='delete',
402
- path=url,
403
- json_req={'asynced': wait})
404
-
405
- if not success:
406
- raise exceptions.PlatformException(response)
407
- response_json = response.json()
408
- command = entities.Command.from_json(_json=response_json,
409
- client_api=self._client_api)
410
- if not wait:
411
- return command
412
- command = command.wait(timeout=0)
413
- if 'deleteTaskId' not in command.spec:
414
- raise exceptions.PlatformException(error='400',
415
- message="deleteTaskId key is missing in command response: {}"
416
- .format(response))
417
- return True
418
-
419
- @_api_reference.add(path='/annotationtasks/{id}', method='patch')
420
- def update(self,
421
- task: entities.Task = None,
422
- system_metadata=False
423
- ) -> entities.Task:
424
- """
425
- Update a Task.
426
-
427
- **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who created that task.
428
-
429
- :param dtlpy.entities.task.Task task: the task object
430
- :param bool system_metadata: DEPRECATED
431
- :return: Task object
432
- :rtype: dtlpy.entities.task.Task
433
-
434
- **Example**:
435
-
436
- .. code-block:: python
437
-
438
- dataset.tasks.update(task='task_entity')
439
- """
440
- url = URL_PATH
441
- url = '{}/{}'.format(url, task.id)
442
-
443
- if system_metadata:
444
- warnings.warn("Task system metadata updates are not permitted. Please store custom metadata in 'task.metadata['user']' instead.", DeprecationWarning)
445
-
446
- success, response = self._client_api.gen_request(req_type='patch',
447
- path=url,
448
- json_req=task.to_json())
449
- if success:
450
- return entities.Task.from_json(_json=response.json(),
451
- client_api=self._client_api, project=self._project, dataset=self._dataset)
452
- else:
453
- raise exceptions.PlatformException(response)
454
-
455
- def create_qa_task(self,
456
- task: entities.Task,
457
- assignee_ids,
458
- due_date=None,
459
- filters=None,
460
- items=None,
461
- query=None,
462
- workload=None,
463
- metadata=None,
464
- available_actions=None,
465
- wait=True,
466
- batch_size=None,
467
- max_batch_workload=None,
468
- allowed_assignees=None,
469
- priority=entities.TaskPriority.MEDIUM
470
- ) -> entities.Task:
471
- """
472
- Create a new QA Task.
473
-
474
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
475
-
476
- :param dtlpy.entities.task.Task task: the parent annotation task object
477
- :param list assignee_ids: list the QA task assignees (contributors) that should be working on the task. Provide a list of users' emails
478
- :param float due_date: date by which the QA task should be finished; for example, due_date=datetime.datetime(day=1, month=1, year=2029).timestamp()
479
- :param entities.Filters filters: dl.Filters entity to filter items for the task
480
- :param List[entities.Item] items: list of items (item Id or objects) to insert to the task
481
- :param dict DQL query: filter items for the task
482
- :param List[WorkloadUnit] workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
483
- :param dict metadata: metadata for the task
484
- :param list available_actions: list of available actions (statuses) that will be available for the task items; The default statuses are: "approved" and "discard"
485
- :param bool wait: wait until create task finish
486
- :param int batch_size: Pulling batch size (items), use with pulling allocation method. Restrictions - Min 3, max 100
487
- :param int max_batch_workload: Max items in assignment, use with pulling allocation method. Restrictions - Min batchSize + 2, max batchSize * 2
488
- :param list allowed_assignees: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
489
- :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
490
- :return: task object
491
- :rtype: dtlpy.entities.task.Task
492
-
493
- **Example**:
494
-
495
- .. code-block:: python
496
-
497
- dataset.tasks.create_qa_task(task= 'task_entity',
498
- due_date = datetime.datetime(day= 1, month= 1, year= 2029).timestamp(),
499
- assignee_ids =[ 'annotator1@dataloop.ai', 'annotator2@dataloop.ai'])
500
- """
501
- source_filter = entities.filters.SingleFilter(
502
- field='metadata.system.refs',
503
- values={
504
- "id": task.id,
505
- "type": "task",
506
- "metadata":
507
- {
508
- "status":
509
- {
510
- "$exists": True
511
- }
512
- }
513
- },
514
- operator=entities.FiltersOperations.MATCH
515
- )
516
-
517
- if query is not None:
518
- and_list = query.get('filter', query).get('$and', None)
519
- if and_list is not None:
520
- and_list.append(source_filter.prepare())
521
- else:
522
- if 'filter' not in query:
523
- query['filter'] = {}
524
- query['filter']['$and'] = [source_filter.prepare()]
525
-
526
- else:
527
- if filters is None and items is None:
528
- filters = entities.Filters()
529
- if filters:
530
- filters.and_filter_list.append(source_filter)
531
-
532
- return self.create(task_name='{}_qa'.format(task.name),
533
- task_type='qa',
534
- task_parent_id=task.id,
535
- assignee_ids=assignee_ids,
536
- workload=workload,
537
- task_owner=task.creator,
538
- project_id=task.project_id,
539
- recipe_id=task.recipe_id,
540
- due_date=due_date,
541
- filters=filters,
542
- items=items,
543
- query=query,
544
- metadata=metadata,
545
- available_actions=available_actions,
546
- wait=wait,
547
- batch_size=batch_size,
548
- max_batch_workload=max_batch_workload,
549
- allowed_assignees=allowed_assignees,
550
- priority=priority
551
- )
552
-
553
- def _add_task_metadata_params(self, metadata, input_value, input_name):
554
- if input_value is not None and not isinstance(input_value, int):
555
- raise exceptions.PlatformException(error='400',
556
- message="{} must be a numbers".format(input_name))
557
- if input_value is not None:
558
- metadata['system'][input_name] = input_value
559
- return metadata
560
-
561
- @_api_reference.add(path='/annotationtasks', method='post')
562
- def create(self,
563
- task_name,
564
- due_date=None,
565
- assignee_ids=None,
566
- workload=None,
567
- dataset=None,
568
- task_owner=None,
569
- task_type='annotation',
570
- task_parent_id=None,
571
- project_id=None,
572
- recipe_id=None,
573
- assignments_ids=None,
574
- metadata=None,
575
- filters=None,
576
- items=None,
577
- query=None,
578
- available_actions=None,
579
- wait=True,
580
- check_if_exist: entities.Filters = False,
581
- limit=None,
582
- batch_size=None,
583
- max_batch_workload=None,
584
- allowed_assignees=None,
585
- priority=entities.TaskPriority.MEDIUM,
586
- consensus_task_type=None,
587
- consensus_percentage=None,
588
- consensus_assignees=None,
589
- scoring=True,
590
- enforce_video_conversion=True,
591
- ) -> entities.Task:
592
- """
593
- Create a new Task (Annotation or QA).
594
-
595
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
596
-
597
- :param str task_name: the name of the task
598
- :param float due_date: date by which the task should be finished; for example, due_date=datetime.datetime(day=1, month=1, year=2029).timestamp()
599
- :param list assignee_ids: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
600
- :param List[WorkloadUnit] List[WorkloadUnit] workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
601
- :param entities.Dataset dataset: dataset object, the dataset that refer to the task
602
- :param str task_owner: task owner. Provide user email
603
- :param str task_type: task type "annotation" or "qa"
604
- :param str task_parent_id: optional if type is qa - parent annotation task id
605
- :param str project_id: the Id of the project where task will be created
606
- :param str recipe_id: recipe id for the task
607
- :param list assignments_ids: assignments ids to the task
608
- :param dict metadata: metadata for the task
609
- :param entities.Filters filters: dl.Filters entity to filter items for the task
610
- :param List[entities.Item] items: list of items (item Id or objects) to insert to the task
611
- :param dict DQL query: filter items for the task
612
- :param list available_actions: list of available actions (statuses) that will be available for the task items; The default statuses are: "completed" and "discard"
613
- :param bool wait: wait until create task finish
614
- :param entities.Filters check_if_exist: dl.Filters check if task exist according to filter
615
- :param int limit: the limit items that the task can include
616
- :param int batch_size: Pulling batch size (items), use with pulling allocation method. Restrictions - Min 3, max 100
617
- :param int max_batch_workload: max_batch_workload: Max items in assignment, use with pulling allocation method. Restrictions - Min batchSize + 2, max batchSize * 2
618
- :param list allowed_assignees: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
619
- :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
620
- :param entities.ConsensusTaskType consensus_task_type: consensus_task_type of the task options in entities.ConsensusTaskType
621
- :param int consensus_percentage: percentage of items to be copied to multiple annotators (consensus items)
622
- :param int consensus_assignees: the number of different annotators per item (number of copies per item)
623
- :param bool scoring: create a scoring app in project
624
- :param bool enforce_video_conversion: Enforce WEBM conversion on video items for frame-accurate annotations. WEBM Conversion will be executed as a project service and incurs compute costs. Service compute resources can be set according to planned workload.
625
- :return: Task object
626
- :rtype: dtlpy.entities.task.Task
627
-
628
- **Example**:
629
-
630
- .. code-block:: python
631
-
632
- dataset.tasks.create(task= 'task_entity',
633
- due_date = datetime.datetime(day= 1, month= 1, year= 2029).timestamp(),
634
- assignee_ids =[ 'annotator1@dataloop.ai', 'annotator2@dataloop.ai'],
635
- available_actions=[dl.ItemAction("discard"), dl.ItemAction("to-check")])
636
- """
637
-
638
- if dataset is None and self._dataset is None:
639
- raise exceptions.PlatformException('400', 'Please provide param dataset')
640
- if due_date is None:
641
- due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).timestamp()
642
- if query is None:
643
- if filters is None and items is None:
644
- query = entities.Filters().prepare()
645
- elif filters is None:
646
- item_list = list()
647
- if isinstance(items, entities.PagedEntities):
648
- for page in items:
649
- for item in page:
650
- item_list.append(item)
651
- elif isinstance(items, list):
652
- item_list = items
653
- elif isinstance(items, entities.Item):
654
- item_list.append(items)
655
- else:
656
- raise exceptions.PlatformException('400', 'Unknown items type')
657
- query = entities.Filters(field='id',
658
- values=[item.id for item in item_list],
659
- operator=entities.FiltersOperations.IN,
660
- use_defaults=False).prepare()
661
- else:
662
- query = filters.prepare()
663
-
664
- if dataset is None:
665
- dataset = self._dataset
666
-
667
- if task_owner is None:
668
- task_owner = self._client_api.info()['user_email']
669
-
670
- if task_type not in ['annotation', 'qa']:
671
- raise ValueError('task_type must be one of: "annotation", "qa". got: {}'.format(task_type))
672
-
673
- if recipe_id is None:
674
- recipe_id = dataset.get_recipe_ids()[0]
675
-
676
- if project_id is None:
677
- if self._project_id is not None:
678
- project_id = self._project_id
679
- else:
680
- raise exceptions.PlatformException('400', 'Must provide a project id')
681
-
682
- if workload is None and assignee_ids is not None:
683
- workload = entities.Workload.generate(assignee_ids=assignee_ids)
684
-
685
- if assignments_ids is None:
686
- assignments_ids = list()
687
-
688
- payload = {'name': task_name,
689
- 'query': "{}".format(json.dumps(query).replace("'", '"')),
690
- 'taskOwner': task_owner,
691
- 'spec': {'type': task_type},
692
- 'datasetId': dataset.id,
693
- 'projectId': project_id,
694
- 'assignmentIds': assignments_ids,
695
- 'recipeId': recipe_id,
696
- 'dueDate': due_date * 1000,
697
- 'asynced': wait,
698
- 'priority': priority
699
- }
700
-
701
- if check_if_exist:
702
- if check_if_exist.resource != entities.FiltersResource.TASK:
703
- raise exceptions.PlatformException(
704
- '407', 'Filter resource for check_if_exist param must be {}, got {}'.format(
705
- entities.FiltersResource.TASK, check_if_exist.resource
706
- )
707
- )
708
- payload['checkIfExist'] = {'query': check_if_exist.prepare()}
709
-
710
- if workload:
711
- payload['workload'] = workload.to_json()
712
-
713
- if limit:
714
- payload['limit'] = limit
715
-
716
- if available_actions is not None:
717
- payload['availableActions'] = [action.to_json() for action in available_actions]
718
-
719
- if task_parent_id is not None:
720
- payload['spec']['parentTaskId'] = task_parent_id
721
-
722
- if not enforce_video_conversion:
723
- payload['disableWebm'] = not enforce_video_conversion
724
-
725
- is_pulling = any([batch_size, max_batch_workload])
726
- is_consensus = any([consensus_percentage, consensus_assignees, consensus_task_type])
727
- if is_pulling and is_consensus:
728
- raise exceptions.PlatformException(error='400',
729
- message="Consensus can not work as a pulling task")
730
- if any([is_pulling, is_consensus]):
731
- if metadata is None:
732
- metadata = {}
733
- if 'system' not in metadata:
734
- metadata['system'] = {}
735
- if allowed_assignees is not None or assignee_ids is not None:
736
- metadata['system']['allowedAssignees'] = allowed_assignees if allowed_assignees else assignee_ids
737
- if consensus_task_type is not None:
738
- metadata['system']['consensusTaskType'] = consensus_task_type
739
- metadata = self._add_task_metadata_params(metadata=metadata,
740
- input_value=batch_size,
741
- input_name='batchSize')
742
- metadata = self._add_task_metadata_params(metadata=metadata,
743
- input_value=max_batch_workload,
744
- input_name='maxBatchWorkload')
745
- metadata = self._add_task_metadata_params(metadata=metadata,
746
- input_value=consensus_percentage,
747
- input_name='consensusPercentage')
748
- metadata = self._add_task_metadata_params(metadata=metadata,
749
- input_value=consensus_assignees,
750
- input_name='consensusAssignees')
751
- metadata = self._add_task_metadata_params(metadata=metadata,
752
- input_value=scoring,
753
- input_name='scoring')
754
-
755
- if metadata is not None:
756
- payload['metadata'] = metadata
757
-
758
- success, response = self._client_api.gen_request(req_type='post',
759
- path=URL_PATH,
760
- json_req=payload)
761
- if success:
762
-
763
- response_json = response.json()
764
- if check_if_exist is not None and 'name' in response_json:
765
- return entities.Task.from_json(
766
- _json=response.json(),
767
- client_api=self._client_api,
768
- project=self._project,
769
- dataset=self._dataset
770
- )
771
-
772
- command = entities.Command.from_json(_json=response_json,
773
- client_api=self._client_api)
774
- if not wait:
775
- return command
776
- command = command.wait(timeout=0)
777
- if 'createTaskPayload' not in command.spec:
778
- raise exceptions.PlatformException(error='400',
779
- message="createTaskPayload key is missing in command response: {}"
780
- .format(response))
781
- task = self.get(task_id=command.spec['createdTaskId'])
782
- else:
783
- raise exceptions.PlatformException(response)
784
-
785
- assert isinstance(task, entities.Task)
786
- return task
787
-
788
- def __item_operations(self, dataset: entities.Dataset, op, task=None, task_id=None, filters=None, items=None):
789
-
790
- if task is None and task_id is None:
791
- raise exceptions.PlatformException('400', 'Must provide either task or task id')
792
- elif task_id is None:
793
- task_id = task.id
794
-
795
- try:
796
- if filters is None and items is None:
797
- raise exceptions.PlatformException('400', 'Must provide either filters or items list')
798
-
799
- if filters is None:
800
- filters = entities.Filters(field='id',
801
- values=[item.id for item in items],
802
- operator=entities.FiltersOperations.IN,
803
- use_defaults=False)
804
-
805
- if op == 'delete':
806
- if task is None:
807
- task = self.get(task_id=task_id)
808
- assignment_ids = task.assignmentIds
809
- filters._ref_assignment = True
810
- filters._ref_assignment_id = assignment_ids
811
-
812
- filters._ref_task = True
813
- filters._ref_task_id = task_id
814
- filters._ref_op = op
815
- return dataset.items.update(filters=filters)
816
- finally:
817
- if filters is not None:
818
- filters._nullify_refs()
819
-
820
- @_api_reference.add(path='/annotationtasks/{id}/addToTask', method='post')
821
- def add_items(self,
822
- task: entities.Task = None,
823
- task_id=None,
824
- filters: entities.Filters = None,
825
- items=None,
826
- assignee_ids=None,
827
- query=None,
828
- workload=None,
829
- limit=None,
830
- wait=True) -> entities.Task:
831
- """
832
- Add items to a Task.
833
-
834
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
835
-
836
- :param dtlpy.entities.task.Task task: task object
837
- :param str task_id: the Id of the task
838
- :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
839
- :param list items: list of items (item Ids or objects) to add to the task
840
- :param list assignee_ids: list to assignee who works in the task
841
- :param dict query: query to filter the items for the task
842
- :param list workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
843
- :param int limit: the limit items that task can include
844
- :param bool wait: wait until add items will to finish
845
- :return: task entity
846
- :rtype: dtlpy.entities.task.Task
847
-
848
- **Example**:
849
-
850
- .. code-block:: python
851
-
852
- dataset.tasks.add_items(task= 'task_entity',
853
- items = [items])
854
- """
855
- if filters is None and items is None and query is None:
856
- raise exceptions.PlatformException('400', 'Must provide either filters, query or items list')
857
-
858
- if task is None and task_id is None:
859
- raise exceptions.PlatformException('400', 'Must provide either task or task_id')
860
-
861
- if query is None:
862
- if filters is None:
863
- if not isinstance(items, list):
864
- items = [items]
865
- filters = entities.Filters(field='id',
866
- values=[item.id for item in items],
867
- operator=entities.FiltersOperations.IN,
868
- use_defaults=False)
869
- query = filters.prepare()
870
-
871
- if workload is None and assignee_ids is not None:
872
- workload = entities.Workload.generate(assignee_ids=assignee_ids)
873
-
874
- if task_id is None:
875
- task_id = task.id
876
-
877
- payload = {
878
- "query": "{}".format(json.dumps(query).replace("'", '"')),
879
- }
880
-
881
- if workload is not None:
882
- payload["workload"] = workload.to_json()
883
-
884
- if limit is not None:
885
- payload['limit'] = limit
886
-
887
- payload['asynced'] = wait
888
-
889
- url = '{}/{}/addToTask'.format(URL_PATH, task_id)
890
-
891
- success, response = self._client_api.gen_request(req_type='post',
892
- path=url,
893
- json_req=payload)
894
-
895
- if success:
896
- command = entities.Command.from_json(_json=response.json(),
897
- client_api=self._client_api)
898
- if not wait:
899
- return command
900
- backoff_factor = 2
901
- if command.type == 'BulkAddToTaskSetting':
902
- backoff_factor = 8
903
- command = command.wait(timeout=0, backoff_factor=backoff_factor)
904
- if task is None:
905
- task = self.get(task_id=task_id)
906
- if 'addToTaskPayload' not in command.spec:
907
- raise exceptions.PlatformException(error='400',
908
- message="addToTaskPayload key is missing in command response: {}"
909
- .format(response))
910
- else:
911
- raise exceptions.PlatformException(response)
912
-
913
- assert isinstance(task, entities.Task)
914
- return task
915
-
916
- # @_api_reference.add(path='/annotationtasks/{id}/removeFromTask', method='post')
917
- def remove_items(self,
918
- task: entities.Task = None,
919
- task_id=None,
920
- filters: entities.Filters = None,
921
- query=None,
922
- items=None,
923
- wait=True):
924
- """
925
- remove items from Task.
926
-
927
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
928
-
929
- :param dtlpy.entities.task.Task task: task object
930
- :param str task_id: the Id of the task
931
- :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
932
- :param dict query: query to filter the items use it
933
- :param list items: list of items to add to the task
934
- :param bool wait: wait until remove items finish
935
- :return: True if success and an error if failed
936
- :rtype: bool
937
-
938
- **Examples**:
939
-
940
- .. code-block:: python
941
-
942
- dataset.tasks.remove_items(task= 'task_entity',
943
- items = [items])
944
-
945
- """
946
- if filters is None and items is None and query is None:
947
- raise exceptions.PlatformException('400', 'Must provide either filters, query or items list')
948
-
949
- if task is None and task_id is None:
950
- raise exceptions.PlatformException('400', 'Must provide either task or task_id')
951
-
952
- if query is None:
953
- if filters is None:
954
- if not isinstance(items, list):
955
- items = [items]
956
- filters = entities.Filters(field='id',
957
- values=[item.id for item in items],
958
- operator=entities.FiltersOperations.IN,
959
- use_defaults=False)
960
- query = filters.prepare()
961
-
962
- if task_id is None:
963
- task_id = task.id
964
-
965
- payload = {"query": "{}".format(json.dumps(query).replace("'", '"')), 'asynced': wait}
966
-
967
- url = '{}/{}/removeFromTask'.format(URL_PATH, task_id)
968
-
969
- success, response = self._client_api.gen_request(req_type='post',
970
- path=url,
971
- json_req=payload)
972
-
973
- if success:
974
- command = entities.Command.from_json(_json=response.json(),
975
- client_api=self._client_api)
976
- if not wait:
977
- return command
978
- command = command.wait(timeout=0)
979
-
980
- if 'removeFromTaskId' not in command.spec:
981
- raise exceptions.PlatformException(error='400',
982
- message="removeFromTaskId key is missing in command response: {}"
983
- .format(response))
984
- else:
985
- raise exceptions.PlatformException(response)
986
- return True
987
-
988
- def get_items(self,
989
- task_id: str = None,
990
- task_name: str = None,
991
- dataset: entities.Dataset = None,
992
- filters: entities.Filters = None,
993
- get_consensus_items: bool = False,
994
- task: entities.Task = None
995
- ) -> entities.PagedEntities:
996
- """
997
- Get the task items to use in your code.
998
-
999
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
1000
-
1001
- If a filters param is provided, you will receive a PagedEntity output of the task items. If no filter is provided, you will receive a list of the items.
1002
-
1003
- :param str task_id: the id of the task
1004
- :param str task_name: the name of the task
1005
- :param bool get_consensus_items: get the items from the consensus assignment
1006
- :param dtlpy.entities.Task task: task object
1007
- :param dtlpy.entities.dataset.Dataset dataset: dataset object that refer to the task
1008
- :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
1009
- :return: list of the items or PagedEntity output of items
1010
- :rtype: list or dtlpy.entities.paged_entities.PagedEntities
1011
-
1012
- **Example**:
1013
-
1014
- .. code-block:: python
1015
-
1016
- dataset.tasks.get_items(task_id= 'task_id')
1017
- """
1018
- if task is None and task_id is None and task_name is None:
1019
- raise exceptions.PlatformException('400', 'Please provide either task_id or task_name')
1020
-
1021
- if task_id is None:
1022
- if task is None:
1023
- task = self.get(task_name=task_name)
1024
- task_id = task.id
1025
-
1026
- if dataset is None and self._dataset is None:
1027
- raise exceptions.PlatformException('400', 'Please provide a dataset entity')
1028
- if dataset is None:
1029
- dataset = self._dataset
1030
-
1031
- if filters is None:
1032
- filters = entities.Filters(use_defaults=False)
1033
- filters.add(field='metadata.system.refs.id', values=[task_id], operator=entities.FiltersOperations.IN)
1034
-
1035
- if not get_consensus_items:
1036
- if task is None:
1037
- task = self.get(task_id=task_id)
1038
- if task.metadata.get('system', dict()).get('consensusAssignmentId', None):
1039
- filters.add(
1040
- field='metadata.system.refs.id',
1041
- values=task.metadata['system']['consensusAssignmentId'],
1042
- operator=entities.FiltersOperations.NOT_EQUAL
1043
- )
1044
-
1045
- return dataset.items.list(filters=filters)
1046
-
1047
- def set_status(self, status: str, operation: str, task_id: str, item_ids: List[str]):
1048
- """
1049
- Update an item status within a task.
1050
-
1051
- **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
1052
-
1053
- :param str status: string the describes the status
1054
- :param str operation: the status action need 'create' or 'delete'
1055
- :param str task_id: the Id of the task
1056
- :param list item_ids: List[str] id items ids
1057
- :return: True if success
1058
- :rtype: bool
1059
-
1060
- **Example**:
1061
-
1062
- .. code-block:: python
1063
-
1064
- dataset.tasks.set_status(task_id= 'task_id', status='complete', operation='create')
1065
- """
1066
- url = '/assignments/items/tasks/{task_id}/status'.format(task_id=task_id)
1067
- payload = {
1068
- 'itemIds': item_ids,
1069
- 'statusPayload': {
1070
- 'operation': operation,
1071
- 'returnLastStatus': True,
1072
- 'status': status
1073
- }
1074
- }
1075
-
1076
- success, response = self._client_api.gen_request(
1077
- req_type='post',
1078
- path=url,
1079
- json_req=payload
1080
- )
1081
-
1082
- if not success:
1083
- raise exceptions.PlatformException(response)
1084
- if response.json() is not None:
1085
- updated_items = set(response.json().keys())
1086
- log_msg = 'Items status was updated successfully.'
1087
- if len(updated_items) != len(item_ids):
1088
- failed_items = set(item_ids).difference(updated_items)
1089
- log_msg = '{success_count} out of TOTAL items were updated. The following items failed to update: {failed_items}'.format(
1090
- success_count=len(updated_items), failed_items=failed_items)
1091
- logger.info(msg=log_msg)
1092
- return True
1093
-
1094
- def task_scores(self, task_id: str = None, page_offset: int = 0, page_size: int = 100):
1095
- """
1096
- Get all entities scores in a task.
1097
-
1098
- :param str task_id: the id of the task
1099
- :param int page_offset: the page offset
1100
- :param int page_size: the page size
1101
- :return: page of the task scores
1102
-
1103
- **Example**:
1104
-
1105
- .. code-block:: python
1106
-
1107
- dataset.tasks.task_scores(task_id= 'task_id')
1108
- """
1109
- if task_id is None:
1110
- raise exceptions.PlatformException('400', 'Please provide task_id')
1111
-
1112
- url = '/scores/tasks/{task_id}?page={page_offset}&pageSize={page_size}'.format(
1113
- task_id=task_id,
1114
- page_offset=page_offset,
1115
- page_size=page_size
1116
- )
1117
- success, response = self._client_api.gen_request(
1118
- req_type='get',
1119
- path=url
1120
- )
1121
-
1122
- if success:
1123
- return response.json()
1124
- else:
1
+ import datetime
2
+ import logging
3
+ import json
4
+ from typing import Union, List
5
+ import warnings
6
+
7
+ from .. import exceptions, miscellaneous, entities, repositories, _api_reference
8
+ from ..services.api_client import ApiClient
9
+
10
+ logger = logging.getLogger(name='dtlpy')
11
+ URL_PATH = '/annotationtasks'
12
+
13
+
14
+ class Tasks:
15
+ """
16
+ Tasks Repository
17
+
18
+ The Tasks class allows the user to manage tasks and their properties.
19
+ For more information, read in our developers' documentation about `Creating Tasks <https://developers.dataloop.ai/tutorials/task_workflows/create_a_task/chapter/>`_, and `Redistributing and Reassigning Tasks <https://developers.dataloop.ai/tutorials/task_workflows/redistributing_and_reassigning_a_task/chapter/>`_.
20
+ """
21
+
22
+ def __init__(self,
23
+ client_api: ApiClient,
24
+ project: entities.Project = None,
25
+ dataset: entities.Dataset = None,
26
+ project_id: str = None):
27
+ self._client_api = client_api
28
+ self._project = project
29
+ self._dataset = dataset
30
+ self._assignments = None
31
+ if project_id is None:
32
+ if self._project is not None:
33
+ project_id = self._project.id
34
+ elif self._dataset is not None:
35
+ if self._dataset._project is not None:
36
+ project_id = self._dataset._project.id
37
+ elif isinstance(self._dataset.projects, list) and len(self._dataset.projects) > 0:
38
+ project_id = self._dataset.projects[0]
39
+ self._project_id = project_id
40
+
41
+ ############
42
+ # entities #
43
+ ############
44
+ @property
45
+ def project(self) -> entities.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.tasks 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 dataset(self) -> entities.Dataset:
61
+ if self._dataset is None:
62
+ raise exceptions.PlatformException(
63
+ error='2001',
64
+ message='Missing "dataset". need to set a Dataset entity or use dataset.tasks repository')
65
+ assert isinstance(self._dataset, entities.Dataset)
66
+ return self._dataset
67
+
68
+ @dataset.setter
69
+ def dataset(self, dataset: entities.Dataset):
70
+ if not isinstance(dataset, entities.Dataset):
71
+ raise ValueError('Must input a valid Dataset entity')
72
+ self._dataset = dataset
73
+
74
+ @property
75
+ def assignments(self) -> repositories.Assignments:
76
+ if self._assignments is None:
77
+ self._assignments = repositories.Assignments(client_api=self._client_api, project=self._project)
78
+ assert isinstance(self._assignments, repositories.Assignments)
79
+ return self._assignments
80
+
81
+ def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Task]:
82
+ pool = self._client_api.thread_pools(pool_name='entity.create')
83
+ jobs = [None for _ in range(len(response_items))]
84
+
85
+ for i_task, task in enumerate(response_items):
86
+ jobs[i_task] = pool.submit(
87
+ entities.Task._protected_from_json,
88
+ **{
89
+ 'client_api': self._client_api,
90
+ '_json': task,
91
+ 'project': self._project,
92
+ 'dataset': self._dataset
93
+ }
94
+ )
95
+
96
+ # get all results
97
+ results = [j.result() for j in jobs]
98
+ # log errors
99
+ _ = [logger.warning(r[1]) for r in results if r[0] is False]
100
+ # return good jobs
101
+ tasks = miscellaneous.List([r[1] for r in results if r[0] is True])
102
+ return tasks
103
+
104
+ def _list(self, filters: entities.Filters):
105
+ url = '{}/query'.format(URL_PATH)
106
+ query = filters.prepare()
107
+ query['context'] = dict(projectIds=[self._project_id])
108
+ success, response = self._client_api.gen_request(
109
+ req_type='post',
110
+ path=url,
111
+ json_req=filters.prepare()
112
+ )
113
+
114
+ if not success:
115
+ raise exceptions.PlatformException(response)
116
+ return response.json()
117
+
118
+ def query(self, filters=None, project_ids=None):
119
+ """
120
+ List all tasks by filter.
121
+
122
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
123
+
124
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
125
+ :param list project_ids: list of project ids of the required tasks
126
+ :return: Paged entity - task pages generator
127
+ :rtype: dtlpy.entities.paged_entities.PagedEntities
128
+
129
+ **Example**:
130
+
131
+ .. code-block:: python
132
+
133
+ dataset.tasks.query(project_ids='project_ids')
134
+ """
135
+ if project_ids is None:
136
+ if self._project_id is not None:
137
+ project_ids = self._project_id
138
+ else:
139
+ raise exceptions.PlatformException('400', 'Please provide param project_ids')
140
+
141
+ if not isinstance(project_ids, list):
142
+ project_ids = [project_ids]
143
+
144
+ if filters is None:
145
+ filters = entities.Filters(resource=entities.FiltersResource.TASK)
146
+ else:
147
+ if not isinstance(filters, entities.Filters):
148
+ raise exceptions.PlatformException('400', 'Unknown filters type')
149
+ if filters.resource != entities.FiltersResource.TASK:
150
+ raise exceptions.PlatformException('400', 'Filter resource must be task')
151
+
152
+ if filters.context is None:
153
+ filters.context = {'projectIds': project_ids}
154
+
155
+ if self._project_id is not None:
156
+ filters.add(field='projectId', values=self._project_id)
157
+
158
+ if self._dataset is not None:
159
+ filters.add(field='datasetId', values=self._dataset.id)
160
+
161
+ paged = entities.PagedEntities(items_repository=self,
162
+ filters=filters,
163
+ page_offset=filters.page,
164
+ page_size=filters.page_size,
165
+ project_id=self._project_id,
166
+ client_api=self._client_api)
167
+ paged.get_page()
168
+ return paged
169
+
170
+ ###########
171
+ # methods #
172
+ ###########
173
+ @_api_reference.add(path='/annotationtasks/query', method='post')
174
+ def list(
175
+ self,
176
+ project_ids=None,
177
+ status=None,
178
+ task_name=None,
179
+ pages_size=None,
180
+ page_offset=None,
181
+ recipe=None,
182
+ creator=None,
183
+ assignments=None,
184
+ min_date=None,
185
+ max_date=None,
186
+ filters: entities.Filters = None
187
+ ) -> Union[miscellaneous.List[entities.Task], entities.PagedEntities]:
188
+ """
189
+ List all tasks.
190
+
191
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
192
+
193
+ :param project_ids: search tasks by given list of project ids
194
+ :param str status: search tasks by a given task status
195
+ :param str task_name: search tasks by a given task name
196
+ :param int pages_size: pages size of the output generator
197
+ :param int page_offset: page offset of the output generator
198
+ :param dtlpy.entities.recipe.Recipe recipe: Search tasks that use a given recipe. Provide the required recipe object
199
+ :param str creator: search tasks created by a given creator (user email)
200
+ :param dtlpy.entities.assignment.Assignment recipe assignments: assignments object
201
+ :param double min_date: search all tasks created AFTER a given date, use a milliseconds format. For example: 1661780622008
202
+ :param double max_date: search all tasks created BEFORE a given date, use a milliseconds format. For example: 1661780622008
203
+ :param dtlpy.entities.filters.Filters filters: dl.Filters entity to filters tasks using DQL
204
+ :return: List of Task objects
205
+
206
+ **Example**:
207
+
208
+ .. code-block:: python
209
+
210
+ dataset.tasks.list(project_ids='project_ids',pages_size=100, page_offset=0)
211
+ """
212
+ # url
213
+ url = URL_PATH + '/query'
214
+
215
+ if filters is None:
216
+ filters = entities.Filters(use_defaults=False, resource=entities.FiltersResource.TASK)
217
+ else:
218
+ return self.query(filters=filters, project_ids=project_ids)
219
+
220
+ if self._dataset is not None:
221
+ filters.add(field='datasetId', values=self._dataset.id)
222
+
223
+ if project_ids is not None:
224
+ if not isinstance(project_ids, list):
225
+ project_ids = [project_ids]
226
+ elif self._project_id is not None:
227
+ project_ids = [self._project_id]
228
+ else:
229
+ raise ('400', 'Must provide project')
230
+ filters.context = {"projectIds": project_ids}
231
+
232
+ if assignments is not None:
233
+ if not isinstance(assignments, list):
234
+ assignments = [assignments]
235
+ assignments = [
236
+ assignments_entity.id if isinstance(assignments_entity, entities.Assignment) else assignments_entity
237
+ for assignments_entity in assignments]
238
+ filters.add(field='assignmentIds', values=assignments, operator=entities.FiltersOperations.IN)
239
+ if status is not None:
240
+ filters.add(field='status', values=status)
241
+ if task_name is not None:
242
+ filters.add(field='name', values=task_name)
243
+ if pages_size is not None:
244
+ filters.page_size = pages_size
245
+ if pages_size is None:
246
+ filters.page_size = 500
247
+ if page_offset is not None:
248
+ filters.page = page_offset
249
+ if recipe is not None:
250
+ if not isinstance(recipe, list):
251
+ recipe = [recipe]
252
+ recipe = [recipe_entity.id if isinstance(recipe_entity, entities.Recipe) else recipe_entity
253
+ for recipe_entity in recipe]
254
+ filters.add(field='recipeId', values=recipe, operator=entities.FiltersOperations.IN)
255
+ if creator is not None:
256
+ filters.add(field='creator', values=creator)
257
+ if min_date is not None:
258
+ filters.add(field='dueDate', values=min_date, operator=entities.FiltersOperations.GREATER_THAN)
259
+ if max_date is not None:
260
+ filters.add(field='dueDate', values=max_date, operator=entities.FiltersOperations.LESS_THAN)
261
+
262
+ success, response = self._client_api.gen_request(req_type='post',
263
+ path=url,
264
+ json_req=filters.prepare())
265
+ if success:
266
+ tasks = miscellaneous.List(
267
+ [entities.Task.from_json(client_api=self._client_api,
268
+ _json=_json, project=self._project, dataset=self._dataset)
269
+ for _json in response.json()['items']])
270
+ else:
271
+ logger.error('Platform error getting annotation task')
272
+ raise exceptions.PlatformException(response)
273
+
274
+ return tasks
275
+
276
+ @_api_reference.add(path='/annotationtasks/{id}', method='get')
277
+ def get(self, task_name=None, task_id=None) -> entities.Task:
278
+ """
279
+ Get a Task object to use in your code.
280
+
281
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
282
+
283
+ :param str task_name: optional - search by name
284
+ :param str task_id: optional - search by id
285
+ :return: task object
286
+ :rtype: dtlpy.entities.task.Task
287
+
288
+ **Example**:
289
+
290
+ .. code-block:: python
291
+
292
+ dataset.tasks.get(task_id='task_id')
293
+ """
294
+
295
+ # url
296
+ url = URL_PATH
297
+
298
+ if task_id is not None:
299
+ url = '{}/{}'.format(url, task_id)
300
+ success, response = self._client_api.gen_request(req_type='get',
301
+ path=url)
302
+ if not success:
303
+ raise exceptions.PlatformException(response)
304
+ else:
305
+ task = entities.Task.from_json(_json=response.json(),
306
+ client_api=self._client_api, project=self._project,
307
+ dataset=self._dataset)
308
+ # verify input task name is same as the given id
309
+ if task_name is not None and task.name != task_name:
310
+ logger.warning(
311
+ "Mismatch found in tasks.get: task_name is different then task.name:"
312
+ " {!r} != {!r}".format(
313
+ task_name,
314
+ task.name))
315
+ elif task_name is not None:
316
+ tasks = self.list(filters=entities.Filters(field='name',
317
+ values=task_name,
318
+ resource=entities.FiltersResource.TASK))
319
+ if tasks.items_count == 0:
320
+ raise exceptions.PlatformException('404', 'Annotation task not found')
321
+ elif tasks.items_count > 1:
322
+ raise exceptions.PlatformException('404',
323
+ 'More than one Annotation task exist with the same name: {}'.format(
324
+ task_name))
325
+ else:
326
+ task = tasks[0][0]
327
+ else:
328
+ raise exceptions.PlatformException('400', 'Must provide either Annotation task name or Annotation task id')
329
+
330
+ assert isinstance(task, entities.Task)
331
+ return task
332
+
333
+ @property
334
+ def platform_url(self):
335
+ return self._client_api._get_resource_url("projects/{}/tasks".format(self.project.id))
336
+
337
+ def open_in_web(self,
338
+ task_name: str = None,
339
+ task_id: str = None,
340
+ task: entities.Task = None):
341
+ """
342
+ Open the task in the web platform.
343
+
344
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who has been assigned the task.
345
+
346
+ :param str task_name: the name of the task
347
+ :param str task_id: the Id of the task
348
+ :param dtlpy.entities.task.Task task: the task object
349
+
350
+ **Example**:
351
+
352
+ .. code-block:: python
353
+
354
+ dataset.tasks.open_in_web(task_id='task_id')
355
+ """
356
+ if task_name is not None:
357
+ task = self.get(task_name=task_name)
358
+ if task is not None:
359
+ task.open_in_web()
360
+ elif task_id is not None:
361
+ self._client_api._open_in_web(url=self.platform_url + '/' + str(task_id))
362
+ else:
363
+ self._client_api._open_in_web(url=self.platform_url)
364
+
365
+ @_api_reference.add(path='/annotationtasks/{id}', method='delete')
366
+ def delete(self,
367
+ task: entities.Task = None,
368
+ task_name: str = None,
369
+ task_id: str = None,
370
+ wait: bool = True):
371
+ """
372
+ Delete the Task.
373
+
374
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who created that task.
375
+
376
+ :param dtlpy.entities.task.Task task: the task object
377
+ :param str task_name: the name of the task
378
+ :param str task_id: the Id of the task
379
+ :param bool wait: wait until delete task finish
380
+ :return: True is success
381
+ :rtype: bool
382
+
383
+ **Example**:
384
+
385
+ .. code-block:: python
386
+
387
+ dataset.tasks.delete(task_id='task_id')
388
+ """
389
+ if task_id is None:
390
+ if task is None:
391
+ if task_name is None:
392
+ raise exceptions.PlatformException('400',
393
+ 'Must provide either annotation task, '
394
+ 'annotation task name or annotation task id')
395
+ else:
396
+ task = self.get(task_name=task_name)
397
+ task_id = task.id
398
+
399
+ url = URL_PATH
400
+ url = '{}/{}'.format(url, task_id)
401
+ success, response = self._client_api.gen_request(req_type='delete',
402
+ path=url,
403
+ json_req={'asynced': wait})
404
+
405
+ if not success:
406
+ raise exceptions.PlatformException(response)
407
+ response_json = response.json()
408
+ command = entities.Command.from_json(_json=response_json,
409
+ client_api=self._client_api)
410
+ if not wait:
411
+ return command
412
+ command = command.wait(timeout=0)
413
+ if 'deleteTaskId' not in command.spec:
414
+ raise exceptions.PlatformException(error='400',
415
+ message="deleteTaskId key is missing in command response: {}"
416
+ .format(response))
417
+ return True
418
+
419
+ @_api_reference.add(path='/annotationtasks/{id}', method='patch')
420
+ def update(self,
421
+ task: entities.Task = None,
422
+ system_metadata=False
423
+ ) -> entities.Task:
424
+ """
425
+ Update a Task.
426
+
427
+ **Prerequisites**: You must be in the role of an *owner* or *developer* or *annotation manager* who created that task.
428
+
429
+ :param dtlpy.entities.task.Task task: the task object
430
+ :param bool system_metadata: DEPRECATED
431
+ :return: Task object
432
+ :rtype: dtlpy.entities.task.Task
433
+
434
+ **Example**:
435
+
436
+ .. code-block:: python
437
+
438
+ dataset.tasks.update(task='task_entity')
439
+ """
440
+ url = URL_PATH
441
+ url = '{}/{}'.format(url, task.id)
442
+
443
+ if system_metadata:
444
+ warnings.warn("Task system metadata updates are not permitted. Please store custom metadata in 'task.metadata['user']' instead.", DeprecationWarning)
445
+
446
+ success, response = self._client_api.gen_request(req_type='patch',
447
+ path=url,
448
+ json_req=task.to_json())
449
+ if success:
450
+ return entities.Task.from_json(_json=response.json(),
451
+ client_api=self._client_api, project=self._project, dataset=self._dataset)
452
+ else:
453
+ raise exceptions.PlatformException(response)
454
+
455
+ def create_qa_task(self,
456
+ task: entities.Task,
457
+ assignee_ids,
458
+ due_date=None,
459
+ filters=None,
460
+ items=None,
461
+ query=None,
462
+ workload=None,
463
+ metadata=None,
464
+ available_actions=None,
465
+ wait=True,
466
+ batch_size=None,
467
+ max_batch_workload=None,
468
+ allowed_assignees=None,
469
+ priority=entities.TaskPriority.MEDIUM
470
+ ) -> entities.Task:
471
+ """
472
+ Create a new QA Task.
473
+
474
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
475
+
476
+ :param dtlpy.entities.task.Task task: the parent annotation task object
477
+ :param list assignee_ids: list the QA task assignees (contributors) that should be working on the task. Provide a list of users' emails
478
+ :param float due_date: date by which the QA task should be finished; for example, due_date=datetime.datetime(day=1, month=1, year=2029).timestamp()
479
+ :param entities.Filters filters: dl.Filters entity to filter items for the task
480
+ :param List[entities.Item] items: list of items (item Id or objects) to insert to the task
481
+ :param dict DQL query: filter items for the task
482
+ :param List[WorkloadUnit] workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
483
+ :param dict metadata: metadata for the task
484
+ :param list available_actions: list of available actions (statuses) that will be available for the task items; The default statuses are: "approved" and "discard"
485
+ :param bool wait: wait until create task finish
486
+ :param int batch_size: Pulling batch size (items), use with pulling allocation method. Restrictions - Min 3, max 100
487
+ :param int max_batch_workload: Max items in assignment, use with pulling allocation method. Restrictions - Min batchSize + 2, max batchSize * 2
488
+ :param list allowed_assignees: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
489
+ :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
490
+ :return: task object
491
+ :rtype: dtlpy.entities.task.Task
492
+
493
+ **Example**:
494
+
495
+ .. code-block:: python
496
+
497
+ dataset.tasks.create_qa_task(task= 'task_entity',
498
+ due_date = datetime.datetime(day= 1, month= 1, year= 2029).timestamp(),
499
+ assignee_ids =[ 'annotator1@dataloop.ai', 'annotator2@dataloop.ai'])
500
+ """
501
+ source_filter = entities.filters.SingleFilter(
502
+ field='metadata.system.refs',
503
+ values={
504
+ "id": task.id,
505
+ "type": "task",
506
+ "metadata":
507
+ {
508
+ "status":
509
+ {
510
+ "$exists": True
511
+ }
512
+ }
513
+ },
514
+ operator=entities.FiltersOperations.MATCH
515
+ )
516
+
517
+ if query is not None:
518
+ and_list = query.get('filter', query).get('$and', None)
519
+ if and_list is not None:
520
+ and_list.append(source_filter.prepare())
521
+ else:
522
+ if 'filter' not in query:
523
+ query['filter'] = {}
524
+ query['filter']['$and'] = [source_filter.prepare()]
525
+
526
+ else:
527
+ if filters is None and items is None:
528
+ filters = entities.Filters()
529
+ if filters:
530
+ filters.and_filter_list.append(source_filter)
531
+
532
+ return self.create(task_name='{}_qa'.format(task.name),
533
+ task_type='qa',
534
+ task_parent_id=task.id,
535
+ assignee_ids=assignee_ids,
536
+ workload=workload,
537
+ task_owner=task.creator,
538
+ project_id=task.project_id,
539
+ recipe_id=task.recipe_id,
540
+ due_date=due_date,
541
+ filters=filters,
542
+ items=items,
543
+ query=query,
544
+ metadata=metadata,
545
+ available_actions=available_actions,
546
+ wait=wait,
547
+ batch_size=batch_size,
548
+ max_batch_workload=max_batch_workload,
549
+ allowed_assignees=allowed_assignees,
550
+ priority=priority
551
+ )
552
+
553
+ def _add_task_metadata_params(self, metadata, input_value, input_name):
554
+ if input_value is not None and not isinstance(input_value, int):
555
+ raise exceptions.PlatformException(error='400',
556
+ message="{} must be a numbers".format(input_name))
557
+ if input_value is not None:
558
+ metadata['system'][input_name] = input_value
559
+ return metadata
560
+
561
+ @_api_reference.add(path='/annotationtasks', method='post')
562
+ def create(self,
563
+ task_name,
564
+ due_date=None,
565
+ assignee_ids=None,
566
+ workload=None,
567
+ dataset=None,
568
+ task_owner=None,
569
+ task_type='annotation',
570
+ task_parent_id=None,
571
+ project_id=None,
572
+ recipe_id=None,
573
+ assignments_ids=None,
574
+ metadata=None,
575
+ filters=None,
576
+ items=None,
577
+ query=None,
578
+ available_actions=None,
579
+ wait=True,
580
+ check_if_exist: entities.Filters = False,
581
+ limit=None,
582
+ batch_size=None,
583
+ max_batch_workload=None,
584
+ allowed_assignees=None,
585
+ priority=entities.TaskPriority.MEDIUM,
586
+ consensus_task_type=None,
587
+ consensus_percentage=None,
588
+ consensus_assignees=None,
589
+ scoring=True,
590
+ enforce_video_conversion=True,
591
+ ) -> entities.Task:
592
+ """
593
+ Create a new Task (Annotation or QA).
594
+
595
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
596
+
597
+ :param str task_name: the name of the task
598
+ :param float due_date: date by which the task should be finished; for example, due_date=datetime.datetime(day=1, month=1, year=2029).timestamp()
599
+ :param list assignee_ids: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
600
+ :param List[WorkloadUnit] List[WorkloadUnit] workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
601
+ :param entities.Dataset dataset: dataset object, the dataset that refer to the task
602
+ :param str task_owner: task owner. Provide user email
603
+ :param str task_type: task type "annotation" or "qa"
604
+ :param str task_parent_id: optional if type is qa - parent annotation task id
605
+ :param str project_id: the Id of the project where task will be created
606
+ :param str recipe_id: recipe id for the task
607
+ :param list assignments_ids: assignments ids to the task
608
+ :param dict metadata: metadata for the task
609
+ :param entities.Filters filters: dl.Filters entity to filter items for the task
610
+ :param List[entities.Item] items: list of items (item Id or objects) to insert to the task
611
+ :param dict DQL query: filter items for the task
612
+ :param list available_actions: list of available actions (statuses) that will be available for the task items; The default statuses are: "completed" and "discard"
613
+ :param bool wait: wait until create task finish
614
+ :param entities.Filters check_if_exist: dl.Filters check if task exist according to filter
615
+ :param int limit: the limit items that the task can include
616
+ :param int batch_size: Pulling batch size (items), use with pulling allocation method. Restrictions - Min 3, max 100
617
+ :param int max_batch_workload: max_batch_workload: Max items in assignment, use with pulling allocation method. Restrictions - Min batchSize + 2, max batchSize * 2
618
+ :param list allowed_assignees: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
619
+ :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
620
+ :param entities.ConsensusTaskType consensus_task_type: consensus_task_type of the task options in entities.ConsensusTaskType
621
+ :param int consensus_percentage: percentage of items to be copied to multiple annotators (consensus items)
622
+ :param int consensus_assignees: the number of different annotators per item (number of copies per item)
623
+ :param bool scoring: create a scoring app in project
624
+ :param bool enforce_video_conversion: Enforce WEBM conversion on video items for frame-accurate annotations. WEBM Conversion will be executed as a project service and incurs compute costs. Service compute resources can be set according to planned workload.
625
+ :return: Task object
626
+ :rtype: dtlpy.entities.task.Task
627
+
628
+ **Example**:
629
+
630
+ .. code-block:: python
631
+
632
+ dataset.tasks.create(task= 'task_entity',
633
+ due_date = datetime.datetime(day= 1, month= 1, year= 2029).timestamp(),
634
+ assignee_ids =[ 'annotator1@dataloop.ai', 'annotator2@dataloop.ai'],
635
+ available_actions=[dl.ItemAction("discard"), dl.ItemAction("to-check")])
636
+ """
637
+
638
+ if dataset is None and self._dataset is None:
639
+ raise exceptions.PlatformException('400', 'Please provide param dataset')
640
+ if due_date is None:
641
+ due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).timestamp()
642
+ if query is None:
643
+ if filters is None and items is None:
644
+ query = entities.Filters().prepare()
645
+ elif filters is None:
646
+ item_list = list()
647
+ if isinstance(items, entities.PagedEntities):
648
+ for page in items:
649
+ for item in page:
650
+ item_list.append(item)
651
+ elif isinstance(items, list):
652
+ item_list = items
653
+ elif isinstance(items, entities.Item):
654
+ item_list.append(items)
655
+ else:
656
+ raise exceptions.PlatformException('400', 'Unknown items type')
657
+ query = entities.Filters(field='id',
658
+ values=[item.id for item in item_list],
659
+ operator=entities.FiltersOperations.IN,
660
+ use_defaults=False).prepare()
661
+ else:
662
+ query = filters.prepare()
663
+
664
+ if dataset is None:
665
+ dataset = self._dataset
666
+
667
+ if task_owner is None:
668
+ task_owner = self._client_api.info()['user_email']
669
+
670
+ if task_type not in ['annotation', 'qa']:
671
+ raise ValueError('task_type must be one of: "annotation", "qa". got: {}'.format(task_type))
672
+
673
+ if recipe_id is None:
674
+ recipe_id = dataset.get_recipe_ids()[0]
675
+
676
+ if project_id is None:
677
+ if self._project_id is not None:
678
+ project_id = self._project_id
679
+ else:
680
+ raise exceptions.PlatformException('400', 'Must provide a project id')
681
+
682
+ if workload is None and assignee_ids is not None:
683
+ workload = entities.Workload.generate(assignee_ids=assignee_ids)
684
+
685
+ if assignments_ids is None:
686
+ assignments_ids = list()
687
+
688
+ payload = {'name': task_name,
689
+ 'query': "{}".format(json.dumps(query).replace("'", '"')),
690
+ 'taskOwner': task_owner,
691
+ 'spec': {'type': task_type},
692
+ 'datasetId': dataset.id,
693
+ 'projectId': project_id,
694
+ 'assignmentIds': assignments_ids,
695
+ 'recipeId': recipe_id,
696
+ 'dueDate': due_date * 1000,
697
+ 'asynced': wait,
698
+ 'priority': priority
699
+ }
700
+
701
+ if check_if_exist:
702
+ if check_if_exist.resource != entities.FiltersResource.TASK:
703
+ raise exceptions.PlatformException(
704
+ '407', 'Filter resource for check_if_exist param must be {}, got {}'.format(
705
+ entities.FiltersResource.TASK, check_if_exist.resource
706
+ )
707
+ )
708
+ payload['checkIfExist'] = {'query': check_if_exist.prepare()}
709
+
710
+ if workload:
711
+ payload['workload'] = workload.to_json()
712
+
713
+ if limit:
714
+ payload['limit'] = limit
715
+
716
+ if available_actions is not None:
717
+ payload['availableActions'] = [action.to_json() for action in available_actions]
718
+
719
+ if task_parent_id is not None:
720
+ payload['spec']['parentTaskId'] = task_parent_id
721
+
722
+ if not enforce_video_conversion:
723
+ payload['disableWebm'] = not enforce_video_conversion
724
+
725
+ is_pulling = any([batch_size, max_batch_workload])
726
+ is_consensus = any([consensus_percentage, consensus_assignees, consensus_task_type])
727
+ if is_pulling and is_consensus:
728
+ raise exceptions.PlatformException(error='400',
729
+ message="Consensus can not work as a pulling task")
730
+ if any([is_pulling, is_consensus]):
731
+ if metadata is None:
732
+ metadata = {}
733
+ if 'system' not in metadata:
734
+ metadata['system'] = {}
735
+ if allowed_assignees is not None or assignee_ids is not None:
736
+ metadata['system']['allowedAssignees'] = allowed_assignees if allowed_assignees else assignee_ids
737
+ if consensus_task_type is not None:
738
+ metadata['system']['consensusTaskType'] = consensus_task_type
739
+ metadata = self._add_task_metadata_params(metadata=metadata,
740
+ input_value=batch_size,
741
+ input_name='batchSize')
742
+ metadata = self._add_task_metadata_params(metadata=metadata,
743
+ input_value=max_batch_workload,
744
+ input_name='maxBatchWorkload')
745
+ metadata = self._add_task_metadata_params(metadata=metadata,
746
+ input_value=consensus_percentage,
747
+ input_name='consensusPercentage')
748
+ metadata = self._add_task_metadata_params(metadata=metadata,
749
+ input_value=consensus_assignees,
750
+ input_name='consensusAssignees')
751
+ metadata = self._add_task_metadata_params(metadata=metadata,
752
+ input_value=scoring,
753
+ input_name='scoring')
754
+
755
+ if metadata is not None:
756
+ payload['metadata'] = metadata
757
+
758
+ success, response = self._client_api.gen_request(req_type='post',
759
+ path=URL_PATH,
760
+ json_req=payload)
761
+ if success:
762
+
763
+ response_json = response.json()
764
+ if check_if_exist is not None and 'name' in response_json:
765
+ return entities.Task.from_json(
766
+ _json=response.json(),
767
+ client_api=self._client_api,
768
+ project=self._project,
769
+ dataset=self._dataset
770
+ )
771
+
772
+ command = entities.Command.from_json(_json=response_json,
773
+ client_api=self._client_api)
774
+ if not wait:
775
+ return command
776
+ command = command.wait(timeout=0)
777
+ if 'createTaskPayload' not in command.spec:
778
+ raise exceptions.PlatformException(error='400',
779
+ message="createTaskPayload key is missing in command response: {}"
780
+ .format(response))
781
+ task = self.get(task_id=command.spec['createdTaskId'])
782
+ else:
783
+ raise exceptions.PlatformException(response)
784
+
785
+ assert isinstance(task, entities.Task)
786
+ return task
787
+
788
+ def __item_operations(self, dataset: entities.Dataset, op, task=None, task_id=None, filters=None, items=None):
789
+
790
+ if task is None and task_id is None:
791
+ raise exceptions.PlatformException('400', 'Must provide either task or task id')
792
+ elif task_id is None:
793
+ task_id = task.id
794
+
795
+ try:
796
+ if filters is None and items is None:
797
+ raise exceptions.PlatformException('400', 'Must provide either filters or items list')
798
+
799
+ if filters is None:
800
+ filters = entities.Filters(field='id',
801
+ values=[item.id for item in items],
802
+ operator=entities.FiltersOperations.IN,
803
+ use_defaults=False)
804
+
805
+ if op == 'delete':
806
+ if task is None:
807
+ task = self.get(task_id=task_id)
808
+ assignment_ids = task.assignmentIds
809
+ filters._ref_assignment = True
810
+ filters._ref_assignment_id = assignment_ids
811
+
812
+ filters._ref_task = True
813
+ filters._ref_task_id = task_id
814
+ filters._ref_op = op
815
+ return dataset.items.update(filters=filters)
816
+ finally:
817
+ if filters is not None:
818
+ filters._nullify_refs()
819
+
820
+ @_api_reference.add(path='/annotationtasks/{id}/addToTask', method='post')
821
+ def add_items(self,
822
+ task: entities.Task = None,
823
+ task_id=None,
824
+ filters: entities.Filters = None,
825
+ items=None,
826
+ assignee_ids=None,
827
+ query=None,
828
+ workload=None,
829
+ limit=None,
830
+ wait=True) -> entities.Task:
831
+ """
832
+ Add items to a Task.
833
+
834
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
835
+
836
+ :param dtlpy.entities.task.Task task: task object
837
+ :param str task_id: the Id of the task
838
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
839
+ :param list items: list of items (item Ids or objects) to add to the task
840
+ :param list assignee_ids: list to assignee who works in the task
841
+ :param dict query: query to filter the items for the task
842
+ :param list workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees. For example: [dl.WorkloadUnit(annotator@hi.com, 80), dl.WorkloadUnit(annotator2@hi.com, 20)]
843
+ :param int limit: the limit items that task can include
844
+ :param bool wait: wait until add items will to finish
845
+ :return: task entity
846
+ :rtype: dtlpy.entities.task.Task
847
+
848
+ **Example**:
849
+
850
+ .. code-block:: python
851
+
852
+ dataset.tasks.add_items(task= 'task_entity',
853
+ items = [items])
854
+ """
855
+ if filters is None and items is None and query is None:
856
+ raise exceptions.PlatformException('400', 'Must provide either filters, query or items list')
857
+
858
+ if task is None and task_id is None:
859
+ raise exceptions.PlatformException('400', 'Must provide either task or task_id')
860
+
861
+ if query is None:
862
+ if filters is None:
863
+ if not isinstance(items, list):
864
+ items = [items]
865
+ filters = entities.Filters(field='id',
866
+ values=[item.id for item in items],
867
+ operator=entities.FiltersOperations.IN,
868
+ use_defaults=False)
869
+ query = filters.prepare()
870
+
871
+ if workload is None and assignee_ids is not None:
872
+ workload = entities.Workload.generate(assignee_ids=assignee_ids)
873
+
874
+ if task_id is None:
875
+ task_id = task.id
876
+
877
+ payload = {
878
+ "query": "{}".format(json.dumps(query).replace("'", '"')),
879
+ }
880
+
881
+ if workload is not None:
882
+ payload["workload"] = workload.to_json()
883
+
884
+ if limit is not None:
885
+ payload['limit'] = limit
886
+
887
+ payload['asynced'] = wait
888
+
889
+ url = '{}/{}/addToTask'.format(URL_PATH, task_id)
890
+
891
+ success, response = self._client_api.gen_request(req_type='post',
892
+ path=url,
893
+ json_req=payload)
894
+
895
+ if success:
896
+ command = entities.Command.from_json(_json=response.json(),
897
+ client_api=self._client_api)
898
+ if not wait:
899
+ return command
900
+ backoff_factor = 2
901
+ if command.type == 'BulkAddToTaskSetting':
902
+ backoff_factor = 8
903
+ command = command.wait(timeout=0, backoff_factor=backoff_factor)
904
+ if task is None:
905
+ task = self.get(task_id=task_id)
906
+ if 'addToTaskPayload' not in command.spec:
907
+ raise exceptions.PlatformException(error='400',
908
+ message="addToTaskPayload key is missing in command response: {}"
909
+ .format(response))
910
+ else:
911
+ raise exceptions.PlatformException(response)
912
+
913
+ assert isinstance(task, entities.Task)
914
+ return task
915
+
916
+ # @_api_reference.add(path='/annotationtasks/{id}/removeFromTask', method='post')
917
+ def remove_items(self,
918
+ task: entities.Task = None,
919
+ task_id=None,
920
+ filters: entities.Filters = None,
921
+ query=None,
922
+ items=None,
923
+ wait=True):
924
+ """
925
+ remove items from Task.
926
+
927
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
928
+
929
+ :param dtlpy.entities.task.Task task: task object
930
+ :param str task_id: the Id of the task
931
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
932
+ :param dict query: query to filter the items use it
933
+ :param list items: list of items to add to the task
934
+ :param bool wait: wait until remove items finish
935
+ :return: True if success and an error if failed
936
+ :rtype: bool
937
+
938
+ **Examples**:
939
+
940
+ .. code-block:: python
941
+
942
+ dataset.tasks.remove_items(task= 'task_entity',
943
+ items = [items])
944
+
945
+ """
946
+ if filters is None and items is None and query is None:
947
+ raise exceptions.PlatformException('400', 'Must provide either filters, query or items list')
948
+
949
+ if task is None and task_id is None:
950
+ raise exceptions.PlatformException('400', 'Must provide either task or task_id')
951
+
952
+ if query is None:
953
+ if filters is None:
954
+ if not isinstance(items, list):
955
+ items = [items]
956
+ filters = entities.Filters(field='id',
957
+ values=[item.id for item in items],
958
+ operator=entities.FiltersOperations.IN,
959
+ use_defaults=False)
960
+ query = filters.prepare()
961
+
962
+ if task_id is None:
963
+ task_id = task.id
964
+
965
+ payload = {"query": "{}".format(json.dumps(query).replace("'", '"')), 'asynced': wait}
966
+
967
+ url = '{}/{}/removeFromTask'.format(URL_PATH, task_id)
968
+
969
+ success, response = self._client_api.gen_request(req_type='post',
970
+ path=url,
971
+ json_req=payload)
972
+
973
+ if success:
974
+ command = entities.Command.from_json(_json=response.json(),
975
+ client_api=self._client_api)
976
+ if not wait:
977
+ return command
978
+ command = command.wait(timeout=0)
979
+
980
+ if 'removeFromTaskId' not in command.spec:
981
+ raise exceptions.PlatformException(error='400',
982
+ message="removeFromTaskId key is missing in command response: {}"
983
+ .format(response))
984
+ else:
985
+ raise exceptions.PlatformException(response)
986
+ return True
987
+
988
+ def get_items(self,
989
+ task_id: str = None,
990
+ task_name: str = None,
991
+ dataset: entities.Dataset = None,
992
+ filters: entities.Filters = None,
993
+ get_consensus_items: bool = False,
994
+ task: entities.Task = None
995
+ ) -> entities.PagedEntities:
996
+ """
997
+ Get the task items to use in your code.
998
+
999
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
1000
+
1001
+ If a filters param is provided, you will receive a PagedEntity output of the task items. If no filter is provided, you will receive a list of the items.
1002
+
1003
+ :param str task_id: the id of the task
1004
+ :param str task_name: the name of the task
1005
+ :param bool get_consensus_items: get the items from the consensus assignment
1006
+ :param dtlpy.entities.Task task: task object
1007
+ :param dtlpy.entities.dataset.Dataset dataset: dataset object that refer to the task
1008
+ :param dtlpy.entities.filters.Filters filters: Filters entity or a dictionary containing filters parameters
1009
+ :return: list of the items or PagedEntity output of items
1010
+ :rtype: list or dtlpy.entities.paged_entities.PagedEntities
1011
+
1012
+ **Example**:
1013
+
1014
+ .. code-block:: python
1015
+
1016
+ dataset.tasks.get_items(task_id= 'task_id')
1017
+ """
1018
+ if task is None and task_id is None and task_name is None:
1019
+ raise exceptions.PlatformException('400', 'Please provide either task_id or task_name')
1020
+
1021
+ if task_id is None:
1022
+ if task is None:
1023
+ task = self.get(task_name=task_name)
1024
+ task_id = task.id
1025
+
1026
+ if dataset is None and self._dataset is None:
1027
+ raise exceptions.PlatformException('400', 'Please provide a dataset entity')
1028
+ if dataset is None:
1029
+ dataset = self._dataset
1030
+
1031
+ if filters is None:
1032
+ filters = entities.Filters(use_defaults=False)
1033
+ filters.add(field='metadata.system.refs.id', values=[task_id], operator=entities.FiltersOperations.IN)
1034
+
1035
+ if not get_consensus_items:
1036
+ if task is None:
1037
+ task = self.get(task_id=task_id)
1038
+ if task.metadata.get('system', dict()).get('consensusAssignmentId', None):
1039
+ filters.add(
1040
+ field='metadata.system.refs.id',
1041
+ values=task.metadata['system']['consensusAssignmentId'],
1042
+ operator=entities.FiltersOperations.NOT_EQUAL
1043
+ )
1044
+
1045
+ return dataset.items.list(filters=filters)
1046
+
1047
+ def set_status(self, status: str, operation: str, task_id: str, item_ids: List[str]):
1048
+ """
1049
+ Update an item status within a task.
1050
+
1051
+ **Prerequisites**: You must be in the role of an *owner*, *developer*, or *annotation manager* who has been assigned to be *owner* of the annotation task.
1052
+
1053
+ :param str status: string the describes the status
1054
+ :param str operation: the status action need 'create' or 'delete'
1055
+ :param str task_id: the Id of the task
1056
+ :param list item_ids: List[str] id items ids
1057
+ :return: True if success
1058
+ :rtype: bool
1059
+
1060
+ **Example**:
1061
+
1062
+ .. code-block:: python
1063
+
1064
+ dataset.tasks.set_status(task_id= 'task_id', status='complete', operation='create')
1065
+ """
1066
+ url = '/assignments/items/tasks/{task_id}/status'.format(task_id=task_id)
1067
+ payload = {
1068
+ 'itemIds': item_ids,
1069
+ 'statusPayload': {
1070
+ 'operation': operation,
1071
+ 'returnLastStatus': True,
1072
+ 'status': status
1073
+ }
1074
+ }
1075
+
1076
+ success, response = self._client_api.gen_request(
1077
+ req_type='post',
1078
+ path=url,
1079
+ json_req=payload
1080
+ )
1081
+
1082
+ if not success:
1083
+ raise exceptions.PlatformException(response)
1084
+ if response.json() is not None:
1085
+ updated_items = set(response.json().keys())
1086
+ log_msg = 'Items status was updated successfully.'
1087
+ if len(updated_items) != len(item_ids):
1088
+ failed_items = set(item_ids).difference(updated_items)
1089
+ log_msg = '{success_count} out of TOTAL items were updated. The following items failed to update: {failed_items}'.format(
1090
+ success_count=len(updated_items), failed_items=failed_items)
1091
+ logger.info(msg=log_msg)
1092
+ return True
1093
+
1094
+ def task_scores(self, task_id: str = None, page_offset: int = 0, page_size: int = 100):
1095
+ """
1096
+ Get all entities scores in a task.
1097
+
1098
+ :param str task_id: the id of the task
1099
+ :param int page_offset: the page offset
1100
+ :param int page_size: the page size
1101
+ :return: page of the task scores
1102
+
1103
+ **Example**:
1104
+
1105
+ .. code-block:: python
1106
+
1107
+ dataset.tasks.task_scores(task_id= 'task_id')
1108
+ """
1109
+ if task_id is None:
1110
+ raise exceptions.PlatformException('400', 'Please provide task_id')
1111
+
1112
+ url = '/scores/tasks/{task_id}?page={page_offset}&pageSize={page_size}'.format(
1113
+ task_id=task_id,
1114
+ page_offset=page_offset,
1115
+ page_size=page_size
1116
+ )
1117
+ success, response = self._client_api.gen_request(
1118
+ req_type='get',
1119
+ path=url
1120
+ )
1121
+
1122
+ if success:
1123
+ return response.json()
1124
+ else:
1125
1125
  raise exceptions.PlatformException(response)