Skip to content

Commit 046570c

Browse files
authored
Merge pull request #94 from kurusugawa-computer/feature/or-none-method
404 ErrorのときはNoneを返すメソッドを追加
2 parents 624370b + 75cb23f commit 046570c

File tree

3 files changed

+180
-23
lines changed

3 files changed

+180
-23
lines changed

annofabapi/utils.py

+30
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,33 @@ def to_iso8601_extension(d: datetime.datetime, tz: Optional[datetime.tzinfo] = N
9595
tz = dateutil.tz.tzlocal()
9696
d = d.astimezone(tz)
9797
return d.isoformat(timespec='milliseconds')
98+
99+
100+
def allow_404_error(function):
101+
"""
102+
Not Found Error(404)を無視(許容)して、処理する。Not Foundのとき戻りはNoneになる。
103+
リソースの存在確認などに利用する。
104+
try-exceptを行う。また404 Errorが発生したときのエラーログを無効化する
105+
"""
106+
def wrapped(*args, **kwargs):
107+
annofabapi_logger_level = logging.getLogger("annofabapi").level
108+
backoff_logger_level = logging.getLogger("backoff").level
109+
110+
try:
111+
# 不要なログが出力されないようにする
112+
logging.getLogger("annofabapi").setLevel(level=logging.INFO)
113+
logging.getLogger("backoff").setLevel(level=logging.CRITICAL)
114+
115+
return function(*args, **kwargs)
116+
117+
except requests.exceptions.HTTPError as e:
118+
if e.response.status_code == requests.codes.not_found:
119+
return None
120+
else:
121+
raise e
122+
finally:
123+
# ロガーの設定を元に戻す
124+
logging.getLogger("annofabapi").setLevel(level=annofabapi_logger_level)
125+
logging.getLogger("backoff").setLevel(level=backoff_logger_level)
126+
127+
return wrapped

annofabapi/wrapper.py

+104-14
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
import annofabapi.utils
1313
from annofabapi import AnnofabApi
1414
from annofabapi.exceptions import AnnofabApiException
15-
from annofabapi.models import (AnnotationSpecs, InputData, Inspection, Instruction, JobInfo, JobType, MyOrganization,
16-
OrganizationMember, Project, ProjectMember, SupplementaryData, Task, InspectionStatus)
15+
from annofabapi.models import (AnnotationSpecs, InputData, Inspection, InspectionStatus, Instruction, JobInfo, JobType,
16+
MyOrganization, Organization, OrganizationMember, Project, ProjectMember,
17+
SupplementaryData, Task)
18+
from annofabapi.utils import allow_404_error
1719

1820
logger = logging.getLogger(__name__)
1921

@@ -103,7 +105,7 @@ def _get_all_objects(func_get_list: Callable, limit: int, **kwargs_for_func_get_
103105
return all_objects
104106

105107
#########################################
106-
# Public Method : AfAnnotationApi
108+
# Public Method : Annotation
107109
#########################################
108110
def download_annotation_archive(self, project_id: str, dest_path: str, v2: bool = False) -> str:
109111
"""
@@ -165,7 +167,7 @@ def get_all_annotation_list(self, project_id: str,
165167
query_params=query_params)
166168

167169
#########################################
168-
# Public Method : AfAnnotationSpecsApi
170+
# Public Method : AnnotationSpecs
169171
#########################################
170172
def copy_annotation_specs(self, src_project_id: str, dest_project_id: str,
171173
comment: Optional[str] = None) -> AnnotationSpecs:
@@ -196,8 +198,23 @@ def copy_annotation_specs(self, src_project_id: str, dest_project_id: str,
196198
return self.api.put_annotation_specs(dest_project_id, request_body=request_body)[0]
197199

198200
#########################################
199-
# Public Method : AfInputApi
201+
# Public Method : Input
200202
#########################################
203+
@allow_404_error
204+
def get_input_data_or_none(self, project_id: str, input_data_id: str) -> Optional[InputData]:
205+
"""
206+
入力データを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
207+
208+
Args:
209+
project_id:
210+
input_data_id:
211+
212+
Returns:
213+
入力データ
214+
"""
215+
input_data, _ = self.api.get_input_data(project_id, input_data_id)
216+
return input_data
217+
201218
def get_all_input_data_list(self, project_id: str,
202219
query_params: Optional[Dict[str, Any]] = None) -> List[InputData]:
203220
"""
@@ -277,7 +294,7 @@ def put_input_data_from_file(self, project_id: str, input_data_id: str, file_pat
277294
return self.api.put_input_data(project_id, input_data_id, request_body=copied_request_body)[0]
278295

279296
#########################################
280-
# Public Method : AfStatisticsApi
297+
# Public Method : Statistics
281298
#########################################
282299
def get_worktime_statistics(self, project_id: str) -> Dict[str, Any]:
283300
"""
@@ -296,7 +313,7 @@ def get_worktime_statistics(self, project_id: str) -> Dict[str, Any]:
296313
return requests.get(url).json()
297314

298315
#########################################
299-
# Public Method : AfSupplementaryApi
316+
# Public Method : Supplementary
300317
#########################################
301318
def put_supplementary_data_from_file(self, project_id, input_data_id: str, supplementary_data_id: str,
302319
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
344361
request_body=copied_request_body)[0]
345362

346363
#########################################
347-
# Public Method : AfInspection
364+
# Public Method : Inspection
348365
#########################################
349366
def update_status_of_inspections(self, project_id: str, task_id: str, input_data_id: str,
350367
filter_inspection: Callable[[Inspection], bool],
@@ -386,7 +403,7 @@ def search_updated_inspections(arg_inspection: Inspection) -> bool:
386403
return content
387404

388405
#########################################
389-
# Public Method : AfMyApi
406+
# Public Method : My
390407
#########################################
391408
def get_all_my_organizations(self) -> List[MyOrganization]:
392409
"""
@@ -398,8 +415,22 @@ def get_all_my_organizations(self) -> List[MyOrganization]:
398415
return self._get_all_objects(self.api.get_my_organizations, limit=200)
399416

400417
#########################################
401-
# Public Method : AfOrganizationApi
418+
# Public Method : Organization
402419
#########################################
420+
@allow_404_error
421+
def get_organization_or_none(self, organization_name: str) -> Optional[Organization]:
422+
"""
423+
組織情報を取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
424+
425+
Args:
426+
organization_name: 組織名
427+
428+
Returns:
429+
組織情報
430+
"""
431+
content, _ = self.api.get_organization(organization_name)
432+
return content
433+
403434
def get_all_projects_of_organization(self, organization_name: str,
404435
query_params: Optional[Dict[str, Any]] = None) -> List[Project]:
405436
"""
@@ -416,8 +447,23 @@ def get_all_projects_of_organization(self, organization_name: str,
416447
organization_name=organization_name, query_params=query_params)
417448

418449
#########################################
419-
# Public Method : AfOrganizationMemberApi
450+
# Public Method : OrganizationMember
420451
#########################################
452+
@allow_404_error
453+
def get_organization_member_or_none(self, organization_name: str, user_id: str) -> Optional[OrganizationMember]:
454+
"""
455+
組織メンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
456+
457+
Args:
458+
organization_name: 組織名
459+
user_id:
460+
461+
Returns:
462+
組織メンバ
463+
"""
464+
content, _ = self.api.get_organization_member(organization_name, user_id)
465+
return content
466+
421467
def get_all_organization_members(self, organization_name: str) -> List[OrganizationMember]:
422468
"""
423469
すべての組織メンバ一覧を取得する
@@ -434,8 +480,22 @@ def get_all_organization_members(self, organization_name: str) -> List[Organizat
434480
return content["list"]
435481

436482
#########################################
437-
# Public Method : AfProjectApi
483+
# Public Method : Project
438484
#########################################
485+
@allow_404_error
486+
def get_project_or_none(self, project_id: str) -> Optional[Project]:
487+
"""
488+
プロジェクトを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
489+
490+
Args:
491+
project_id:
492+
493+
Returns:
494+
プロジェクト
495+
"""
496+
content, _ = self.api.get_project(project_id)
497+
return content
498+
439499
def download_project_tasks_url(self, project_id: str, dest_path: str) -> str:
440500
"""
441501
プロジェクトのタスク全件ファイルをダウンロードする。
@@ -494,8 +554,23 @@ def download_project_task_history_events_url(self, project_id: str, dest_path: s
494554
return url
495555

496556
#########################################
497-
# Public Method : AfProjectMemberApi
557+
# Public Method : ProjectMember
498558
#########################################
559+
@allow_404_error
560+
def get_project_member_or_none(self, project_id: str, user_id: str) -> Optional[ProjectMember]:
561+
"""
562+
プロジェクトメンバを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
563+
564+
Args:
565+
project_id:
566+
user_id:
567+
568+
Returns:
569+
プロジェクトメンバ
570+
"""
571+
content, _ = self.api.get_project_member(project_id, user_id)
572+
return content
573+
499574
def get_all_project_members(self, project_id: str,
500575
query_params: Optional[Dict[str, Any]] = None) -> List[ProjectMember]:
501576
"""
@@ -640,7 +715,7 @@ def to_inactive(arg_member):
640715
return self.put_project_members(dest_project_id, src_project_members)
641716

642717
#########################################
643-
# Public Method : AfTaskApi
718+
# Public Method : Task
644719
#########################################
645720
def initiate_tasks_generation_by_csv(self, project_id: str, csvfile_path: str,
646721
task_id_prefix: str) -> Dict[str, Any]:
@@ -669,6 +744,21 @@ def initiate_tasks_generation_by_csv(self, project_id: str, csvfile_path: str,
669744
}
670745
return self.api.initiate_tasks_generation(project_id, request_body=request_body)[0]
671746

747+
@allow_404_error
748+
def get_task_or_none(self, project_id: str, task_id: str) -> Optional[Task]:
749+
"""
750+
タスクを取得する。存在しない場合(HTTP 404 Error)はNoneを返す。
751+
752+
Args:
753+
project_id:
754+
task_id:
755+
756+
Returns:
757+
タスク
758+
"""
759+
content, _ = self.api.get_task(project_id, task_id)
760+
return content
761+
672762
def get_all_tasks(self, project_id: str, query_params: Optional[Dict[str, Any]] = None) -> List[Task]:
673763
"""
674764
すべてのタスクを取得する。

tests/test_api.py

+46-9
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141

4242
annofab_user_id = service.api.login_user_id
4343

44+
task_id = test_wrapper.get_first_task_id(project_id)
45+
input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id)
46+
4447

4548
def test_account():
4649
pass
@@ -84,9 +87,6 @@ def test_annotation():
8487
batchUpdateAnnotations, putAnnotationはテストしない.
8588
"""
8689

87-
task_id = test_wrapper.get_first_task_id(project_id)
88-
input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id)
89-
9090
print("get_annotation_list in wrapper.get_all_annotation_list")
9191
assert len(wrapper.get_all_annotation_list(project_id, {"query": {"task_id": task_id}})) >= 0
9292

@@ -182,8 +182,6 @@ def test_input():
182182

183183

184184
def test_supplementary():
185-
input_data_id = test_wrapper.get_first_input_data(project_id)['input_data_id']
186-
187185
print("wrapper.put_supplementary_data_from_file(内部でput_supplementary_dataが実行される)")
188186
supplementary_data_id = str(uuid.uuid4())
189187
request_body = {'supplementary_data_number': 1}
@@ -206,9 +204,6 @@ def test_inspection():
206204
batchUpdateInspectionsはテストしない.
207205
"""
208206

209-
task_id = test_wrapper.get_first_task_id(project_id)
210-
input_data_id = test_wrapper.get_first_input_data_id_in_task(project_id, task_id)
211-
212207
# # 作業中のタスクでなくても、検査コメントは付与できる
213208
# req_inspection = [{
214209
# "data": {
@@ -327,7 +322,6 @@ def test_statistics(self):
327322
def test_graph_marker(self):
328323
print("get_markers")
329324
content, _ = api.get_markers(project_id)
330-
print(content)
331325
assert type(content) == dict
332326

333327
markers = [{
@@ -464,3 +458,46 @@ def test_webhook():
464458

465459
print("delete_webhook")
466460
assert type(api.delete_webhook(project_id, test_webhook_id)[0]) == dict
461+
462+
463+
class TestGetObjOrNone:
464+
"""
465+
wrapper.get_xxx_or_none メソッドの確認
466+
"""
467+
def test_get_input_data_or_none(self):
468+
assert type(wrapper.get_input_data_or_none(project_id, input_data_id)) == dict
469+
470+
assert wrapper.get_input_data_or_none(project_id, "not-exists") is None
471+
472+
assert wrapper.get_input_data_or_none("not-exists", input_data_id) is None
473+
474+
def test_get_organization_or_none(self):
475+
assert type(wrapper.get_organization_or_none(organization_name)) == dict
476+
477+
assert wrapper.get_organization_or_none("not-exists") is None
478+
479+
def test_get_organization_member_or_none(self):
480+
assert type(wrapper.get_organization_member_or_none(organization_name, annofab_user_id)) == dict
481+
482+
assert wrapper.get_organization_member_or_none("not-exists", annofab_user_id) is None
483+
484+
assert wrapper.get_organization_member_or_none(organization_name, "not-exists") is None
485+
486+
def test_get_project_or_none(self):
487+
assert type(wrapper.get_project_or_none(project_id)) == dict
488+
489+
assert wrapper.get_project_or_none("not-exists") is None
490+
491+
def test_get_project_member_or_none(self):
492+
assert type(wrapper.get_project_member_or_none(project_id, annofab_user_id)) == dict
493+
494+
assert wrapper.get_project_member_or_none(project_id, "not-exists") is None
495+
496+
assert wrapper.get_project_member_or_none("not-exists", annofab_user_id) is None
497+
498+
def test_get_task_or_none(self):
499+
assert type(wrapper.get_task_or_none(project_id, task_id)) == dict
500+
501+
assert wrapper.get_task_or_none(project_id, "not-exists") is None
502+
503+
assert wrapper.get_task_or_none("not-exists", task_id) is None

0 commit comments

Comments
 (0)