eodag 3.10.1__py3-none-any.whl → 4.0.0a2__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 -1
- eodag/api/collection.py +353 -0
- eodag/api/core.py +606 -641
- eodag/api/product/__init__.py +3 -3
- eodag/api/product/_product.py +74 -56
- eodag/api/product/drivers/__init__.py +4 -46
- eodag/api/product/drivers/base.py +0 -28
- eodag/api/product/metadata_mapping.py +178 -216
- eodag/api/search_result.py +156 -15
- eodag/cli.py +83 -403
- eodag/config.py +81 -51
- eodag/plugins/apis/base.py +2 -2
- eodag/plugins/apis/ecmwf.py +36 -25
- eodag/plugins/apis/usgs.py +55 -40
- eodag/plugins/authentication/base.py +1 -3
- eodag/plugins/crunch/filter_date.py +3 -3
- eodag/plugins/crunch/filter_latest_intersect.py +2 -2
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/download/aws.py +46 -42
- eodag/plugins/download/base.py +13 -14
- eodag/plugins/download/http.py +65 -65
- eodag/plugins/manager.py +28 -29
- eodag/plugins/search/__init__.py +6 -4
- eodag/plugins/search/base.py +131 -80
- eodag/plugins/search/build_search_result.py +245 -173
- eodag/plugins/search/cop_marine.py +87 -56
- eodag/plugins/search/csw.py +47 -37
- eodag/plugins/search/qssearch.py +653 -429
- eodag/plugins/search/stac_list_assets.py +1 -1
- eodag/plugins/search/static_stac_search.py +43 -44
- eodag/resources/{product_types.yml → collections.yml} +2594 -2453
- eodag/resources/ext_collections.json +1 -1
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +2706 -2733
- eodag/resources/stac_provider.yml +50 -92
- eodag/resources/user_conf_template.yml +9 -0
- eodag/types/__init__.py +2 -0
- eodag/types/queryables.py +70 -91
- eodag/types/search_args.py +1 -1
- eodag/utils/__init__.py +97 -21
- eodag/utils/dates.py +0 -12
- eodag/utils/exceptions.py +6 -6
- eodag/utils/free_text_search.py +3 -3
- eodag/utils/repr.py +2 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
- eodag-4.0.0a2.dist-info/RECORD +93 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
- eodag/plugins/authentication/oauth.py +0 -60
- eodag/plugins/download/creodias_s3.py +0 -71
- eodag/plugins/download/s3rest.py +0 -351
- eodag/plugins/search/data_request_search.py +0 -565
- eodag/resources/stac.yml +0 -294
- eodag/resources/stac_api.yml +0 -2105
- eodag/rest/__init__.py +0 -24
- eodag/rest/cache.py +0 -70
- eodag/rest/config.py +0 -67
- eodag/rest/constants.py +0 -26
- eodag/rest/core.py +0 -764
- eodag/rest/errors.py +0 -210
- eodag/rest/server.py +0 -604
- eodag/rest/server.wsgi +0 -6
- eodag/rest/stac.py +0 -1032
- eodag/rest/templates/README +0 -1
- eodag/rest/types/__init__.py +0 -18
- eodag/rest/types/collections_search.py +0 -44
- eodag/rest/types/eodag_search.py +0 -386
- eodag/rest/types/queryables.py +0 -174
- eodag/rest/types/stac_search.py +0 -272
- eodag/rest/utils/__init__.py +0 -207
- eodag/rest/utils/cql_evaluate.py +0 -119
- eodag/rest/utils/rfc3339.py +0 -64
- eodag-3.10.1.dist-info/RECORD +0 -116
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
eodag/plugins/download/s3rest.py
DELETED
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Copyright 2018, CS GROUP - France, https://www.csgroup.eu/
|
|
3
|
-
#
|
|
4
|
-
# This file is part of EODAG project
|
|
5
|
-
# https://www.github.com/CS-SI/EODAG
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
-
# you may not use this file except in compliance with the License.
|
|
9
|
-
# You may obtain a copy of the License at
|
|
10
|
-
#
|
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
-
#
|
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
# See the License for the specific language governing permissions and
|
|
17
|
-
# limitations under the License.
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
import logging
|
|
21
|
-
import os
|
|
22
|
-
import os.path
|
|
23
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
24
|
-
from urllib.parse import unquote, urljoin
|
|
25
|
-
from xml.dom import minidom
|
|
26
|
-
from xml.parsers.expat import ExpatError
|
|
27
|
-
|
|
28
|
-
import requests
|
|
29
|
-
from requests import RequestException
|
|
30
|
-
from requests.auth import AuthBase
|
|
31
|
-
|
|
32
|
-
from eodag.api.product.metadata_mapping import OFFLINE_STATUS, ONLINE_STATUS
|
|
33
|
-
from eodag.plugins.download.base import Download
|
|
34
|
-
from eodag.plugins.download.http import HTTPDownload
|
|
35
|
-
from eodag.utils import (
|
|
36
|
-
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
37
|
-
DEFAULT_DOWNLOAD_WAIT,
|
|
38
|
-
DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
39
|
-
HTTP_REQ_TIMEOUT,
|
|
40
|
-
USER_AGENT,
|
|
41
|
-
ProgressCallback,
|
|
42
|
-
_deprecated,
|
|
43
|
-
get_bucket_name_and_prefix,
|
|
44
|
-
path_to_uri,
|
|
45
|
-
)
|
|
46
|
-
from eodag.utils.exceptions import (
|
|
47
|
-
AuthenticationError,
|
|
48
|
-
DownloadError,
|
|
49
|
-
MisconfiguredError,
|
|
50
|
-
NotAvailableError,
|
|
51
|
-
RequestError,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if TYPE_CHECKING:
|
|
55
|
-
from mypy_boto3_s3 import S3ServiceResource
|
|
56
|
-
|
|
57
|
-
from eodag.api.product import EOProduct
|
|
58
|
-
from eodag.config import PluginConfig
|
|
59
|
-
from eodag.types import S3SessionKwargs
|
|
60
|
-
from eodag.types.download_args import DownloadConf
|
|
61
|
-
from eodag.utils import Unpack
|
|
62
|
-
|
|
63
|
-
logger = logging.getLogger("eodag.download.s3rest")
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@_deprecated(
|
|
67
|
-
reason="Plugin that was used in previous mundi provider configuration, but not used anymore",
|
|
68
|
-
version="3.7.1",
|
|
69
|
-
)
|
|
70
|
-
class S3RestDownload(Download):
|
|
71
|
-
"""Http download on S3-like object storage location
|
|
72
|
-
|
|
73
|
-
For example using Mundi REST API (free account)
|
|
74
|
-
https://mundiwebservices.com/keystoneapi/uploads/documents/CWS-DATA-MUT-087-EN-Mundi_Download_v1.1.pdf#page=13
|
|
75
|
-
|
|
76
|
-
Re-use AwsDownload bucket and some handling methods
|
|
77
|
-
|
|
78
|
-
:param provider: provider name
|
|
79
|
-
:param config: Download plugin configuration:
|
|
80
|
-
|
|
81
|
-
* :attr:`~eodag.config.PluginConfig.base_uri` (``str``) (**mandatory**): default endpoint url
|
|
82
|
-
* :attr:`~eodag.config.PluginConfig.extract` (``bool``): extract downloaded archive or not
|
|
83
|
-
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): authentication error code
|
|
84
|
-
* :attr:`~eodag.config.PluginConfig.bucket_path_level` (``int``): bucket location index in ``path.split('/')``
|
|
85
|
-
* :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): whether order is enabled
|
|
86
|
-
or not if product is `OFFLINE`
|
|
87
|
-
* :attr:`~eodag.config.PluginConfig.order_method` (``str``) HTTP request method, ``GET`` (default) or ``POST``
|
|
88
|
-
* :attr:`~eodag.config.PluginConfig.order_headers` (``[dict[str, str]]``): order request headers
|
|
89
|
-
* :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
|
|
90
|
-
a typed dictionary containing the key :attr:`~eodag.config.PluginConfig.OrderOnResponse.metadata_mapping`
|
|
91
|
-
which can be used to add new product properties based on the data in response to the order request
|
|
92
|
-
* :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
|
|
93
|
-
Order status handling
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
97
|
-
super().__init__(provider, config)
|
|
98
|
-
self.http_download_plugin = HTTPDownload(self.provider, self.config)
|
|
99
|
-
|
|
100
|
-
def download(
|
|
101
|
-
self,
|
|
102
|
-
product: EOProduct,
|
|
103
|
-
auth: Optional[Union[AuthBase, S3SessionKwargs, S3ServiceResource]] = None,
|
|
104
|
-
progress_callback: Optional[ProgressCallback] = None,
|
|
105
|
-
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
106
|
-
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
107
|
-
**kwargs: Unpack[DownloadConf],
|
|
108
|
-
) -> Optional[str]:
|
|
109
|
-
"""Download method for S3 REST API.
|
|
110
|
-
|
|
111
|
-
:param product: The EO product to download
|
|
112
|
-
:param auth: (optional) authenticated object
|
|
113
|
-
:param progress_callback: (optional) A method or a callable object
|
|
114
|
-
which takes a current size and a maximum
|
|
115
|
-
size as inputs and handle progress bar
|
|
116
|
-
creation and update to give the user a
|
|
117
|
-
feedback on the download progress
|
|
118
|
-
:param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
|
|
119
|
-
and `dl_url_params` (dict) can be provided as additional kwargs
|
|
120
|
-
and will override any other values defined in a configuration
|
|
121
|
-
file or with environment variables.
|
|
122
|
-
:returns: The absolute path to the downloaded product in the local filesystem
|
|
123
|
-
"""
|
|
124
|
-
if auth is not None and not isinstance(auth, AuthBase):
|
|
125
|
-
raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
|
|
126
|
-
|
|
127
|
-
if progress_callback is None:
|
|
128
|
-
logger.info(
|
|
129
|
-
"Progress bar unavailable, please call product.download() instead of plugin.download()"
|
|
130
|
-
)
|
|
131
|
-
progress_callback = ProgressCallback(disable=True)
|
|
132
|
-
|
|
133
|
-
# order product if it is offline
|
|
134
|
-
ordered_message = ""
|
|
135
|
-
if (
|
|
136
|
-
"orderLink" in product.properties
|
|
137
|
-
and "storageStatus" in product.properties
|
|
138
|
-
and product.properties["storageStatus"] != ONLINE_STATUS
|
|
139
|
-
):
|
|
140
|
-
self.http_download_plugin._order(product=product, auth=auth)
|
|
141
|
-
|
|
142
|
-
@self._order_download_retry(product, wait, timeout)
|
|
143
|
-
def download_request(
|
|
144
|
-
product: EOProduct,
|
|
145
|
-
auth: AuthBase,
|
|
146
|
-
progress_callback: ProgressCallback,
|
|
147
|
-
ordered_message: str,
|
|
148
|
-
**kwargs: Unpack[DownloadConf],
|
|
149
|
-
):
|
|
150
|
-
# check order status
|
|
151
|
-
if product.properties.get("orderStatusLink"):
|
|
152
|
-
self.http_download_plugin._order_status(product=product, auth=auth)
|
|
153
|
-
|
|
154
|
-
# get bucket urls
|
|
155
|
-
bucket_name, prefix = get_bucket_name_and_prefix(
|
|
156
|
-
url=product.location, bucket_path_level=self.config.bucket_path_level
|
|
157
|
-
)
|
|
158
|
-
if prefix is None:
|
|
159
|
-
raise DownloadError(f"Could not extract prefix from {product.location}")
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
bucket_name is None
|
|
163
|
-
and "storageStatus" in product.properties
|
|
164
|
-
and product.properties["storageStatus"] == OFFLINE_STATUS
|
|
165
|
-
):
|
|
166
|
-
raise NotAvailableError(
|
|
167
|
-
"%s is not available for download on %s (status = %s)"
|
|
168
|
-
% (
|
|
169
|
-
product.properties["title"],
|
|
170
|
-
self.provider,
|
|
171
|
-
product.properties["storageStatus"],
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
bucket_url = urljoin(
|
|
176
|
-
product.downloader.config.base_uri.strip("/") + "/", bucket_name
|
|
177
|
-
)
|
|
178
|
-
nodes_list_url = bucket_url + "?prefix=" + prefix.strip("/")
|
|
179
|
-
|
|
180
|
-
# get nodes/files list contained in the bucket
|
|
181
|
-
logger.debug("Retrieving product content from %s", nodes_list_url)
|
|
182
|
-
|
|
183
|
-
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
184
|
-
timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
|
|
185
|
-
|
|
186
|
-
bucket_contents = requests.get(
|
|
187
|
-
nodes_list_url,
|
|
188
|
-
auth=auth,
|
|
189
|
-
headers=USER_AGENT,
|
|
190
|
-
timeout=timeout,
|
|
191
|
-
verify=ssl_verify,
|
|
192
|
-
)
|
|
193
|
-
try:
|
|
194
|
-
bucket_contents.raise_for_status()
|
|
195
|
-
except requests.RequestException as err:
|
|
196
|
-
# check if error is identified as auth_error in provider conf
|
|
197
|
-
auth_errors = getattr(self.config, "auth_error_code", [None])
|
|
198
|
-
if not isinstance(auth_errors, list):
|
|
199
|
-
auth_errors = [auth_errors]
|
|
200
|
-
if err.response and err.response.status_code in auth_errors:
|
|
201
|
-
raise AuthenticationError(
|
|
202
|
-
f"Please check your credentials for {self.provider}.",
|
|
203
|
-
f"HTTP Error {err.response.status_code} returned.",
|
|
204
|
-
err.response.text.strip(),
|
|
205
|
-
)
|
|
206
|
-
# product not available
|
|
207
|
-
elif (
|
|
208
|
-
product.properties.get("storageStatus", ONLINE_STATUS)
|
|
209
|
-
!= ONLINE_STATUS
|
|
210
|
-
):
|
|
211
|
-
msg = (
|
|
212
|
-
ordered_message
|
|
213
|
-
if ordered_message
|
|
214
|
-
and err.response
|
|
215
|
-
and not err.response.text.strip()
|
|
216
|
-
else err.response and err.response.text.strip()
|
|
217
|
-
)
|
|
218
|
-
raise NotAvailableError(
|
|
219
|
-
"%s(initially %s) requested, returned: %s"
|
|
220
|
-
% (
|
|
221
|
-
product.properties["title"],
|
|
222
|
-
product.properties["storageStatus"],
|
|
223
|
-
msg,
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
# other error
|
|
227
|
-
else:
|
|
228
|
-
logger.exception(
|
|
229
|
-
"Could not get content from %s (provider:%s, plugin:%s)\n%s",
|
|
230
|
-
nodes_list_url,
|
|
231
|
-
self.provider,
|
|
232
|
-
self.__class__.__name__,
|
|
233
|
-
bucket_contents.text,
|
|
234
|
-
)
|
|
235
|
-
raise RequestError.from_error(err) from err
|
|
236
|
-
try:
|
|
237
|
-
xmldoc = minidom.parseString(bucket_contents.text)
|
|
238
|
-
except ExpatError as err:
|
|
239
|
-
logger.exception("Could not parse xml data from %s", bucket_contents)
|
|
240
|
-
raise DownloadError(str(err))
|
|
241
|
-
nodes_xml_list = xmldoc.getElementsByTagName("Contents")
|
|
242
|
-
|
|
243
|
-
if len(nodes_xml_list) == 0:
|
|
244
|
-
logger.warning("Could not load any content from %s", nodes_list_url)
|
|
245
|
-
|
|
246
|
-
# destination product path
|
|
247
|
-
output_dir = kwargs.pop("output_dir", None) or self.config.output_dir
|
|
248
|
-
abs_output_dir = os.path.abspath(output_dir)
|
|
249
|
-
product_local_path = os.path.join(abs_output_dir, prefix.split("/")[-1])
|
|
250
|
-
|
|
251
|
-
# .downloaded cache record directory
|
|
252
|
-
download_records_dir = os.path.join(abs_output_dir, ".downloaded")
|
|
253
|
-
try:
|
|
254
|
-
os.makedirs(download_records_dir)
|
|
255
|
-
except OSError as exc:
|
|
256
|
-
import errno
|
|
257
|
-
|
|
258
|
-
if exc.errno != errno.EEXIST: # Skip error if dir exists
|
|
259
|
-
import traceback as tb
|
|
260
|
-
|
|
261
|
-
logger.warning(
|
|
262
|
-
"Unable to create records directory. Got:\n%s", tb.format_exc()
|
|
263
|
-
)
|
|
264
|
-
# check if product has already been downloaded
|
|
265
|
-
record_filename = os.path.join(
|
|
266
|
-
download_records_dir, self.generate_record_hash(product)
|
|
267
|
-
)
|
|
268
|
-
if os.path.isfile(record_filename) and os.path.exists(product_local_path):
|
|
269
|
-
product.location = path_to_uri(product_local_path)
|
|
270
|
-
return product_local_path
|
|
271
|
-
# Remove the record file if product_local_path is absent (e.g. it was deleted while record wasn't)
|
|
272
|
-
elif os.path.isfile(record_filename):
|
|
273
|
-
logger.debug(
|
|
274
|
-
"Record file found (%s) but not the actual file", record_filename
|
|
275
|
-
)
|
|
276
|
-
logger.debug("Removing record file : %s", record_filename)
|
|
277
|
-
os.remove(record_filename)
|
|
278
|
-
|
|
279
|
-
# total size for progress_callback
|
|
280
|
-
size_list: list[int] = [
|
|
281
|
-
int(node.firstChild.nodeValue or 0)
|
|
282
|
-
for node in xmldoc.getElementsByTagName("Size")
|
|
283
|
-
if node.firstChild is not None
|
|
284
|
-
]
|
|
285
|
-
total_size = sum(size_list)
|
|
286
|
-
progress_callback.reset(total=total_size)
|
|
287
|
-
|
|
288
|
-
# download each node key
|
|
289
|
-
for node_xml in nodes_xml_list:
|
|
290
|
-
node_key = node_xml.getElementsByTagName("Key")[0].firstChild.nodeValue # type: ignore[union-attr]
|
|
291
|
-
if node_key is None:
|
|
292
|
-
logger.debug(
|
|
293
|
-
"Node key is None, skipping this node: %s", node_xml.toxml()
|
|
294
|
-
)
|
|
295
|
-
continue
|
|
296
|
-
node_key = unquote(node_key)
|
|
297
|
-
# As "Key", "Size" and "ETag" (md5 hash) can also be retrieved from node_xml
|
|
298
|
-
node_url = urljoin(bucket_url.strip("/") + "/", node_key.strip("/"))
|
|
299
|
-
# output file location
|
|
300
|
-
local_filename_suffix_list = node_key.split("/")[6:]
|
|
301
|
-
if local_filename_suffix_list[0] == os.path.basename(
|
|
302
|
-
product_local_path
|
|
303
|
-
):
|
|
304
|
-
local_filename_suffix_list.pop(0)
|
|
305
|
-
# single file: remove nested sub dirs
|
|
306
|
-
if len(nodes_xml_list) == 1:
|
|
307
|
-
local_filename_suffix_list = [local_filename_suffix_list[-1]]
|
|
308
|
-
local_filename = os.path.join(
|
|
309
|
-
product_local_path, *local_filename_suffix_list
|
|
310
|
-
)
|
|
311
|
-
local_filename_dir = os.path.dirname(os.path.realpath(local_filename))
|
|
312
|
-
if not os.path.isdir(local_filename_dir):
|
|
313
|
-
os.makedirs(local_filename_dir)
|
|
314
|
-
|
|
315
|
-
with requests.get(
|
|
316
|
-
node_url,
|
|
317
|
-
stream=True,
|
|
318
|
-
auth=auth,
|
|
319
|
-
headers=USER_AGENT,
|
|
320
|
-
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
321
|
-
verify=ssl_verify,
|
|
322
|
-
) as stream:
|
|
323
|
-
try:
|
|
324
|
-
stream.raise_for_status()
|
|
325
|
-
except RequestException:
|
|
326
|
-
import traceback as tb
|
|
327
|
-
|
|
328
|
-
logger.error(
|
|
329
|
-
"Error while getting resource :\n%s", tb.format_exc()
|
|
330
|
-
)
|
|
331
|
-
else:
|
|
332
|
-
with open(local_filename, "wb") as fhandle:
|
|
333
|
-
for chunk in stream.iter_content(chunk_size=64 * 1024):
|
|
334
|
-
if chunk:
|
|
335
|
-
fhandle.write(chunk)
|
|
336
|
-
progress_callback(len(chunk))
|
|
337
|
-
|
|
338
|
-
with open(record_filename, "w") as fh:
|
|
339
|
-
fh.write(product.remote_location)
|
|
340
|
-
logger.debug("Download recorded in %s", record_filename)
|
|
341
|
-
|
|
342
|
-
product.location = path_to_uri(product_local_path)
|
|
343
|
-
return product_local_path
|
|
344
|
-
|
|
345
|
-
return download_request(
|
|
346
|
-
product,
|
|
347
|
-
auth,
|
|
348
|
-
progress_callback,
|
|
349
|
-
ordered_message,
|
|
350
|
-
**kwargs,
|
|
351
|
-
)
|