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

Commit 8cb6558

Browse files
committed
implement robust download
1 parent ba1d384 commit 8cb6558

File tree

2 files changed

+78
-9
lines changed

2 files changed

+78
-9
lines changed

cads_api_client/processing.py

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

668+
def _download(self, url: str, target: str) -> requests.Response:
669+
download_options = {"stream": True, "resume_transfers": True}
670+
download_options.update(self.download_options)
671+
multiurl.download(
672+
url,
673+
target=target,
674+
**self.retry_options,
675+
**self.request_options,
676+
**download_options,
677+
)
678+
return requests.Response() # mutliurl robust needs a response
679+
668680
def download(
669681
self,
670682
target: str | None = None,
@@ -686,15 +698,11 @@ def download(
686698
parts = urllib.parse.urlparse(url)
687699
target = parts.path.strip("/").split("/")[-1]
688700

689-
download_options = {"stream": True}
690-
download_options.update(self.download_options)
691-
multiurl.download(
692-
url,
693-
target=target,
694-
**self.retry_options,
695-
**self.request_options,
696-
**download_options,
697-
)
701+
with open(target, "wb"): # Clear existing file
702+
pass
703+
704+
robust_download = multiurl.robust(self._download, **self.retry_options)
705+
robust_download(url, target)
698706
self._check_size(target)
699707
return target
700708

tests/integration_test_40_results.py

Lines changed: 61 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,58 @@ 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.exceptions.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.exceptions.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()

0 commit comments

Comments
 (0)