From 85ad277db3658fbb78ef31e23b68df04f79e9c68 Mon Sep 17 00:00:00 2001 From: yuji38kwmt Date: Thu, 12 Mar 2026 12:17:52 +0900 Subject: [PATCH 1/4] =?UTF-8?q?download=5Fannotation=5Farchive=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=81=AB=E6=9B=B4=E6=96=B0=E3=82=B8?= =?UTF-8?q?=E3=83=A7=E3=83=96=E3=81=AE=E5=BE=85=E6=A9=9F=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- annofabapi/wrapper.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index 5cd442e6..acab8f18 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -291,20 +291,48 @@ def get_editor_annotation_or_none( _raise_for_status(response) return content - def download_annotation_archive(self, project_id: str, dest_path: str | Path) -> str: + def download_annotation_archive( + self, + project_id: str, + dest_path: str | Path, + *, + job_access_interval: int = 60, + max_job_access: int = 360, + ) -> str: """ simpleアノテーションZIPをダウンロードする。 + アノテーションZIPの更新中(HTTP 409, WORKER_ALREADY_INVOKED)の場合は、 + 更新ジョブ(gen-annotation)が完了するまで待ってからダウンロードする。 + Args: project_id: プロジェクトID dest_path: ダウンロード先のファイルパス + job_access_interval: アノテーションZIPの更新ジョブにアクセスする間隔[秒] + max_job_access: アノテーションZIPの更新ジョブに最大何回アクセスするか Returns: ダウンロード元のURL """ - # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 - _, response = self.api.get_annotation_archive(project_id) + try: + # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 + _, response = self.api.get_annotation_archive(project_id) + except requests.exceptions.HTTPError as e: + if e.response is not None and e.response.status_code == requests.codes.conflict: + logger.info( + "project_id='%s' :: アノテーションZIPの更新中のため、更新ジョブ(gen-annotation)が完了するまで待ちます。", + project_id, + ) + self.wait_until_job_finished( + project_id, + ProjectJobType.GEN_ANNOTATION, + job_access_interval=job_access_interval, + max_job_access=max_job_access, + ) + _, response = self.api.get_annotation_archive(project_id) + else: + raise url = response.headers["Location"] self.download(url, dest_path, logger_prefix=f"project_id='{project_id}', ダウンロード対象のファイル='SimpleアノテーションZIP'") return url From 40c20436e8a73bb1eecdd4e328ef05d3e668a313 Mon Sep 17 00:00:00 2001 From: yuji38kwmt Date: Thu, 12 Mar 2026 12:24:19 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=E3=82=A2=E3=83=8E=E3=83=86=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3ZIP=E3=81=AE=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=B8=AD=E3=81=AB=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E5=87=A6=E7=90=86=E3=82=92=E6=94=B9=E5=96=84?= =?UTF-8?q?=E3=81=97=E3=80=81=E7=89=B9=E5=AE=9A=E3=81=AE=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AB=E5=9F=BA=E3=81=A5?= =?UTF-8?q?=E3=81=84=E3=81=A6=E5=BE=85=E6=A9=9F=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- annofabapi/wrapper.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index acab8f18..09b09962 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -320,17 +320,24 @@ def download_annotation_archive( _, response = self.api.get_annotation_archive(project_id) except requests.exceptions.HTTPError as e: if e.response is not None and e.response.status_code == requests.codes.conflict: - logger.info( - "project_id='%s' :: アノテーションZIPの更新中のため、更新ジョブ(gen-annotation)が完了するまで待ちます。", - project_id, - ) - self.wait_until_job_finished( - project_id, - ProjectJobType.GEN_ANNOTATION, - job_access_interval=job_access_interval, - max_job_access=max_job_access, - ) - _, response = self.api.get_annotation_archive(project_id) + try: + error_codes = [err["error_code"] for err in e.response.json()["errors"]] + except Exception: + error_codes = [] + if "WORKER_ALREADY_INVOKED" in error_codes: + logger.info( + "project_id='%s' :: アノテーションZIPの更新中のため、更新ジョブ(gen-annotation)が完了するまで待ちます。", + project_id, + ) + self.wait_until_job_finished( + project_id, + ProjectJobType.GEN_ANNOTATION, + job_access_interval=job_access_interval, + max_job_access=max_job_access, + ) + _, response = self.api.get_annotation_archive(project_id) + else: + raise else: raise url = response.headers["Location"] From 915d97bf1736ce329a5b54382981570b3e61f669 Mon Sep 17 00:00:00 2001 From: yuji38kwmt Date: Thu, 12 Mar 2026 12:35:04 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E3=81=AEcontent-type=E3=81=AB=E9=96=A2=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=80=81Location=E3=83=98=E3=83=83=E3=83=80?= =?UTF-8?q?=E3=81=AE=E5=8F=82=E7=85=A7=E3=82=92=E6=98=8E=E7=A2=BA=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- annofabapi/wrapper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index 09b09962..49d72673 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -316,7 +316,6 @@ def download_annotation_archive( """ try: - # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 _, response = self.api.get_annotation_archive(project_id) except requests.exceptions.HTTPError as e: if e.response is not None and e.response.status_code == requests.codes.conflict: @@ -340,6 +339,8 @@ def download_annotation_archive( raise else: raise + + # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 url = response.headers["Location"] self.download(url, dest_path, logger_prefix=f"project_id='{project_id}', ダウンロード対象のファイル='SimpleアノテーションZIP'") return url From db7ac1d2aca6f426d86b4c8a5803dec6f0874fe1 Mon Sep 17 00:00:00 2001 From: yuji38kwmt Date: Thu, 12 Mar 2026 12:41:44 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=E3=82=92=E6=94=B9?= =?UTF-8?q?=E5=96=84=E3=81=97=E3=80=81=E7=89=B9=E5=AE=9A=E3=81=AE=E4=BE=8B?= =?UTF-8?q?=E5=A4=96=E3=81=AE=E3=81=BF=E3=82=92=E3=82=AD=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=81=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=82=E3=81=BE=E3=81=9F=E3=80=81=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AE=E4=BD=8D=E7=BD=AE=E3=82=92=E8=AA=BF?= =?UTF-8?q?=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- annofabapi/wrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/annofabapi/wrapper.py b/annofabapi/wrapper.py index 49d72673..20eada09 100644 --- a/annofabapi/wrapper.py +++ b/annofabapi/wrapper.py @@ -321,7 +321,7 @@ def download_annotation_archive( if e.response is not None and e.response.status_code == requests.codes.conflict: try: error_codes = [err["error_code"] for err in e.response.json()["errors"]] - except Exception: + except (ValueError, KeyError, TypeError): error_codes = [] if "WORKER_ALREADY_INVOKED" in error_codes: logger.info( @@ -340,7 +340,7 @@ def download_annotation_archive( else: raise - # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 + # 2022/01時点でレスポンスのcontent-typeが"text/plain"なので、contentの型がdictにならない。したがって、Locationヘッダを参照する。 url = response.headers["Location"] self.download(url, dest_path, logger_prefix=f"project_id='{project_id}', ダウンロード対象のファイル='SimpleアノテーションZIP'") return url