Skip to content

Commit 4bb3817

Browse files
authored
fix(core): get quicklook from assets (#2217)
1 parent 510ebcb commit 4bb3817

3 files changed

Lines changed: 51 additions & 48 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Run Tests
22

33
on:
44
push:
5-
branches: [master, develop, v3.10.x]
5+
branches: [master, develop, v5_download_link_asset]
66
pull_request:
7-
branches: [master, develop, v3.10.x]
7+
branches: [master, develop, v5_download_link_asset]
88
schedule:
99
- cron: "0 7 * * 1"
1010
workflow_dispatch:

eodag/api/product/_product.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ def _init_progress_bar(
669669
def _download_quicklook(
670670
self,
671671
quicklook_file: str,
672+
quicklook_url: str,
672673
progress_callback: ProgressCallback,
673674
ssl_verify: Optional[bool] = None,
674675
auth: Optional[AuthBase] = None,
@@ -680,6 +681,7 @@ def _download_quicklook(
680681
authentication, and can display a download progress if a callback is provided.
681682
682683
:param quicklook_file: The full path (including filename) where the quicklook will be saved.
684+
:param quicklook_url: The quicklook URL to fetch.
683685
:param progress_callback: A callable that accepts the current and total download sizes
684686
to display or log the download progress. It must support `reset(total)`
685687
and be callable with downloaded chunk sizes.
@@ -689,7 +691,7 @@ def _download_quicklook(
689691
:raises HTTPError: If the HTTP request to the quicklook URL fails.
690692
"""
691693
with requests.get(
692-
self.properties["eodag:quicklook"],
694+
quicklook_url,
693695
stream=True,
694696
auth=auth,
695697
headers=USER_AGENT,
@@ -730,29 +732,33 @@ def get_quicklook(
730732
:returns: The absolute path of the downloaded quicklook
731733
"""
732734

733-
def format_quicklook_address() -> None:
735+
quicklook_asset = self.assets.get("quicklook")
736+
quicklook_url = quicklook_asset.get("href") if quicklook_asset else None
737+
738+
def format_quicklook_address(url: str) -> str:
734739
"""If the quicklook address is a Python format string, resolve the
735740
formatting with the properties of the product."""
736-
fstrmatch = re.match(r".*{.+}*.*", self.properties["eodag:quicklook"])
741+
fstrmatch = re.match(r".*{.+}*.*", url)
737742
if fstrmatch:
738-
self.properties["eodag:quicklook"] = format_string(
743+
return format_string(
739744
None,
740-
self.properties["eodag:quicklook"],
745+
url,
741746
**{
742747
prop_key: prop_val
743748
for prop_key, prop_val in self.properties.items()
744749
if prop_key != "eodag:quicklook"
745750
},
746751
)
752+
return url
747753

748-
if self.properties.get("eodag:quicklook") is None:
754+
if quicklook_url is None:
749755
logger.warning(
750756
"Missing information to retrieve quicklook for EO product: %s",
751757
self.properties["id"],
752758
)
753759
return ""
754760

755-
format_quicklook_address()
761+
quicklook_url = format_quicklook_address(quicklook_url)
756762

757763
if output_dir is not None:
758764
quicklooks_output_dir = os.path.abspath(os.path.realpath(output_dir))
@@ -787,11 +793,10 @@ def format_quicklook_address() -> None:
787793
# it is a HTTP URL. If not, we assume it is a base64 string, in which case
788794
# we just decode the content, write it into the quicklook_file and return it.
789795
if not (
790-
self.properties["eodag:quicklook"].startswith("http")
791-
or self.properties["eodag:quicklook"].startswith("https")
796+
quicklook_url.startswith("http") or quicklook_url.startswith("https")
792797
):
793798
with open(quicklook_file, "wb") as fd:
794-
img = self.properties["eodag:quicklook"].encode("ascii")
799+
img = quicklook_url.encode("ascii")
795800
fd.write(base64.b64decode(img))
796801
return quicklook_file
797802

@@ -811,15 +816,19 @@ def format_quicklook_address() -> None:
811816
)
812817
try:
813818
self._download_quicklook(
814-
quicklook_file, progress_callback, ssl_verify, auth
819+
quicklook_file, quicklook_url, progress_callback, ssl_verify, auth
815820
)
816821
except RequestException as e:
817822
logger.debug(
818823
f"Error while getting resource with authentication. {e} \nTrying without authentication..."
819824
)
820825
try:
821826
self._download_quicklook(
822-
quicklook_file, progress_callback, ssl_verify, None
827+
quicklook_file,
828+
quicklook_url,
829+
progress_callback,
830+
ssl_verify,
831+
None,
823832
)
824833
except RequestException as e_no_auth:
825834
logger.error(

tests/units/test_eoproduct.py

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def test_eoproduct_from_geointerface(self):
175175

176176
@responses.activate
177177
def test_eoproduct_get_quicklook_no_quicklook_url(self):
178-
"""EOProduct.get_quicklook must return an empty string if no quicklook property""" # noqa
178+
"""EOProduct.get_quicklook must return an empty string if no quicklook asset"""
179179
responses.add(
180180
responses.GET,
181181
"https://fake.url.to/quicklook",
@@ -184,23 +184,17 @@ def test_eoproduct_get_quicklook_no_quicklook_url(self):
184184
auto_calculate_content_length=True,
185185
)
186186
product = self._dummy_product()
187-
product.properties["eodag:quicklook"] = None
187+
self.assertNotIn("quicklook", product.assets)
188188

189189
quicklook_file_path = product.get_quicklook()
190190
self.assertEqual(quicklook_file_path, "")
191191
responses.assert_call_count("https://fake.url.to/quicklook", 0)
192192

193193
@responses.activate
194194
def test_eoproduct_get_quicklook_http_error(self):
195-
"""EOProduct.get_quicklook must return an empty string if there was an error during retrieval""" # noqa
196-
product = self._dummy_product(
197-
properties=dict(
198-
self.eoproduct_props,
199-
**{
200-
"eodag:quicklook": "https://fake.url.to/quicklook",
201-
},
202-
)
203-
)
195+
"""EOProduct.get_quicklook must return an empty string if there was an error during retrieval"""
196+
product = self._dummy_product()
197+
product.assets.update({"quicklook": {"href": "https://fake.url.to/quicklook"}})
204198
product.register_downloader(self.get_mock_downloader(), None)
205199
responses.add(
206200
responses.GET,
@@ -215,15 +209,15 @@ def test_eoproduct_get_quicklook_http_error(self):
215209

216210
@responses.activate
217211
def test_eoproduct_get_quicklook_ok_without_auth(self):
218-
"""EOProduct.get_quicklook must retrieve the quicklook without authentication.""" # noqa
212+
"""EOProduct.get_quicklook must retrieve the quicklook without authentication."""
219213
product = self._dummy_product()
220214
responses.add(
221215
responses.GET,
222216
"https://fake.url.to/quicklook",
223217
body=b"Quicklook content",
224218
status=200,
225219
)
226-
product.properties["eodag:quicklook"] = "https://fake.url.to/quicklook"
220+
product.assets.update({"quicklook": {"href": "https://fake.url.to/quicklook"}})
227221
product.register_downloader(self.get_mock_downloader(), None)
228222

229223
with tempfile.TemporaryDirectory() as output_dir:
@@ -237,7 +231,7 @@ def test_eoproduct_get_quicklook_ok_without_auth(self):
237231

238232
@responses.activate
239233
def test_eoproduct_get_quicklook_ok(self):
240-
"""EOProduct.get_quicklook must return the path to the successfully downloaded quicklook""" # noqa
234+
"""EOProduct.get_quicklook must return the path to the successfully downloaded quicklook"""
241235
product = self._dummy_product()
242236

243237
responses.add(
@@ -246,7 +240,7 @@ def test_eoproduct_get_quicklook_ok(self):
246240
body=b"Quicklook content",
247241
status=200,
248242
)
249-
product.properties["eodag:quicklook"] = "https://fake.url.to/quicklook"
243+
product.assets.update({"quicklook": {"href": "https://fake.url.to/quicklook"}})
250244
product.register_downloader(self.get_mock_downloader(), None)
251245

252246
quicklook_file_path = product.get_quicklook()
@@ -276,7 +270,7 @@ def test_eoproduct_get_quicklook_ok(self):
276270

277271
@responses.activate
278272
def test_eoproduct_get_quicklook_ok_existing(self):
279-
"""EOProduct.get_quicklook must return the path to an already downloaded quicklook""" # noqa
273+
"""EOProduct.get_quicklook must return the path to an already downloaded quicklook"""
280274

281275
# Tmp dir
282276
quicklook_dir = os.path.join(self.output_dir, "quicklooks")
@@ -290,7 +284,7 @@ def test_eoproduct_get_quicklook_ok_existing(self):
290284
fh.write(b"content")
291285

292286
product = self._dummy_product()
293-
product.properties["eodag:quicklook"] = "https://fake.url.to/quicklook"
287+
product.assets.update({"quicklook": {"href": "https://fake.url.to/quicklook"}})
294288
product.register_downloader(self.get_mock_downloader(), None)
295289
responses.add(
296290
responses.GET,
@@ -307,7 +301,7 @@ def test_eoproduct_get_quicklook_ok_existing(self):
307301

308302
@responses.activate
309303
def test_eoproduct_download_http_default(self):
310-
"""eoproduct.download must save the product at output_dir and create a .downloaded dir""" # noqa
304+
"""EOProduct.download must save the product at output_dir and create a .downloaded dir"""
311305

312306
product = self._dummy_downloadable_product(extract=True)
313307

@@ -371,7 +365,7 @@ def test_eoproduct_download_http_default(self):
371365

372366
@responses.activate
373367
def test_eoproduct_download_http_delete_archive(self):
374-
"""eoproduct.download must delete the downloaded archive""" # noqa
368+
"""EOProduct.download must delete the downloaded archive"""
375369

376370
product = self._dummy_downloadable_product(
377371
product=self._dummy_product(
@@ -417,7 +411,7 @@ def test_eoproduct_download_http_delete_archive(self):
417411

418412
@responses.activate
419413
def test_eoproduct_download_http_extract(self):
420-
"""eoproduct.download over must be able to extract a product"""
414+
"""EOProduct.download over must be able to extract a product"""
421415
# Setup
422416
product = self._dummy_downloadable_product(extract=True)
423417
product_dir_path = product.download()
@@ -441,7 +435,7 @@ def test_eoproduct_download_http_extract(self):
441435

442436
@responses.activate
443437
def test_eoproduct_stream_download(self):
444-
"""eoproduct.stream_download return a product file as StreamResponse""" # noqa
438+
"""EOProduct.stream_download return a product file as StreamResponse"""
445439

446440
# Setup
447441
product = self._dummy_downloadable_product(
@@ -478,7 +472,7 @@ def test_eoproduct_stream_download(self):
478472

479473
@responses.activate
480474
def test_eoproduct_asset_stream_download(self):
481-
"""eoproduct.assets[x].stream_download return a asset file as StreamResponse""" # noqa
475+
"""EOProduct.assets[x].stream_download return a asset file as StreamResponse"""
482476
# Setup
483477
product = self._dummy_downloadable_product(
484478
assets={
@@ -513,7 +507,7 @@ def test_eoproduct_asset_stream_download(self):
513507

514508
@responses.activate
515509
def test_eoproduct_download_http_dynamic_options(self):
516-
"""eoproduct.download must accept the download options to be set automatically"""
510+
"""EOProduct.download must accept the download options to be set automatically"""
517511

518512
product = self._dummy_downloadable_product(
519513
product=self._dummy_product(
@@ -550,7 +544,7 @@ def test_eoproduct_download_http_dynamic_options(self):
550544

551545
@responses.activate
552546
def test_eoproduct_download_progress_bar(self):
553-
"""eoproduct.download must show a progress bar"""
547+
"""EOProduct.download must show a progress bar"""
554548
product = self._dummy_downloadable_product(
555549
product=self._dummy_product(
556550
properties=dict(
@@ -588,7 +582,7 @@ def test_eoproduct_download_progress_bar(self):
588582
self.assertEqual(progress_callback.pos, 1)
589583

590584
def test_eoproduct_register_downloader(self):
591-
"""eoproduct.register_donwloader must set download and auth plugins"""
585+
"""EOProduct.register_downloader must set download and auth plugins"""
592586
product = self._dummy_product()
593587

594588
self.assertIsNone(product.downloader)
@@ -604,7 +598,7 @@ def test_eoproduct_register_downloader(self):
604598

605599
@responses.activate
606600
def test_eoproduct_register_downloader_resolve_ok(self):
607-
"""eoproduct.register_donwloader must resolve locations and properties"""
601+
"""EOProduct.register_downloader must resolve locations and properties"""
608602
downloadable_product = self._dummy_downloadable_product(
609603
product=self._dummy_product(
610604
properties=dict(
@@ -636,7 +630,7 @@ def test_eoproduct_register_downloader_resolve_ok(self):
636630

637631
@responses.activate
638632
def test_eoproduct_register_downloader_resolve_ignored(self):
639-
"""eoproduct.register_donwloader must ignore unresolvable locations and properties"""
633+
"""EOProduct.register_downloader must ignore unresolvable locations and properties"""
640634

641635
logger = logging.getLogger("eodag.product")
642636
with mock.patch.object(logger, "debug") as mock_debug:
@@ -676,7 +670,7 @@ def test_eoproduct_register_downloader_resolve_ignored(self):
676670
self.assertIn(needed_log, str(mock_debug.call_args_list))
677671

678672
def test_eoproduct_repr_html(self):
679-
"""eoproduct html repr must be correctly formatted"""
673+
"""EOProduct html repr must be correctly formatted"""
680674
product = self._dummy_product()
681675
product_repr = html.fromstring(product._repr_html_())
682676
self.assertIn("EOProduct", product_repr.xpath("//thead/tr/td")[0].text)
@@ -691,7 +685,7 @@ def test_eoproduct_repr_html(self):
691685
self.assertIn("Asset", asset_repr.xpath("//thead/tr/td")[0].text)
692686

693687
def test_eoproduct_assets_get_values(self):
694-
"""eoproduct.assets.get_values must return the expected values"""
688+
"""EOProduct.assets.get_values must return the expected values"""
695689
product = self._dummy_product()
696690
product.assets.update(
697691
{
@@ -708,7 +702,7 @@ def test_eoproduct_assets_get_values(self):
708702
self.assertEqual(product.assets.get_values("foo?o,o")[0]["href"], "foooo.href")
709703

710704
def test_eoproduct_sorted_properties(self):
711-
"""eoproduct.properties must be sorted"""
705+
"""EOProduct.properties must be sorted"""
712706
product = self._dummy_product(
713707
properties={
714708
"geometry": "POINT (0 0)",
@@ -734,7 +728,7 @@ def test_eoproduct_sorted_properties(self):
734728
)
735729

736730
def test_eoproduct_none_properties(self):
737-
"""eoproduct none properties must be kept"""
731+
"""EOProduct none properties must be kept"""
738732
product = self._dummy_product(
739733
properties={
740734
"geometry": "POINT (0 0)",
@@ -752,7 +746,7 @@ def test_eoproduct_none_properties(self):
752746
)
753747

754748
def test_eoproduct_serialize(self):
755-
"""eoproduct.as_dict must include the required STAC extensions"""
749+
"""EOProduct.as_dict must include the required STAC extensions"""
756750
product = self._dummy_product()
757751
product.properties["grid:code"] = "MGRS-31TCJ"
758752
product.properties["eo:cloud_cover"] = "bad-formatted"
@@ -788,7 +782,7 @@ def test_eoproduct_serialize(self):
788782
)
789783

790784
def test_eoproduct_as_pystac_object(self):
791-
"""eoproduct.as_pystac_object must return a pystac.Item"""
785+
"""EOProduct.as_pystac_object must return a pystac.Item"""
792786
product = self._dummy_product(
793787
properties={"id": "dummy_id", "datetime": "2021-01-01T00:00:00Z"}
794788
)
@@ -797,7 +791,7 @@ def test_eoproduct_as_pystac_object(self):
797791
pystac_item.validate()
798792

799793
def test_eoproduct_from_pystac(self):
800-
"""eoproduct.from_pystac must return an EOProduct instance from a pystac.Item"""
794+
"""EOProduct.from_pystac must return an EOProduct instance from a pystac.Item"""
801795
product = self._dummy_product(
802796
properties={"id": "dummy_id", "datetime": "2021-01-01T00:00:00Z"}
803797
)

0 commit comments

Comments
 (0)