Skip to content
Merged
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
1 change: 1 addition & 0 deletions ayon_api/server_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,7 @@ def _do_rest_request(
response = None
new_response = None
for retry_idx in reversed(range(max_retries)):
new_response = None
try:
response = function(url, **kwargs)

Expand Down
41 changes: 41 additions & 0 deletions tests/fake_transfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Helpers to test requests and file transfers without AYON server."""
import requests
from requests.structures import CaseInsensitiveDict


class FakeResponse:
"""Minimal stand-in for 'requests.Response'."""
def __init__(
self, status_code=200, content=b"", headers=None, json_data=None
):
self.status_code = status_code
self.content = content
self.headers = CaseInsensitiveDict(headers or {})
self._json_data = json_data
self.reason = "Reason"
self.text = content.decode(errors="ignore")

@property
def ok(self):
return self.status_code < 400

def raise_for_status(self):
if not self.ok:
raise requests.exceptions.HTTPError(
f"{self.status_code} Error", response=self
)

def json(self):
if self._json_data is None:
raise ValueError("No json")
return self._json_data

def iter_content(self, chunk_size=1):
for idx in range(0, len(self.content), chunk_size):
yield self.content[idx:idx + chunk_size]

def __enter__(self):
return self

def __exit__(self, *args):
return False
35 changes: 35 additions & 0 deletions tests/test_request_retries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Retries of REST requests. Does not require running AYON server."""
import pytest
import requests

from ayon_api.server_api import ServerAPI

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=3)


@pytest.mark.parametrize(
"failure",
[requests.exceptions.ConnectionError("down"), FakeResponse(503)],
)
def test_successful_retry_returns_successful_response(con, failure):
responses = [failure, FakeResponse(200, json_data={"ok": True})]

# `_do_rest_request` expects a callable that takes a URL and returns a
# response. So we just return fake responses to simulate it.
def request_func(url, **kwargs):
item = responses.pop(0)
if isinstance(item, Exception):
raise item
return item

response = con._do_rest_request(
request_func, "http://localhost:0/api/x", handle_invalid_token=False
)
assert response.status_code == 200
assert response.data == {"ok": True}
Loading