Skip to content

Commit a8423df

Browse files
committed
Iterface chages
1 parent 59f3e34 commit a8423df

File tree

14 files changed

+255
-67
lines changed

14 files changed

+255
-67
lines changed

docs/source/api_reference/api_metadata.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Project metadata example:
2020
"attachment_path": None,
2121
"entropy_status": 1,
2222
"status": "NotStarted",
23+
"item_count": 123,
2324
"...": "..."
2425
}
2526

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ minversion = 3.7
33
log_cli=true
44
python_files = test_*.py
55
;pytest_plugins = ['pytest_profiling']
6-
addopts = -n auto --dist=loadscope
6+
;addopts = -n auto --dist=loadscope

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import sys
77
import tempfile
8+
import warnings
89
from pathlib import Path
910
from typing import Callable
1011
from typing import Dict
@@ -51,6 +52,7 @@
5152
from lib.core.conditions import Condition
5253
from lib.core.conditions import EmptyCondition
5354
from lib.core.entities import AttachmentEntity
55+
from lib.core.entities import WorkflowEntity
5456
from lib.core.entities import SettingEntity
5557
from lib.core.entities.classes import AnnotationClassEntity
5658
from lib.core.entities.classes import AttributeGroup
@@ -233,7 +235,7 @@ def search_projects(
233235
self,
234236
name: Optional[NotEmptyStr] = None,
235237
return_metadata: bool = False,
236-
include_complete_image_count: bool = False,
238+
include_complete_item_count: bool = False,
237239
status: Optional[Union[PROJECT_STATUS, List[PROJECT_STATUS]]] = None,
238240
):
239241
"""
@@ -246,8 +248,9 @@ def search_projects(
246248
:param return_metadata: return metadata of projects instead of names
247249
:type return_metadata: bool
248250
249-
:param include_complete_image_count: return projects that have completed images and include the number of completed images in response.
250-
:type include_complete_image_count: bool
251+
:param include_complete_item_count: return projects that have completed items and include
252+
the number of completed items in response.
253+
:type include_complete_item_count: bool
251254
252255
:param status: search projects via project status
253256
:type status: str
@@ -265,9 +268,9 @@ def search_projects(
265268
condition = Condition.get_empty_condition()
266269
if name:
267270
condition &= Condition("name", name, EQ)
268-
if include_complete_image_count:
271+
if include_complete_item_count:
269272
condition &= Condition(
270-
"completeImagesCount", include_complete_image_count, EQ
273+
"completeImagesCount", include_complete_item_count, EQ
271274
)
272275
for status in statuses:
273276
condition &= Condition(
@@ -280,7 +283,13 @@ def search_projects(
280283
if return_metadata:
281284
return [
282285
ProjectSerializer(project).serialize(
283-
exclude={"settings", "workflows", "contributors", "classes"}
286+
exclude={
287+
"settings",
288+
"workflows",
289+
"contributors",
290+
"classes",
291+
"item_count",
292+
}
284293
)
285294
for project in response.data
286295
]
@@ -293,6 +302,9 @@ def create_project(
293302
project_description: NotEmptyStr,
294303
project_type: PROJECT_TYPE,
295304
settings: List[Setting] = None,
305+
classes: List[AnnotationClassEntity] = None,
306+
workflows: List = None,
307+
instructions_link: str = None,
296308
):
297309
"""Create a new project in the team.
298310
@@ -308,25 +320,71 @@ def create_project(
308320
:param settings: list of settings objects
309321
:type settings: list of dicts
310322
323+
:param classes: list of class objects
324+
:type classes: list of dicts
325+
326+
:param workflows: list of information for each step
327+
:type workflows: list of dicts
328+
329+
:param instructions_link: str of instructions URL
330+
:type instructions_link: str
331+
311332
:return: dict object metadata the new project
312333
:rtype: dict
313334
"""
335+
if workflows:
336+
if project_type.capitalize() not in (
337+
constants.ProjectType.VECTOR.name,
338+
constants.ProjectType.PIXEL.name,
339+
):
340+
raise AppException(
341+
f"Workflow is not supported in {project_type} project."
342+
)
343+
parse_obj_as(List[WorkflowEntity], workflows)
344+
if workflows and not classes:
345+
raise AppException(
346+
"Project with workflows can not be created without classes."
347+
)
314348
if settings:
315349
settings = parse_obj_as(List[SettingEntity], settings)
316350
else:
317351
settings = []
318-
response = self.controller.projects.create(
352+
if classes:
353+
classes = parse_obj_as(List[AnnotationClassEntity], classes)
354+
if workflows and classes:
355+
invalid_classes = []
356+
class_names = [_class.name for _class in classes]
357+
for step in workflows:
358+
if step["className"] not in class_names:
359+
invalid_classes.append(step["className"])
360+
if invalid_classes:
361+
raise AppException(
362+
f"There are no [{', '.join(invalid_classes)}] classes created in the project."
363+
)
364+
project_response = self.controller.projects.create(
319365
entities.ProjectEntity(
320366
name=project_name,
321367
description=project_description,
322368
type=constants.ProjectType.get_value(project_type),
323369
settings=settings,
370+
instructions_link=instructions_link,
324371
)
325372
)
326-
if response.errors:
327-
raise AppException(response.errors)
328-
329-
return ProjectSerializer(response.data).serialize()
373+
project_response.raise_for_status()
374+
project = project_response.data
375+
if classes:
376+
classes_response = self.controller.annotation_classes.create_multiple(
377+
project, classes
378+
)
379+
classes_response.raise_for_status()
380+
project.classes = classes_response.data
381+
if workflows:
382+
workflow_response = self.controller.projects.set_workflows(
383+
project, workflows
384+
)
385+
workflow_response.raise_for_status()
386+
project.workflows = self.controller.projects.list_workflow(project).data
387+
return ProjectSerializer(project).serialize()
330388

331389
def create_project_from_metadata(self, project_metadata: Project):
332390
"""Create a new project in the team using project metadata object dict.
@@ -340,6 +398,10 @@ def create_project_from_metadata(self, project_metadata: Project):
340398
:return: dict object metadata the new project
341399
:rtype: dict
342400
"""
401+
deprecation_msg = '"create_project_from_metadata" is deprecated and replaced by "create_project"'
402+
warnings.warn(deprecation_msg, DeprecationWarning)
403+
logger.warning(deprecation_msg)
404+
343405
project_metadata = project_metadata.dict()
344406
if project_metadata["type"] not in enums.ProjectType.titles():
345407
raise AppException(
@@ -557,7 +619,7 @@ def get_project_metadata(
557619
include_settings: Optional[StrictBool] = False,
558620
include_workflow: Optional[StrictBool] = False,
559621
include_contributors: Optional[StrictBool] = False,
560-
include_complete_image_count: Optional[StrictBool] = False,
622+
include_complete_item_count: Optional[StrictBool] = False,
561623
):
562624
"""Returns project metadata
563625
@@ -576,9 +638,9 @@ def get_project_metadata(
576638
the key "contributors"
577639
:type include_contributors: bool
578640
579-
:param include_complete_image_count: enables project complete image count output under
580-
the key "completed_images_count"
581-
:type include_complete_image_count: bool
641+
:param include_complete_item_count: enables project complete item count output under
642+
the key "completed_items_count"
643+
:type include_complete_item_count: bool
582644
583645
:return: metadata of project
584646
:rtype: dict
@@ -591,7 +653,7 @@ def get_project_metadata(
591653
include_settings,
592654
include_workflow,
593655
include_contributors,
594-
include_complete_image_count,
656+
include_complete_item_count,
595657
)
596658
if response.errors:
597659
raise AppException(response.errors)
@@ -968,7 +1030,13 @@ def get_project_image_count(
9681030
:return: number of images in the project
9691031
:rtype: int
9701032
"""
1033+
deprecation_msg = (
1034+
"“get_project_image_count” is deprecated and replaced"
1035+
" by “item_count” value will be included in project metadata."
1036+
)
9711037

1038+
warnings.warn(deprecation_msg, DeprecationWarning)
1039+
logger.warning(deprecation_msg)
9721040
project_name, folder_name = extract_project_folder(project)
9731041

9741042
response = self.controller.get_project_image_count(

src/superannotate/lib/core/entities/project.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ class ProjectEntity(TimedBaseModel):
9797
settings: List[SettingEntity] = []
9898
classes: List[AnnotationClassEntity] = []
9999
workflows: Optional[List[WorkflowEntity]] = []
100-
completed_images_count: Optional[int] = Field(None, alias="completedImagesCount")
101-
root_folder_completed_images_count: Optional[int] = Field(
100+
item_count: Optional[int] = Field(None, alias="imageCount")
101+
completed_items_count: Optional[int] = Field(None, alias="completedImagesCount")
102+
root_folder_completed_items_count: Optional[int] = Field(
102103
None, alias="rootFolderCompletedImagesCount"
103104
)
104105

src/superannotate/lib/core/response.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from typing import NoReturn
12
from typing import Union
23

4+
from lib.core.exceptions import AppException
5+
36

47
class Response:
58
def __init__(self, status: str = None, data: Union[dict, list] = None):
@@ -11,6 +14,10 @@ def __init__(self, status: str = None, data: Union[dict, list] = None):
1114
def __str__(self):
1215
return f"Response object with status:{self.status}, data : {self.data}, errors: {self.errors} "
1316

17+
def raise_for_status(self) -> NoReturn:
18+
if self.errors:
19+
raise AppException(self.errors)
20+
1421
@property
1522
def data(self):
1623
return self._data

src/superannotate/lib/core/usecases/classes.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from lib.core.entities import ProjectEntity
99
from lib.core.enums import ProjectType
1010
from lib.core.exceptions import AppException
11-
from lib.core.reporter import Spinner
1211
from lib.core.serviceproviders import BaseServiceProvider
1312
from lib.core.usecases.base import BaseUseCase
1413

@@ -150,21 +149,17 @@ def execute(self):
150149
)
151150
created = []
152151
chunk_failed = False
153-
with Spinner():
154-
# this is in reverse order because of the front-end
155-
for i in range(len(unique_annotation_classes), 0, -self.CHUNK_SIZE):
156-
response = (
157-
self._service_provider.annotation_classes.create_multiple(
158-
project=self._project,
159-
classes=unique_annotation_classes[
160-
i - self.CHUNK_SIZE : i
161-
], # noqa
162-
)
163-
)
164-
if response.ok:
165-
created.extend(response.data)
166-
else:
167-
chunk_failed = True
152+
# this is in reverse order because of the front-end
153+
for i in range(len(unique_annotation_classes), 0, -self.CHUNK_SIZE):
154+
response = self._service_provider.annotation_classes.create_multiple(
155+
project=self._project,
156+
classes=unique_annotation_classes[i - self.CHUNK_SIZE : i], # noqa
157+
)
158+
if response.ok:
159+
created.extend(response.data)
160+
else:
161+
logger.debug(response.error)
162+
chunk_failed = True
168163
if created:
169164
logger.info(
170165
f"{len(created)} annotation classes were successfully created in {self._project.name}."

src/superannotate/lib/core/usecases/projects.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ def execute(self):
137137
root_completed_count = folder.completedCount # noqa
138138
except AttributeError:
139139
pass
140-
project.root_folder_completed_images_count = root_completed_count
141-
project.completed_images_count = total_completed_count
140+
project.root_folder_completed_items_count = root_completed_count
141+
project.completed_items_count = total_completed_count
142142
if self._include_annotation_classes:
143143
project.classes = self._service_provider.annotation_classes.list(
144144
Condition("project_id", self._project.id, EQ)
@@ -836,10 +836,7 @@ def execute(self):
836836
del step["id"]
837837
step["class_id"] = annotation_classes_map.get(step["className"], None)
838838
if not step["class_id"]:
839-
raise AppException(
840-
"Annotation class not found in set_project_workflow."
841-
)
842-
839+
raise AppException("Annotation class not found.")
843840
self._service_provider.projects.set_workflows(
844841
project=self._project,
845842
steps=self._steps,
@@ -864,7 +861,7 @@ def execute(self):
864861
None,
865862
):
866863
raise AppException(
867-
"Attribute group name or attribute name not found in set_project_workflow."
864+
f"Attribute group name or attribute name not found {attribute_group_name}."
868865
)
869866

870867
if not existing_workflows_map.get(step["step"], None):

tests/integration/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ def tearDown(self) -> None:
3434
except Exception as e:
3535
print(str(e))
3636

37-
def _attach_items(self):
37+
def _attach_items(self, count=5, folder=None):
38+
path = self.PROJECT_NAME
39+
if folder:
40+
path = f"{self.PROJECT_NAME}/{folder}"
3841
sa.attach_items(
39-
self.PROJECT_NAME,
42+
path,
4043
[
4144
{"name": f"example_image_{i}.jpg", "url": f"url_{i}"}
42-
for i in range(1, 5)
45+
for i in range(1, count + 1)
4346
], # noqa
4447
)

tests/integration/folders/test_folders.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ def test_project_completed_count(self):
236236
project, self.folder_path, annotation_status="Completed"
237237
)
238238
project_metadata = sa.get_project_metadata(
239-
self.PROJECT_NAME, include_complete_image_count=True
239+
self.PROJECT_NAME, include_complete_item_count=True
240240
)
241-
self.assertEqual(project_metadata["completed_images_count"], 8)
242-
self.assertEqual(project_metadata["root_folder_completed_images_count"], 4)
241+
self.assertEqual(project_metadata["completed_items_count"], 8)
242+
self.assertEqual(project_metadata["root_folder_completed_items_count"], 4)
243243

244244
def test_folder_misnamed(self):
245245
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1)

tests/integration/mixpanel/test_mixpanel_decorator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def test_search_team_contributors(self, track_method):
7878
def test_search_projects(self, track_method):
7979
kwargs = {
8080
"name": self.PROJECT_NAME,
81-
"include_complete_image_count": True,
81+
"include_complete_item_count": True,
8282
"status": "NotStarted",
8383
"return_metadata": False,
8484
}
@@ -96,6 +96,9 @@ def test_create_project(self, track_method):
9696
"project_description": self.PROJECT_DESCRIPTION,
9797
"project_type": self.PROJECT_TYPE,
9898
"settings": {"a": 1, "b": 2},
99+
"classes": None,
100+
"workflows": None,
101+
'instructions_link': None
99102
}
100103
try:
101104
self.CLIENT.create_project(**kwargs)

0 commit comments

Comments
 (0)