diff --git a/annofabapi/dataclass/annotation.py b/annofabapi/dataclass/annotation.py index b6f8caba..b2c4c997 100644 --- a/annofabapi/dataclass/annotation.py +++ b/annofabapi/dataclass/annotation.py @@ -25,7 +25,7 @@ ) AnnotationData = Union[str, Dict[str, Any]] -FullAnnotationData = Dict[str, Any] +FullAnnotationData = Any AdditionalDataValue = Dict[str, Any] @@ -191,7 +191,7 @@ class FullAnnotationDetail(DataClassJsonMixin): data_holding_type: Optional[AnnotationDataHoldingType] """""" - data: Optional[FullAnnotationData] + data: FullAnnotationData """""" additional_data_list: Optional[List[FullAnnotationAdditionalData]] @@ -223,7 +223,7 @@ class FullAnnotation(DataClassJsonMixin): input_data_name: Optional[str] """""" - details: Optional[List[FullAnnotationDetail]] + details: List[FullAnnotationDetail] """""" updated_datetime: Optional[str] diff --git a/annofabapi/parser.py b/annofabapi/parser.py index 621e2df9..1375d6f7 100644 --- a/annofabapi/parser.py +++ b/annofabapi/parser.py @@ -3,11 +3,13 @@ import os import zipfile from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional +from typing import Any, Callable, Dict, Iterator, List, Optional from annofabapi.dataclass.annotation import FullAnnotation, SimpleAnnotation from annofabapi.exceptions import AnnotationOuterFileNotFoundError +CONVERT_ANNOTATION_DETAIL_DATA_FUNC = Callable[[Dict[str, Any]], Any] + def _trim_extension(file_path: str) -> str: """ファイルパスから拡張子を除去した文字列を返す""" @@ -72,12 +74,25 @@ def open_outer_file(self, data_uri: str): """ - @abc.abstractmethod - def parse(self) -> SimpleAnnotation: - """ - JSONファイルをパースする。 + def parse( + self, convert_deitail_data_func: Optional[CONVERT_ANNOTATION_DETAIL_DATA_FUNC] = None + ) -> SimpleAnnotation: + """JSONファイルをパースする + + Args: + convert_deitail_data_func: SimpleAnnotationDetailクラスのdataプロパティを変換する関数を指定します。 + dictからdataclassに変換する際に使います。 + + Returns: + SimpleAnnotationインスタンス """ + simple_annotation = SimpleAnnotation.from_dict(self.load_json()) # type: ignore + if convert_deitail_data_func is not None: + for detail in simple_annotation.details: + detail.data = convert_deitail_data_func(detail.data) + return simple_annotation + @abc.abstractmethod def load_json(self) -> Any: """ @@ -144,11 +159,28 @@ def open_outer_file(self, data_uri: str): """ @abc.abstractmethod - def parse(self) -> FullAnnotation: + def load_json(self) -> Any: """ - JSONファイルをパースする。 + JSONファイルをloadします。 """ + def parse(self, convert_deitail_data_func: Optional[CONVERT_ANNOTATION_DETAIL_DATA_FUNC] = None) -> FullAnnotation: + """JSONファイルをパースする + + Args: + convert_deitail_data_func: FullAnnotationDetailクラスのdataプロパティを変換する関数を指定します。 + dictからdataclassに変換する際に使います。 + + Returns: + FullAnnotationインスタンス + """ + + full_annotation = FullAnnotation.from_dict(self.load_json()) # type: ignore + if convert_deitail_data_func is not None: + for detail in full_annotation.details: + detail.data = convert_deitail_data_func(detail.data) + return full_annotation + class SimpleAnnotationZipParser(SimpleAnnotationParser): """ @@ -172,9 +204,6 @@ def __init__(self, zip_file: zipfile.ZipFile, json_file_path: str): self.__zip_file = zip_file super().__init__(json_file_path) - def parse(self) -> SimpleAnnotation: - return SimpleAnnotation.from_dict(self.load_json()) # type: ignore - def load_json(self) -> Any: with self.__zip_file.open(self.json_file_path) as entry: return json.load(entry) @@ -208,9 +237,6 @@ class SimpleAnnotationDirParser(SimpleAnnotationParser): def __init__(self, json_file_path: Path): super().__init__(str(json_file_path)) - def parse(self) -> SimpleAnnotation: - return SimpleAnnotation.from_dict(self.load_json()) # type: ignore - def load_json(self) -> Any: with open(self.json_file_path, encoding="utf-8") as f: return json.load(f) @@ -245,11 +271,9 @@ def __init__(self, zip_file: zipfile.ZipFile, json_file_path: str): self.__zip_file = zip_file super().__init__(json_file_path) - def parse(self) -> FullAnnotation: + def load_json(self) -> Any: with self.__zip_file.open(self.json_file_path) as entry: - anno_dict: dict = json.load(entry) - # mypyの "has no attribute "from_dict" " をignore - return FullAnnotation.from_dict(anno_dict) # type: ignore + return json.load(entry) def open_outer_file(self, data_uri: str): outer_file_path = _trim_extension(self.json_file_path) + "/" + data_uri @@ -281,10 +305,9 @@ class FullAnnotationDirParser(FullAnnotationParser): def __init__(self, json_file_path: Path): super().__init__(str(json_file_path)) - def parse(self) -> FullAnnotation: + def load_json(self) -> Any: with open(self.json_file_path, encoding="utf-8") as f: - anno_dict: dict = json.load(f) - return FullAnnotation.from_dict(anno_dict) # type: ignore + return json.load(f) def open_outer_file(self, data_uri: str): outer_file_path = _trim_extension(self.json_file_path) + "/" + data_uri diff --git a/generate/partial-header/dataclass/annotation.py b/generate/partial-header/dataclass/annotation.py index 1f6e2978..f72b0e39 100644 --- a/generate/partial-header/dataclass/annotation.py +++ b/generate/partial-header/dataclass/annotation.py @@ -1,12 +1,12 @@ from annofabapi.models import ( - AnnotationDataHoldingType, - InternationalizationMessage, AdditionalDataDefinitionType, + AnnotationDataHoldingType, AnnotationType, + InternationalizationMessage, TaskPhase, TaskStatus, ) AnnotationData = Union[str, Dict[str, Any]] -FullAnnotationData = Dict[str, Any] +FullAnnotationData = Any AdditionalDataValue = Dict[str, Any] diff --git a/tests/test_local_parser.py b/tests/test_local_parser.py index eda526a8..a7fbe156 100644 --- a/tests/test_local_parser.py +++ b/tests/test_local_parser.py @@ -8,7 +8,7 @@ import annofabapi import annofabapi.parser import annofabapi.utils -from annofabapi.dataclass.annotation import FullAnnotation, SimpleAnnotation +from annofabapi.dataclass.annotation import FullAnnotation, FullAnnotationDataPoints, SimpleAnnotation from annofabapi.exceptions import AnnotationOuterFileNotFoundError from annofabapi.parser import ( FullAnnotationDirParser, @@ -43,6 +43,24 @@ def test_SimpleAnnotationZipParser(self): with pytest.raises(AnnotationOuterFileNotFoundError): parser.open_outer_file("foo") + def convert_deitail_data(self, dict_data): + if dict_data["_type"] == "Points": + dict_data["type"] = dict_data["_type"] + return FullAnnotationDataPoints.from_dict(dict_data) + else: + return dict_data + + def test_parse_for_zip(self): + zip_path = Path(test_dir / "simple-annotation.zip") + with zipfile.ZipFile(zip_path) as zip_file: + parser = SimpleAnnotationZipParser(zip_file, "sample_1/c86205d1-bdd4-4110-ae46-194e661d622b.json") + + simple_annotation = parser.parse() + assert type(simple_annotation.details[0].data) == dict + + simple_annotation2 = parser.parse(self.convert_deitail_data) + assert type(simple_annotation2.details[0].data) == FullAnnotationDataPoints + def test_SimpleAnnotationDirParser(self): dir_path = Path(test_dir / "simple-annotation") @@ -118,6 +136,10 @@ def test_simple_annotation_dir(self): assert type(simple_annotation) == SimpleAnnotation index += 1 + dict_simple_annotation = parser.load_json() + assert type(dict_simple_annotation) == dict + assert "details" in dict_simple_annotation + assert index == 4 def test_lazy_parse_simple_annotation_zip_by_task(self): @@ -166,6 +188,11 @@ def test_full_annotation_zip(self): parser = FullAnnotationZipParser(zip_file, "sample_1/c86205d1-bdd4-4110-ae46-194e661d622b.json") assert parser.task_id == "sample_1" assert parser.input_data_id == "c86205d1-bdd4-4110-ae46-194e661d622b" + + dict_simple_annotation = parser.load_json() + assert type(dict_simple_annotation) == dict + assert "details" in dict_simple_annotation + with pytest.raises(AnnotationOuterFileNotFoundError): parser.open_outer_file("foo") @@ -186,5 +213,28 @@ def test_full_annotation_dir(self): ) assert parser.task_id == "sample_1" assert parser.input_data_id == "c86205d1-bdd4-4110-ae46-194e661d622b" + + dict_simple_annotation = parser.load_json() + assert type(dict_simple_annotation) == dict + assert "details" in dict_simple_annotation + with pytest.raises(AnnotationOuterFileNotFoundError): parser.open_outer_file("foo") + + def convert_deitail_data(self, dict_data): + if dict_data["_type"] == "Points": + dict_data["type"] = dict_data["_type"] + return FullAnnotationDataPoints.from_dict(dict_data) + else: + return dict_data + + def test_parse_for_zip(self): + zip_path = Path(test_dir / "full-annotation.zip") + with zipfile.ZipFile(zip_path) as zip_file: + parser = FullAnnotationZipParser(zip_file, "sample_1/c86205d1-bdd4-4110-ae46-194e661d622b.json") + + full_annotation = parser.parse() + assert type(full_annotation.details[0].data) == dict + + full_annotation2 = parser.parse(self.convert_deitail_data) + assert type(full_annotation2.details[0].data) == FullAnnotationDataPoints