Skip to content

Commit 00d486c

Browse files
committed
fix(core): get quicklook from assets (#2217)
1 parent 2510e03 commit 00d486c

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
@@ -687,6 +687,7 @@ def _init_progress_bar(
687687
def _download_quicklook(
688688
self,
689689
quicklook_file: str,
690+
quicklook_url: str,
690691
progress_callback: ProgressCallback,
691692
ssl_verify: Optional[bool] = None,
692693
auth: Optional[AuthBase] = None,
@@ -698,6 +699,7 @@ def _download_quicklook(
698699
authentication, and can display a download progress if a callback is provided.
699700
700701
:param quicklook_file: The full path (including filename) where the quicklook will be saved.
702+
:param quicklook_url: The quicklook URL to fetch.
701703
:param progress_callback: A callable that accepts the current and total download sizes
702704
to display or log the download progress. It must support `reset(total)`
703705
and be callable with downloaded chunk sizes.
@@ -707,7 +709,7 @@ def _download_quicklook(
707709
:raises HTTPError: If the HTTP request to the quicklook URL fails.
708710
"""
709711
with requests.get(
710-
self.properties["eodag:quicklook"],
712+
quicklook_url,
711713
stream=True,
712714
auth=auth,
713715
headers=USER_AGENT,
@@ -748,29 +750,33 @@ def get_quicklook(
748750
:returns: The absolute path of the downloaded quicklook
749751
"""
750752

751-
def format_quicklook_address() -> None:
753+
quicklook_asset = self.assets.get("quicklook")
754+
quicklook_url = quicklook_asset.get("href") if quicklook_asset else None
755+
756+
def format_quicklook_address(url: str) -> str:
752757
"""If the quicklook address is a Python format string, resolve the
753758
formatting with the properties of the product."""
754-
fstrmatch = re.match(r".*{.+}*.*", self.properties["eodag:quicklook"])
759+
fstrmatch = re.match(r".*{.+}*.*", url)
755760
if fstrmatch:
756-
self.properties["eodag:quicklook"] = format_string(
761+
return format_string(
757762
None,
758-
self.properties["eodag:quicklook"],
763+
url,
759764
**{
760765
prop_key: prop_val
761766
for prop_key, prop_val in self.properties.items()
762767
if prop_key != "eodag:quicklook"
763768
},
764769
)
770+
return url
765771

766-
if self.properties.get("eodag:quicklook") is None:
772+
if quicklook_url is None:
767773
logger.warning(
768774
"Missing information to retrieve quicklook for EO product: %s",
769775
self.properties["id"],
770776
)
771777
return ""
772778

773-
format_quicklook_address()
779+
quicklook_url = format_quicklook_address(quicklook_url)
774780

775781
if output_dir is not None:
776782
quicklooks_output_dir = os.path.abspath(os.path.realpath(output_dir))
@@ -805,11 +811,10 @@ def format_quicklook_address() -> None:
805811
# it is a HTTP URL. If not, we assume it is a base64 string, in which case
806812
# we just decode the content, write it into the quicklook_file and return it.
807813
if not (
808-
self.properties["eodag:quicklook"].startswith("http")
809-
or self.properties["eodag:quicklook"].startswith("https")
814+
quicklook_url.startswith("http") or quicklook_url.startswith("https")
810815
):
811816
with open(quicklook_file, "wb") as fd:
812-
img = self.properties["eodag:quicklook"].encode("ascii")
817+
img = quicklook_url.encode("ascii")
813818
fd.write(base64.b64decode(img))
814819
return quicklook_file
815820

@@ -829,15 +834,19 @@ def format_quicklook_address() -> None:
829834
)
830835
try:
831836
self._download_quicklook(
832-
quicklook_file, progress_callback, ssl_verify, auth
837+
quicklook_file, quicklook_url, progress_callback, ssl_verify, auth
833838
)
834839
except RequestException as e:
835840
logger.debug(
836841
f"Error while getting resource with authentication. {e} \nTrying without authentication..."
837842
)
838843
try:
839844
self._download_quicklook(
840-
quicklook_file, progress_callback, ssl_verify, None
845+
quicklook_file,
846+
quicklook_url,
847+
progress_callback,
848+
ssl_verify,
849+
None,
841850
)
842851
except RequestException as e_no_auth:
843852
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)