Skip to content

Commit 7c26ca6

Browse files
authored
Merge pull request #493 from superannotateai/fix_validate_annotations
changed some fields validations
2 parents fe3ebcc + 33a9bf6 commit 7c26ca6

File tree

6 files changed

+74
-40
lines changed

6 files changed

+74
-40
lines changed

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from pydantic import MappingIntStrAny # noqa
3030
except ImportError:
3131
pass
32+
_missing = object()
3233

3334

3435
class BaseModel(PydanticBaseModel):
@@ -37,6 +38,70 @@ class BaseModel(PydanticBaseModel):
3738
- use_enum_names: that's for BaseTitledEnum to use names instead of enum objects
3839
"""
3940

41+
def _iter(
42+
self,
43+
to_dict: bool = False,
44+
by_alias: bool = False,
45+
include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
46+
exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
47+
exclude_unset: bool = False,
48+
exclude_defaults: bool = False,
49+
exclude_none: bool = False,
50+
) -> "TupleGenerator": # noqa
51+
52+
# Merge field set excludes with explicit exclude parameter with explicit overriding field set options.
53+
# The extra "is not None" guards are not logically necessary but optimizes performance for the simple case.
54+
if exclude is not None or self.__exclude_fields__ is not None:
55+
exclude = ValueItems.merge(self.__exclude_fields__, exclude)
56+
57+
if include is not None or self.__include_fields__ is not None:
58+
include = ValueItems.merge(self.__include_fields__, include, intersect=True)
59+
60+
allowed_keys = self._calculate_keys(
61+
include=include, exclude=exclude, exclude_unset=exclude_unset # type: ignore
62+
)
63+
if allowed_keys is None and not (
64+
by_alias or exclude_unset or exclude_defaults or exclude_none
65+
):
66+
# huge boost for plain _iter()
67+
yield from self.__dict__.items()
68+
return
69+
70+
value_exclude = ValueItems(self, exclude) if exclude is not None else None
71+
value_include = ValueItems(self, include) if include is not None else None
72+
73+
for field_key, v in self.__dict__.items():
74+
if (allowed_keys is not None and field_key not in allowed_keys) or (
75+
exclude_none and v is None
76+
):
77+
continue
78+
79+
if exclude_defaults:
80+
model_field = self.__fields__.get(field_key)
81+
if (
82+
not getattr(model_field, "required", True)
83+
and getattr(model_field, "default", _missing) == v
84+
):
85+
continue
86+
87+
if by_alias and field_key in self.__fields__:
88+
dict_key = self.__fields__[field_key].alias
89+
else:
90+
dict_key = field_key
91+
92+
# if to_dict or value_include or value_exclude:
93+
v = self._get_value(
94+
v,
95+
to_dict=to_dict,
96+
by_alias=by_alias,
97+
include=value_include and value_include.for_element(field_key),
98+
exclude=value_exclude and value_exclude.for_element(field_key),
99+
exclude_unset=exclude_unset,
100+
exclude_defaults=exclude_defaults,
101+
exclude_none=exclude_none,
102+
)
103+
yield dict_key, v
104+
40105
@classmethod
41106
@no_type_check
42107
def _get_value(

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,4 @@ class Config:
102102
BaseTitledEnum: lambda v: v.value,
103103
}
104104
validate_assignment = True
105-
# exclude_none = True
106105
use_enum_names = True

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,8 @@ class ValidateAnnotationUseCase(BaseReportableUseCase):
11451145
DEFAULT_VERSION = "V1.00"
11461146
SCHEMAS: Dict[str, Draft7Validator] = {}
11471147
PATTERN_MAP = {
1148-
"\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(?:\\.\\d{3})Z": "YYYY-MM-DDTHH:MM:SS.fffZ"
1148+
"\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(?:\\.\\d{3})Z": "does not match YYYY-MM-DDTHH:MM:SS.fffZ",
1149+
"^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$": "invalid email",
11491150
}
11501151

11511152
def __init__(
@@ -1228,7 +1229,7 @@ def oneOf(validator, oneOf, instance, schema): # noqa
12281229
def _pattern(validator, patrn, instance, schema):
12291230
if validator.is_type(instance, "string") and not re.search(patrn, instance):
12301231
yield ValidationError(
1231-
f"{instance} does not match {ValidateAnnotationUseCase.PATTERN_MAP.get(patrn, patrn)}"
1232+
f"{instance} {ValidateAnnotationUseCase.PATTERN_MAP.get(patrn, patrn)}"
12321233
)
12331234

12341235
@staticmethod

src/superannotate/lib/infrastructure/services.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
class PydanticEncoder(json.JSONEncoder):
4747
def default(self, obj):
4848
if isinstance(obj, BaseModel):
49-
return json.loads(obj.json(exclude_unset=True, exclude_none=True))
49+
return json.loads(obj.json(exclude_none=True))
5050
return json.JSONEncoder.default(self, obj)
5151

5252

@@ -555,7 +555,8 @@ def create_annotation_classes(
555555
"post",
556556
params=params,
557557
data={
558-
"classes": [i.dict(exclude_none=True, exclude_unset=True) for i in data]
558+
# "classes": [json.loads(i.json(exclude_none=True, exclude_unset=True)) for i in data]
559+
"classes": data
559560
},
560561
content_type=List[AnnotationClassEntity],
561562
)

tests/integration/annotations/validations/test_vector_annotation_validation.py

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -59,36 +59,4 @@ def test_validate_create_dby(self, mock_print):
5959
"""instances[0] 'points' is a required property
6060
instances[0].createdBy.role 'dmin' is not one of ['Customer', 'Admin', 'Annotator', 'QA']
6161
instances[1] 'points' is a required property"""
62-
)
63-
64-
def test_should_be_valid(self):
65-
is_valid = sa.validate_annotations(
66-
"Vector",
67-
{
68-
"metadata": {"name": "12"},
69-
"instances": [
70-
{
71-
"type": "bbox",
72-
"createdAt": '2021-11-18T13.36.53.293Z',
73-
"createdBy": {'email': 'arturn@superannotate.com', 'role': 'dmin'},
74-
"points": {
75-
"x1": 437.16,
76-
"x2": 465.23,
77-
"y1": 341.5,
78-
"y2": 357.09
79-
},
80-
"attributes": [
81-
{
82-
"name": 4,
83-
"groupName": "Num doors1"
84-
},
85-
{
86-
"name": 4,
87-
"groupName": "Num doors1"
88-
}
89-
]
90-
91-
},
92-
]
93-
}
94-
)
62+
)

tests/integration/classes/test_classes_serialization.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ def test_empty_multiselect_excluded(self):
3939
annotation_class = AnnotationClassEntity(name="asd", color="blue",
4040
attribute_groups=[AttributeGroup(name="sad")])
4141
serializer_data = json.loads(json.dumps(annotation_class, cls=PydanticEncoder))
42-
assert {'name': 'asd', 'color': '#0000FF', 'attribute_groups': [{'name': 'sad'}]} == serializer_data
42+
assert {'type': 1, 'name': 'asd', 'color': '#0000FF', 'attribute_groups': [{'name': 'sad'}]} == serializer_data
4343

4444
def test_empty_multiselect_bool_serializer(self):
4545
annotation_class = AnnotationClassEntity(
4646
name="asd", color="blue", attribute_groups=[AttributeGroup(name="sad", is_multiselect="True")]
4747
)
4848
serializer_data = json.loads(json.dumps(annotation_class, cls=PydanticEncoder))
49-
assert {'name': 'asd', 'color': '#0000FF',
49+
assert {'type': 1,'name': 'asd', 'color': '#0000FF',
5050
'attribute_groups': [{'name': 'sad', 'is_multiselect': True}]} == serializer_data
5151

5252
def test_group_type_wrong_arg(self):

0 commit comments

Comments
 (0)