diff --git a/annofabapi/utils.py b/annofabapi/utils.py index 912534d0..80f0cc18 100644 --- a/annofabapi/utils.py +++ b/annofabapi/utils.py @@ -95,3 +95,33 @@ def to_iso8601_extension(d: datetime.datetime, tz: Optional[datetime.tzinfo] = N tz = dateutil.tz.tzlocal() d = d.astimezone(tz) return d.isoformat(timespec='milliseconds') + + +def allow_404_error(function): + """ + Not Found Error(404)を無視(許容)して、処理する。Not Foundのとき戻りはNoneになる。 + リソースの存在確認などに利用する。 + try-exceptを行う。また404 Errorが発生したときのエラーログを無効化する + """ + def wrapped(*args, **kwargs): + annofabapi_logger_level = logging.getLogger("annofabapi").level + backoff_logger_level = logging.getLogger("backoff").level + + try: + # 不要なログが出力されないようにする + logging.getLogger("annofabapi").setLevel(level=logging.INFO) + logging.getLogger("backoff").setLevel(level=logging.CRITICAL) + + return function(*args, **kwargs) + + except requests.exceptions.HTTPError as e: + if e.response.status_code == requests.codes.not_found: + return None + else: + raise e + finally: + # ロガーの設定を元に戻す + logging.getLogger("annofabapi").setLevel(level=annofabapi_logger_level) + logging.getLogger("backoff").setLevel(level=backoff_logger_level) + + return wrapped diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index c7d55078..ba032e99 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -12,8 +12,10 @@ import annofabapi.utils from annofabapi import AnnofabApi from annofabapi.exceptions import AnnofabApiException -from annofabapi.models import (AnnotationSpecs, InputData, Inspection, Instruction, JobInfo, JobType, MyOrganization, - OrganizationMember, Project, ProjectMember, SupplementaryData, Task, InspectionStatus) +from annofabapi.models import (AnnotationSpecs, InputData, Inspection, InspectionStatus, Instruction, JobInfo, JobType, + MyOrganization, Organization, OrganizationMember, Project, ProjectMember, + SupplementaryData, Task) +from annofabapi.utils import allow_404_error logger = logging.getLogger(__name__) @@ -103,7 +105,7 @@ def _get_all_objects(func_get_list: Callable, limit: int, **kwargs_for_func_get_ return all_objects ######################################### - # Public Method : AfAnnotationApi + # Public Method : Annotation ######################################### def download_annotation_archive(self, project_id: str, dest_path: str, v2: bool = False) -> str: """ @@ -165,7 +167,7 @@ def get_all_annotation_list(self, project_id: str, query_params=query_params) ######################################### - # Public Method : AfAnnotationSpecsApi + # Public Method : AnnotationSpecs ######################################### def copy_annotation_specs(self, src_project_id: str, dest_project_id: str, comment: Optional[str] = None) -> AnnotationSpecs: @@ -196,8 +198,23 @@ def copy_annotation_specs(self, src_project_id: str, dest_project_id: str, return self.api.put_annotation_specs(dest_project_id, request_body=request_body)[0] ######################################### - # Public Method : AfInputApi + # Public Method : Input ######################################### + @allow_404_error + def get_input_data_or_none(self, project_id: str, input_data_id: str) -> Optional[InputData]: + """ + 入力データを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + project_id: + input_data_id: + + Returns: + 入力データ + """ + input_data, _ = self.api.get_input_data(project_id, input_data_id) + return input_data + def get_all_input_data_list(self, project_id: str, query_params: Optional[Dict[str, Any]] = None) -> List[InputData]: """ @@ -277,7 +294,7 @@ def put_input_data_from_file(self, project_id: str, input_data_id: str, file_pat return self.api.put_input_data(project_id, input_data_id, request_body=copied_request_body)[0] ######################################### - # Public Method : AfStatisticsApi + # Public Method : Statistics ######################################### def get_worktime_statistics(self, project_id: str) -> Dict[str, Any]: """ @@ -296,7 +313,7 @@ def get_worktime_statistics(self, project_id: str) -> Dict[str, Any]: return requests.get(url).json() ######################################### - # Public Method : AfSupplementaryApi + # Public Method : Supplementary ######################################### def put_supplementary_data_from_file(self, project_id, input_data_id: str, supplementary_data_id: str, file_path: str, request_body: Dict[str, Any], @@ -344,7 +361,7 @@ def put_supplementary_data_from_file(self, project_id, input_data_id: str, suppl request_body=copied_request_body)[0] ######################################### - # Public Method : AfInspection + # Public Method : Inspection ######################################### def update_status_of_inspections(self, project_id: str, task_id: str, input_data_id: str, filter_inspection: Callable[[Inspection], bool], @@ -386,7 +403,7 @@ def search_updated_inspections(arg_inspection: Inspection) -> bool: return content ######################################### - # Public Method : AfMyApi + # Public Method : My ######################################### def get_all_my_organizations(self) -> List[MyOrganization]: """ @@ -398,8 +415,22 @@ def get_all_my_organizations(self) -> List[MyOrganization]: return self._get_all_objects(self.api.get_my_organizations, limit=200) ######################################### - # Public Method : AfOrganizationApi + # Public Method : Organization ######################################### + @allow_404_error + def get_organization_or_none(self, organization_name: str) -> Optional[Organization]: + """ + 組織情報を取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + organization_name: 組織名 + + Returns: + 組織情報 + """ + content, _ = self.api.get_organization(organization_name) + return content + def get_all_projects_of_organization(self, organization_name: str, query_params: Optional[Dict[str, Any]] = None) -> List[Project]: """ @@ -416,8 +447,23 @@ def get_all_projects_of_organization(self, organization_name: str, organization_name=organization_name, query_params=query_params) ######################################### - # Public Method : AfOrganizationMemberApi + # Public Method : OrganizationMember ######################################### + @allow_404_error + def get_organization_member_or_none(self, organization_name: str, user_id: str) -> Optional[OrganizationMember]: + """ + 組織メンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + organization_name: 組織名 + user_id: + + Returns: + 組織メンバ + """ + content, _ = self.api.get_organization_member(organization_name, user_id) + return content + def get_all_organization_members(self, organization_name: str) -> List[OrganizationMember]: """ すべての組織メンバ一覧を取得する @@ -434,8 +480,22 @@ def get_all_organization_members(self, organization_name: str) -> List[Organizat return content["list"] ######################################### - # Public Method : AfProjectApi + # Public Method : Project ######################################### + @allow_404_error + def get_project_or_none(self, project_id: str) -> Optional[Project]: + """ + プロジェクトを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + project_id: + + Returns: + プロジェクト + """ + content, _ = self.api.get_project(project_id) + return content + def download_project_tasks_url(self, project_id: str, dest_path: str) -> str: """ プロジェクトのタスク全件ファイルをダウンロードする。 @@ -494,8 +554,23 @@ def download_project_task_history_events_url(self, project_id: str, dest_path: s return url ######################################### - # Public Method : AfProjectMemberApi + # Public Method : ProjectMember ######################################### + @allow_404_error + def get_project_member_or_none(self, project_id: str, user_id: str) -> Optional[ProjectMember]: + """ + プロジェクトメンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + project_id: + user_id: + + Returns: + プロジェクトメンバ + """ + content, _ = self.api.get_project_member(project_id, user_id) + return content + def get_all_project_members(self, project_id: str, query_params: Optional[Dict[str, Any]] = None) -> List[ProjectMember]: """ @@ -640,7 +715,7 @@ def to_inactive(arg_member): return self.put_project_members(dest_project_id, src_project_members) ######################################### - # Public Method : AfTaskApi + # Public Method : Task ######################################### def initiate_tasks_generation_by_csv(self, project_id: str, csvfile_path: str, task_id_prefix: str) -> Dict[str, Any]: @@ -669,6 +744,21 @@ def initiate_tasks_generation_by_csv(self, project_id: str, csvfile_path: str, } return self.api.initiate_tasks_generation(project_id, request_body=request_body)[0] + @allow_404_error + def get_task_or_none(self, project_id: str, task_id: str) -> Optional[Task]: + """ + タスクを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。 + + Args: + project_id: + task_id: + + Returns: + タスク + """ + content, _ = self.api.get_task(project_id, task_id) + return content + def get_all_tasks(self, project_id: str, query_params: Optional[Dict[str, Any]] = None) -> List[Task]: """ すべてのタスクを取得する。 diff --git a/tests/test_api.py b/tests/test_api.py index 50e86174..48362e55 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -41,6 +41,9 @@ annofab_user_id = service.api.login_user_id +task_id = test_wrapper.get_first_task_id(project_id) +input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id) + def test_account(): pass @@ -84,9 +87,6 @@ def test_annotation(): batchUpdateAnnotations, putAnnotationはテストしない. """ - task_id = test_wrapper.get_first_task_id(project_id) - input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id) - print("get_annotation_list in wrapper.get_all_annotation_list") assert len(wrapper.get_all_annotation_list(project_id, {"query": {"task_id": task_id}})) >= 0 @@ -182,8 +182,6 @@ def test_input(): def test_supplementary(): - input_data_id = test_wrapper.get_first_input_data(project_id)['input_data_id'] - print("wrapper.put_supplementary_data_from_file(内部でput_supplementary_dataが実行される)") supplementary_data_id = str(uuid.uuid4()) request_body = {'supplementary_data_number': 1} @@ -206,9 +204,6 @@ def test_inspection(): batchUpdateInspectionsはテストしない. """ - task_id = test_wrapper.get_first_task_id(project_id) - input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id) - # # 作業中のタスクでなくても、検査コメントは付与できる # req_inspection = [{ # "data": { @@ -327,7 +322,6 @@ def test_statistics(self): def test_graph_marker(self): print("get_markers") content, _ = api.get_markers(project_id) - print(content) assert type(content) == dict markers = [{ @@ -464,3 +458,46 @@ def test_webhook(): print("delete_webhook") assert type(api.delete_webhook(project_id, test_webhook_id)[0]) == dict + + +class TestGetObjOrNone: + """ + wrapper.get_xxx_or_none メソッドの確認 + """ + def test_get_input_data_or_none(self): + assert type(wrapper.get_input_data_or_none(project_id, input_data_id)) == dict + + assert wrapper.get_input_data_or_none(project_id, "not-exists") is None + + assert wrapper.get_input_data_or_none("not-exists", input_data_id) is None + + def test_get_organization_or_none(self): + assert type(wrapper.get_organization_or_none(organization_name)) == dict + + assert wrapper.get_organization_or_none("not-exists") is None + + def test_get_organization_member_or_none(self): + assert type(wrapper.get_organization_member_or_none(organization_name, annofab_user_id)) == dict + + assert wrapper.get_organization_member_or_none("not-exists", annofab_user_id) is None + + assert wrapper.get_organization_member_or_none(organization_name, "not-exists") is None + + def test_get_project_or_none(self): + assert type(wrapper.get_project_or_none(project_id)) == dict + + assert wrapper.get_project_or_none("not-exists") is None + + def test_get_project_member_or_none(self): + assert type(wrapper.get_project_member_or_none(project_id, annofab_user_id)) == dict + + assert wrapper.get_project_member_or_none(project_id, "not-exists") is None + + assert wrapper.get_project_member_or_none("not-exists", annofab_user_id) is None + + def test_get_task_or_none(self): + assert type(wrapper.get_task_or_none(project_id, task_id)) == dict + + assert wrapper.get_task_or_none(project_id, "not-exists") is None + + assert wrapper.get_task_or_none("not-exists", task_id) is None