From 0bac6b72c5b5e10c5570710c83fe6a63f43eeb91 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 12:47:11 +0100 Subject: [PATCH 01/10] Retry with backoff on annotation upload error --- roboflow/core/project.py | 37 ++++++++++++++++++++++++------------- roboflow/util/general.py | 17 ++++++++++++++--- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/roboflow/core/project.py b/roboflow/core/project.py index cfad1dde..cc29aaf1 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -515,26 +515,34 @@ def save_annotation( job_name=None, is_prediction: bool = False, annotation_overwrite=False, + num_retry_uploads=0, ): project_url = self.id.rsplit("/")[1] annotation_name, annotation_str = self._annotation_params(annotation_path) t0 = time.time() + upload_retry_attempts = 0 + retry = Retry(num_retry_uploads, ImageUploadError) - annotation = rfapi.save_annotation( - self.__api_key, - project_url, - annotation_name, # type: ignore[type-var] - annotation_str, # type: ignore[type-var] - image_id, - job_name=job_name, # type: ignore[type-var] - is_prediction=is_prediction, - annotation_labelmap=annotation_labelmap, - overwrite=annotation_overwrite, - ) + try: + annotation = rfapi.save_annotation( + self.__api_key, + project_url, + annotation_name, # type: ignore[type-var] + annotation_str, # type: ignore[type-var] + image_id, + job_name=job_name, # type: ignore[type-var] + is_prediction=is_prediction, + annotation_labelmap=annotation_labelmap, + overwrite=annotation_overwrite, + ) + upload_retry_attempts = retry.retries + except ImageUploadError as e: + e.retries = upload_retry_attempts + raise upload_time = time.time() - t0 - return annotation, upload_time + return annotation, upload_time, upload_retry_attempts def single_upload( self, @@ -563,6 +571,7 @@ def single_upload( uploaded_image, uploaded_annotation = None, None upload_time, annotation_time = None, None upload_retry_attempts = 0 + annotation_upload_retry_attempts = 0 if image_path: uploaded_image, upload_time, upload_retry_attempts = self.upload_image( @@ -579,13 +588,14 @@ def single_upload( image_id = uploaded_image["id"] # type: ignore[index] if annotation_path and image_id: - uploaded_annotation, annotation_time = self.save_annotation( + uploaded_annotation, annotation_time, annotation_upload_retry_attempts = self.save_annotation( annotation_path, annotation_labelmap, image_id, batch_name, is_prediction, annotation_overwrite, + num_retry_uploads=num_retry_uploads ) return { @@ -594,6 +604,7 @@ def single_upload( "upload_time": upload_time, "annotation_time": annotation_time, "upload_retry_attempts": upload_retry_attempts, + "annotation_upload_retry_attempts": annotation_upload_retry_attempts, } def _annotation_params(self, annotation_path): diff --git a/roboflow/util/general.py b/roboflow/util/general.py index fa6a29dd..a79659d6 100644 --- a/roboflow/util/general.py +++ b/roboflow/util/general.py @@ -1,4 +1,6 @@ import sys +import time +from random import random def write_line(line): @@ -12,9 +14,17 @@ def __init__(self, max_retries, retry_on): self.max_retries = max_retries self.retry_on = retry_on self.retries = 0 + + def backoff(self): + """ + Backoff for a random time based on number of retries. + """ + base_t_ms = 100 + max_t_ms = 30000 + sleep_ms = random() * min(max_t_ms, base_t_ms * 2 ** self.retries) + time.sleep(int(sleep_ms) / 1000) def __call__(self, func, *args, **kwargs): - self.retries = 0 retry_on = self.retry_on if not retry_on: retry_on = (Exception,) @@ -24,8 +34,9 @@ def __call__(self, func, *args, **kwargs): return func(*args, **kwargs) except BaseException as e: if isinstance(e, retry_on): - self.retries += 1 - if self.retries > self.max_retries: + if self.retries >= self.max_retries: raise + self.backoff() + self.retries += 1 else: raise From 53daefb68a29b1ff72b88813d25ed012ac91efbb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:49:15 +0000 Subject: [PATCH 02/10] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roboflow/core/project.py | 2 +- roboflow/util/general.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roboflow/core/project.py b/roboflow/core/project.py index cc29aaf1..a6f7c55c 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -595,7 +595,7 @@ def single_upload( batch_name, is_prediction, annotation_overwrite, - num_retry_uploads=num_retry_uploads + num_retry_uploads=num_retry_uploads, ) return { diff --git a/roboflow/util/general.py b/roboflow/util/general.py index a79659d6..9c92e552 100644 --- a/roboflow/util/general.py +++ b/roboflow/util/general.py @@ -14,14 +14,14 @@ def __init__(self, max_retries, retry_on): self.max_retries = max_retries self.retry_on = retry_on self.retries = 0 - + def backoff(self): """ Backoff for a random time based on number of retries. """ base_t_ms = 100 max_t_ms = 30000 - sleep_ms = random() * min(max_t_ms, base_t_ms * 2 ** self.retries) + sleep_ms = random() * min(max_t_ms, base_t_ms * 2**self.retries) time.sleep(int(sleep_ms) / 1000) def __call__(self, func, *args, **kwargs): From f00d587c7c77bdb988797830d899e5b2bcba7506 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 13:04:19 +0100 Subject: [PATCH 03/10] fixup! Retry with backoff on annotation upload error --- roboflow/core/workspace.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roboflow/core/workspace.py b/roboflow/core/workspace.py index 815c494f..52a404e7 100644 --- a/roboflow/core/workspace.py +++ b/roboflow/core/workspace.py @@ -349,6 +349,7 @@ def _upload_image(imagedesc): batch_name=batch_name, sequence_number=imagedesc.get("index"), sequence_size=len(images), + num_retry_uploads=num_retries, ) return image, upload_time, upload_retry_attempts @@ -376,6 +377,7 @@ def _save_annotation(image_id, imagedesc): annotation_labelmap=labelmap, image_id=image_id, job_name=batch_name, + num_retry_uploads=num_retries ) return annotation, upload_time From 19b18bacb4e909580df6566255675ec2a7859416 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:13:07 +0000 Subject: [PATCH 04/10] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roboflow/core/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roboflow/core/workspace.py b/roboflow/core/workspace.py index 52a404e7..3a59b8f4 100644 --- a/roboflow/core/workspace.py +++ b/roboflow/core/workspace.py @@ -377,7 +377,7 @@ def _save_annotation(image_id, imagedesc): annotation_labelmap=labelmap, image_id=image_id, job_name=batch_name, - num_retry_uploads=num_retries + num_retry_uploads=num_retries, ) return annotation, upload_time From a8551c002fb3458c38fd2380b50462fe20130e0b Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 13:36:52 +0100 Subject: [PATCH 05/10] Wrap `RequestException` --- roboflow/adapters/rfapi.py | 29 ++++++++++++++++++++--------- roboflow/core/project.py | 6 +++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/roboflow/adapters/rfapi.py b/roboflow/adapters/rfapi.py index af5a24c8..b6a0a188 100644 --- a/roboflow/adapters/rfapi.py +++ b/roboflow/adapters/rfapi.py @@ -4,6 +4,7 @@ from typing import Optional import requests +from requests.exceptions import RequestException from requests_toolbelt.multipart.encoder import MultipartEncoder from roboflow.config import API_URL, DEFAULT_BATCH_NAME, DEFAULT_JOB_NAME @@ -85,14 +86,21 @@ def upload_image( "file": ("imageToUpload", imgjpeg, "image/jpeg"), } ) - response = requests.post(upload_url, data=m, headers={"Content-Type": m.content_type}, timeout=(300, 300)) + + try: + response = requests.post(upload_url, data=m, headers={"Content-Type": m.content_type}, timeout=(300, 300)) + except RequestException as e: + raise ImageUploadError(str(e)) from e else: # Hosted image upload url upload_url = _hosted_upload_url(api_key, project_url, image_path, split, coalesced_batch_name, tag_names) - # Get response - response = requests.post(upload_url, timeout=(300, 300)) + try: + # Get response + response = requests.post(upload_url, timeout=(300, 300)) + except RequestException as e: + raise ImageUploadError(str(e)) from e responsejson = None try: @@ -147,12 +155,15 @@ def save_annotation( api_key, project_url, annotation_name, image_id, job_name, is_prediction, overwrite ) - response = requests.post( - upload_url, - data=json.dumps({"annotationFile": annotation_string, "labelmap": annotation_labelmap}), - headers={"Content-Type": "application/json"}, - timeout=(60, 60), - ) + try: + response = requests.post( + upload_url, + data=json.dumps({"annotationFile": annotation_string, "labelmap": annotation_labelmap}), + headers={"Content-Type": "application/json"}, + timeout=(60, 60), + ) + except RequestException as e: + raise AnnotationSaveError(str(e)) from e # Handle response responsejson = None diff --git a/roboflow/core/project.py b/roboflow/core/project.py index a6f7c55c..50b1801f 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -11,7 +11,7 @@ import requests from roboflow.adapters import rfapi -from roboflow.adapters.rfapi import ImageUploadError +from roboflow.adapters.rfapi import ImageUploadError, AnnotationSaveError from roboflow.config import API_URL, DEMO_KEYS from roboflow.core.version import Version from roboflow.util.general import Retry @@ -521,7 +521,7 @@ def save_annotation( annotation_name, annotation_str = self._annotation_params(annotation_path) t0 = time.time() upload_retry_attempts = 0 - retry = Retry(num_retry_uploads, ImageUploadError) + retry = Retry(num_retry_uploads, AnnotationSaveError) try: annotation = rfapi.save_annotation( @@ -536,7 +536,7 @@ def save_annotation( overwrite=annotation_overwrite, ) upload_retry_attempts = retry.retries - except ImageUploadError as e: + except AnnotationSaveError as e: e.retries = upload_retry_attempts raise From 021e48f0f7f2f0ec47102ecc9c4c1612ccced23c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 12:37:11 +0000 Subject: [PATCH 06/10] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- roboflow/adapters/rfapi.py | 2 +- roboflow/core/project.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/roboflow/adapters/rfapi.py b/roboflow/adapters/rfapi.py index b6a0a188..4e1a07a9 100644 --- a/roboflow/adapters/rfapi.py +++ b/roboflow/adapters/rfapi.py @@ -87,7 +87,7 @@ def upload_image( } ) - try: + try: response = requests.post(upload_url, data=m, headers={"Content-Type": m.content_type}, timeout=(300, 300)) except RequestException as e: raise ImageUploadError(str(e)) from e diff --git a/roboflow/core/project.py b/roboflow/core/project.py index 50b1801f..374c864f 100644 --- a/roboflow/core/project.py +++ b/roboflow/core/project.py @@ -11,7 +11,7 @@ import requests from roboflow.adapters import rfapi -from roboflow.adapters.rfapi import ImageUploadError, AnnotationSaveError +from roboflow.adapters.rfapi import AnnotationSaveError, ImageUploadError from roboflow.config import API_URL, DEMO_KEYS from roboflow.core.version import Version from roboflow.util.general import Retry From b3306b24e6de427196a3428a7cced05551d99432 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 13:39:48 +0100 Subject: [PATCH 07/10] fixup! Wrap `RequestException` --- roboflow/adapters/rfapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/roboflow/adapters/rfapi.py b/roboflow/adapters/rfapi.py index 4e1a07a9..0fd5a3ec 100644 --- a/roboflow/adapters/rfapi.py +++ b/roboflow/adapters/rfapi.py @@ -27,6 +27,7 @@ class AnnotationSaveError(RoboflowError): def __init__(self, message, status_code=None): self.message = message self.status_code = status_code + self.retries = 0 super().__init__(self.message) From 52e3db9918911636a2eb1bfd804a774a0058316f Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 16:23:30 +0100 Subject: [PATCH 08/10] version bump --- roboflow/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roboflow/__init__.py b/roboflow/__init__.py index 4b6d7476..f79b807b 100644 --- a/roboflow/__init__.py +++ b/roboflow/__init__.py @@ -15,7 +15,7 @@ from roboflow.models import CLIPModel, GazeModel # noqa: F401 from roboflow.util.general import write_line -__version__ = "1.1.54" +__version__ = "1.1.55" def check_key(api_key, model, notebook, num_retries=0): @@ -205,7 +205,7 @@ def __init__( self.api_key = api_key if self.api_key is None: self.api_key = load_roboflow_api_key() - + print("API_KEY", self.api_key) self.model_format = model_format self.notebook = notebook self.onboarding = False From 20e3df64e21fb584db4dbeed2ec7a3517c95462e Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 16:28:30 +0100 Subject: [PATCH 09/10] fixup! version bump --- roboflow/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/roboflow/__init__.py b/roboflow/__init__.py index f79b807b..f0517f75 100644 --- a/roboflow/__init__.py +++ b/roboflow/__init__.py @@ -205,7 +205,6 @@ def __init__( self.api_key = api_key if self.api_key is None: self.api_key = load_roboflow_api_key() - print("API_KEY", self.api_key) self.model_format = model_format self.notebook = notebook self.onboarding = False From efc69ad1cab4d369b50324472dee150bdfdc5609 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 10 Mar 2025 16:29:01 +0100 Subject: [PATCH 10/10] fixup! fixup! version bump --- roboflow/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/roboflow/__init__.py b/roboflow/__init__.py index f0517f75..851a5406 100644 --- a/roboflow/__init__.py +++ b/roboflow/__init__.py @@ -205,6 +205,7 @@ def __init__( self.api_key = api_key if self.api_key is None: self.api_key = load_roboflow_api_key() + self.model_format = model_format self.notebook = notebook self.onboarding = False