eodag 3.0.0b2__py3-none-any.whl → 3.0.1__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 (84) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +295 -287
  3. eodag/api/product/__init__.py +10 -4
  4. eodag/api/product/_assets.py +2 -14
  5. eodag/api/product/_product.py +16 -30
  6. eodag/api/product/drivers/__init__.py +7 -2
  7. eodag/api/product/drivers/base.py +0 -3
  8. eodag/api/product/metadata_mapping.py +12 -31
  9. eodag/api/search_result.py +33 -12
  10. eodag/cli.py +35 -19
  11. eodag/config.py +455 -155
  12. eodag/plugins/apis/base.py +13 -7
  13. eodag/plugins/apis/ecmwf.py +16 -7
  14. eodag/plugins/apis/usgs.py +68 -16
  15. eodag/plugins/authentication/aws_auth.py +25 -7
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +14 -3
  18. eodag/plugins/authentication/header.py +12 -4
  19. eodag/plugins/authentication/keycloak.py +41 -22
  20. eodag/plugins/authentication/oauth.py +11 -1
  21. eodag/plugins/authentication/openid_connect.py +183 -167
  22. eodag/plugins/authentication/qsauth.py +12 -4
  23. eodag/plugins/authentication/sas_auth.py +19 -2
  24. eodag/plugins/authentication/token.py +59 -11
  25. eodag/plugins/authentication/token_exchange.py +19 -19
  26. eodag/plugins/crunch/base.py +7 -2
  27. eodag/plugins/crunch/filter_date.py +8 -11
  28. eodag/plugins/crunch/filter_latest_intersect.py +5 -7
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
  30. eodag/plugins/crunch/filter_overlap.py +9 -15
  31. eodag/plugins/crunch/filter_property.py +9 -14
  32. eodag/plugins/download/aws.py +84 -99
  33. eodag/plugins/download/base.py +36 -77
  34. eodag/plugins/download/creodias_s3.py +11 -2
  35. eodag/plugins/download/http.py +134 -109
  36. eodag/plugins/download/s3rest.py +37 -43
  37. eodag/plugins/manager.py +173 -41
  38. eodag/plugins/search/__init__.py +9 -9
  39. eodag/plugins/search/base.py +35 -35
  40. eodag/plugins/search/build_search_result.py +55 -64
  41. eodag/plugins/search/cop_marine.py +113 -32
  42. eodag/plugins/search/creodias_s3.py +20 -8
  43. eodag/plugins/search/csw.py +41 -1
  44. eodag/plugins/search/data_request_search.py +119 -14
  45. eodag/plugins/search/qssearch.py +619 -197
  46. eodag/plugins/search/static_stac_search.py +25 -23
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +211 -56
  49. eodag/resources/providers.yml +1762 -1809
  50. eodag/resources/stac.yml +3 -163
  51. eodag/resources/user_conf_template.yml +134 -119
  52. eodag/rest/config.py +1 -2
  53. eodag/rest/constants.py +0 -1
  54. eodag/rest/core.py +70 -92
  55. eodag/rest/errors.py +181 -0
  56. eodag/rest/server.py +24 -330
  57. eodag/rest/stac.py +105 -630
  58. eodag/rest/types/eodag_search.py +17 -15
  59. eodag/rest/types/queryables.py +5 -14
  60. eodag/rest/types/stac_search.py +18 -13
  61. eodag/rest/utils/rfc3339.py +0 -1
  62. eodag/types/__init__.py +24 -6
  63. eodag/types/download_args.py +14 -5
  64. eodag/types/queryables.py +1 -2
  65. eodag/types/search_args.py +10 -11
  66. eodag/types/whoosh.py +0 -2
  67. eodag/utils/__init__.py +97 -136
  68. eodag/utils/constraints.py +0 -8
  69. eodag/utils/exceptions.py +23 -9
  70. eodag/utils/import_system.py +0 -4
  71. eodag/utils/logging.py +37 -80
  72. eodag/utils/notebook.py +4 -4
  73. eodag/utils/requests.py +13 -23
  74. eodag/utils/rest.py +0 -4
  75. eodag/utils/stac_reader.py +3 -15
  76. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
  77. eodag-3.0.1.dist-info/RECORD +109 -0
  78. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
  79. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
  80. eodag/resources/constraints/climate-dt.json +0 -13
  81. eodag/resources/constraints/extremes-dt.json +0 -8
  82. eodag-3.0.0b2.dist-info/RECORD +0 -110
  83. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
  84. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
@@ -19,12 +19,14 @@ 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
25
26
  from datetime import datetime
26
27
  from email.message import Message
27
28
  from itertools import chain
29
+ from json import JSONDecodeError
28
30
  from typing import (
29
31
  TYPE_CHECKING,
30
32
  Any,
@@ -32,7 +34,6 @@ from typing import (
32
34
  Iterator,
33
35
  List,
34
36
  Optional,
35
- Tuple,
36
37
  TypedDict,
37
38
  Union,
38
39
  cast,
@@ -44,6 +45,7 @@ import requests
44
45
  from lxml import etree
45
46
  from requests import RequestException
46
47
  from requests.auth import AuthBase
48
+ from requests.structures import CaseInsensitiveDict
47
49
  from stream_zip import ZIP_AUTO, stream_zip
48
50
 
49
51
  from eodag.api.product.metadata_mapping import (
@@ -78,6 +80,7 @@ from eodag.utils.exceptions import (
78
80
  DownloadError,
79
81
  MisconfiguredError,
80
82
  NotAvailableError,
83
+ RequestError,
81
84
  TimeOutError,
82
85
  )
83
86
 
@@ -97,31 +100,56 @@ class HTTPDownload(Download):
97
100
  """HTTPDownload plugin. Handles product download over HTTP protocol
98
101
 
99
102
  :param provider: provider name
100
- :type provider: str
101
103
  :param config: Download plugin configuration:
102
104
 
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
115
-
116
-
117
- :type config: :class:`~eodag.config.PluginConfig`
105
+ * :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): ``HTTPDownload``
106
+ * :attr:`~eodag.config.PluginConfig.base_uri` (``str``): default endpoint url
107
+ * :attr:`~eodag.config.PluginConfig.method` (``str``): HTTP request method for the download request (``GET`` or
108
+ ``POST``); default: ``GET``
109
+ * :attr:`~eodag.config.PluginConfig.extract` (``bool``): if the content of the downloaded file should be
110
+ extracted; default: ``True``
111
+ * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is returned in case of an
112
+ authentication error
113
+ * :attr:`~eodag.config.PluginConfig.dl_url_params` (``Dict[str, Any]``): parameters to be
114
+ added to the query params of the request
115
+ * :attr:`~eodag.config.PluginConfig.archive_depth` (``int``): level in extracted path tree where to find data;
116
+ default: ``1``
117
+ * :attr:`~eodag.config.PluginConfig.flatten_top_dirs` (``bool``): if the directory structure should be
118
+ flattened; default: ``True``
119
+ * :attr:`~eodag.config.PluginConfig.ignore_assets` (``bool``): ignore assets and download using downloadLink;
120
+ default: ``False``
121
+ * :attr:`~eodag.config.PluginConfig.timeout` (``int``): time to wait until request timeout in seconds;
122
+ default: ``5``
123
+ * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be verified in
124
+ requests; default: ``True``
125
+ * :attr:`~eodag.config.PluginConfig.output_extension` (``str``): which extension should be used for the
126
+ downloaded file
127
+ * :attr:`~eodag.config.PluginConfig.no_auth_download` (``bool``): if the download should be done without
128
+ authentication; default: ``True``
129
+ * :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): if the product has to be ordered to download it;
130
+ if this parameter is set to true, a mapping for the orderLink has to be added to the metadata mapping of
131
+ the search plugin used for the provider; default: ``False``
132
+ * :attr:`~eodag.config.PluginConfig.order_method` (``str``): HTTP request method for the order request (``GET``
133
+ or ``POST``); default: ``GET``
134
+ * :attr:`~eodag.config.PluginConfig.order_headers` (``[Dict[str, str]]``): headers to be added to the order
135
+ request
136
+ * :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
137
+ a typed dictionary containing the key ``metadata_mapping`` which can be used to add new product properties
138
+ based on the data in response to the order request
139
+ * :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
140
+ configuration to handle the order status; contains information which method to use, how the response data is
141
+ interpreted, which status corresponds to success, ordered and error and what should be done on success.
142
+ * :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type specific config; the
143
+ keys are the product types, the values are dictionaries which can contain the keys
144
+ :attr:`~eodag.config.PluginConfig.output_extension` and :attr:`~eodag.config.PluginConfig.extract` to
145
+ overwrite the provider config for a specific product type
118
146
 
119
147
  """
120
148
 
121
149
  def __init__(self, provider: str, config: PluginConfig) -> None:
122
150
  super(HTTPDownload, self).__init__(provider, config)
123
151
 
124
- def orderDownload(
152
+ def order_download(
125
153
  self,
126
154
  product: EOProduct,
127
155
  auth: Optional[AuthBase] = None,
@@ -133,12 +161,13 @@ class HTTPDownload(Download):
133
161
  and has `orderLink` in its properties.
134
162
  Product ordering can be configured using the following download plugin parameters:
135
163
 
136
- - **order_enabled**: Wether order is enabled or not (may not use this method
164
+ - :attr:`~eodag.config.PluginConfig.order_enabled`: Wether order is enabled or not (may not use this method
137
165
  if no `orderLink` exists)
138
166
 
139
- - **order_method**: (optional) HTTP request method, GET (default) or POST
167
+ - :attr:`~eodag.config.PluginConfig.order_method`: (optional) HTTP request method, GET (default) or POST
140
168
 
141
- - **order_on_response**: (optional) things to do with obtained order response:
169
+ - :attr:`~eodag.config.PluginConfig.order_on_response`: (optional) things to do with obtained order
170
+ response:
142
171
 
143
172
  - *metadata_mapping*: edit or add new product propoerties properties
144
173
 
@@ -147,13 +176,9 @@ class HTTPDownload(Download):
147
176
  - **orderLink**: order request URL
148
177
 
149
178
  :param product: The EO product to order
150
- :type product: :class:`~eodag.api.product._product.EOProduct`
151
179
  :param auth: (optional) authenticated object
152
- :type auth: Optional[AuthBase]
153
180
  :param kwargs: download additional kwargs
154
- :type kwargs: Union[str, bool, dict]
155
181
  :returns: the returned json status response
156
- :rtype: dict
157
182
  """
158
183
  product.properties["storageStatus"] = STAGING_STATUS
159
184
 
@@ -167,10 +192,15 @@ class HTTPDownload(Download):
167
192
  if order_method == "POST":
168
193
  # separate url & parameters
169
194
  parts = urlparse(str(product.properties["orderLink"]))
170
- query_dict = parse_qs(parts.query)
171
- if not query_dict and parts.query:
195
+ query_dict = {}
196
+ # `parts.query` may be a JSON with query strings as one of values. If `parse_qs` is executed as first step,
197
+ # the resulting `query_dict` would be erroneous.
198
+ try:
172
199
  query_dict = geojson.loads(parts.query)
173
- order_url = parts._replace(query=None).geturl()
200
+ except JSONDecodeError:
201
+ if parts.query:
202
+ query_dict = parse_qs(parts.query)
203
+ order_url = parts._replace(query="").geturl()
174
204
  if query_dict:
175
205
  order_kwargs["json"] = query_dict
176
206
  else:
@@ -195,18 +225,11 @@ class HTTPDownload(Download):
195
225
  logger.debug(ordered_message)
196
226
  product.properties["storageStatus"] = STAGING_STATUS
197
227
  except RequestException as e:
198
- if hasattr(e, "response") and (
199
- content := getattr(e.response, "content", None)
200
- ):
201
- error_message = f"{content.decode('utf-8')} - {e}"
202
- else:
203
- error_message = str(e)
204
- logger.warning(
205
- "%s could not be ordered, request returned %s",
206
- product.properties["title"],
207
- error_message,
208
- )
209
228
  self._check_auth_exception(e)
229
+ title = product.properties["title"]
230
+ message = f"{title} could not be ordered"
231
+ raise RequestError.from_error(e, message) from e
232
+
210
233
  return self.order_response_process(response, product)
211
234
  except requests.exceptions.Timeout as exc:
212
235
  raise TimeOutError(exc, timeout=timeout) from exc
@@ -217,11 +240,8 @@ class HTTPDownload(Download):
217
240
  """Process order response
218
241
 
219
242
  :param response: The order response
220
- :type response: :class:`~requests.Response`
221
243
  :param product: The orderd EO product
222
- :type product: :class:`~eodag.api.product._product.EOProduct`
223
244
  :returns: the returned json status response
224
- :rtype: dict
225
245
  """
226
246
  on_response_mm = getattr(self.config, "order_on_response", {}).get(
227
247
  "metadata_mapping", {}
@@ -251,7 +271,7 @@ class HTTPDownload(Download):
251
271
 
252
272
  return json_response
253
273
 
254
- def orderDownloadStatus(
274
+ def order_download_status(
255
275
  self,
256
276
  product: EOProduct,
257
277
  auth: Optional[AuthBase] = None,
@@ -261,18 +281,15 @@ class HTTPDownload(Download):
261
281
  It will be executed before each download retry.
262
282
  Product order status request can be configured using the following download plugin parameters:
263
283
 
264
- - **order_status**: :class:`~eodag.config.PluginConfig.OrderStatus`
284
+ - :attr:`~eodag.config.PluginConfig.order_status`: :class:`~eodag.config.PluginConfig.OrderStatus`
265
285
 
266
286
  Product properties used for order status:
267
287
 
268
288
  - **orderStatusLink**: order status request URL
269
289
 
270
290
  :param product: The ordered EO product
271
- :type product: :class:`~eodag.api.product._product.EOProduct`
272
291
  :param auth: (optional) authenticated object
273
- :type auth: Optional[AuthBase]
274
292
  :param kwargs: download additional kwargs
275
- :type kwargs: Union[str, bool, dict]
276
293
  """
277
294
 
278
295
  status_config = getattr(self.config, "order_status", {})
@@ -325,7 +342,7 @@ class HTTPDownload(Download):
325
342
  if status_request_method == "POST":
326
343
  # separate url & parameters
327
344
  parts = urlparse(str(product.properties["orderStatusLink"]))
328
- status_url = parts._replace(query=None).geturl()
345
+ status_url = parts._replace(query="").geturl()
329
346
  query_dict = parse_qs(parts.query)
330
347
  if not query_dict and parts.query:
331
348
  query_dict = geojson.loads(parts.query)
@@ -568,12 +585,12 @@ class HTTPDownload(Download):
568
585
  )
569
586
  progress_callback = ProgressCallback(disable=True)
570
587
 
571
- outputs_extension = getattr(self.config, "products", {}).get(
588
+ output_extension = getattr(self.config, "products", {}).get(
572
589
  product.product_type, {}
573
- ).get("outputs_extension", None) or getattr(
574
- self.config, "outputs_extension", ".zip"
590
+ ).get("output_extension", None) or getattr(
591
+ self.config, "output_extension", ".zip"
575
592
  )
576
- kwargs["outputs_extension"] = kwargs.get("outputs_extension", outputs_extension)
593
+ kwargs["output_extension"] = kwargs.get("output_extension", output_extension)
577
594
 
578
595
  fs_path, record_filename = self._prepare_download(
579
596
  product,
@@ -637,7 +654,7 @@ class HTTPDownload(Download):
637
654
  logger.debug("Download recorded in %s", record_filename)
638
655
 
639
656
  # Check that the downloaded file is really a zip file
640
- if not zipfile.is_zipfile(fs_path) and outputs_extension == ".zip":
657
+ if not zipfile.is_zipfile(fs_path) and output_extension == ".zip":
641
658
  logger.warning(
642
659
  "Downloaded product is not a Zip File. Please check its file type before using it"
643
660
  )
@@ -658,7 +675,7 @@ class HTTPDownload(Download):
658
675
  if not new_fs_path.endswith(".tar"):
659
676
  new_fs_path += ".tar"
660
677
  shutil.move(fs_path, new_fs_path)
661
- kwargs["outputs_extension"] = ".tar"
678
+ kwargs["output_extension"] = ".tar"
662
679
  product_path = self._finalize(
663
680
  new_fs_path,
664
681
  progress_callback=progress_callback,
@@ -727,13 +744,13 @@ class HTTPDownload(Download):
727
744
  if ext:
728
745
  filename += ext
729
746
  else:
730
- outputs_extension: Optional[str] = (
747
+ output_extension: Optional[str] = (
731
748
  getattr(self.config, "products", {})
732
749
  .get(product.product_type, {})
733
- .get("outputs_extension")
750
+ .get("output_extension")
734
751
  )
735
- if outputs_extension:
736
- filename += outputs_extension
752
+ if output_extension:
753
+ filename += output_extension
737
754
 
738
755
  return filename
739
756
 
@@ -747,27 +764,20 @@ class HTTPDownload(Download):
747
764
  **kwargs: Unpack[DownloadConf],
748
765
  ) -> StreamResponse:
749
766
  r"""
750
- Returns dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
767
+ Returns dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
751
768
  It contains a generator to streamed download chunks and the response headers.
752
769
 
753
770
  :param product: The EO product to download
754
- :type product: :class:`~eodag.api.product._product.EOProduct`
755
771
  :param auth: (optional) authenticated object
756
- :type auth: Optional[Union[AuthBase, Dict[str, str]]]
757
772
  :param progress_callback: (optional) A progress callback
758
- :type progress_callback: :class:`~eodag.utils.ProgressCallback`
759
773
  :param wait: (optional) If download fails, wait time in minutes between two download tries
760
- :type wait: int
761
774
  :param timeout: (optional) If download fails, maximum time in minutes before stop retrying
762
775
  to download
763
- :type timeout: int
764
- :param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
776
+ :param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
765
777
  and `dl_url_params` (dict) can be provided as additional kwargs
766
778
  and will override any other values defined in a configuration
767
779
  file or with environment variables.
768
- :type kwargs: Union[str, bool, dict]
769
- :returns: Dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
770
- :rtype: dict
780
+ :returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
771
781
  """
772
782
  if auth is not None and not isinstance(auth, AuthBase):
773
783
  raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
@@ -852,12 +862,9 @@ class HTTPDownload(Download):
852
862
  and e.response.status_code in auth_errors
853
863
  ):
854
864
  raise AuthenticationError(
855
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
856
- % (
857
- e.response.status_code,
858
- response_text,
859
- self.provider,
860
- )
865
+ f"Please check your credentials for {self.provider}.",
866
+ f"HTTP Error {e.response.status_code} returned.",
867
+ response_text,
861
868
  )
862
869
 
863
870
  def _process_exception(
@@ -906,35 +913,33 @@ class HTTPDownload(Download):
906
913
  fetches a zip file containing the assets of a given product as a stream
907
914
  and returns a generator yielding the chunks of the file
908
915
  :param product: product for which the assets should be downloaded
909
- :type product: :class:`~eodag.api.product._product.EOProduct`
910
916
  :param auth: The configuration of a plugin of type Authentication
911
- :type auth: Optional[Union[AuthBase, Dict[str, str]]]
912
917
  :param progress_callback: A method or a callable object
913
918
  which takes a current size and a maximum
914
919
  size as inputs and handle progress bar
915
920
  creation and update to give the user a
916
921
  feedback on the download progress
917
- :type progress_callback: :class:`~eodag.utils.ProgressCallback`
918
922
  :param kwargs: additional arguments
919
- :type kwargs: dict
920
923
  """
921
924
  if progress_callback is None:
922
925
  logger.info("Progress bar unavailable, please call product.download()")
923
926
  progress_callback = ProgressCallback(disable=True)
924
927
 
928
+ ssl_verify = getattr(self.config, "ssl_verify", True)
929
+
925
930
  ordered_message = ""
926
931
  if (
927
932
  "orderLink" in product.properties
928
933
  and product.properties.get("storageStatus") == OFFLINE_STATUS
929
934
  and not product.properties.get("orderStatus")
930
935
  ):
931
- self.orderDownload(product=product, auth=auth)
936
+ self.order_download(product=product, auth=auth)
932
937
 
933
938
  if (
934
939
  product.properties.get("orderStatusLink", None)
935
940
  and product.properties.get("storageStatus") != ONLINE_STATUS
936
941
  ):
937
- self.orderDownloadStatus(product=product, auth=auth)
942
+ self.order_download_status(product=product, auth=auth)
938
943
 
939
944
  params = kwargs.pop("dl_url_params", None) or getattr(
940
945
  self.config, "dl_url_params", {}
@@ -951,7 +956,7 @@ class HTTPDownload(Download):
951
956
  query_dict = parse_qs(parts.query)
952
957
  if not query_dict and parts.query:
953
958
  query_dict = geojson.loads(parts.query)
954
- req_url = parts._replace(query=None).geturl()
959
+ req_url = parts._replace(query="").geturl()
955
960
  req_kwargs: Dict[str, Any] = {"json": query_dict} if query_dict else {}
956
961
  else:
957
962
  req_url = url
@@ -972,6 +977,7 @@ class HTTPDownload(Download):
972
977
  params=params,
973
978
  headers=USER_AGENT,
974
979
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
980
+ verify=ssl_verify,
975
981
  **req_kwargs,
976
982
  ) as self.stream:
977
983
  try:
@@ -1004,8 +1010,11 @@ class HTTPDownload(Download):
1004
1010
  "content-disposition"
1005
1011
  ] = f"attachment; filename={filename}"
1006
1012
  content_type = product.headers.get("Content-Type")
1007
- if filename and not content_type:
1008
- product.headers["Content-Type"] = guess_file_type(filename)
1013
+ guessed_content_type = (
1014
+ guess_file_type(filename) if filename and not content_type else None
1015
+ )
1016
+ if guessed_content_type is not None:
1017
+ product.headers["Content-Type"] = guessed_content_type
1009
1018
 
1010
1019
  progress_callback.reset(total=stream_size)
1011
1020
  for chunk in self.stream.iter_content(chunk_size=64 * 1024):
@@ -1020,7 +1029,7 @@ class HTTPDownload(Download):
1020
1029
  progress_callback: Optional[ProgressCallback] = None,
1021
1030
  assets_values: List[Asset] = [],
1022
1031
  **kwargs: Unpack[DownloadConf],
1023
- ) -> Iterator[Tuple[str, datetime, int, Any, Iterator[Any]]]:
1032
+ ) -> Iterator[Any]:
1024
1033
  if progress_callback is None:
1025
1034
  logger.info("Progress bar unavailable, please call product.download()")
1026
1035
  progress_callback = ProgressCallback(disable=True)
@@ -1071,20 +1080,34 @@ class HTTPDownload(Download):
1071
1080
  "flatten_top_dirs", getattr(self.config, "flatten_top_dirs", True)
1072
1081
  )
1073
1082
  ssl_verify = getattr(self.config, "ssl_verify", True)
1083
+ matching_url = (
1084
+ getattr(product.downloader_auth.config, "matching_url", "")
1085
+ if product.downloader_auth
1086
+ else ""
1087
+ )
1088
+ matching_conf = (
1089
+ getattr(product.downloader_auth.config, "matching_conf", None)
1090
+ if product.downloader_auth
1091
+ else None
1092
+ )
1074
1093
 
1075
1094
  # loop for assets download
1076
1095
  for asset in assets_values:
1077
-
1078
1096
  if not asset["href"] or asset["href"].startswith("file:"):
1079
1097
  logger.info(
1080
1098
  f"Local asset detected. Download skipped for {asset['href']}"
1081
1099
  )
1082
1100
  continue
1083
-
1101
+ if matching_conf or (
1102
+ matching_url and re.match(matching_url, asset["href"])
1103
+ ):
1104
+ auth_object = auth
1105
+ else:
1106
+ auth_object = None
1084
1107
  with requests.get(
1085
1108
  asset["href"],
1086
1109
  stream=True,
1087
- auth=auth,
1110
+ auth=auth_object,
1088
1111
  params=params,
1089
1112
  headers=USER_AGENT,
1090
1113
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
@@ -1097,8 +1120,7 @@ class HTTPDownload(Download):
1097
1120
  exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT
1098
1121
  ) from exc
1099
1122
  except RequestException as e:
1100
- raise_errors = True if len(assets_values) == 1 else False
1101
- self._handle_asset_exception(e, asset, raise_errors=raise_errors)
1123
+ self._handle_asset_exception(e, asset)
1102
1124
  else:
1103
1125
  asset_rel_path = (
1104
1126
  asset.rel_path.replace(assets_common_subdir, "").strip(os.sep)
@@ -1194,7 +1216,9 @@ class HTTPDownload(Download):
1194
1216
  # start reading chunks to set asset.rel_path
1195
1217
  first_chunks_tuple = next(chunks_tuples)
1196
1218
  chunks = chain(iter([first_chunks_tuple]), chunks_tuples)
1197
- chunks_tuples = [(assets_values[0].rel_path, None, None, None, chunks)]
1219
+ chunks_tuples = iter(
1220
+ [(assets_values[0].rel_path, None, None, None, chunks)]
1221
+ )
1198
1222
 
1199
1223
  for chunk_tuple in chunks_tuples:
1200
1224
  asset_path = chunk_tuple[0]
@@ -1254,27 +1278,22 @@ class HTTPDownload(Download):
1254
1278
 
1255
1279
  return fs_dir_path
1256
1280
 
1257
- def _handle_asset_exception(
1258
- self, e: RequestException, asset: Asset, raise_errors: bool = False
1259
- ) -> None:
1281
+ def _handle_asset_exception(self, e: RequestException, asset: Asset) -> None:
1260
1282
  # check if error is identified as auth_error in provider conf
1261
1283
  auth_errors = getattr(self.config, "auth_error_code", [None])
1262
1284
  if not isinstance(auth_errors, list):
1263
1285
  auth_errors = [auth_errors]
1264
1286
  if e.response is not None and e.response.status_code in auth_errors:
1265
1287
  raise AuthenticationError(
1266
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
1267
- % (
1268
- e.response.status_code,
1269
- e.response.text.strip(),
1270
- self.provider,
1271
- )
1288
+ f"Please check your credentials for {self.provider}.",
1289
+ f"HTTP Error {e.response.status_code} returned.",
1290
+ e.response.text.strip(),
1272
1291
  )
1273
- elif raise_errors:
1274
- raise DownloadError(e)
1275
1292
  else:
1276
- logger.warning("Unexpected error: %s" % e)
1277
- logger.warning("Skipping %s" % asset["href"])
1293
+ logger.error(
1294
+ "Unexpected error at download of asset %s: %s", asset["href"], e
1295
+ )
1296
+ raise DownloadError(e)
1278
1297
 
1279
1298
  def _get_asset_sizes(
1280
1299
  self,
@@ -1291,12 +1310,18 @@ class HTTPDownload(Download):
1291
1310
  for asset in assets_values:
1292
1311
  if asset["href"] and not asset["href"].startswith("file:"):
1293
1312
  # HEAD request for size & filename
1294
- asset_headers = requests.head(
1295
- asset["href"],
1296
- auth=auth,
1297
- headers=USER_AGENT,
1298
- timeout=timeout,
1299
- ).headers
1313
+ try:
1314
+ asset_headers = requests.head(
1315
+ asset["href"],
1316
+ auth=auth,
1317
+ params=params,
1318
+ headers=USER_AGENT,
1319
+ timeout=timeout,
1320
+ verify=ssl_verify,
1321
+ ).headers
1322
+ except RequestException as e:
1323
+ logger.debug(f"HEAD request failed: {str(e)}")
1324
+ asset_headers = CaseInsensitiveDict()
1300
1325
 
1301
1326
  if not getattr(asset, "size", 0):
1302
1327
  # size from HEAD header / Content-length