Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit 9e42f2f

Browse files
authored
robust download (#100)
* implement robust download * robust download * better tests * cleanup
1 parent 09a771b commit 9e42f2f

File tree

2 files changed

+80
-9
lines changed

2 files changed

+80
-9
lines changed

cads_api_client/processing.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,18 @@ def asset(self) -> dict[str, Any]:
678678
"""
679679
return dict(self.json["asset"]["value"])
680680

681+
def _download(self, url: str, target: str) -> requests.Response:
682+
download_options = {"stream": True, "resume_transfers": True}
683+
download_options.update(self.download_options)
684+
multiurl.download(
685+
url,
686+
target=target,
687+
**self.retry_options,
688+
**self.request_options,
689+
**download_options,
690+
)
691+
return requests.Response() # mutliurl robust needs a response
692+
681693
def download(
682694
self,
683695
target: str | None = None,
@@ -699,15 +711,11 @@ def download(
699711
parts = urllib.parse.urlparse(url)
700712
target = parts.path.strip("/").split("/")[-1]
701713

702-
download_options = {"stream": True}
703-
download_options.update(self.download_options)
704-
multiurl.download(
705-
url,
706-
target=target,
707-
**self.retry_options,
708-
**self.request_options,
709-
**download_options,
710-
)
714+
if os.path.exists(target):
715+
os.remove(target)
716+
717+
robust_download = multiurl.robust(self._download, **self.retry_options)
718+
robust_download(url, target)
711719
self._check_size(target)
712720
return target
713721

tests/integration_test_40_results.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
import contextlib
12
import os
23
import pathlib
4+
import random
5+
from typing import Any
36

47
import pytest
8+
import requests
59

610
from cads_api_client import ApiClient, Results
711

12+
does_not_raise = contextlib.nullcontext
13+
814

915
@pytest.fixture
1016
def results(api_anon_client: ApiClient) -> Results:
@@ -45,3 +51,60 @@ def test_results_progress(
4551
submitted.download(target=str(tmp_path / "test.grib"))
4652
captured = capsys.readouterr()
4753
assert captured.err if progress else not captured.err
54+
55+
56+
@pytest.mark.parametrize(
57+
"maximum_tries,raises",
58+
[
59+
(500, does_not_raise()),
60+
(1, pytest.raises(requests.ConnectionError, match="Random error.")),
61+
],
62+
)
63+
def test_results_robust_download(
64+
api_root_url: str,
65+
api_anon_key: str,
66+
monkeypatch: pytest.MonkeyPatch,
67+
tmp_path: pathlib.Path,
68+
maximum_tries: int,
69+
raises: contextlib.nullcontext[Any],
70+
) -> None:
71+
from multiurl.http import FullHTTPDownloader
72+
73+
def patched_iter_content(self, *args, **kwargs): # type: ignore
74+
for chunk in self.iter_content(chunk_size=1):
75+
if random.choice([True, False]):
76+
raise requests.ConnectionError("Random error.")
77+
yield chunk
78+
79+
def make_stream(self): # type: ignore
80+
request = self.issue_request(self.range)
81+
return request.patched_iter_content
82+
83+
client = ApiClient(
84+
url=api_root_url, key=api_anon_key, retry_after=0, maximum_tries=maximum_tries
85+
)
86+
results = client.submit_and_wait_on_results("test-adaptor-dummy", size=10)
87+
monkeypatch.setattr(
88+
requests.Response, "patched_iter_content", patched_iter_content, raising=False
89+
)
90+
monkeypatch.setattr(FullHTTPDownloader, "make_stream", make_stream)
91+
92+
target = tmp_path / "test.grib"
93+
with raises:
94+
results.download(str(target))
95+
96+
97+
def test_results_override(api_anon_client: ApiClient, tmp_path: pathlib.Path) -> None:
98+
target_1 = tmp_path / "tmp1.grib"
99+
api_anon_client.retrieve("test-adaptor-dummy", size=1, target=str(target_1))
100+
101+
target_2 = tmp_path / "tmp2.grib"
102+
api_anon_client.retrieve("test-adaptor-dummy", size=2, target=str(target_2))
103+
104+
target = tmp_path / "tmp.grib"
105+
api_anon_client.retrieve("test-adaptor-dummy", size=1, target=str(target))
106+
assert target.read_bytes() == target_1.read_bytes()
107+
api_anon_client.retrieve("test-adaptor-dummy", size=2, target=str(target))
108+
assert target.read_bytes() == target_2.read_bytes()
109+
api_anon_client.retrieve("test-adaptor-dummy", size=1, target=str(target))
110+
assert target.read_bytes() == target_1.read_bytes()

0 commit comments

Comments
 (0)