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.
- eodag/__init__.py +6 -8
- eodag/api/core.py +295 -287
- eodag/api/product/__init__.py +10 -4
- eodag/api/product/_assets.py +2 -14
- eodag/api/product/_product.py +16 -30
- eodag/api/product/drivers/__init__.py +7 -2
- eodag/api/product/drivers/base.py +0 -3
- eodag/api/product/metadata_mapping.py +12 -31
- eodag/api/search_result.py +33 -12
- eodag/cli.py +35 -19
- eodag/config.py +455 -155
- eodag/plugins/apis/base.py +13 -7
- eodag/plugins/apis/ecmwf.py +16 -7
- eodag/plugins/apis/usgs.py +68 -16
- eodag/plugins/authentication/aws_auth.py +25 -7
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +183 -167
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +59 -11
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +7 -2
- eodag/plugins/crunch/filter_date.py +8 -11
- eodag/plugins/crunch/filter_latest_intersect.py +5 -7
- eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
- eodag/plugins/crunch/filter_overlap.py +9 -15
- eodag/plugins/crunch/filter_property.py +9 -14
- eodag/plugins/download/aws.py +84 -99
- eodag/plugins/download/base.py +36 -77
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +134 -109
- eodag/plugins/download/s3rest.py +37 -43
- eodag/plugins/manager.py +173 -41
- eodag/plugins/search/__init__.py +9 -9
- eodag/plugins/search/base.py +35 -35
- eodag/plugins/search/build_search_result.py +55 -64
- eodag/plugins/search/cop_marine.py +113 -32
- eodag/plugins/search/creodias_s3.py +20 -8
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +119 -14
- eodag/plugins/search/qssearch.py +619 -197
- eodag/plugins/search/static_stac_search.py +25 -23
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +211 -56
- eodag/resources/providers.yml +1762 -1809
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +134 -119
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +70 -92
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -330
- eodag/rest/stac.py +105 -630
- eodag/rest/types/eodag_search.py +17 -15
- eodag/rest/types/queryables.py +5 -14
- eodag/rest/types/stac_search.py +18 -13
- eodag/rest/utils/rfc3339.py +0 -1
- eodag/types/__init__.py +24 -6
- eodag/types/download_args.py +14 -5
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +10 -11
- eodag/types/whoosh.py +0 -2
- eodag/utils/__init__.py +97 -136
- eodag/utils/constraints.py +0 -8
- eodag/utils/exceptions.py +23 -9
- eodag/utils/import_system.py +0 -4
- eodag/utils/logging.py +37 -80
- eodag/utils/notebook.py +4 -4
- eodag/utils/requests.py +13 -23
- eodag/utils/rest.py +0 -4
- eodag/utils/stac_reader.py +3 -15
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag-3.0.0b2.dist-info/RECORD +0 -110
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
eodag/plugins/download/http.py
CHANGED
|
@@ -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
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
167
|
+
- :attr:`~eodag.config.PluginConfig.order_method`: (optional) HTTP request method, GET (default) or POST
|
|
140
168
|
|
|
141
|
-
-
|
|
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 =
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
-
|
|
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=
|
|
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
|
-
|
|
588
|
+
output_extension = getattr(self.config, "products", {}).get(
|
|
572
589
|
product.product_type, {}
|
|
573
|
-
).get("
|
|
574
|
-
self.config, "
|
|
590
|
+
).get("output_extension", None) or getattr(
|
|
591
|
+
self.config, "output_extension", ".zip"
|
|
575
592
|
)
|
|
576
|
-
kwargs["
|
|
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
|
|
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["
|
|
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
|
-
|
|
747
|
+
output_extension: Optional[str] = (
|
|
731
748
|
getattr(self.config, "products", {})
|
|
732
749
|
.get(product.product_type, {})
|
|
733
|
-
.get("
|
|
750
|
+
.get("output_extension")
|
|
734
751
|
)
|
|
735
|
-
if
|
|
736
|
-
filename +=
|
|
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
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
"
|
|
856
|
-
|
|
857
|
-
|
|
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.
|
|
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.
|
|
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=
|
|
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
|
-
|
|
1008
|
-
|
|
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[
|
|
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=
|
|
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
|
-
|
|
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 =
|
|
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
|
-
"
|
|
1267
|
-
|
|
1268
|
-
|
|
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.
|
|
1277
|
-
|
|
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
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|