diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8c2015de..fd936b55 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,7 +19,9 @@ "forwardPorts": [], "runArgs": [ "--init", - "--net=host" + "--net=host", + "--env=ANNOFAB_USER_ID", + "--env=ANNOFAB_PASSWORD" ], "containerEnv": { "CONTAINER_WORKSPACE": "${containerWorkspaceFolder}", @@ -31,6 +33,7 @@ "ms-python.python", "ms-python.vscode-pylance", "streetsidesoftware.code-spell-checker", - "bungcip.better-toml" + "bungcip.better-toml", + "njpwerner.autodocstring" ] } \ No newline at end of file diff --git a/.flake8 b/.flake8 index de5b7dde..3808f5fb 100644 --- a/.flake8 +++ b/.flake8 @@ -5,4 +5,5 @@ max-line-length = 120 # https://black.readthedocs.io/en/stable/the_black_code_style.html#line-length extend-ignore = E203, W503 +exclude = test_*.py diff --git a/.travis.yml b/.travis.yml index 126c8281..32c9ba89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -dist: xenial +version: ~> 1.0 language: python python: - "3.6" @@ -8,7 +8,9 @@ python: install: - pip install poetry - poetry install - - echo -e "machine annofab.com\nlogin FOO\npassword BAR\n" > ~/.netrc && chmod 600 ~/.netrc +env: + # PyPIへのアクセス過多などで失敗するケースがあるので、タイムアウトを設定する + - PIP_DEFAULT_TIMEOUT=100 script: - make lint - pytest tests/test_local*.py diff --git a/.vscode/cspell.json b/.vscode/cspell.json new file mode 100644 index 00000000..672afefc --- /dev/null +++ b/.vscode/cspell.json @@ -0,0 +1,25 @@ +{ + "version": "0.1", + "ignorePaths": [ + "**/.git/objects/**", + ".devcontainer/", + ".vscode/", + "LICENSE", + "tests/", + // 以下は自動生成されるファイルなのでチェックしない + "generated_*.py", + "models.py", + "annofabapi/dataclass/*.py" + ], + "enabledLanguageIds": [ + "python" + ], + "words": [ + "additionals", + "astimezone", + "asyncio", + "dateutil", + "pylint", + "tzlocal" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c8adac05..45586fc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,19 @@ { - "python.linting.pylintEnabled": true, + + "python.pythonPath": ".venv/bin/python", + "python.languageServer": "Pylance", + "python.autoComplete.addBrackets": true, "python.autoComplete.extraPaths": [ ".venv/lib/python3.8/site-packages/" ], + // "poetry run"コマンドで実行することを想定しているので、venv環境を自動的にactivateしないようにする + "python.terminal.activateEnvironment":false, "python.formatting.blackPath": ".venv/bin/black", "python.formatting.provider": "black", "python.linting.enabled": true, "python.linting.lintOnSave": true, + "python.linting.pylintEnabled": true, "python.linting.pylintPath": ".venv/bin/pylint", "python.linting.pylintArgs": [ "--rcfile setup.cfg", @@ -37,14 +43,6 @@ "source.organizeImports": true }, }, - "cSpell.ignorePaths": [ - "**/.git/objects/**", - ".devcontainer/", - ".vscode/", - "LICENSE" - ], - "cSpell.words": [ - "pylint", - "asyncio" - ] + "python.testing.autoTestDiscoverOnSaveEnabled": false, + } \ No newline at end of file diff --git a/annofabapi/__version__.py b/annofabapi/__version__.py index 4d8afa5b..058b03fe 100644 --- a/annofabapi/__version__.py +++ b/annofabapi/__version__.py @@ -1 +1 @@ -__version__ = "0.45.0" +__version__ = "0.45.1" diff --git a/annofabapi/api2.py b/annofabapi/api2.py index 89dd5625..0975b1b5 100644 --- a/annofabapi/api2.py +++ b/annofabapi/api2.py @@ -45,7 +45,7 @@ def _request_wrapper( request_body: Optional[Any] = None, ) -> Tuple[Any, requests.Response]: """ - HTTP Requestを投げて、Reponseを返す。 + HTTP Requestを投げて、Responseを返す。 Args: http_method: url_path: diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index 90b582fc..919a661f 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -542,13 +542,49 @@ def __to_annotation_detail_for_request( return dest_obj + def __convert_annotation_specs_labels_v2_to_v1( + self, labels_v2: List[Dict[str, Any]], additionals_v2: List[Dict[str, Any]] + ) -> List[LabelV1]: + """アノテーション仕様のV2版からV1版に変換する。V1版の方が扱いやすいので。 + + Args: + labels_v2 (List[Dict[str, Any]]): V2版のラベル情報 + additionals_v2 (List[Dict[str, Any]]): V2版の属性情報 + + Returns: + List[LabelV1]: V1版のラベル情報 + """ + + def get_additional(additional_data_definition_id: str) -> Optional[Dict[str, Any]]: + return _first_true( + additionals_v2, pred=lambda e: e["additional_data_definition_id"] == additional_data_definition_id + ) + + def to_label_v1(label_v2) -> LabelV1: + additional_data_definition_id_list = label_v2["additional_data_definitions"] + new_additional_data_definitions = [] + for additional_data_definition_id in additional_data_definition_id_list: + additional = get_additional(additional_data_definition_id) + if additional is not None: + new_additional_data_definitions.append(additional) + else: + raise ValueError( + f"additional_data_definition_id='{additional_data_definition_id}' に対応する属性情報が存在しません。" + f"label_id='{label_v2['label_id']}', label_name_en='{self.__get_label_name_en(label_v2)}'" + ) + label_v2["additional_data_definitions"] = new_additional_data_definitions + return label_v2 + + return [to_label_v1(label_v2) for label_v2 in labels_v2] + def put_annotation_for_simple_annotation_json( self, project_id: str, task_id: str, input_data_id: str, simple_annotation_json: str, - annotation_specs_labels: List[LabelV1], + annotation_specs_labels: List[Dict[str, Any]], + annotation_specs_additionals: Optional[List[Dict[str, Any]]] = None, ) -> bool: """ AnnoFabからダウンロードしたアノテーションzip配下のJSONと同じフォーマット(Simple Annotation)の内容から、アノテーションを登録する。 @@ -557,10 +593,15 @@ def put_annotation_for_simple_annotation_json( project_id: task_id: input_data_id: - simple_annotation_json: + simple_annotation_json: AnnoFabからダウンロードしたアノテーションzip配下のJSONのパス + annotation_specs_labels: アノテーション仕様のラベル情報。annotation_specs_additionalsが指定されている場合はV2版、指定されない場合はV1版。 + annotation_specs_additionals: アノテーション仕様の属性情報(V2版) Returns: True:アノテーション情報をした。False: 登録するアノテーション情報がなかったため、登録しなかった。 + + Notes: + 2021/07以降、引数annotation_specs_labelsはV1版をサポートしなくなる予定です。 """ parser = SimpleAnnotationDirParser(Path(simple_annotation_json)) annotation = parser.load_json() @@ -571,9 +612,14 @@ def put_annotation_for_simple_annotation_json( return False request_details: List[Dict[str, Any]] = [] + annotation_specs_labels_v1 = ( + self.__convert_annotation_specs_labels_v2_to_v1(annotation_specs_labels, annotation_specs_additionals) + if annotation_specs_additionals is not None + else annotation_specs_labels + ) for detail in details: request_detail = self.__to_annotation_detail_for_request( - project_id, parser, detail, annotation_specs_labels + project_id, parser, detail, annotation_specs_labels_v1 ) if request_detail is not None: request_details.append(request_detail) diff --git a/pyproject.toml b/pyproject.toml index bd70dccf..113538ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "annofabapi" -version = "0.45.0" +version = "0.45.1" description = "Python Clinet Library of AnnoFab WebAPI (https://annofab.com/docs/api/)" authors = ["yuji38kwmt"] license = "MIT" diff --git a/pytest.ini b/pytest.ini index 3555975c..22a3fcc8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,7 +2,7 @@ [pytest] # Don't write `pytest-cov` Option -addopts = --verbose --capture=no -rs +addopts = --verbose --capture=no -rs --ignore=tests/test_sandbox.py [annofab] endpoint_url = https://annofab.com diff --git a/tests/test_local_resource.py b/tests/test_local_resource.py index e35b1389..46c9d83b 100644 --- a/tests/test_local_resource.py +++ b/tests/test_local_resource.py @@ -13,9 +13,9 @@ class TestBuild: - def test_build_from_netrc(self): - # ".netrc"ファイルが存在すること前提 - assert isinstance(build_from_netrc(), annofabapi.Resource) + # def test_build_from_netrc(self): + # # ".netrc"ファイルが存在すること前提 + # assert isinstance(build_from_netrc(), annofabapi.Resource) def test_raise_ValueError(self): with pytest.raises(ValueError): diff --git a/tests/test_sandbox.py b/tests/test_sandbox.py new file mode 100644 index 00000000..6247c0b4 --- /dev/null +++ b/tests/test_sandbox.py @@ -0,0 +1,64 @@ +""" +AnnoFabプロジェクトやタスクに大きく依存したテストコードです。 +""" +import configparser +import datetime +import os +import uuid + +import pytest +import requests +from more_itertools import first_true + +import annofabapi +import annofabapi.utils +from annofabapi.models import GraphType, JobType +from tests.utils_for_test import WrapperForTest, create_csv_for_task + +# プロジェクトトップに移動する +os.chdir(os.path.dirname(os.path.abspath(__file__)) + "/../") +inifile = configparser.ConfigParser() +inifile.read("./pytest.ini", "UTF-8") + +project_id = inifile["annofab"]["project_id"] +task_id = inifile["annofab"]["task_id"] + + +test_dir = "./tests/data" +out_dir = "./tests/out" + +endpoint_url = inifile["annofab"].get("endpoint_url", None) +if endpoint_url is not None: + service = annofabapi.build(endpoint_url=endpoint_url) +else: + service = annofabapi.build() + + +class TestAnnotation: + def test_wrapper_put_annotation_for_simple_annotation_json_v1(self): + """2021/07以降に廃止する予定""" + project_id = "bf530c4e-1185-4a0c-994f-502fb01ea37e" + annotation_specs_v1, _ = service.api.get_annotation_specs(project_id, query_params={"v": "1"}) + service.wrapper.put_annotation_for_simple_annotation_json( + project_id=project_id, + task_id="sample_0", + input_data_id="0733d1e1-ef85-455e-aec0-ff05c499b711", + simple_annotation_json=str( + test_dir + "/simple-annotation/sample_1/c6e1c2ec-6c7c-41c6-9639-4244c2ed2839.json" + ), + annotation_specs_labels=annotation_specs_v1["labels"], + ) + + def test_wrapper_put_annotation_for_simple_annotation_json_v2(self): + project_id = "bf530c4e-1185-4a0c-994f-502fb01ea37e" + annotation_specs_v2, _ = service.api.get_annotation_specs(project_id, query_params={"v": "2"}) + service.wrapper.put_annotation_for_simple_annotation_json( + project_id=project_id, + task_id="sample_0", + input_data_id="0733d1e1-ef85-455e-aec0-ff05c499b711", + simple_annotation_json=str( + test_dir + "/simple-annotation/sample_1/c6e1c2ec-6c7c-41c6-9639-4244c2ed2839.json" + ), + annotation_specs_labels=annotation_specs_v2["labels"], + annotation_specs_additionals=annotation_specs_v2["additionals"], + )