dtlpy 1.114.13__py3-none-any.whl → 1.114.14__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.
@@ -7,8 +7,8 @@ import warnings
7
7
  from .. import exceptions, miscellaneous, entities, repositories, _api_reference
8
8
  from ..services.api_client import ApiClient
9
9
 
10
- logger = logging.getLogger(name='dtlpy')
11
- URL_PATH = '/annotationtasks'
10
+ logger = logging.getLogger(name="dtlpy")
11
+ URL_PATH = "/annotationtasks"
12
12
 
13
13
 
14
14
  class Tasks:
@@ -19,23 +19,17 @@ class Tasks:
19
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
20
  """
21
21
 
22
- def __init__(self,
23
- client_api: ApiClient,
24
- project: entities.Project = None,
25
- dataset: entities.Dataset = None,
26
- project_id: str = None):
22
+ def __init__(
23
+ self,
24
+ client_api: ApiClient,
25
+ project: entities.Project = None,
26
+ dataset: entities.Dataset = None,
27
+ project_id: str = None,
28
+ ):
27
29
  self._client_api = client_api
28
30
  self._project = project
29
31
  self._dataset = dataset
30
32
  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
33
  self._project_id = project_id
40
34
 
41
35
  ############
@@ -43,32 +37,38 @@ class Tasks:
43
37
  ############
44
38
  @property
45
39
  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)
40
+ if self._project is None and self._project_id is None:
41
+ if self._dataset is None:
42
+ raise exceptions.PlatformException(
43
+ error="2001",
44
+ message='Missing "project". need to set a Project entity or use project.tasks repository',
45
+ )
46
+ else:
47
+ self._project = self._dataset.project
48
+ self._project_id = self._project.id
49
+ if self._project is None and self._project_id is not None:
50
+ self._project = self._client_api.projects.get(project_id=self._project_id)
51
51
  return self._project
52
52
 
53
53
  @project.setter
54
54
  def project(self, project: entities.Project):
55
55
  if not isinstance(project, entities.Project):
56
- raise ValueError('Must input a valid Project entity')
56
+ raise ValueError("Must input a valid Project entity")
57
57
  self._project = project
58
58
 
59
59
  @property
60
60
  def dataset(self) -> entities.Dataset:
61
61
  if self._dataset is None:
62
62
  raise exceptions.PlatformException(
63
- error='2001',
64
- message='Missing "dataset". need to set a Dataset entity or use dataset.tasks repository')
63
+ error="2001", message='Missing "dataset". need to set a Dataset entity or use dataset.tasks repository'
64
+ )
65
65
  assert isinstance(self._dataset, entities.Dataset)
66
66
  return self._dataset
67
67
 
68
68
  @dataset.setter
69
69
  def dataset(self, dataset: entities.Dataset):
70
70
  if not isinstance(dataset, entities.Dataset):
71
- raise ValueError('Must input a valid Dataset entity')
71
+ raise ValueError("Must input a valid Dataset entity")
72
72
  self._dataset = dataset
73
73
 
74
74
  @property
@@ -79,18 +79,13 @@ class Tasks:
79
79
  return self._assignments
80
80
 
81
81
  def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Task]:
82
- pool = self._client_api.thread_pools(pool_name='entity.create')
82
+ pool = self._client_api.thread_pools(pool_name="entity.create")
83
83
  jobs = [None for _ in range(len(response_items))]
84
84
 
85
85
  for i_task, task in enumerate(response_items):
86
86
  jobs[i_task] = pool.submit(
87
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
- }
88
+ **{"client_api": self._client_api, "_json": task, "project": self._project, "dataset": self._dataset},
94
89
  )
95
90
 
96
91
  # get all results
@@ -102,14 +97,10 @@ class Tasks:
102
97
  return tasks
103
98
 
104
99
  def _list(self, filters: entities.Filters):
105
- url = '{}/query'.format(URL_PATH)
100
+ url = "{}/query".format(URL_PATH)
106
101
  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
- )
102
+ query["context"] = dict(projectIds=[self._project_id])
103
+ success, response = self._client_api.gen_request(req_type="post", path=url, json_req=filters.prepare())
113
104
 
114
105
  if not success:
115
106
  raise exceptions.PlatformException(response)
@@ -136,7 +127,7 @@ class Tasks:
136
127
  if self._project_id is not None:
137
128
  project_ids = self._project_id
138
129
  else:
139
- raise exceptions.PlatformException('400', 'Please provide param project_ids')
130
+ raise exceptions.PlatformException("400", "Please provide param project_ids")
140
131
 
141
132
  if not isinstance(project_ids, list):
142
133
  project_ids = [project_ids]
@@ -145,45 +136,47 @@ class Tasks:
145
136
  filters = entities.Filters(resource=entities.FiltersResource.TASK)
146
137
  else:
147
138
  if not isinstance(filters, entities.Filters):
148
- raise exceptions.PlatformException('400', 'Unknown filters type')
139
+ raise exceptions.PlatformException("400", "Unknown filters type")
149
140
  if filters.resource != entities.FiltersResource.TASK:
150
- raise exceptions.PlatformException('400', 'Filter resource must be task')
141
+ raise exceptions.PlatformException("400", "Filter resource must be task")
151
142
 
152
143
  if filters.context is None:
153
- filters.context = {'projectIds': project_ids}
144
+ filters.context = {"projectIds": project_ids}
154
145
 
155
146
  if self._project_id is not None:
156
- filters.add(field='projectId', values=self._project_id)
147
+ filters.add(field="projectId", values=self._project_id)
157
148
 
158
149
  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)
150
+ filters.add(field="datasetId", values=self._dataset.id)
151
+
152
+ paged = entities.PagedEntities(
153
+ items_repository=self,
154
+ filters=filters,
155
+ page_offset=filters.page,
156
+ page_size=filters.page_size,
157
+ project_id=self._project_id,
158
+ client_api=self._client_api,
159
+ )
167
160
  paged.get_page()
168
161
  return paged
169
162
 
170
163
  ###########
171
164
  # methods #
172
165
  ###########
173
- @_api_reference.add(path='/annotationtasks/query', method='post')
166
+ @_api_reference.add(path="/annotationtasks/query", method="post")
174
167
  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
168
+ self,
169
+ project_ids=None,
170
+ status=None,
171
+ task_name=None,
172
+ pages_size=None,
173
+ page_offset=None,
174
+ recipe=None,
175
+ creator=None,
176
+ assignments=None,
177
+ min_date=None,
178
+ max_date=None,
179
+ filters: entities.Filters = None,
187
180
  ) -> Union[miscellaneous.List[entities.Task], entities.PagedEntities]:
188
181
  """
189
182
  List all tasks.
@@ -210,7 +203,7 @@ class Tasks:
210
203
  dataset.tasks.list(project_ids='project_ids',pages_size=100, page_offset=0)
211
204
  """
212
205
  # url
213
- url = URL_PATH + '/query'
206
+ url = URL_PATH + "/query"
214
207
 
215
208
  if filters is None:
216
209
  filters = entities.Filters(use_defaults=False, resource=entities.FiltersResource.TASK)
@@ -218,7 +211,7 @@ class Tasks:
218
211
  return self.query(filters=filters, project_ids=project_ids)
219
212
 
220
213
  if self._dataset is not None:
221
- filters.add(field='datasetId', values=self._dataset.id)
214
+ filters.add(field="datasetId", values=self._dataset.id)
222
215
 
223
216
  if project_ids is not None:
224
217
  if not isinstance(project_ids, list):
@@ -226,7 +219,7 @@ class Tasks:
226
219
  elif self._project_id is not None:
227
220
  project_ids = [self._project_id]
228
221
  else:
229
- raise ('400', 'Must provide project')
222
+ raise exceptions.PlatformException("400", "Must provide project")
230
223
  filters.context = {"projectIds": project_ids}
231
224
 
232
225
  if assignments is not None:
@@ -234,12 +227,13 @@ class Tasks:
234
227
  assignments = [assignments]
235
228
  assignments = [
236
229
  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)
230
+ for assignments_entity in assignments
231
+ ]
232
+ filters.add(field="assignmentIds", values=assignments, operator=entities.FiltersOperations.IN)
239
233
  if status is not None:
240
- filters.add(field='status', values=status)
234
+ filters.add(field="status", values=status)
241
235
  if task_name is not None:
242
- filters.add(field='name', values=task_name)
236
+ filters.add(field="name", values=task_name)
243
237
  if pages_size is not None:
244
238
  filters.page_size = pages_size
245
239
  if pages_size is None:
@@ -249,31 +243,35 @@ class Tasks:
249
243
  if recipe is not None:
250
244
  if not isinstance(recipe, list):
251
245
  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)
246
+ recipe = [
247
+ recipe_entity.id if isinstance(recipe_entity, entities.Recipe) else recipe_entity
248
+ for recipe_entity in recipe
249
+ ]
250
+ filters.add(field="recipeId", values=recipe, operator=entities.FiltersOperations.IN)
255
251
  if creator is not None:
256
- filters.add(field='creator', values=creator)
252
+ filters.add(field="creator", values=creator)
257
253
  if min_date is not None:
258
- filters.add(field='dueDate', values=min_date, operator=entities.FiltersOperations.GREATER_THAN)
254
+ filters.add(field="dueDate", values=min_date, operator=entities.FiltersOperations.GREATER_THAN)
259
255
  if max_date is not None:
260
- filters.add(field='dueDate', values=max_date, operator=entities.FiltersOperations.LESS_THAN)
256
+ filters.add(field="dueDate", values=max_date, operator=entities.FiltersOperations.LESS_THAN)
261
257
 
262
- success, response = self._client_api.gen_request(req_type='post',
263
- path=url,
264
- json_req=filters.prepare())
258
+ success, response = self._client_api.gen_request(req_type="post", path=url, json_req=filters.prepare())
265
259
  if success:
266
260
  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']])
261
+ [
262
+ entities.Task.from_json(
263
+ client_api=self._client_api, _json=_json, project=self._project, dataset=self._dataset
264
+ )
265
+ for _json in response.json()["items"]
266
+ ]
267
+ )
270
268
  else:
271
- logger.error('Platform error getting annotation task')
269
+ logger.error("Platform error getting annotation task")
272
270
  raise exceptions.PlatformException(response)
273
271
 
274
272
  return tasks
275
273
 
276
- @_api_reference.add(path='/annotationtasks/{id}', method='get')
274
+ @_api_reference.add(path="/annotationtasks/{id}", method="get")
277
275
  def get(self, task_name=None, task_id=None) -> entities.Task:
278
276
  """
279
277
  Get a Task object to use in your code.
@@ -296,48 +294,43 @@ class Tasks:
296
294
  url = URL_PATH
297
295
 
298
296
  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)
297
+ url = "{}/{}".format(url, task_id)
298
+ success, response = self._client_api.gen_request(req_type="get", path=url)
302
299
  if not success:
303
300
  raise exceptions.PlatformException(response)
304
301
  else:
305
- task = entities.Task.from_json(_json=response.json(),
306
- client_api=self._client_api, project=self._project,
307
- dataset=self._dataset)
302
+ task = entities.Task.from_json(
303
+ _json=response.json(), client_api=self._client_api, project=self._project, dataset=self._dataset
304
+ )
308
305
  # verify input task name is same as the given id
309
306
  if task_name is not None and task.name != task_name:
310
307
  logger.warning(
311
308
  "Mismatch found in tasks.get: task_name is different then task.name:"
312
- " {!r} != {!r}".format(
313
- task_name,
314
- task.name))
309
+ " {!r} != {!r}".format(task_name, task.name)
310
+ )
315
311
  elif task_name is not None:
316
- tasks = self.list(filters=entities.Filters(field='name',
317
- values=task_name,
318
- resource=entities.FiltersResource.TASK))
312
+ tasks = self.list(
313
+ filters=entities.Filters(field="name", values=task_name, resource=entities.FiltersResource.TASK)
314
+ )
319
315
  if tasks.items_count == 0:
320
- raise exceptions.PlatformException('404', 'Annotation task not found')
316
+ raise exceptions.PlatformException("404", "Annotation task not found")
321
317
  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))
318
+ raise exceptions.PlatformException(
319
+ "404", f"More than one Annotation task exist with the same name: {task_name}"
320
+ )
325
321
  else:
326
322
  task = tasks[0][0]
327
323
  else:
328
- raise exceptions.PlatformException('400', 'Must provide either Annotation task name or Annotation task id')
324
+ raise exceptions.PlatformException("400", "Must provide either Annotation task name or Annotation task id")
329
325
 
330
326
  assert isinstance(task, entities.Task)
331
327
  return task
332
328
 
333
329
  @property
334
330
  def platform_url(self):
335
- return self._client_api._get_resource_url("projects/{}/tasks".format(self.project.id))
331
+ return self._client_api._get_resource_url(f"projects/{self.project.id}/tasks")
336
332
 
337
- def open_in_web(self,
338
- task_name: str = None,
339
- task_id: str = None,
340
- task: entities.Task = None):
333
+ def open_in_web(self, task_name: str = None, task_id: str = None, task: entities.Task = None):
341
334
  """
342
335
  Open the task in the web platform.
343
336
 
@@ -358,16 +351,12 @@ class Tasks:
358
351
  if task is not None:
359
352
  task.open_in_web()
360
353
  elif task_id is not None:
361
- self._client_api._open_in_web(url=self.platform_url + '/' + str(task_id))
354
+ self._client_api._open_in_web(url=self.platform_url + "/" + str(task_id))
362
355
  else:
363
356
  self._client_api._open_in_web(url=self.platform_url)
364
357
 
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):
358
+ @_api_reference.add(path="/annotationtasks/{id}", method="delete")
359
+ def delete(self, task: entities.Task = None, task_name: str = None, task_id: str = None, wait: bool = True):
371
360
  """
372
361
  Delete the Task.
373
362
 
@@ -389,38 +378,32 @@ class Tasks:
389
378
  if task_id is None:
390
379
  if task is None:
391
380
  if task_name is None:
392
- raise exceptions.PlatformException('400',
393
- 'Must provide either annotation task, '
394
- 'annotation task name or annotation task id')
381
+ raise exceptions.PlatformException(
382
+ "400", "Must provide either annotation task, " "annotation task name or annotation task id"
383
+ )
395
384
  else:
396
385
  task = self.get(task_name=task_name)
397
386
  task_id = task.id
398
387
 
399
388
  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})
389
+ url = f"{url}/{task_id}"
390
+ success, response = self._client_api.gen_request(req_type="delete", path=url, json_req={"asynced": wait})
404
391
 
405
392
  if not success:
406
393
  raise exceptions.PlatformException(response)
407
394
  response_json = response.json()
408
- command = entities.Command.from_json(_json=response_json,
409
- client_api=self._client_api)
395
+ command = entities.Command.from_json(_json=response_json, client_api=self._client_api)
410
396
  if not wait:
411
397
  return command
412
398
  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))
399
+ if "deleteTaskId" not in command.spec:
400
+ raise exceptions.PlatformException(
401
+ error="400", message="deleteTaskId key is missing in command response: {}".format(response)
402
+ )
417
403
  return True
418
404
 
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:
405
+ @_api_reference.add(path="/annotationtasks/{id}", method="patch")
406
+ def update(self, task: entities.Task = None, system_metadata=False) -> entities.Task:
424
407
  """
425
408
  Update a Task.
426
409
 
@@ -438,17 +421,19 @@ class Tasks:
438
421
  dataset.tasks.update(task='task_entity')
439
422
  """
440
423
  url = URL_PATH
441
- url = '{}/{}'.format(url, task.id)
424
+ url = f"{url}/{task.id}"
442
425
 
443
426
  if system_metadata:
444
- warnings.warn("Task system metadata updates are not permitted. Please store custom metadata in 'task.metadata['user']' instead.", DeprecationWarning)
427
+ warnings.warn(
428
+ "Task system metadata updates are not permitted. Please store custom metadata in 'task.metadata['user']' instead.",
429
+ DeprecationWarning,
430
+ )
445
431
 
446
- success, response = self._client_api.gen_request(req_type='patch',
447
- path=url,
448
- json_req=task.to_json())
432
+ success, response = self._client_api.gen_request(req_type="patch", path=url, json_req=task.to_json())
449
433
  if success:
450
- return entities.Task.from_json(_json=response.json(),
451
- client_api=self._client_api, project=self._project, dataset=self._dataset)
434
+ return entities.Task.from_json(
435
+ _json=response.json(), client_api=self._client_api, project=self._project, dataset=self._dataset
436
+ )
452
437
  else:
453
438
  raise exceptions.PlatformException(response)
454
439
 
@@ -550,45 +535,450 @@ class Tasks:
550
535
  priority=priority
551
536
  )
552
537
 
538
+ def create_honeypot_task(
539
+ self,
540
+ name: str,
541
+ dataset: entities.Dataset = None,
542
+ due_date: float = None,
543
+ filters: entities.Filters = None,
544
+ owner: str = None,
545
+ recipe_id: str = None,
546
+ assignee_ids: List[str] = None,
547
+ workload=None,
548
+ available_actions=None,
549
+ priority=entities.TaskPriority.MEDIUM,
550
+ consensus_percentage=None,
551
+ consensus_assignees=None,
552
+ scoring=True,
553
+ limit=None,
554
+ wait=True,
555
+ enforce_video_conversion=True,
556
+ ) -> entities.Task:
557
+ """
558
+ Create a new Consensus Task.
559
+
560
+ **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.
561
+
562
+ :param str name: the name of the task
563
+ :param entities.Dataset dataset: dataset object, the dataset that refer to the task
564
+ :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()
565
+ :param entities.Filters filters: dl.Filters entity to filter items for the task
566
+ :param str owner: task owner. Provide user email
567
+ :param str recipe_id: recipe id for the task
568
+ :param list assignee_ids: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
569
+ :param workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees
570
+ :param list available_actions: list of available actions (statuses) that will be available for the task items
571
+ :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
572
+ :param str consensus_task_type: consensus task type - "consensus", "qualification", or "honeypot"
573
+ :param int consensus_percentage: percentage of items to be copied to multiple annotators (consensus items)
574
+ :param int consensus_assignees: the number of different annotators per item (number of copies per item)
575
+ :param bool scoring: create a scoring app in project
576
+ :param int limit: the limit items that the task can include
577
+ :param bool wait: wait until create task finish
578
+ :param bool enforce_video_conversion: Enforce WEBM conversion on video items for frame-accurate annotations
579
+ :return: Task object
580
+ :rtype: dtlpy.entities.task.Task
581
+
582
+ **Example**:
583
+
584
+ .. code-block:: python
585
+
586
+ # Create a consensus task
587
+ dataset.tasks.create_consensus_task(name='my_consensus_task',
588
+ assignee_ids=['annotator1@dataloop.ai', 'annotator2@dataloop.ai'],
589
+ consensus_percentage=66,
590
+ consensus_assignees=2)
591
+ """
592
+ return self.create_consensus_task(
593
+ name=name,
594
+ dataset=dataset,
595
+ due_date=due_date,
596
+ filters=filters,
597
+ owner=owner,
598
+ recipe_id=recipe_id,
599
+ assignee_ids=assignee_ids,
600
+ workload=workload,
601
+ available_actions=available_actions,
602
+ priority=priority,
603
+ consensus_task_type=entities.ConsensusTaskType.HONEYPOT,
604
+ consensus_percentage=consensus_percentage,
605
+ consensus_assignees=consensus_assignees,
606
+ scoring=scoring,
607
+ limit=limit,
608
+ wait=wait,
609
+ enforce_video_conversion=enforce_video_conversion,
610
+ )
611
+
612
+ def create_qualification_task(
613
+ self,
614
+ name: str,
615
+ dataset: entities.Dataset = None,
616
+ due_date: float = None,
617
+ filters: entities.Filters = None,
618
+ owner: str = None,
619
+ recipe_id: str = None,
620
+ assignee_ids: List[str] = None,
621
+ workload=None,
622
+ available_actions=None,
623
+ priority=entities.TaskPriority.MEDIUM,
624
+ consensus_percentage=None,
625
+ consensus_assignees=None,
626
+ limit=None,
627
+ wait=True,
628
+ enforce_video_conversion=True,
629
+ ) -> entities.Task:
630
+ """
631
+ Create a new Qualification Task.
632
+ """
633
+ return self.create_consensus_task(
634
+ name=name,
635
+ dataset=dataset,
636
+ due_date=due_date,
637
+ filters=filters,
638
+ owner=owner,
639
+ recipe_id=recipe_id,
640
+ assignee_ids=assignee_ids,
641
+ workload=workload,
642
+ available_actions=available_actions,
643
+ priority=priority,
644
+ consensus_task_type=entities.ConsensusTaskType.QUALIFICATION,
645
+ consensus_percentage=consensus_percentage,
646
+ consensus_assignees=consensus_assignees,
647
+ scoring=True,
648
+ limit=limit,
649
+ wait=wait,
650
+ enforce_video_conversion=enforce_video_conversion,
651
+ )
652
+
653
+ def create_consensus_task(
654
+ self,
655
+ name: str,
656
+ dataset: entities.Dataset = None,
657
+ due_date: float = None,
658
+ filters: entities.Filters = None,
659
+ owner: str = None,
660
+ recipe_id: str = None,
661
+ assignee_ids: List[str] = None,
662
+ workload=None,
663
+ available_actions=None,
664
+ priority=entities.TaskPriority.MEDIUM,
665
+ metadata=None,
666
+ consensus_task_type: entities.ConsensusTaskType = entities.ConsensusTaskType.CONSENSUS,
667
+ consensus_percentage=None,
668
+ consensus_assignees=None,
669
+ scoring=True,
670
+ limit=None,
671
+ wait=True,
672
+ enforce_video_conversion=True,
673
+ ) -> entities.Task:
674
+ """
675
+ Create a new Consensus Task.
676
+
677
+ **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.
678
+
679
+ :param str name: the name of the task
680
+ :param entities.Dataset dataset: dataset object, the dataset that refer to the task
681
+ :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()
682
+ :param entities.Filters filters: dl.Filters entity to filter items for the task
683
+ :param str owner: task owner. Provide user email
684
+ :param str recipe_id: recipe id for the task
685
+ :param list assignee_ids: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
686
+ :param workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees
687
+ :param list available_actions: list of available actions (statuses) that will be available for the task items
688
+ :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
689
+ :param dict metadata: metadata for the task
690
+ :param str consensus_task_type: consensus task type - "consensus", "qualification", or "honeypot"
691
+ :param int consensus_percentage: percentage of items to be copied to multiple annotators (consensus items)
692
+ :param int consensus_assignees: the number of different annotators per item (number of copies per item)
693
+ :param bool scoring: create a scoring app in project
694
+ :param int limit: the limit items that the task can include
695
+ :param bool wait: wait until create task finish
696
+ :param bool enforce_video_conversion: Enforce WEBM conversion on video items for frame-accurate annotations
697
+ :return: Task object
698
+ :rtype: dtlpy.entities.task.Task
699
+
700
+ **Example**:
701
+
702
+ .. code-block:: python
703
+
704
+ # Create a consensus task
705
+ dataset.tasks.create_consensus_task(name='my_consensus_task',
706
+ assignee_ids=['annotator1@dataloop.ai', 'annotator2@dataloop.ai'],
707
+ consensus_percentage=66,
708
+ consensus_assignees=2)
709
+ """
710
+
711
+ if dataset is None:
712
+ dataset = self.dataset
713
+
714
+ if due_date is None:
715
+ due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).timestamp()
716
+
717
+ if filters is None:
718
+ filters = entities.Filters()
719
+
720
+ if owner is None:
721
+ owner = self._client_api.info()["user_email"]
722
+
723
+ if recipe_id is None:
724
+ recipe_id = dataset.get_recipe_ids()[0]
725
+
726
+ if workload is None and assignee_ids is not None:
727
+ workload = entities.Workload.generate(assignee_ids=assignee_ids)
728
+
729
+ # Handle metadata for consensus tasks
730
+ if metadata is None:
731
+ metadata = {}
732
+ if "system" not in metadata:
733
+ metadata["system"] = {}
734
+ if assignee_ids is not None:
735
+ metadata["system"]["allowedAssignees"] = assignee_ids
736
+ if consensus_task_type is not None:
737
+ metadata["system"]["consensusTaskType"] = consensus_task_type
738
+ metadata = self._add_task_metadata_params(
739
+ metadata=metadata, input_value=consensus_percentage, input_name="consensusPercentage"
740
+ )
741
+ metadata = self._add_task_metadata_params(
742
+ metadata=metadata, input_value=consensus_assignees, input_name="consensusAssignees"
743
+ )
744
+ metadata = self._add_task_metadata_params(metadata=metadata, input_value=scoring, input_name="scoring")
745
+
746
+ # Create payload for consensus task
747
+ payload = {
748
+ "name": name,
749
+ "query": "{}".format(json.dumps(filters.prepare()).replace("'", '"')),
750
+ "taskOwner": owner,
751
+ "spec": {"type": "annotation"},
752
+ "datasetId": dataset.id,
753
+ "projectId": self.project.id,
754
+ "assignmentIds": [],
755
+ "recipeId": recipe_id,
756
+ "dueDate": due_date * 1000,
757
+ "asynced": wait,
758
+ "priority": priority,
759
+ "percentage": True,
760
+ }
761
+
762
+ # Add workload if provided
763
+ if workload:
764
+ payload["workload"] = workload.to_json()
765
+
766
+ # Add limit if provided
767
+ if limit:
768
+ payload["limit"] = limit
769
+
770
+ # Add available actions if provided
771
+ if available_actions is not None:
772
+ payload["availableActions"] = [action.to_json() for action in available_actions]
773
+
774
+ # Handle video conversion
775
+ if not enforce_video_conversion:
776
+ payload["disableWebm"] = not enforce_video_conversion
777
+
778
+ # Handle metadata for consensus tasks
779
+ if metadata is not None:
780
+ payload["metadata"] = metadata
781
+
782
+ return self._create_task(payload, wait=wait)
783
+
553
784
  def _add_task_metadata_params(self, metadata, input_value, input_name):
554
785
  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))
786
+ raise exceptions.PlatformException(error="400", message=f"{input_name} must be a numbers")
557
787
  if input_value is not None:
558
- metadata['system'][input_name] = input_value
788
+ metadata["system"][input_name] = input_value
559
789
  return metadata
560
790
 
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:
791
+ def create_labeling_task(
792
+ self,
793
+ name: str,
794
+ dataset: entities.Dataset = None,
795
+ due_date: float = None,
796
+ filters: entities.Filters = None,
797
+ owner: str = None,
798
+ recipe_id: str = None,
799
+ assignee_ids: List[str] = None,
800
+ workload=None,
801
+ available_actions=None,
802
+ priority=entities.TaskPriority.MEDIUM,
803
+ metadata=None,
804
+ batch_size=None,
805
+ max_batch_workload=None,
806
+ allowed_assignees=None,
807
+ limit=None,
808
+ wait=True,
809
+ enforce_video_conversion=True,
810
+ ) -> entities.Task:
811
+ """
812
+ Create a new Annotation Task (Distribution or Pulling).
813
+
814
+ **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.
815
+
816
+ :param str name: the name of the task
817
+ :param entities.Dataset dataset: dataset object, the dataset that refer to the task
818
+ :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()
819
+ :param entities.Filters filters: dl.Filters entity to filter items for the task
820
+ :param str owner: task owner. Provide user email
821
+ :param str recipe_id: recipe id for the task
822
+ :param list assignee_ids: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
823
+ :param workload: list of WorkloadUnit objects. Customize distribution (percentage) between the task assignees
824
+ :param list available_actions: list of available actions (statuses) that will be available for the task items
825
+ :param entities.TaskPriority priority: priority of the task options in entities.TaskPriority
826
+ :param dict metadata: metadata for the task
827
+ :param int batch_size: Pulling batch size (items), use with pulling allocation method. Restrictions - Min 3, max 100
828
+ :param int max_batch_workload: Max items in assignment, use with pulling allocation method. Restrictions - Min batchSize + 2, max batchSize * 2
829
+ :param list allowed_assignees: list the task assignees (contributors) that should be working on the task. Provide a list of users' emails
830
+ :param int limit: the limit items that the task can include
831
+ :param bool wait: wait until create task finish
832
+ :param bool enforce_video_conversion: Enforce WEBM conversion on video items for frame-accurate annotations
833
+ :return: Task object
834
+ :rtype: dtlpy.entities.task.Task
835
+
836
+ **Example**:
837
+
838
+ .. code-block:: python
839
+
840
+ # Create a distribution task
841
+ dataset.tasks.create_labeling(name='my_distribution_task',
842
+ assignee_ids=['annotator1@dataloop.ai', 'annotator2@dataloop.ai'])
843
+
844
+ # Create a pulling task
845
+ dataset.tasks.create_labeling(name='my_pulling_task',
846
+ assignee_ids=['annotator1@dataloop.ai', 'annotator2@dataloop.ai'],
847
+ batch_size=5,
848
+ max_batch_workload=7)
849
+ """
850
+
851
+ if dataset is None:
852
+ dataset = self.dataset
853
+
854
+ if due_date is None:
855
+ due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).timestamp()
856
+
857
+ if filters is None:
858
+ filters = entities.Filters()
859
+
860
+ if owner is None:
861
+ owner = self._client_api.info()["user_email"]
862
+
863
+ if recipe_id is None:
864
+ recipe_id = dataset.get_recipe_ids()[0]
865
+
866
+ if workload is None and assignee_ids is not None:
867
+ workload = entities.Workload.generate(assignee_ids=assignee_ids)
868
+
869
+ if metadata is None:
870
+ metadata = {}
871
+ if any([batch_size, max_batch_workload]):
872
+ if "system" not in metadata:
873
+ metadata["system"] = {}
874
+ if allowed_assignees is not None or assignee_ids is not None:
875
+ metadata["system"]["allowedAssignees"] = allowed_assignees if allowed_assignees else assignee_ids
876
+ metadata = self._add_task_metadata_params(metadata=metadata, input_value=batch_size, input_name="batchSize")
877
+ metadata = self._add_task_metadata_params(
878
+ metadata=metadata, input_value=max_batch_workload, input_name="maxBatchWorkload"
879
+ )
880
+
881
+ # Create payload for annotation task
882
+ payload = {
883
+ "name": name,
884
+ "query": "{}".format(json.dumps(filters.prepare()).replace("'", '"')),
885
+ "taskOwner": owner,
886
+ "spec": {"type": "annotation"},
887
+ "datasetId": dataset.id,
888
+ "projectId": self.project.id,
889
+ "assignmentIds": [],
890
+ "recipeId": recipe_id,
891
+ "dueDate": due_date * 1000,
892
+ "asynced": wait,
893
+ "priority": priority,
894
+ }
895
+
896
+ # Add workload if provided
897
+ if workload:
898
+ payload["workload"] = workload.to_json()
899
+
900
+ # Add limit if provided
901
+ if limit:
902
+ payload["limit"] = limit
903
+
904
+ # Add available actions if provided
905
+ if available_actions is not None:
906
+ payload["availableActions"] = [action.to_json() for action in available_actions]
907
+
908
+ # Handle video conversion
909
+ if not enforce_video_conversion:
910
+ payload["disableWebm"] = not enforce_video_conversion
911
+
912
+ # Handle metadata for pulling tasks
913
+ if metadata is not None:
914
+ payload["metadata"] = metadata
915
+
916
+ return self._create_task(payload, wait=wait)
917
+
918
+ def _create_task(self, payload: dict, wait: bool = True) -> entities.Task:
919
+ """
920
+ Private function to create a task from a prepared payload.
921
+
922
+ :param dict payload: the prepared payload for task creation
923
+ :param bool wait: whether to wait for task creation to complete
924
+ :return: created Task object
925
+ :rtype: dtlpy.entities.task.Task
926
+ """
927
+ success, response = self._client_api.gen_request(req_type="post", path=URL_PATH, json_req=payload)
928
+ if success:
929
+ response_json = response.json()
930
+ if payload.get("checkIfExist") is not None and "name" in response_json:
931
+ return entities.Task.from_json(
932
+ _json=response.json(), client_api=self._client_api, project=self._project, dataset=self._dataset
933
+ )
934
+
935
+ command = entities.Command.from_json(_json=response_json, client_api=self._client_api)
936
+ if not wait:
937
+ return command
938
+ command = command.wait(timeout=0)
939
+ if "createTaskPayload" not in command.spec:
940
+ raise exceptions.PlatformException(
941
+ error="400", message="createTaskPayload key is missing in command response: {}".format(response)
942
+ )
943
+ task = self.get(task_id=command.spec["createdTaskId"])
944
+ else:
945
+ raise exceptions.PlatformException(response)
946
+
947
+ assert isinstance(task, entities.Task)
948
+ return task
949
+
950
+ @_api_reference.add(path="/annotationtasks", method="post")
951
+ def create(
952
+ self,
953
+ task_name, # all
954
+ due_date=None, # all
955
+ assignee_ids=None, # all
956
+ workload=None,
957
+ dataset=None, # all
958
+ task_owner=None, # all
959
+ task_type="annotation", # distribution / pulling /
960
+ task_parent_id=None, # qa from task
961
+ project_id=None, # all
962
+ recipe_id=None, # all
963
+ assignments_ids=None, # Check
964
+ metadata=None, # all
965
+ filters=None, # all
966
+ items=None, # deprecate
967
+ query=None, # deprecate
968
+ available_actions=None, # all
969
+ wait=True, # all
970
+ check_if_exist: entities.Filters = False, # all
971
+ limit=None, # all
972
+ batch_size=None, # Pulling
973
+ max_batch_workload=None, # Pulling
974
+ allowed_assignees=None, # pulling - check distribution
975
+ priority=entities.TaskPriority.MEDIUM, # all
976
+ consensus_task_type=None, # qualification / honeypot / consensus
977
+ consensus_percentage=None, # qualification / honeypot / consensus
978
+ consensus_assignees=None, # qualification / honeypot / consensus Check qualifications
979
+ scoring=True, # quality task
980
+ enforce_video_conversion=True, # all
981
+ ) -> entities.Task:
592
982
  """
593
983
  Create a new Task (Annotation or QA).
594
984
 
@@ -636,7 +1026,7 @@ class Tasks:
636
1026
  """
637
1027
 
638
1028
  if dataset is None and self._dataset is None:
639
- raise exceptions.PlatformException('400', 'Please provide param dataset')
1029
+ raise exceptions.PlatformException("400", "Please provide param dataset")
640
1030
  if due_date is None:
641
1031
  due_date = (datetime.datetime.now() + datetime.timedelta(days=7)).timestamp()
642
1032
  if query is None:
@@ -653,11 +1043,13 @@ class Tasks:
653
1043
  elif isinstance(items, entities.Item):
654
1044
  item_list.append(items)
655
1045
  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()
1046
+ raise exceptions.PlatformException("400", "Unknown items type")
1047
+ query = entities.Filters(
1048
+ field="id",
1049
+ values=[item.id for item in item_list],
1050
+ operator=entities.FiltersOperations.IN,
1051
+ use_defaults=False,
1052
+ ).prepare()
661
1053
  else:
662
1054
  query = filters.prepare()
663
1055
 
@@ -665,9 +1057,9 @@ class Tasks:
665
1057
  dataset = self._dataset
666
1058
 
667
1059
  if task_owner is None:
668
- task_owner = self._client_api.info()['user_email']
1060
+ task_owner = self._client_api.info()["user_email"]
669
1061
 
670
- if task_type not in ['annotation', 'qa']:
1062
+ if task_type not in ["annotation", "qa"]:
671
1063
  raise ValueError('task_type must be one of: "annotation", "qa". got: {}'.format(task_type))
672
1064
 
673
1065
  if recipe_id is None:
@@ -677,7 +1069,7 @@ class Tasks:
677
1069
  if self._project_id is not None:
678
1070
  project_id = self._project_id
679
1071
  else:
680
- raise exceptions.PlatformException('400', 'Must provide a project id')
1072
+ raise exceptions.PlatformException("400", "Must provide a project id")
681
1073
 
682
1074
  if workload is None and assignee_ids is not None:
683
1075
  workload = entities.Workload.generate(assignee_ids=assignee_ids)
@@ -685,124 +1077,95 @@ class Tasks:
685
1077
  if assignments_ids is None:
686
1078
  assignments_ids = list()
687
1079
 
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
- }
1080
+ payload = {
1081
+ "name": task_name,
1082
+ "query": "{}".format(json.dumps(query).replace("'", '"')),
1083
+ "taskOwner": task_owner,
1084
+ "spec": {"type": task_type},
1085
+ "datasetId": dataset.id,
1086
+ "projectId": project_id,
1087
+ "assignmentIds": assignments_ids,
1088
+ "recipeId": recipe_id,
1089
+ "dueDate": due_date * 1000,
1090
+ "asynced": wait,
1091
+ "priority": priority,
1092
+ }
700
1093
 
701
1094
  if check_if_exist:
702
1095
  if check_if_exist.resource != entities.FiltersResource.TASK:
703
1096
  raise exceptions.PlatformException(
704
- '407', 'Filter resource for check_if_exist param must be {}, got {}'.format(
1097
+ "407",
1098
+ "Filter resource for check_if_exist param must be {}, got {}".format(
705
1099
  entities.FiltersResource.TASK, check_if_exist.resource
706
- )
1100
+ ),
707
1101
  )
708
- payload['checkIfExist'] = {'query': check_if_exist.prepare()}
1102
+ payload["checkIfExist"] = {"query": check_if_exist.prepare()}
709
1103
 
710
1104
  if workload:
711
- payload['workload'] = workload.to_json()
1105
+ payload["workload"] = workload.to_json()
712
1106
 
713
1107
  if limit:
714
- payload['limit'] = limit
1108
+ payload["limit"] = limit
715
1109
 
716
1110
  if available_actions is not None:
717
- payload['availableActions'] = [action.to_json() for action in available_actions]
1111
+ payload["availableActions"] = [action.to_json() for action in available_actions]
718
1112
 
719
1113
  if task_parent_id is not None:
720
- payload['spec']['parentTaskId'] = task_parent_id
1114
+ payload["spec"]["parentTaskId"] = task_parent_id
721
1115
 
722
1116
  if not enforce_video_conversion:
723
- payload['disableWebm'] = not enforce_video_conversion
1117
+ payload["disableWebm"] = not enforce_video_conversion
724
1118
 
725
1119
  is_pulling = any([batch_size, max_batch_workload])
726
1120
  is_consensus = any([consensus_percentage, consensus_assignees, consensus_task_type])
727
1121
  if is_pulling and is_consensus:
728
- raise exceptions.PlatformException(error='400',
729
- message="Consensus can not work as a pulling task")
1122
+ raise exceptions.PlatformException(error="400", message="Consensus can not work as a pulling task")
730
1123
  if any([is_pulling, is_consensus]):
731
1124
  if metadata is None:
732
1125
  metadata = {}
733
- if 'system' not in metadata:
734
- metadata['system'] = {}
1126
+ if "system" not in metadata:
1127
+ metadata["system"] = {}
735
1128
  if allowed_assignees is not None or assignee_ids is not None:
736
- metadata['system']['allowedAssignees'] = allowed_assignees if allowed_assignees else assignee_ids
1129
+ metadata["system"]["allowedAssignees"] = allowed_assignees if allowed_assignees else assignee_ids
737
1130
  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')
1131
+ metadata["system"]["consensusTaskType"] = consensus_task_type
1132
+ metadata = self._add_task_metadata_params(metadata=metadata, input_value=batch_size, input_name="batchSize")
1133
+ metadata = self._add_task_metadata_params(
1134
+ metadata=metadata, input_value=max_batch_workload, input_name="maxBatchWorkload"
1135
+ )
1136
+ metadata = self._add_task_metadata_params(
1137
+ metadata=metadata, input_value=consensus_percentage, input_name="consensusPercentage"
1138
+ )
1139
+ metadata = self._add_task_metadata_params(
1140
+ metadata=metadata, input_value=consensus_assignees, input_name="consensusAssignees"
1141
+ )
1142
+ metadata = self._add_task_metadata_params(metadata=metadata, input_value=scoring, input_name="scoring")
754
1143
 
755
1144
  if metadata is not None:
756
- payload['metadata'] = metadata
1145
+ payload["metadata"] = metadata
757
1146
 
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
1147
+ return self._create_task(payload, wait=wait)
787
1148
 
788
1149
  def __item_operations(self, dataset: entities.Dataset, op, task=None, task_id=None, filters=None, items=None):
789
1150
 
790
1151
  if task is None and task_id is None:
791
- raise exceptions.PlatformException('400', 'Must provide either task or task id')
1152
+ raise exceptions.PlatformException("400", "Must provide either task or task id")
792
1153
  elif task_id is None:
793
1154
  task_id = task.id
794
1155
 
795
1156
  try:
796
1157
  if filters is None and items is None:
797
- raise exceptions.PlatformException('400', 'Must provide either filters or items list')
1158
+ raise exceptions.PlatformException("400", "Must provide either filters or items list")
798
1159
 
799
1160
  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)
1161
+ filters = entities.Filters(
1162
+ field="id",
1163
+ values=[item.id for item in items],
1164
+ operator=entities.FiltersOperations.IN,
1165
+ use_defaults=False,
1166
+ )
804
1167
 
805
- if op == 'delete':
1168
+ if op == "delete":
806
1169
  if task is None:
807
1170
  task = self.get(task_id=task_id)
808
1171
  assignment_ids = task.assignmentIds
@@ -817,17 +1180,19 @@ class Tasks:
817
1180
  if filters is not None:
818
1181
  filters._nullify_refs()
819
1182
 
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:
1183
+ @_api_reference.add(path="/annotationtasks/{id}/addToTask", method="post")
1184
+ def add_items(
1185
+ self,
1186
+ task: entities.Task = None,
1187
+ task_id=None,
1188
+ filters: entities.Filters = None,
1189
+ items=None,
1190
+ assignee_ids=None,
1191
+ query=None,
1192
+ workload=None,
1193
+ limit=None,
1194
+ wait=True,
1195
+ ) -> entities.Task:
831
1196
  """
832
1197
  Add items to a Task.
833
1198
 
@@ -853,19 +1218,21 @@ class Tasks:
853
1218
  items = [items])
854
1219
  """
855
1220
  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')
1221
+ raise exceptions.PlatformException("400", "Must provide either filters, query or items list")
857
1222
 
858
1223
  if task is None and task_id is None:
859
- raise exceptions.PlatformException('400', 'Must provide either task or task_id')
1224
+ raise exceptions.PlatformException("400", "Must provide either task or task_id")
860
1225
 
861
1226
  if query is None:
862
1227
  if filters is None:
863
1228
  if not isinstance(items, list):
864
1229
  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)
1230
+ filters = entities.Filters(
1231
+ field="id",
1232
+ values=[item.id for item in items],
1233
+ operator=entities.FiltersOperations.IN,
1234
+ use_defaults=False,
1235
+ )
869
1236
  query = filters.prepare()
870
1237
 
871
1238
  if workload is None and assignee_ids is not None:
@@ -874,39 +1241,34 @@ class Tasks:
874
1241
  if task_id is None:
875
1242
  task_id = task.id
876
1243
 
877
- payload = {
878
- "query": "{}".format(json.dumps(query).replace("'", '"')),
879
- }
1244
+ payload = {"query": "{}".format(json.dumps(query).replace("'", '"'))}
880
1245
 
881
1246
  if workload is not None:
882
1247
  payload["workload"] = workload.to_json()
883
1248
 
884
1249
  if limit is not None:
885
- payload['limit'] = limit
1250
+ payload["limit"] = limit
886
1251
 
887
- payload['asynced'] = wait
1252
+ payload["asynced"] = wait
888
1253
 
889
- url = '{}/{}/addToTask'.format(URL_PATH, task_id)
1254
+ url = "{}/{}/addToTask".format(URL_PATH, task_id)
890
1255
 
891
- success, response = self._client_api.gen_request(req_type='post',
892
- path=url,
893
- json_req=payload)
1256
+ success, response = self._client_api.gen_request(req_type="post", path=url, json_req=payload)
894
1257
 
895
1258
  if success:
896
- command = entities.Command.from_json(_json=response.json(),
897
- client_api=self._client_api)
1259
+ command = entities.Command.from_json(_json=response.json(), client_api=self._client_api)
898
1260
  if not wait:
899
1261
  return command
900
1262
  backoff_factor = 2
901
- if command.type == 'BulkAddToTaskSetting':
1263
+ if command.type == "BulkAddToTaskSetting":
902
1264
  backoff_factor = 8
903
1265
  command = command.wait(timeout=0, backoff_factor=backoff_factor)
904
1266
  if task is None:
905
1267
  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))
1268
+ if "addToTaskPayload" not in command.spec:
1269
+ raise exceptions.PlatformException(
1270
+ error="400", message="addToTaskPayload key is missing in command response: {}".format(response)
1271
+ )
910
1272
  else:
911
1273
  raise exceptions.PlatformException(response)
912
1274
 
@@ -914,13 +1276,15 @@ class Tasks:
914
1276
  return task
915
1277
 
916
1278
  # @_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):
1279
+ def remove_items(
1280
+ self,
1281
+ task: entities.Task = None,
1282
+ task_id=None,
1283
+ filters: entities.Filters = None,
1284
+ query=None,
1285
+ items=None,
1286
+ wait=True,
1287
+ ):
924
1288
  """
925
1289
  remove items from Task.
926
1290
 
@@ -944,55 +1308,55 @@ class Tasks:
944
1308
 
945
1309
  """
946
1310
  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')
1311
+ raise exceptions.PlatformException("400", "Must provide either filters, query or items list")
948
1312
 
949
1313
  if task is None and task_id is None:
950
- raise exceptions.PlatformException('400', 'Must provide either task or task_id')
1314
+ raise exceptions.PlatformException("400", "Must provide either task or task_id")
951
1315
 
952
1316
  if query is None:
953
1317
  if filters is None:
954
1318
  if not isinstance(items, list):
955
1319
  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)
1320
+ filters = entities.Filters(
1321
+ field="id",
1322
+ values=[item.id for item in items],
1323
+ operator=entities.FiltersOperations.IN,
1324
+ use_defaults=False,
1325
+ )
960
1326
  query = filters.prepare()
961
1327
 
962
1328
  if task_id is None:
963
1329
  task_id = task.id
964
1330
 
965
- payload = {"query": "{}".format(json.dumps(query).replace("'", '"')), 'asynced': wait}
1331
+ payload = {"query": "{}".format(json.dumps(query).replace("'", '"')), "asynced": wait}
966
1332
 
967
- url = '{}/{}/removeFromTask'.format(URL_PATH, task_id)
1333
+ url = "{}/{}/removeFromTask".format(URL_PATH, task_id)
968
1334
 
969
- success, response = self._client_api.gen_request(req_type='post',
970
- path=url,
971
- json_req=payload)
1335
+ success, response = self._client_api.gen_request(req_type="post", path=url, json_req=payload)
972
1336
 
973
1337
  if success:
974
- command = entities.Command.from_json(_json=response.json(),
975
- client_api=self._client_api)
1338
+ command = entities.Command.from_json(_json=response.json(), client_api=self._client_api)
976
1339
  if not wait:
977
1340
  return command
978
1341
  command = command.wait(timeout=0)
979
1342
 
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))
1343
+ if "removeFromTaskId" not in command.spec:
1344
+ raise exceptions.PlatformException(
1345
+ error="400", message="removeFromTaskId key is missing in command response: {}".format(response)
1346
+ )
984
1347
  else:
985
1348
  raise exceptions.PlatformException(response)
986
1349
  return True
987
1350
 
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:
1351
+ def get_items(
1352
+ self,
1353
+ task_id: str = None,
1354
+ task_name: str = None,
1355
+ dataset: entities.Dataset = None,
1356
+ filters: entities.Filters = None,
1357
+ get_consensus_items: bool = False,
1358
+ task: entities.Task = None,
1359
+ ) -> entities.PagedEntities:
996
1360
  """
997
1361
  Get the task items to use in your code.
998
1362
 
@@ -1016,7 +1380,7 @@ class Tasks:
1016
1380
  dataset.tasks.get_items(task_id= 'task_id')
1017
1381
  """
1018
1382
  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')
1383
+ raise exceptions.PlatformException("400", "Please provide either task_id or task_name")
1020
1384
 
1021
1385
  if task_id is None:
1022
1386
  if task is None:
@@ -1024,22 +1388,22 @@ class Tasks:
1024
1388
  task_id = task.id
1025
1389
 
1026
1390
  if dataset is None and self._dataset is None:
1027
- raise exceptions.PlatformException('400', 'Please provide a dataset entity')
1391
+ raise exceptions.PlatformException("400", "Please provide a dataset entity")
1028
1392
  if dataset is None:
1029
1393
  dataset = self._dataset
1030
1394
 
1031
1395
  if filters is None:
1032
1396
  filters = entities.Filters(use_defaults=False)
1033
- filters.add(field='metadata.system.refs.id', values=[task_id], operator=entities.FiltersOperations.IN)
1397
+ filters.add(field="metadata.system.refs.id", values=[task_id], operator=entities.FiltersOperations.IN)
1034
1398
 
1035
1399
  if not get_consensus_items:
1036
1400
  if task is None:
1037
1401
  task = self.get(task_id=task_id)
1038
- if task.metadata.get('system', dict()).get('consensusAssignmentId', None):
1402
+ if task.metadata.get("system", dict()).get("consensusAssignmentId", None):
1039
1403
  filters.add(
1040
- field='metadata.system.refs.id',
1041
- values=task.metadata['system']['consensusAssignmentId'],
1042
- operator=entities.FiltersOperations.NOT_EQUAL
1404
+ field="metadata.system.refs.id",
1405
+ values=task.metadata["system"]["consensusAssignmentId"],
1406
+ operator=entities.FiltersOperations.NOT_EQUAL,
1043
1407
  )
1044
1408
 
1045
1409
  return dataset.items.list(filters=filters)
@@ -1063,34 +1427,27 @@ class Tasks:
1063
1427
 
1064
1428
  dataset.tasks.set_status(task_id= 'task_id', status='complete', operation='create')
1065
1429
  """
1066
- url = '/assignments/items/tasks/{task_id}/status'.format(task_id=task_id)
1430
+ url = "/assignments/items/tasks/{task_id}/status".format(task_id=task_id)
1067
1431
  payload = {
1068
- 'itemIds': item_ids,
1069
- 'statusPayload': {
1070
- 'operation': operation,
1071
- 'returnLastStatus': True,
1072
- 'status': status
1073
- }
1432
+ "itemIds": item_ids,
1433
+ "statusPayload": {"operation": operation, "returnLastStatus": True, "status": status},
1074
1434
  }
1075
1435
 
1076
- success, response = self._client_api.gen_request(
1077
- req_type='post',
1078
- path=url,
1079
- json_req=payload
1080
- )
1436
+ success, response = self._client_api.gen_request(req_type="post", path=url, json_req=payload)
1081
1437
 
1082
1438
  if not success:
1083
1439
  raise exceptions.PlatformException(response)
1084
1440
  if response.json() is not None:
1085
1441
  updated_items = set(response.json().keys())
1086
- log_msg = 'Items status was updated successfully.'
1442
+ log_msg = "Items status was updated successfully."
1087
1443
  if len(updated_items) != len(item_ids):
1088
1444
  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)
1445
+ log_msg = "{success_count} out of TOTAL items were updated. The following items failed to update: {failed_items}".format(
1446
+ success_count=len(updated_items), failed_items=failed_items
1447
+ )
1091
1448
  logger.info(msg=log_msg)
1092
1449
  return True
1093
-
1450
+
1094
1451
  def task_scores(self, task_id: str = None, page_offset: int = 0, page_size: int = 100):
1095
1452
  """
1096
1453
  Get all entities scores in a task.
@@ -1107,19 +1464,14 @@ class Tasks:
1107
1464
  dataset.tasks.task_scores(task_id= 'task_id')
1108
1465
  """
1109
1466
  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
1467
+ raise exceptions.PlatformException("400", "Please provide task_id")
1468
+
1469
+ url = "/scores/tasks/{task_id}?page={page_offset}&pageSize={page_size}".format(
1470
+ task_id=task_id, page_offset=page_offset, page_size=page_size
1120
1471
  )
1472
+ success, response = self._client_api.gen_request(req_type="get", path=url)
1121
1473
 
1122
1474
  if success:
1123
1475
  return response.json()
1124
1476
  else:
1125
- raise exceptions.PlatformException(response)
1477
+ raise exceptions.PlatformException(response)