eodag 3.10.0__py3-none-any.whl → 4.0.0a1__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 (68) hide show
  1. eodag/api/core.py +378 -419
  2. eodag/api/product/__init__.py +3 -3
  3. eodag/api/product/_product.py +68 -40
  4. eodag/api/product/drivers/__init__.py +3 -5
  5. eodag/api/product/drivers/base.py +1 -18
  6. eodag/api/product/metadata_mapping.py +151 -215
  7. eodag/api/search_result.py +13 -7
  8. eodag/cli.py +72 -395
  9. eodag/config.py +46 -50
  10. eodag/plugins/apis/base.py +2 -2
  11. eodag/plugins/apis/ecmwf.py +20 -21
  12. eodag/plugins/apis/usgs.py +37 -33
  13. eodag/plugins/authentication/base.py +1 -3
  14. eodag/plugins/crunch/filter_date.py +3 -3
  15. eodag/plugins/crunch/filter_latest_intersect.py +2 -2
  16. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  17. eodag/plugins/download/aws.py +45 -41
  18. eodag/plugins/download/base.py +13 -14
  19. eodag/plugins/download/http.py +65 -65
  20. eodag/plugins/manager.py +28 -29
  21. eodag/plugins/search/__init__.py +3 -4
  22. eodag/plugins/search/base.py +128 -77
  23. eodag/plugins/search/build_search_result.py +105 -107
  24. eodag/plugins/search/cop_marine.py +44 -47
  25. eodag/plugins/search/csw.py +33 -33
  26. eodag/plugins/search/qssearch.py +335 -354
  27. eodag/plugins/search/stac_list_assets.py +1 -1
  28. eodag/plugins/search/static_stac_search.py +31 -31
  29. eodag/resources/{product_types.yml → collections.yml} +2353 -2429
  30. eodag/resources/ext_collections.json +1 -1
  31. eodag/resources/providers.yml +2427 -2719
  32. eodag/resources/stac_provider.yml +46 -90
  33. eodag/types/queryables.py +55 -91
  34. eodag/types/search_args.py +1 -1
  35. eodag/utils/__init__.py +94 -21
  36. eodag/utils/exceptions.py +6 -6
  37. eodag/utils/free_text_search.py +3 -3
  38. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/METADATA +10 -87
  39. eodag-4.0.0a1.dist-info/RECORD +92 -0
  40. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/entry_points.txt +0 -4
  41. eodag/plugins/authentication/oauth.py +0 -60
  42. eodag/plugins/download/creodias_s3.py +0 -71
  43. eodag/plugins/download/s3rest.py +0 -351
  44. eodag/plugins/search/data_request_search.py +0 -565
  45. eodag/resources/stac.yml +0 -294
  46. eodag/resources/stac_api.yml +0 -2105
  47. eodag/rest/__init__.py +0 -24
  48. eodag/rest/cache.py +0 -70
  49. eodag/rest/config.py +0 -67
  50. eodag/rest/constants.py +0 -26
  51. eodag/rest/core.py +0 -764
  52. eodag/rest/errors.py +0 -210
  53. eodag/rest/server.py +0 -604
  54. eodag/rest/server.wsgi +0 -6
  55. eodag/rest/stac.py +0 -1032
  56. eodag/rest/templates/README +0 -1
  57. eodag/rest/types/__init__.py +0 -18
  58. eodag/rest/types/collections_search.py +0 -44
  59. eodag/rest/types/eodag_search.py +0 -386
  60. eodag/rest/types/queryables.py +0 -174
  61. eodag/rest/types/stac_search.py +0 -272
  62. eodag/rest/utils/__init__.py +0 -207
  63. eodag/rest/utils/cql_evaluate.py +0 -119
  64. eodag/rest/utils/rfc3339.py +0 -64
  65. eodag-3.10.0.dist-info/RECORD +0 -116
  66. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/WHEEL +0 -0
  67. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/licenses/LICENSE +0 -0
  68. {eodag-3.10.0.dist-info → eodag-4.0.0a1.dist-info}/top_level.txt +0 -0
@@ -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
- )