Skip to content

Commit 40915e0

Browse files
authored
refactor(core): authentication for get_quicklook (#1608)
1 parent a9fafbc commit 40915e0

File tree

2 files changed

+103
-36
lines changed

2 files changed

+103
-36
lines changed

eodag/api/product/_product.py

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ def download(
313313
"EO product is unable to download itself due to lacking of a "
314314
"download plugin"
315315
)
316-
317316
auth = (
318317
self.downloader_auth.authenticate()
319318
if self.downloader_auth is not None
@@ -323,7 +322,6 @@ def download(
323322
progress_callback, close_progress_callback = self._init_progress_bar(
324323
progress_callback
325324
)
326-
327325
fs_path = self.downloader.download(
328326
self,
329327
auth=auth,
@@ -369,6 +367,47 @@ def _init_progress_bar(
369367
progress_callback.refresh()
370368
return (progress_callback, close_progress_callback)
371369

370+
def _download_quicklook(
371+
self,
372+
quicklook_file: str,
373+
progress_callback: ProgressCallback,
374+
ssl_verify: Optional[bool] = None,
375+
auth: Optional[AuthBase] = None,
376+
):
377+
378+
"""Download the quicklook image from the EOProduct's quicklook URL.
379+
380+
This method performs an HTTP GET request to retrieve the quicklook image and saves it
381+
locally at the specified path. It optionally verifies SSL certificates, uses HTTP
382+
authentication, and can display a download progress if a callback is provided.
383+
384+
:param quicklook_file: The full path (including filename) where the quicklook will be saved.
385+
:param progress_callback: A callable that accepts the current and total download sizes
386+
to display or log the download progress. It must support `reset(total)`
387+
and be callable with downloaded chunk sizes.
388+
:param ssl_verify: (optional) Whether to verify SSL certificates. Defaults to True.
389+
:param auth: (optional) Authentication credentials (e.g., tuple or object) used for the
390+
HTTP request if the resource requires authentication.
391+
:raises HTTPError: If the HTTP request to the quicklook URL fails.
392+
"""
393+
with requests.get(
394+
self.properties["quicklook"],
395+
stream=True,
396+
auth=auth,
397+
headers=USER_AGENT,
398+
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
399+
verify=ssl_verify,
400+
) as stream:
401+
stream.raise_for_status()
402+
stream_size = int(stream.headers.get("content-length", 0))
403+
progress_callback.reset(stream_size)
404+
with open(quicklook_file, "wb") as fhandle:
405+
for chunk in stream.iter_content(chunk_size=64 * 1024):
406+
if chunk:
407+
fhandle.write(chunk)
408+
progress_callback(len(chunk))
409+
logger.info("Download recorded in %s", quicklook_file)
410+
372411
def get_quicklook(
373412
self,
374413
filename: Optional[str] = None,
@@ -378,6 +417,9 @@ def get_quicklook(
378417
"""Download the quicklook image of a given EOProduct from its provider if it
379418
exists.
380419
420+
This method retrieves the quicklook URL from the EOProduct metadata and delegates
421+
the download to the internal `download_quicklook` method.
422+
381423
:param filename: (optional) The name to give to the downloaded quicklook. If not
382424
given, it defaults to the product's ID (without file extension).
383425
:param output_dir: (optional) The absolute path of the directory where to store
@@ -403,18 +445,6 @@ def format_quicklook_address() -> None:
403445
}
404446
)
405447

406-
# progress bar init
407-
if progress_callback is None:
408-
progress_callback = ProgressCallback()
409-
# one shot progress callback to close after download
410-
close_progress_callback = True
411-
else:
412-
close_progress_callback = False
413-
# update units as bar may have been previously used for extraction
414-
progress_callback.unit = "B"
415-
progress_callback.unit_scale = True
416-
progress_callback.desc = "quicklooks/%s" % self.properties.get("id", "")
417-
418448
if self.properties.get("quicklook", None) is None:
419449
logger.warning(
420450
"Missing information to retrieve quicklook for EO product: %s",
@@ -442,6 +472,18 @@ def format_quicklook_address() -> None:
442472
)
443473

444474
if not os.path.isfile(quicklook_file):
475+
476+
# progress bar init
477+
if progress_callback is None:
478+
progress_callback = ProgressCallback()
479+
# one shot progress callback to close after download
480+
close_progress_callback = True
481+
else:
482+
close_progress_callback = False
483+
# update units as bar may have been previously used for extraction
484+
progress_callback.unit = "B"
485+
progress_callback.unit_scale = True
486+
progress_callback.desc = "quicklooks/%s" % self.properties.get("id", "")
445487
# VERY SPECIAL CASE (introduced by the onda provider): first check if
446488
# it is a HTTP URL. If not, we assume it is a base64 string, in which case
447489
# we just decode the content, write it into the quicklook_file and return it.
@@ -468,30 +510,25 @@ def format_quicklook_address() -> None:
468510
if self.downloader
469511
else True
470512
)
471-
with requests.get(
472-
self.properties["quicklook"],
473-
stream=True,
474-
auth=auth,
475-
headers=USER_AGENT,
476-
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
477-
verify=ssl_verify,
478-
) as stream:
479-
try:
480-
stream.raise_for_status()
481-
except RequestException as e:
482-
import traceback as tb
513+
try:
514+
self._download_quicklook(
515+
quicklook_file, progress_callback, ssl_verify, auth
516+
)
517+
except RequestException as e:
483518

484-
logger.error("Error while getting resource :\n%s", tb.format_exc())
519+
logger.debug(
520+
f"Error while getting resource with authentication. {e} \nTrying without authentication..."
521+
)
522+
try:
523+
self._download_quicklook(
524+
quicklook_file, progress_callback, ssl_verify, None
525+
)
526+
except RequestException as e_no_auth:
527+
logger.error(
528+
f"Failed to get resource with authentication: {e} \n \
529+
Failed to get resource even without authentication. {e_no_auth}"
530+
)
485531
return str(e)
486-
else:
487-
stream_size = int(stream.headers.get("content-length", 0))
488-
progress_callback.reset(stream_size)
489-
with open(quicklook_file, "wb") as fhandle:
490-
for chunk in stream.iter_content(chunk_size=64 * 1024):
491-
if chunk:
492-
fhandle.write(chunk)
493-
progress_callback(len(chunk))
494-
logger.info("Download recorded in %s", quicklook_file)
495532

496533
# close progress bar if needed
497534
if close_progress_callback:

tests/units/test_eoproduct.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def test_eoproduct_get_quicklook_http_error(self):
178178
product.register_downloader(mock_downloader, None)
179179

180180
quicklook_file_path = product.get_quicklook()
181+
self.assertEqual(self.requests_http_get.call_count, 2)
181182
self.requests_http_get.assert_called_with(
182183
"https://fake.url.to/quicklook",
183184
stream=True,
@@ -188,6 +189,35 @@ def test_eoproduct_get_quicklook_http_error(self):
188189
)
189190
self.assertEqual(quicklook_file_path, "")
190191

192+
def test_eoproduct_get_quicklook_ok_without_auth(self):
193+
"""EOProduct.get_quicklook must retrieve the quicklook without authentication.""" # noqa
194+
product = self._dummy_product()
195+
product.properties["quicklook"] = "https://fake.url.to/quicklook"
196+
197+
self.requests_http_get.return_value.__enter__.return_value.raise_for_status.side_effect = ( # noqa
198+
requests.HTTPError,
199+
None,
200+
)
201+
mock_downloader = mock.MagicMock(
202+
spec_set=Download(provider=self.provider, config=None)
203+
)
204+
mock_downloader.config = config.PluginConfig.from_mapping(
205+
{"output_dir": tempfile.gettempdir()}
206+
)
207+
product.register_downloader(mock_downloader, None)
208+
209+
quicklook_file_path = product.get_quicklook()
210+
self.assertEqual(self.requests_http_get.call_count, 2)
211+
self.requests_http_get.assert_called_with(
212+
"https://fake.url.to/quicklook",
213+
stream=True,
214+
auth=None,
215+
headers=USER_AGENT,
216+
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
217+
verify=True,
218+
)
219+
os.remove(quicklook_file_path)
220+
191221
def test_eoproduct_get_quicklook_ok(self):
192222
"""EOProduct.get_quicklook must return the path to the successfully downloaded quicklook""" # noqa
193223
product = self._dummy_product()

0 commit comments

Comments
 (0)