eodag 3.0.0b3__py3-none-any.whl → 3.1.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. eodag/api/core.py +292 -198
  2. eodag/api/product/_assets.py +6 -6
  3. eodag/api/product/_product.py +18 -18
  4. eodag/api/product/metadata_mapping.py +51 -14
  5. eodag/api/search_result.py +29 -3
  6. eodag/cli.py +57 -20
  7. eodag/config.py +413 -117
  8. eodag/plugins/apis/base.py +10 -4
  9. eodag/plugins/apis/ecmwf.py +49 -16
  10. eodag/plugins/apis/usgs.py +30 -7
  11. eodag/plugins/authentication/aws_auth.py +14 -5
  12. eodag/plugins/authentication/base.py +10 -1
  13. eodag/plugins/authentication/generic.py +14 -3
  14. eodag/plugins/authentication/header.py +12 -4
  15. eodag/plugins/authentication/keycloak.py +41 -22
  16. eodag/plugins/authentication/oauth.py +11 -1
  17. eodag/plugins/authentication/openid_connect.py +178 -163
  18. eodag/plugins/authentication/qsauth.py +12 -4
  19. eodag/plugins/authentication/sas_auth.py +19 -2
  20. eodag/plugins/authentication/token.py +93 -15
  21. eodag/plugins/authentication/token_exchange.py +19 -19
  22. eodag/plugins/crunch/base.py +4 -1
  23. eodag/plugins/crunch/filter_date.py +5 -2
  24. eodag/plugins/crunch/filter_latest_intersect.py +5 -4
  25. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  26. eodag/plugins/crunch/filter_overlap.py +5 -7
  27. eodag/plugins/crunch/filter_property.py +6 -6
  28. eodag/plugins/download/aws.py +50 -34
  29. eodag/plugins/download/base.py +41 -50
  30. eodag/plugins/download/creodias_s3.py +40 -2
  31. eodag/plugins/download/http.py +221 -195
  32. eodag/plugins/download/s3rest.py +25 -25
  33. eodag/plugins/manager.py +168 -23
  34. eodag/plugins/search/base.py +106 -39
  35. eodag/plugins/search/build_search_result.py +1065 -324
  36. eodag/plugins/search/cop_marine.py +112 -29
  37. eodag/plugins/search/creodias_s3.py +45 -24
  38. eodag/plugins/search/csw.py +41 -1
  39. eodag/plugins/search/data_request_search.py +109 -9
  40. eodag/plugins/search/qssearch.py +549 -257
  41. eodag/plugins/search/static_stac_search.py +20 -21
  42. eodag/resources/ext_product_types.json +1 -1
  43. eodag/resources/product_types.yml +577 -87
  44. eodag/resources/providers.yml +1619 -2776
  45. eodag/resources/stac.yml +3 -163
  46. eodag/resources/user_conf_template.yml +112 -97
  47. eodag/rest/config.py +1 -2
  48. eodag/rest/constants.py +0 -1
  49. eodag/rest/core.py +138 -98
  50. eodag/rest/errors.py +181 -0
  51. eodag/rest/server.py +55 -329
  52. eodag/rest/stac.py +93 -544
  53. eodag/rest/types/eodag_search.py +19 -8
  54. eodag/rest/types/queryables.py +6 -8
  55. eodag/rest/types/stac_search.py +11 -2
  56. eodag/rest/utils/__init__.py +3 -0
  57. eodag/types/__init__.py +71 -18
  58. eodag/types/download_args.py +3 -3
  59. eodag/types/queryables.py +180 -73
  60. eodag/types/search_args.py +3 -3
  61. eodag/types/whoosh.py +126 -0
  62. eodag/utils/__init__.py +147 -66
  63. eodag/utils/exceptions.py +47 -26
  64. eodag/utils/logging.py +37 -77
  65. eodag/utils/repr.py +65 -6
  66. eodag/utils/requests.py +11 -13
  67. eodag/utils/stac_reader.py +1 -1
  68. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
  69. eodag-3.1.0b1.dist-info/RECORD +108 -0
  70. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
  71. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
  72. eodag/resources/constraints/climate-dt.json +0 -13
  73. eodag/resources/constraints/extremes-dt.json +0 -8
  74. eodag/utils/constraints.py +0 -244
  75. eodag-3.0.0b3.dist-info/RECORD +0 -110
  76. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
  77. {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import logging
21
21
  import os
22
+ import re
22
23
  import shutil
23
24
  import tarfile
24
25
  import zipfile
@@ -26,6 +27,7 @@ from datetime import datetime
26
27
  from email.message import Message
27
28
  from itertools import chain
28
29
  from json import JSONDecodeError
30
+ from pathlib import Path
29
31
  from typing import (
30
32
  TYPE_CHECKING,
31
33
  Any,
@@ -80,6 +82,7 @@ from eodag.utils.exceptions import (
80
82
  MisconfiguredError,
81
83
  NotAvailableError,
82
84
  TimeOutError,
85
+ ValidationError,
83
86
  )
84
87
 
85
88
  if TYPE_CHECKING:
@@ -100,25 +103,51 @@ class HTTPDownload(Download):
100
103
  :param provider: provider name
101
104
  :param config: Download plugin configuration:
102
105
 
103
- * ``config.base_uri`` (str) - (optional) default endpoint url
104
- * ``config.extract`` (bool) - (optional) extract downloaded archive or not
105
- * ``config.auth_error_code`` (int) - (optional) authentication error code
106
- * ``config.dl_url_params`` (dict) - (optional) attitional parameters to send in the request
107
- * ``config.archive_depth`` (int) - (optional) level in extracted path tree where to find data
108
- * ``config.flatten_top_dirs`` (bool) - (optional) flatten directory structure
109
- * ``config.ignore_assets`` (bool) - (optional) ignore assets and download using downloadLink
110
- * ``config.order_enabled`` (bool) - (optional) wether order is enabled or not if product is `OFFLINE`
111
- * ``config.order_method`` (str) - (optional) HTTP request method, GET (default) or POST
112
- * ``config.order_headers`` (dict) - (optional) order request headers
113
- * ``config.order_on_response`` (dict) - (optional) edit or add new product properties
114
- * ``config.order_status`` (:class:`~eodag.config.PluginConfig.OrderStatus`) - (optional) Order status handling
106
+ * :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): ``HTTPDownload``
107
+ * :attr:`~eodag.config.PluginConfig.base_uri` (``str``): default endpoint url
108
+ * :attr:`~eodag.config.PluginConfig.method` (``str``): HTTP request method for the download request (``GET`` or
109
+ ``POST``); default: ``GET``
110
+ * :attr:`~eodag.config.PluginConfig.extract` (``bool``): if the content of the downloaded file should be
111
+ extracted; default: ``True``
112
+ * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is returned in case of an
113
+ authentication error
114
+ * :attr:`~eodag.config.PluginConfig.dl_url_params` (``Dict[str, Any]``): parameters to be
115
+ added to the query params of the request
116
+ * :attr:`~eodag.config.PluginConfig.archive_depth` (``int``): level in extracted path tree where to find data;
117
+ default: ``1``
118
+ * :attr:`~eodag.config.PluginConfig.flatten_top_dirs` (``bool``): if the directory structure should be
119
+ flattened; default: ``True``
120
+ * :attr:`~eodag.config.PluginConfig.ignore_assets` (``bool``): ignore assets and download using downloadLink;
121
+ default: ``False``
122
+ * :attr:`~eodag.config.PluginConfig.timeout` (``int``): time to wait until request timeout in seconds;
123
+ default: ``5``
124
+ * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be verified in
125
+ requests; default: ``True``
126
+ * :attr:`~eodag.config.PluginConfig.no_auth_download` (``bool``): if the download should be done without
127
+ authentication; default: ``True``
128
+ * :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): if the product has to be ordered to download it;
129
+ if this parameter is set to true, a mapping for the orderLink has to be added to the metadata mapping of
130
+ the search plugin used for the provider; default: ``False``
131
+ * :attr:`~eodag.config.PluginConfig.order_method` (``str``): HTTP request method for the order request (``GET``
132
+ or ``POST``); default: ``GET``
133
+ * :attr:`~eodag.config.PluginConfig.order_headers` (``[Dict[str, str]]``): headers to be added to the order
134
+ request
135
+ * :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
136
+ a typed dictionary containing the key ``metadata_mapping`` which can be used to add new product properties
137
+ based on the data in response to the order request
138
+ * :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
139
+ configuration to handle the order status; contains information which method to use, how the response data is
140
+ interpreted, which status corresponds to success, ordered and error and what should be done on success.
141
+ * :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type specific config; the
142
+ keys are the product types, the values are dictionaries which can contain the key
143
+ :attr:`~eodag.config.PluginConfig.extract` to overwrite the provider config for a specific product type
115
144
 
116
145
  """
117
146
 
118
147
  def __init__(self, provider: str, config: PluginConfig) -> None:
119
148
  super(HTTPDownload, self).__init__(provider, config)
120
149
 
121
- def order_download(
150
+ def _order(
122
151
  self,
123
152
  product: EOProduct,
124
153
  auth: Optional[AuthBase] = None,
@@ -130,12 +159,13 @@ class HTTPDownload(Download):
130
159
  and has `orderLink` in its properties.
131
160
  Product ordering can be configured using the following download plugin parameters:
132
161
 
133
- - **order_enabled**: Wether order is enabled or not (may not use this method
162
+ - :attr:`~eodag.config.PluginConfig.order_enabled`: Wether order is enabled or not (may not use this method
134
163
  if no `orderLink` exists)
135
164
 
136
- - **order_method**: (optional) HTTP request method, GET (default) or POST
165
+ - :attr:`~eodag.config.PluginConfig.order_method`: (optional) HTTP request method, GET (default) or POST
137
166
 
138
- - **order_on_response**: (optional) things to do with obtained order response:
167
+ - :attr:`~eodag.config.PluginConfig.order_on_response`: (optional) things to do with obtained order
168
+ response:
139
169
 
140
170
  - *metadata_mapping*: edit or add new product propoerties properties
141
171
 
@@ -193,18 +223,13 @@ class HTTPDownload(Download):
193
223
  logger.debug(ordered_message)
194
224
  product.properties["storageStatus"] = STAGING_STATUS
195
225
  except RequestException as e:
196
- if hasattr(e, "response") and (
197
- content := getattr(e.response, "content", None)
198
- ):
199
- error_message = f"{content.decode('utf-8')} - {e}"
200
- else:
201
- error_message = str(e)
202
- logger.warning(
203
- "%s could not be ordered, request returned %s",
204
- product.properties["title"],
205
- error_message,
206
- )
207
226
  self._check_auth_exception(e)
227
+ msg = f'{product.properties["title"]} could not be ordered'
228
+ if e.response is not None and e.response.status_code == 400:
229
+ raise ValidationError.from_error(e, msg) from e
230
+ else:
231
+ raise DownloadError.from_error(e, msg) from e
232
+
208
233
  return self.order_response_process(response, product)
209
234
  except requests.exceptions.Timeout as exc:
210
235
  raise TimeOutError(exc, timeout=timeout) from exc
@@ -246,7 +271,7 @@ class HTTPDownload(Download):
246
271
 
247
272
  return json_response
248
273
 
249
- def order_download_status(
274
+ def _order_status(
250
275
  self,
251
276
  product: EOProduct,
252
277
  auth: Optional[AuthBase] = None,
@@ -256,7 +281,7 @@ class HTTPDownload(Download):
256
281
  It will be executed before each download retry.
257
282
  Product order status request can be configured using the following download plugin parameters:
258
283
 
259
- - **order_status**: :class:`~eodag.config.PluginConfig.OrderStatus`
284
+ - :attr:`~eodag.config.PluginConfig.order_status`: :class:`~eodag.config.PluginConfig.OrderStatus`
260
285
 
261
286
  Product properties used for order status:
262
287
 
@@ -373,13 +398,11 @@ class HTTPDownload(Download):
373
398
  # success and no need to get status response content
374
399
  skip_parsing_status_response = True
375
400
  except RequestException as e:
376
- raise DownloadError(
377
- "%s order status could not be checked, request returned %s"
378
- % (
379
- product.properties["title"],
380
- e,
381
- )
382
- ) from e
401
+ msg = f'{product.properties["title"]} order status could not be checked'
402
+ if e.response is not None and e.response.status_code == 400:
403
+ raise ValidationError.from_error(e, msg) from e
404
+ else:
405
+ raise DownloadError.from_error(e, msg) from e
383
406
 
384
407
  if not skip_parsing_status_response:
385
408
  # status request
@@ -541,8 +564,8 @@ class HTTPDownload(Download):
541
564
  product: EOProduct,
542
565
  auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
543
566
  progress_callback: Optional[ProgressCallback] = None,
544
- wait: int = DEFAULT_DOWNLOAD_WAIT,
545
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
567
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
568
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
546
569
  **kwargs: Unpack[DownloadConf],
547
570
  ) -> Optional[str]:
548
571
  """Download a product using HTTP protocol.
@@ -560,13 +583,6 @@ class HTTPDownload(Download):
560
583
  )
561
584
  progress_callback = ProgressCallback(disable=True)
562
585
 
563
- output_extension = getattr(self.config, "products", {}).get(
564
- product.product_type, {}
565
- ).get("output_extension", None) or getattr(
566
- self.config, "output_extension", ".zip"
567
- )
568
- kwargs["output_extension"] = kwargs.get("output_extension", output_extension)
569
-
570
586
  fs_path, record_filename = self._prepare_download(
571
587
  product,
572
588
  progress_callback=progress_callback,
@@ -585,7 +601,7 @@ class HTTPDownload(Download):
585
601
  try:
586
602
  fs_path = self._download_assets(
587
603
  product,
588
- fs_path.replace(".zip", ""),
604
+ fs_path,
589
605
  record_filename,
590
606
  auth,
591
607
  progress_callback,
@@ -602,82 +618,62 @@ class HTTPDownload(Download):
602
618
 
603
619
  url = product.remote_location
604
620
 
605
- @self._download_retry(product, wait, timeout)
621
+ @self._order_download_retry(product, wait, timeout)
606
622
  def download_request(
607
623
  product: EOProduct,
608
624
  auth: AuthBase,
609
625
  progress_callback: ProgressCallback,
610
- wait: int,
611
- timeout: int,
626
+ wait: float,
627
+ timeout: float,
612
628
  **kwargs: Unpack[DownloadConf],
613
- ) -> None:
614
- chunks = self._stream_download(product, auth, progress_callback, **kwargs)
629
+ ) -> os.PathLike:
615
630
  is_empty = True
631
+ chunk_iterator = self._stream_download(
632
+ product, auth, progress_callback, **kwargs
633
+ )
634
+ if fs_path is not None:
635
+ ext = Path(product.filename).suffix
636
+ path = Path(fs_path).with_suffix(ext)
637
+
638
+ with open(path, "wb") as fhandle:
639
+ for chunk in chunk_iterator:
640
+ is_empty = False
641
+ progress_callback(len(chunk))
642
+ fhandle.write(chunk)
643
+ self.stream.close() # Closing response stream
616
644
 
617
- with open(fs_path, "wb") as fhandle:
618
- for chunk in chunks:
619
- is_empty = False
620
- fhandle.write(chunk)
645
+ if is_empty:
646
+ raise DownloadError(f"product {product.properties['id']} is empty")
621
647
 
622
- if is_empty:
623
- raise DownloadError(f"product {product.properties['id']} is empty")
648
+ return path
649
+ else:
650
+ raise DownloadError(
651
+ f"download of product {product.properties['id']} failed"
652
+ )
624
653
 
625
- download_request(product, auth, progress_callback, wait, timeout, **kwargs)
654
+ path = download_request(
655
+ product, auth, progress_callback, wait, timeout, **kwargs
656
+ )
626
657
 
627
658
  with open(record_filename, "w") as fh:
628
659
  fh.write(url)
629
660
  logger.debug("Download recorded in %s", record_filename)
630
661
 
631
- # Check that the downloaded file is really a zip file
632
- if not zipfile.is_zipfile(fs_path) and output_extension == ".zip":
633
- logger.warning(
634
- "Downloaded product is not a Zip File. Please check its file type before using it"
635
- )
636
- new_fs_path = os.path.join(
637
- os.path.dirname(fs_path),
638
- sanitize(product.properties["title"]),
639
- )
640
- if os.path.isfile(fs_path) and not tarfile.is_tarfile(fs_path):
641
- if not os.path.isdir(new_fs_path):
642
- os.makedirs(new_fs_path)
643
- shutil.move(fs_path, new_fs_path)
644
- file_path = os.path.join(new_fs_path, os.path.basename(fs_path))
645
- new_file_path = file_path[: file_path.index(".zip")]
646
- shutil.move(file_path, new_file_path)
647
- # in the case where the outputs extension has not been set
648
- # to ".tar" in the product type nor provider configuration
649
- elif tarfile.is_tarfile(fs_path):
650
- if not new_fs_path.endswith(".tar"):
651
- new_fs_path += ".tar"
652
- shutil.move(fs_path, new_fs_path)
653
- kwargs["output_extension"] = ".tar"
654
- product_path = self._finalize(
655
- new_fs_path,
656
- progress_callback=progress_callback,
657
- **kwargs,
658
- )
659
- product.location = path_to_uri(product_path)
660
- return product_path
661
- else:
662
- # not a file (dir with zip extension)
663
- shutil.move(fs_path, new_fs_path)
664
- product.location = path_to_uri(new_fs_path)
665
- return new_fs_path
666
-
667
- if os.path.isfile(fs_path) and not (
668
- zipfile.is_zipfile(fs_path) or tarfile.is_tarfile(fs_path)
662
+ if os.path.isfile(path) and not (
663
+ zipfile.is_zipfile(path) or tarfile.is_tarfile(path)
669
664
  ):
670
665
  new_fs_path = os.path.join(
671
- os.path.dirname(fs_path),
666
+ os.path.dirname(path),
672
667
  sanitize(product.properties["title"]),
673
668
  )
674
669
  if not os.path.isdir(new_fs_path):
675
670
  os.makedirs(new_fs_path)
676
- shutil.move(fs_path, new_fs_path)
671
+ shutil.move(path, new_fs_path)
677
672
  product.location = path_to_uri(new_fs_path)
678
673
  return new_fs_path
674
+
679
675
  product_path = self._finalize(
680
- fs_path,
676
+ str(path),
681
677
  progress_callback=progress_callback,
682
678
  **kwargs,
683
679
  )
@@ -718,15 +714,6 @@ class HTTPDownload(Download):
718
714
  ext = guess_extension(content_type)
719
715
  if ext:
720
716
  filename += ext
721
- else:
722
- output_extension: Optional[str] = (
723
- getattr(self.config, "products", {})
724
- .get(product.product_type, {})
725
- .get("output_extension")
726
- )
727
- if output_extension:
728
- filename += output_extension
729
-
730
717
  return filename
731
718
 
732
719
  def _stream_download_dict(
@@ -734,12 +721,12 @@ class HTTPDownload(Download):
734
721
  product: EOProduct,
735
722
  auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
736
723
  progress_callback: Optional[ProgressCallback] = None,
737
- wait: int = DEFAULT_DOWNLOAD_WAIT,
738
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
724
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
725
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
739
726
  **kwargs: Unpack[DownloadConf],
740
727
  ) -> StreamResponse:
741
728
  r"""
742
- Returns dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
729
+ Returns dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
743
730
  It contains a generator to streamed download chunks and the response headers.
744
731
 
745
732
  :param product: The EO product to download
@@ -752,7 +739,7 @@ class HTTPDownload(Download):
752
739
  and `dl_url_params` (dict) can be provided as additional kwargs
753
740
  and will override any other values defined in a configuration
754
741
  file or with environment variables.
755
- :returns: Dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
742
+ :returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
756
743
  """
757
744
  if auth is not None and not isinstance(auth, AuthBase):
758
745
  raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
@@ -809,17 +796,20 @@ class HTTPDownload(Download):
809
796
  else:
810
797
  pass
811
798
 
812
- chunks = self._stream_download(product, auth, progress_callback, **kwargs)
799
+ chunk_iterator = self._stream_download(
800
+ product, auth, progress_callback, **kwargs
801
+ )
802
+
813
803
  # start reading chunks to set product.headers
814
804
  try:
815
- first_chunk = next(chunks)
805
+ first_chunk = next(chunk_iterator)
816
806
  except StopIteration:
817
807
  # product is empty file
818
808
  logger.error("product %s is empty", product.properties["id"])
819
809
  raise NotAvailableError(f"product {product.properties['id']} is empty")
820
810
 
821
811
  return StreamResponse(
822
- content=chain(iter([first_chunk]), chunks),
812
+ content=chain(iter([first_chunk]), chunk_iterator),
823
813
  headers=product.headers,
824
814
  )
825
815
 
@@ -837,12 +827,9 @@ class HTTPDownload(Download):
837
827
  and e.response.status_code in auth_errors
838
828
  ):
839
829
  raise AuthenticationError(
840
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
841
- % (
842
- e.response.status_code,
843
- response_text,
844
- self.provider,
845
- )
830
+ f"Please check your credentials for {self.provider}.",
831
+ f"HTTP Error {e.response.status_code} returned.",
832
+ response_text,
846
833
  )
847
834
 
848
835
  def _process_exception(
@@ -880,6 +867,44 @@ class HTTPDownload(Download):
880
867
  else:
881
868
  logger.error("Error while getting resource :\n%s", tb.format_exc())
882
869
 
870
+ def _order_request(
871
+ self,
872
+ product: EOProduct,
873
+ auth: Optional[AuthBase],
874
+ ) -> None:
875
+ if (
876
+ "orderLink" in product.properties
877
+ and product.properties.get("storageStatus") == OFFLINE_STATUS
878
+ and not product.properties.get("orderStatus")
879
+ ):
880
+ self._order(product=product, auth=auth)
881
+
882
+ if (
883
+ product.properties.get("orderStatusLink", None)
884
+ and product.properties.get("storageStatus") != ONLINE_STATUS
885
+ ):
886
+ self._order_status(product=product, auth=auth)
887
+
888
+ def order(
889
+ self,
890
+ product: EOProduct,
891
+ auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
892
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
893
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
894
+ ) -> None:
895
+ """
896
+ Order product and poll to check its status
897
+
898
+ :param product: The EO product to download
899
+ :param auth: (optional) authenticated object
900
+ :param wait: (optional) Wait time in minutes between two order status check
901
+ :param timeout: (optional) Maximum time in minutes before stop checking
902
+ order status
903
+ """
904
+ self._order_download_retry(product, wait, timeout)(self._order_request)(
905
+ product, auth
906
+ )
907
+
883
908
  def _stream_download(
884
909
  self,
885
910
  product: EOProduct,
@@ -888,8 +913,9 @@ class HTTPDownload(Download):
888
913
  **kwargs: Unpack[DownloadConf],
889
914
  ) -> Iterator[Any]:
890
915
  """
891
- fetches a zip file containing the assets of a given product as a stream
916
+ Fetches a zip file containing the assets of a given product as a stream
892
917
  and returns a generator yielding the chunks of the file
918
+
893
919
  :param product: product for which the assets should be downloaded
894
920
  :param auth: The configuration of a plugin of type Authentication
895
921
  :param progress_callback: A method or a callable object
@@ -903,19 +929,11 @@ class HTTPDownload(Download):
903
929
  logger.info("Progress bar unavailable, please call product.download()")
904
930
  progress_callback = ProgressCallback(disable=True)
905
931
 
906
- ordered_message = ""
907
- if (
908
- "orderLink" in product.properties
909
- and product.properties.get("storageStatus") == OFFLINE_STATUS
910
- and not product.properties.get("orderStatus")
911
- ):
912
- self.order_download(product=product, auth=auth)
932
+ ssl_verify = getattr(self.config, "ssl_verify", True)
913
933
 
914
- if (
915
- product.properties.get("orderStatusLink", None)
916
- and product.properties.get("storageStatus") != ONLINE_STATUS
917
- ):
918
- self.order_download_status(product=product, auth=auth)
934
+ ordered_message = ""
935
+ # retry handled at download level
936
+ self._order_request(product, auth)
919
937
 
920
938
  params = kwargs.pop("dl_url_params", None) or getattr(
921
939
  self.config, "dl_url_params", {}
@@ -945,7 +963,7 @@ class HTTPDownload(Download):
945
963
  auth = None
946
964
 
947
965
  s = requests.Session()
948
- with s.request(
966
+ self.stream = s.request(
949
967
  req_method,
950
968
  req_url,
951
969
  stream=True,
@@ -953,49 +971,48 @@ class HTTPDownload(Download):
953
971
  params=params,
954
972
  headers=USER_AGENT,
955
973
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
974
+ verify=ssl_verify,
956
975
  **req_kwargs,
957
- ) as self.stream:
958
- try:
959
- self.stream.raise_for_status()
960
- except requests.exceptions.Timeout as exc:
961
- raise TimeOutError(
962
- exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT
963
- ) from exc
964
- except RequestException as e:
965
- self._process_exception(e, product, ordered_message)
966
- else:
967
- # check if product was ordered
968
-
969
- if getattr(
970
- self.stream, "status_code", None
971
- ) is not None and self.stream.status_code == getattr(
972
- self.config, "order_status", {}
973
- ).get(
974
- "ordered", {}
975
- ).get(
976
- "http_code"
977
- ):
978
- product.properties["storageStatus"] = "ORDERED"
979
- self._process_exception(None, product, ordered_message)
980
- stream_size = self._check_stream_size(product) or None
981
-
982
- product.headers = self.stream.headers
983
- filename = self._check_product_filename(product) or None
984
- product.headers[
985
- "content-disposition"
986
- ] = f"attachment; filename={filename}"
987
- content_type = product.headers.get("Content-Type")
988
- guessed_content_type = (
989
- guess_file_type(filename) if filename and not content_type else None
990
- )
991
- if guessed_content_type is not None:
992
- product.headers["Content-Type"] = guessed_content_type
976
+ )
977
+ try:
978
+ self.stream.raise_for_status()
979
+ except requests.exceptions.Timeout as exc:
980
+ raise TimeOutError(exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT) from exc
981
+ except RequestException as e:
982
+ self._process_exception(e, product, ordered_message)
983
+ raise DownloadError(
984
+ f"download of {product.properties['id']} is empty"
985
+ ) from e
986
+ else:
987
+ # check if product was ordered
988
+
989
+ if getattr(
990
+ self.stream, "status_code", None
991
+ ) is not None and self.stream.status_code == getattr(
992
+ self.config, "order_status", {}
993
+ ).get(
994
+ "ordered", {}
995
+ ).get(
996
+ "http_code"
997
+ ):
998
+ product.properties["storageStatus"] = "ORDERED"
999
+ self._process_exception(None, product, ordered_message)
1000
+ stream_size = self._check_stream_size(product) or None
1001
+
1002
+ product.headers = self.stream.headers
1003
+ filename = self._check_product_filename(product)
1004
+ product.headers["content-disposition"] = f"attachment; filename={filename}"
1005
+ content_type = product.headers.get("Content-Type")
1006
+ guessed_content_type = (
1007
+ guess_file_type(filename) if filename and not content_type else None
1008
+ )
1009
+ if guessed_content_type is not None:
1010
+ product.headers["Content-Type"] = guessed_content_type
993
1011
 
994
- progress_callback.reset(total=stream_size)
995
- for chunk in self.stream.iter_content(chunk_size=64 * 1024):
996
- if chunk:
997
- progress_callback(len(chunk))
998
- yield chunk
1012
+ progress_callback.reset(total=stream_size)
1013
+
1014
+ product.filename = filename
1015
+ return self.stream.iter_content(chunk_size=64 * 1024)
999
1016
 
1000
1017
  def _stream_download_assets(
1001
1018
  self,
@@ -1055,6 +1072,16 @@ class HTTPDownload(Download):
1055
1072
  "flatten_top_dirs", getattr(self.config, "flatten_top_dirs", True)
1056
1073
  )
1057
1074
  ssl_verify = getattr(self.config, "ssl_verify", True)
1075
+ matching_url = (
1076
+ getattr(product.downloader_auth.config, "matching_url", "")
1077
+ if product.downloader_auth
1078
+ else ""
1079
+ )
1080
+ matching_conf = (
1081
+ getattr(product.downloader_auth.config, "matching_conf", None)
1082
+ if product.downloader_auth
1083
+ else None
1084
+ )
1058
1085
 
1059
1086
  # loop for assets download
1060
1087
  for asset in assets_values:
@@ -1063,11 +1090,16 @@ class HTTPDownload(Download):
1063
1090
  f"Local asset detected. Download skipped for {asset['href']}"
1064
1091
  )
1065
1092
  continue
1066
-
1093
+ if matching_conf or (
1094
+ matching_url and re.match(matching_url, asset["href"])
1095
+ ):
1096
+ auth_object = auth
1097
+ else:
1098
+ auth_object = None
1067
1099
  with requests.get(
1068
1100
  asset["href"],
1069
1101
  stream=True,
1070
- auth=auth,
1102
+ auth=auth_object,
1071
1103
  params=params,
1072
1104
  headers=USER_AGENT,
1073
1105
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
@@ -1080,8 +1112,7 @@ class HTTPDownload(Download):
1080
1112
  exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT
1081
1113
  ) from exc
1082
1114
  except RequestException as e:
1083
- raise_errors = True if len(assets_values) == 1 else False
1084
- self._handle_asset_exception(e, asset, raise_errors=raise_errors)
1115
+ self._handle_asset_exception(e, asset)
1085
1116
  else:
1086
1117
  asset_rel_path = (
1087
1118
  asset.rel_path.replace(assets_common_subdir, "").strip(os.sep)
@@ -1239,27 +1270,22 @@ class HTTPDownload(Download):
1239
1270
 
1240
1271
  return fs_dir_path
1241
1272
 
1242
- def _handle_asset_exception(
1243
- self, e: RequestException, asset: Asset, raise_errors: bool = False
1244
- ) -> None:
1273
+ def _handle_asset_exception(self, e: RequestException, asset: Asset) -> None:
1245
1274
  # check if error is identified as auth_error in provider conf
1246
1275
  auth_errors = getattr(self.config, "auth_error_code", [None])
1247
1276
  if not isinstance(auth_errors, list):
1248
1277
  auth_errors = [auth_errors]
1249
1278
  if e.response is not None and e.response.status_code in auth_errors:
1250
1279
  raise AuthenticationError(
1251
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
1252
- % (
1253
- e.response.status_code,
1254
- e.response.text.strip(),
1255
- self.provider,
1256
- )
1280
+ f"Please check your credentials for {self.provider}.",
1281
+ f"HTTP Error {e.response.status_code} returned.",
1282
+ e.response.text.strip(),
1257
1283
  )
1258
- elif raise_errors:
1259
- raise DownloadError(e)
1260
1284
  else:
1261
- logger.warning("Unexpected error: %s" % e)
1262
- logger.warning("Skipping %s" % asset["href"])
1285
+ logger.error(
1286
+ "Unexpected error at download of asset %s: %s", asset["href"], e
1287
+ )
1288
+ raise DownloadError(e)
1263
1289
 
1264
1290
  def _get_asset_sizes(
1265
1291
  self,
@@ -1341,8 +1367,8 @@ class HTTPDownload(Download):
1341
1367
  auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
1342
1368
  downloaded_callback: Optional[DownloadedCallback] = None,
1343
1369
  progress_callback: Optional[ProgressCallback] = None,
1344
- wait: int = DEFAULT_DOWNLOAD_WAIT,
1345
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
1370
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
1371
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
1346
1372
  **kwargs: Unpack[DownloadConf],
1347
1373
  ):
1348
1374
  """