From 31d6427f94279dfe67e888ffdef3dd14471b96e7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:14:49 +0100 Subject: [PATCH 1/5] added attempt to transfer progress --- ayon_api/utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ayon_api/utils.py b/ayon_api/utils.py index baf271277..29e1d2163 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -796,6 +796,7 @@ class TransferProgress: """Object to store progress of download/upload from/to server.""" def __init__(self): + self._attempt: int = 0 self._started: bool = False self._transfer_done: bool = False self._transferred: int = 0 @@ -850,6 +851,18 @@ def set_started(self): if self._started: raise ValueError("Progress already started") self._started = True + self._attempt = 1 + + def get_attempt(self) -> int: + """Find out which attempt of progress it is.""" + return self._attempt + + def next_attempt(self) -> None: + """Start new attempt of progress.""" + if not self._started: + raise ValueError("Progress did not start yet") + self._attempt += 1 + self._transferred = 0 def get_transfer_done(self) -> bool: """Transfer finished. From 2b2f1946219280cbee002e0d8f2cc8cfb473b1e3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:17:30 +0100 Subject: [PATCH 2/5] fix _endpoint_to_url and use it --- ayon_api/server_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 292cdfdfe..db2251b86 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1329,7 +1329,7 @@ def delete(self, entrypoint: str, **kwargs): def _endpoint_to_url( self, endpoint: str, - use_rest: Optional[bool] = True + use_rest: bool = True, ) -> str: """Cleanup endpoint and return full url to AYON server. @@ -1347,7 +1347,7 @@ def _endpoint_to_url( endpoint = endpoint.lstrip("/").rstrip("/") if endpoint.startswith(self._base_url): return endpoint - base_url = self._rest_url if use_rest else self._graphql_url + base_url = self._rest_url if use_rest else self._base_url return f"{base_url}/{endpoint}" def _download_file_to_stream( @@ -1399,7 +1399,7 @@ def download_file_to_stream( if not chunk_size: chunk_size = self.default_download_chunk_size - url = self._endpoint_to_url(endpoint) + url = self._endpoint_to_url(endpoint, use_rest=False) if progress is None: progress = TransferProgress() @@ -1580,7 +1580,7 @@ def upload_file_from_stream( requests.Response: Response object """ - url = self._endpoint_to_url(endpoint) + url = self._endpoint_to_url(endpoint, use_rest=False) # Create dummy object so the function does not have to check # 'progress' variable everywhere From 57db0a1805bc7a7c4c46b3f11c855371130961fb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:18:06 +0100 Subject: [PATCH 3/5] implement download retries --- ayon_api/server_api.py | 44 +++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index db2251b86..42032d2fd 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1351,21 +1351,47 @@ def _endpoint_to_url( return f"{base_url}/{endpoint}" def _download_file_to_stream( - self, url: str, stream, chunk_size, progress + self, + url: str, + stream: StreamType, + chunk_size: int, + progress: TransferProgress, ): - kwargs = {"stream": True} + headers = self.get_headers() + kwargs = { + "stream": True, + "headers": headers, + } if self._session is None: - kwargs["headers"] = self.get_headers() get_func = self._base_functions_mapping[RequestTypes.get] else: get_func = self._session_functions_mapping[RequestTypes.get] - with get_func(url, **kwargs) as response: - response.raise_for_status() - progress.set_content_size(response.headers["Content-length"]) - for chunk in response.iter_content(chunk_size=chunk_size): - stream.write(chunk) - progress.add_transferred_chunk(len(chunk)) + retries = self.get_default_max_retries() + for attempt in range(retries): + # Continue in download + offset = progress.get_transferred_size() + if offset > 0: + headers["Range"] = f"bytes={offset}-" + + try: + with get_func(url, **kwargs) as response: + response.raise_for_status() + progress.set_content_size( + response.headers["Content-length"] + ) + for chunk in response.iter_content(chunk_size=chunk_size): + stream.write(chunk) + progress.add_transferred_chunk(len(chunk)) + break + + except ( + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + ): + if attempt == retries: + raise + progress.next_attempt() def download_file_to_stream( self, From b642fc3cddc6224bf2ac3d6035e49d771cdfa22d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:18:14 +0100 Subject: [PATCH 4/5] implement upload retries --- ayon_api/server_api.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 42032d2fd..ded3cdc3d 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1569,11 +1569,26 @@ def _upload_file( if not chunk_size: chunk_size = self.default_upload_chunk_size - response = post_func( - url, - data=self._upload_chunks_iter(stream, progress, chunk_size), - **kwargs - ) + retries = self.get_default_max_retries() + response = None + for attempt in range(retries): + try: + response = post_func( + url, + data=self._upload_chunks_iter( + stream, progress, chunk_size + ), + **kwargs + ) + break + + except ( + requests.exceptions.Timeout, + requests.exceptions.ConnectionError, + ): + if attempt == retries: + raise + progress.next_attempt() response.raise_for_status() return response From eff935084318d2641b1cd89f3e801e79b1b51016 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:13:17 +0100 Subject: [PATCH 5/5] add explicit reset of transferred --- ayon_api/server_api.py | 1 + ayon_api/utils.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index ded3cdc3d..e2d8524e3 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -1589,6 +1589,7 @@ def _upload_file( if attempt == retries: raise progress.next_attempt() + progress.reset_transferred() response.raise_for_status() return response diff --git a/ayon_api/utils.py b/ayon_api/utils.py index 29e1d2163..49917c7bb 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -862,7 +862,6 @@ def next_attempt(self) -> None: if not self._started: raise ValueError("Progress did not start yet") self._attempt += 1 - self._transferred = 0 def get_transfer_done(self) -> bool: """Transfer finished. @@ -934,6 +933,10 @@ def set_transferred_size(self, transferred: int): """ self._transferred = transferred + def reset_transferred(self) -> None: + """Reset transferred size to initial value.""" + self._transferred = 0 + def add_transferred_chunk(self, chunk_size: int): """Add transferred chunk size in bytes.