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.
- eodag/api/core.py +292 -198
- eodag/api/product/_assets.py +6 -6
- eodag/api/product/_product.py +18 -18
- eodag/api/product/metadata_mapping.py +51 -14
- eodag/api/search_result.py +29 -3
- eodag/cli.py +57 -20
- eodag/config.py +413 -117
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +49 -16
- eodag/plugins/apis/usgs.py +30 -7
- eodag/plugins/authentication/aws_auth.py +14 -5
- 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 +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +93 -15
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +6 -6
- eodag/plugins/download/aws.py +50 -34
- eodag/plugins/download/base.py +41 -50
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +221 -195
- eodag/plugins/download/s3rest.py +25 -25
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +106 -39
- eodag/plugins/search/build_search_result.py +1065 -324
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +45 -24
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +549 -257
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +577 -87
- eodag/resources/providers.yml +1619 -2776
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +138 -98
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +55 -329
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +19 -8
- eodag/rest/types/queryables.py +6 -8
- eodag/rest/types/stac_search.py +11 -2
- eodag/rest/utils/__init__.py +3 -0
- eodag/types/__init__.py +71 -18
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +180 -73
- eodag/types/search_args.py +3 -3
- eodag/types/whoosh.py +126 -0
- eodag/utils/__init__.py +147 -66
- eodag/utils/exceptions.py +47 -26
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +11 -13
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
- eodag-3.1.0b1.dist-info/RECORD +108 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
eodag/plugins/download/http.py
CHANGED
|
@@ -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
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
165
|
+
- :attr:`~eodag.config.PluginConfig.order_method`: (optional) HTTP request method, GET (default) or POST
|
|
137
166
|
|
|
138
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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:
|
|
545
|
-
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
|
|
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.
|
|
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:
|
|
611
|
-
timeout:
|
|
626
|
+
wait: float,
|
|
627
|
+
timeout: float,
|
|
612
628
|
**kwargs: Unpack[DownloadConf],
|
|
613
|
-
) ->
|
|
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
|
-
|
|
618
|
-
|
|
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
|
-
|
|
623
|
-
|
|
648
|
+
return path
|
|
649
|
+
else:
|
|
650
|
+
raise DownloadError(
|
|
651
|
+
f"download of product {product.properties['id']} failed"
|
|
652
|
+
)
|
|
624
653
|
|
|
625
|
-
download_request(
|
|
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
|
-
|
|
632
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
738
|
-
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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]),
|
|
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
|
-
"
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
"
|
|
1252
|
-
|
|
1253
|
-
|
|
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.
|
|
1262
|
-
|
|
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:
|
|
1345
|
-
timeout:
|
|
1370
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
1371
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1346
1372
|
**kwargs: Unpack[DownloadConf],
|
|
1347
1373
|
):
|
|
1348
1374
|
"""
|