Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 40 additions & 22 deletions ayon_api/server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1662,7 +1662,9 @@ def _download_file_to_stream(

retries = max(self.max_retries, 1)
api_prepended = False
for attempt in range(retries):
attempt = 0
while True:
attempt += 1
# Continue in download
offset = progress.get_transferred_size()
if offset > 0:
Expand All @@ -1671,20 +1673,29 @@ def _download_file_to_stream(
try:
with get_func(url, **kwargs) as response:
# Auto-fix missing 'api/'
# NOTE Web frontend returns 'index.html' with status 200
# for unknown urls, which is not a file to download.
is_frontend_page = (
response.ok
and "text/html" in response.headers.get(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me the question is if this is wrong? It has been changed so long ago that anyone using it without the api/ probably already did fix it. I would rather remove the backwards compatibility instead of adding more checks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

"Content-Type", ""
)
)
if (
response.status_code in (404, 405)
(
response.status_code in (404, 405)
or is_frontend_page
)
and not api_prepended
and not endpoint.startswith(self._base_url)
and not endpoint.startswith("api/")
):
api_prepended = True
if (
not endpoint.startswith(self._base_url)
and not endpoint.startswith("api/")
):
url = self._endpoint_to_url(
endpoint, use_rest=True
)
progress.set_destination_url(url)
continue
url = self._endpoint_to_url(endpoint, use_rest=True)
progress.set_source_url(url)
# Endpoint fix is not a failed attempt
attempt -= 1
continue
response.raise_for_status()
if offset > 0 and response.status_code != 206:
# Server ignored 'Range' and sends whole file again,
Expand Down Expand Up @@ -1722,7 +1733,7 @@ def _download_file_to_stream(
requests.exceptions.ConnectionError,
requests.exceptions.ChunkedEncodingError,
):
if attempt == retries - 1:
if attempt >= retries:
raise
progress.next_attempt()

Expand Down Expand Up @@ -2127,7 +2138,9 @@ def _upload_file(
progress.set_content_size(size)

api_prepended = False
for attempt in range(retries):
attempt = 0
while True:
attempt += 1
try:
response = post_func(
url,
Expand All @@ -2137,22 +2150,27 @@ def _upload_file(
**kwargs
)
# Auto-fix missing 'api/'
if response.status_code in (404, 405) and not api_prepended:
if (
response.status_code in (404, 405)
and not api_prepended
and not endpoint.startswith(self._base_url)
and not endpoint.startswith("api/")
):
api_prepended = True
if (
not endpoint.startswith(self._base_url)
and not endpoint.startswith("api/")
):
url = self._endpoint_to_url(endpoint, use_rest=True)
progress.set_destination_url(url)
continue
url = self._endpoint_to_url(endpoint, use_rest=True)
progress.set_destination_url(url)
# Content is sent again and endpoint fix is not a failed
# attempt
progress.reset_transferred()
attempt -= 1
continue
break

except (
requests.exceptions.Timeout,
requests.exceptions.ConnectionError,
):
if attempt == retries - 1:
if attempt >= retries:
raise
progress.next_attempt()
progress.reset_transferred()
Expand Down
53 changes: 53 additions & 0 deletions tests/test_transfer_api_prefix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Auto-fix of missing 'api/' in transfer endpoints.

Does not require running AYON server.
"""
import io

import pytest

from ayon_api.server_api import ServerAPI
from ayon_api.utils import RequestTypes, TransferProgress

from .fake_transfer import FakeResponse


@pytest.fixture
def con(monkeypatch):
monkeypatch.setattr("time.sleep", lambda *args, **kwargs: None)
return ServerAPI("http://localhost:0", create_session=False, max_retries=1)


def test_download_frontend_page_uses_api_url(con):
urls = []

def get_func(url, **kwargs):
urls.append(url)
if "/api/" not in url:
# Web frontend serves 'index.html' for unknown urls
return FakeResponse(200, b"<html>", {"Content-Type": "text/html"})
return FakeResponse(200, b"PNG", {"Content-Length": "3"})

con._base_functions_mapping[RequestTypes.get] = get_func
stream = io.BytesIO()
con.download_file_to_stream("projects/p/thumbnails/1", stream)

assert stream.getvalue() == b"PNG"
assert urls[-1] == "http://localhost:0/api/projects/p/thumbnails/1"


def test_upload_autofix_with_single_attempt_and_progress(con):
def put_func(url, data=None, **kwargs):
list(data)
if "/api/" not in url:
return FakeResponse(404)
return FakeResponse(200)

con._base_functions_mapping[RequestTypes.put] = put_func
progress = TransferProgress()
response = con.upload_file_from_stream(
"upload", io.BytesIO(b"0123456789"), progress
)

assert response.status_code == 200
assert progress.transferred_size == 10