Skip to content

Commit 03017b1

Browse files
authored
Merge pull request #296 from superannotateai/validations_interface_function
Validations interface function
2 parents d943dc1 + 5c02a3e commit 03017b1

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3638,12 +3638,23 @@ def attach_document_urls_to_project(
36383638
def validate_annotations(
36393639
project_type: ProjectTypes, annotations_json: Union[NotEmptyStr, Path]
36403640
):
3641+
"""
3642+
Validates given annotation JSON.
3643+
:param project_type: project_type (str) – the project type Vector, Pixel, Video or Document
3644+
:type project_type: str
3645+
:param annotation_json: path to annotation JSON
3646+
:type annotation_json: Path-like (str or Path)
3647+
3648+
:return: The success of the validation
3649+
:rtype: bool
3650+
"""
36413651
with open(annotations_json) as file:
36423652
annotation_data = json.loads(file.read())
36433653
response = controller.validate_annotations(project_type, annotation_data)
36443654
if response.errors:
36453655
raise AppException(response.errors)
3646-
if response.data:
3656+
is_valid, _ = response.data
3657+
if is_valid:
36473658
return True
36483659
print(response.report)
36493660
return False

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import List
55
from typing import Optional
66

7-
from pydantic import BaseModel
7+
from pydantic import BaseModel as PyDanticBaseModel
88
from pydantic import conlist
99
from pydantic import constr
1010
from pydantic import EmailStr
@@ -18,6 +18,16 @@
1818
NotEmptyStr = constr(strict=True, min_length=1)
1919

2020

21+
class BaseModel(PyDanticBaseModel):
22+
class Config:
23+
use_enum_values = True
24+
error_msg_templates = {
25+
"type_error.integer": "integer type expected",
26+
"type_error.string": "str type expected",
27+
"value_error.missing": "field required",
28+
}
29+
30+
2131
class StringDate(datetime):
2232
@classmethod
2333
def __get_validators__(cls):
@@ -28,6 +38,7 @@ def __get_validators__(cls):
2838
def validate(cls, v: datetime):
2939
return f'{v.replace(tzinfo=None).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z'
3040

41+
3142
class VectorAnnotationTypeEnum(str, Enum):
3243
BBOX = "bbox"
3344
ELLIPSE = "ellipse"
@@ -107,6 +118,10 @@ class TrackableModel(BaseModel):
107118
updated_by: Optional[UserAction] = Field(None, alias="updatedBy")
108119
creation_type: Optional[CreationTypeEnum] = Field(CreationTypeEnum.PRE_ANNOTATION.value, alias="creationType")
109120

121+
@validator("creation_type")
122+
def clean_creation_type(cls, value):
123+
return value or CreationTypeEnum.PRE_ANNOTATION.value
124+
110125

111126
class LastUserAction(BaseModel):
112127
email: EmailStr
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import tempfile
2+
from collections import defaultdict
3+
from pathlib import Path
4+
import os
5+
from os.path import join
6+
import json
7+
import pytest
8+
from unittest.mock import patch
9+
from unittest.mock import MagicMock
10+
11+
import src.superannotate as sa
12+
from src.superannotate.lib.core.entities.utils import CreationTypeEnum
13+
from tests.integration.base import BaseTestCase
14+
15+
16+
class TestAnnotationUploadVector(BaseTestCase):
17+
PROJECT_NAME = "TestAnnotationUploadVector"
18+
PROJECT_DESCRIPTION = "Desc"
19+
PROJECT_TYPE = "Vector"
20+
S3_FOLDER_PATH = "sample_project_pixel"
21+
TEST_FOLDER_PATH = "data_set/sample_project_vector"
22+
IMAGE_NAME = "example_image_1.jpg"
23+
24+
@property
25+
def folder_path(self):
26+
return os.path.join(Path(__file__).parent.parent.parent, self.TEST_FOLDER_PATH)
27+
28+
@pytest.mark.flaky(reruns=2)
29+
@patch("lib.infrastructure.controller.Reporter")
30+
def test_annotation_last_action_and_creation_type(self, reporter):
31+
reporter_mock = MagicMock()
32+
reporter.return_value = reporter_mock
33+
annotation_path = join(self.folder_path, f"{self.IMAGE_NAME}___objects.json")
34+
sa.upload_image_to_project(self.PROJECT_NAME, join(self.folder_path, self.IMAGE_NAME))
35+
sa.upload_image_annotations(self.PROJECT_NAME, self.IMAGE_NAME, annotation_path)
36+
call_groups = defaultdict(list)
37+
reporter_calls = reporter_mock.method_calls
38+
for call in reporter_calls:
39+
call_groups[call[0]].append(call[1])
40+
self.assertEqual(len(call_groups["log_warning"]), len(call_groups["store_message"]))
41+
with tempfile.TemporaryDirectory() as tmp_dir:
42+
sa.download_image_annotations(self.PROJECT_NAME, self.IMAGE_NAME, tmp_dir)
43+
annotation = json.load(open(join(tmp_dir, f"{self.IMAGE_NAME}___objects.json")))
44+
for instance in annotation["instances"]:
45+
self.assertEqual(instance["creationType"], CreationTypeEnum.PRE_ANNOTATION.value)
46+
self.assertEqual(
47+
type(annotation["metadata"]["lastAction"]["email"]),
48+
type(sa.controller.team_data.data.creator_id)
49+
)
50+
self.assertEqual(
51+
type(annotation["metadata"]["lastAction"]["timestamp"]),
52+
int
53+
)
54+

0 commit comments

Comments
 (0)