eodag 2.12.0__py3-none-any.whl → 3.0.0__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 (93) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +654 -538
  3. eodag/api/product/__init__.py +12 -2
  4. eodag/api/product/_assets.py +59 -16
  5. eodag/api/product/_product.py +100 -93
  6. eodag/api/product/drivers/__init__.py +7 -2
  7. eodag/api/product/drivers/base.py +0 -3
  8. eodag/api/product/metadata_mapping.py +192 -96
  9. eodag/api/search_result.py +69 -10
  10. eodag/cli.py +55 -25
  11. eodag/config.py +391 -116
  12. eodag/plugins/apis/base.py +11 -165
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +80 -35
  15. eodag/plugins/authentication/aws_auth.py +13 -4
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +2 -2
  18. eodag/plugins/authentication/header.py +31 -6
  19. eodag/plugins/authentication/keycloak.py +17 -84
  20. eodag/plugins/authentication/oauth.py +3 -3
  21. eodag/plugins/authentication/openid_connect.py +268 -49
  22. eodag/plugins/authentication/qsauth.py +4 -1
  23. eodag/plugins/authentication/sas_auth.py +9 -2
  24. eodag/plugins/authentication/token.py +98 -47
  25. eodag/plugins/authentication/token_exchange.py +122 -0
  26. eodag/plugins/crunch/base.py +3 -1
  27. eodag/plugins/crunch/filter_date.py +3 -9
  28. eodag/plugins/crunch/filter_latest_intersect.py +0 -3
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
  30. eodag/plugins/crunch/filter_overlap.py +4 -8
  31. eodag/plugins/crunch/filter_property.py +5 -11
  32. eodag/plugins/download/aws.py +149 -185
  33. eodag/plugins/download/base.py +88 -97
  34. eodag/plugins/download/creodias_s3.py +1 -1
  35. eodag/plugins/download/http.py +638 -310
  36. eodag/plugins/download/s3rest.py +47 -45
  37. eodag/plugins/manager.py +228 -88
  38. eodag/plugins/search/__init__.py +36 -0
  39. eodag/plugins/search/base.py +239 -30
  40. eodag/plugins/search/build_search_result.py +382 -37
  41. eodag/plugins/search/cop_marine.py +441 -0
  42. eodag/plugins/search/creodias_s3.py +25 -20
  43. eodag/plugins/search/csw.py +5 -7
  44. eodag/plugins/search/data_request_search.py +61 -30
  45. eodag/plugins/search/qssearch.py +713 -255
  46. eodag/plugins/search/static_stac_search.py +106 -40
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +1921 -34
  49. eodag/resources/providers.yml +4091 -3655
  50. eodag/resources/stac.yml +50 -216
  51. eodag/resources/stac_api.yml +71 -25
  52. eodag/resources/stac_provider.yml +5 -0
  53. eodag/resources/user_conf_template.yml +89 -32
  54. eodag/rest/__init__.py +6 -0
  55. eodag/rest/cache.py +70 -0
  56. eodag/rest/config.py +68 -0
  57. eodag/rest/constants.py +26 -0
  58. eodag/rest/core.py +735 -0
  59. eodag/rest/errors.py +178 -0
  60. eodag/rest/server.py +264 -431
  61. eodag/rest/stac.py +442 -836
  62. eodag/rest/types/collections_search.py +44 -0
  63. eodag/rest/types/eodag_search.py +238 -47
  64. eodag/rest/types/queryables.py +164 -0
  65. eodag/rest/types/stac_search.py +273 -0
  66. eodag/rest/utils/__init__.py +216 -0
  67. eodag/rest/utils/cql_evaluate.py +119 -0
  68. eodag/rest/utils/rfc3339.py +64 -0
  69. eodag/types/__init__.py +106 -10
  70. eodag/types/bbox.py +15 -14
  71. eodag/types/download_args.py +40 -0
  72. eodag/types/search_args.py +57 -7
  73. eodag/types/whoosh.py +79 -0
  74. eodag/utils/__init__.py +110 -91
  75. eodag/utils/constraints.py +37 -45
  76. eodag/utils/exceptions.py +39 -22
  77. eodag/utils/import_system.py +0 -4
  78. eodag/utils/logging.py +37 -80
  79. eodag/utils/notebook.py +4 -4
  80. eodag/utils/repr.py +113 -0
  81. eodag/utils/requests.py +128 -0
  82. eodag/utils/rest.py +100 -0
  83. eodag/utils/stac_reader.py +93 -21
  84. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/METADATA +88 -53
  85. eodag-3.0.0.dist-info/RECORD +109 -0
  86. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/WHEEL +1 -1
  87. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/entry_points.txt +7 -5
  88. eodag/plugins/apis/cds.py +0 -540
  89. eodag/rest/types/stac_queryables.py +0 -134
  90. eodag/rest/utils.py +0 -1133
  91. eodag-2.12.0.dist-info/RECORD +0 -94
  92. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/LICENSE +0 -0
  93. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/top_level.txt +0 -0
@@ -17,16 +17,16 @@
17
17
  # limitations under the License.
18
18
  from __future__ import annotations
19
19
 
20
- import hashlib
21
20
  import logging
22
21
  import os
23
22
  import os.path
24
- from typing import TYPE_CHECKING, Any, Dict, Optional, Union
23
+ from typing import TYPE_CHECKING, Dict, List, Optional, Union
25
24
  from xml.dom import minidom
26
25
  from xml.parsers.expat import ExpatError
27
26
 
28
27
  import requests
29
28
  from requests import RequestException
29
+ from requests.auth import AuthBase
30
30
 
31
31
  from eodag.api.product.metadata_mapping import OFFLINE_STATUS, ONLINE_STATUS
32
32
  from eodag.plugins.download.base import Download
@@ -46,6 +46,7 @@ from eodag.utils import (
46
46
  from eodag.utils.exceptions import (
47
47
  AuthenticationError,
48
48
  DownloadError,
49
+ MisconfiguredError,
49
50
  NotAvailableError,
50
51
  RequestError,
51
52
  )
@@ -53,6 +54,8 @@ from eodag.utils.exceptions import (
53
54
  if TYPE_CHECKING:
54
55
  from eodag.api.product import EOProduct
55
56
  from eodag.config import PluginConfig
57
+ from eodag.types.download_args import DownloadConf
58
+ from eodag.utils import Unpack
56
59
 
57
60
  logger = logging.getLogger("eodag.download.s3rest")
58
61
 
@@ -65,7 +68,6 @@ class S3RestDownload(Download):
65
68
  Re-use AwsDownload bucket some handling methods
66
69
 
67
70
  :param provider: provider name
68
- :type provider: str
69
71
  :param config: Download plugin configuration:
70
72
 
71
73
  * ``config.base_uri`` (str) - default endpoint url
@@ -76,12 +78,7 @@ class S3RestDownload(Download):
76
78
  * ``config.order_method`` (str) - (optional) HTTP request method, GET (default) or POST
77
79
  * ``config.order_headers`` (dict) - (optional) order request headers
78
80
  * ``config.order_on_response`` (dict) - (optional) edit or add new product properties
79
- * ``config.order_status_method`` (str) - (optional) status HTTP request method, GET (default) or POST
80
- * ``config.order_status_percent`` (str) - (optional) progress percentage key in obtained status response
81
- * ``config.order_status_success`` (dict) - (optional) key/value identifying an error success
82
- * ``config.order_status_on_success`` (dict) - (optional) edit or add new product properties
83
-
84
- :type config: :class:`~eodag.config.PluginConfig`
81
+ * ``config.order_status`` (:class:`~eodag.config.PluginConfig.OrderStatus`) - Order status handling
85
82
  """
86
83
 
87
84
  def __init__(self, provider: str, config: PluginConfig) -> None:
@@ -91,32 +88,30 @@ class S3RestDownload(Download):
91
88
  def download(
92
89
  self,
93
90
  product: EOProduct,
94
- auth: Optional[PluginConfig] = None,
91
+ auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
95
92
  progress_callback: Optional[ProgressCallback] = None,
96
93
  wait: int = DEFAULT_DOWNLOAD_WAIT,
97
94
  timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
98
- **kwargs: Union[str, bool, Dict[str, Any]],
95
+ **kwargs: Unpack[DownloadConf],
99
96
  ) -> Optional[str]:
100
97
  """Download method for S3 REST API.
101
98
 
102
99
  :param product: The EO product to download
103
- :type product: :class:`~eodag.api.product._product.EOProduct`
104
- :param auth: (optional) The configuration of a plugin of type Authentication
105
- :type auth: :class:`~eodag.config.PluginConfig`
100
+ :param auth: (optional) authenticated object
106
101
  :param progress_callback: (optional) A method or a callable object
107
102
  which takes a current size and a maximum
108
103
  size as inputs and handle progress bar
109
104
  creation and update to give the user a
110
105
  feedback on the download progress
111
- :type progress_callback: :class:`~eodag.utils.ProgressCallback` or None
112
- :param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
106
+ :param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
113
107
  and `dl_url_params` (dict) can be provided as additional kwargs
114
108
  and will override any other values defined in a configuration
115
109
  file or with environment variables.
116
- :type kwargs: Union[str, bool, dict]
117
110
  :returns: The absolute path to the downloaded product in the local filesystem
118
- :rtype: str
119
111
  """
112
+ if auth is not None and not isinstance(auth, AuthBase):
113
+ raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
114
+
120
115
  if progress_callback is None:
121
116
  logger.info(
122
117
  "Progress bar unavailable, please call product.download() instead of plugin.download()"
@@ -130,19 +125,19 @@ class S3RestDownload(Download):
130
125
  and "storageStatus" in product.properties
131
126
  and product.properties["storageStatus"] != ONLINE_STATUS
132
127
  ):
133
- self.http_download_plugin.orderDownload(product=product, auth=auth)
128
+ self.http_download_plugin.order_download(product=product, auth=auth)
134
129
 
135
130
  @self._download_retry(product, wait, timeout)
136
131
  def download_request(
137
132
  product: EOProduct,
138
- auth: PluginConfig,
133
+ auth: AuthBase,
139
134
  progress_callback: ProgressCallback,
140
135
  ordered_message: str,
141
- **kwargs: Any,
136
+ **kwargs: Unpack[DownloadConf],
142
137
  ):
143
138
  # check order status
144
139
  if product.properties.get("orderStatusLink", None):
145
- self.http_download_plugin.orderDownloadStatus(
140
+ self.http_download_plugin.order_download_status(
146
141
  product=product, auth=auth
147
142
  )
148
143
 
@@ -150,6 +145,8 @@ class S3RestDownload(Download):
150
145
  bucket_name, prefix = get_bucket_name_and_prefix(
151
146
  url=product.location, bucket_path_level=self.config.bucket_path_level
152
147
  )
148
+ if prefix is None:
149
+ raise DownloadError(f"Could not extract prefix from {product.location}")
153
150
 
154
151
  if (
155
152
  bucket_name is None
@@ -172,8 +169,16 @@ class S3RestDownload(Download):
172
169
 
173
170
  # get nodes/files list contained in the bucket
174
171
  logger.debug("Retrieving product content from %s", nodes_list_url)
172
+
173
+ ssl_verify = getattr(self.config, "ssl_verify", True)
174
+ timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
175
+
175
176
  bucket_contents = requests.get(
176
- nodes_list_url, auth=auth, headers=USER_AGENT, timeout=HTTP_REQ_TIMEOUT
177
+ nodes_list_url,
178
+ auth=auth,
179
+ headers=USER_AGENT,
180
+ timeout=timeout,
181
+ verify=ssl_verify,
177
182
  )
178
183
  try:
179
184
  bucket_contents.raise_for_status()
@@ -184,12 +189,9 @@ class S3RestDownload(Download):
184
189
  auth_errors = [auth_errors]
185
190
  if err.response and err.response.status_code in auth_errors:
186
191
  raise AuthenticationError(
187
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
188
- % (
189
- err.response.status_code,
190
- err.response.text.strip(),
191
- self.provider,
192
- )
192
+ f"Please check your credentials for {self.provider}.",
193
+ f"HTTP Error {err.response.status_code} returned.",
194
+ err.response.text.strip(),
193
195
  )
194
196
  # product not available
195
197
  elif (
@@ -220,7 +222,7 @@ class S3RestDownload(Download):
220
222
  self.__class__.__name__,
221
223
  bucket_contents.text,
222
224
  )
223
- raise RequestError(str(err))
225
+ raise RequestError.from_error(err) from err
224
226
  try:
225
227
  xmldoc = minidom.parseString(bucket_contents.text)
226
228
  except ExpatError as err:
@@ -232,14 +234,12 @@ class S3RestDownload(Download):
232
234
  logger.warning("Could not load any content from %s", nodes_list_url)
233
235
 
234
236
  # destination product path
235
- outputs_prefix = (
236
- kwargs.pop("outputs_prefix", None) or self.config.outputs_prefix
237
- )
238
- abs_outputs_prefix = os.path.abspath(outputs_prefix)
239
- product_local_path = os.path.join(abs_outputs_prefix, prefix.split("/")[-1])
237
+ output_dir = kwargs.pop("output_dir", None) or self.config.output_dir
238
+ abs_output_dir = os.path.abspath(output_dir)
239
+ product_local_path = os.path.join(abs_output_dir, prefix.split("/")[-1])
240
240
 
241
241
  # .downloaded cache record directory
242
- download_records_dir = os.path.join(abs_outputs_prefix, ".downloaded")
242
+ download_records_dir = os.path.join(abs_output_dir, ".downloaded")
243
243
  try:
244
244
  os.makedirs(download_records_dir)
245
245
  except OSError as exc:
@@ -252,8 +252,9 @@ class S3RestDownload(Download):
252
252
  "Unable to create records directory. Got:\n%s", tb.format_exc()
253
253
  )
254
254
  # check if product has already been downloaded
255
- url_hash = hashlib.md5(product.remote_location.encode("utf-8")).hexdigest()
256
- record_filename = os.path.join(download_records_dir, url_hash)
255
+ record_filename = os.path.join(
256
+ download_records_dir, self.generate_record_hash(product)
257
+ )
257
258
  if os.path.isfile(record_filename) and os.path.exists(product_local_path):
258
259
  product.location = path_to_uri(product_local_path)
259
260
  return product_local_path
@@ -266,18 +267,18 @@ class S3RestDownload(Download):
266
267
  os.remove(record_filename)
267
268
 
268
269
  # total size for progress_callback
269
- total_size = sum(
270
- [
271
- int(node.firstChild.nodeValue)
272
- for node in xmldoc.getElementsByTagName("Size")
273
- ]
274
- )
270
+ size_list: List[int] = [
271
+ int(node.firstChild.nodeValue) # type: ignore[attr-defined]
272
+ for node in xmldoc.getElementsByTagName("Size")
273
+ if node.firstChild is not None
274
+ ]
275
+ total_size = sum(size_list)
275
276
  progress_callback.reset(total=total_size)
276
277
 
277
278
  # download each node key
278
279
  for node_xml in nodes_xml_list:
279
280
  node_key = unquote(
280
- node_xml.getElementsByTagName("Key")[0].firstChild.nodeValue
281
+ node_xml.getElementsByTagName("Key")[0].firstChild.nodeValue # type: ignore[union-attr]
281
282
  )
282
283
  # As "Key", "Size" and "ETag" (md5 hash) can also be retrieved from node_xml
283
284
  node_url = urljoin(bucket_url.strip("/") + "/", node_key.strip("/"))
@@ -303,6 +304,7 @@ class S3RestDownload(Download):
303
304
  auth=auth,
304
305
  headers=USER_AGENT,
305
306
  timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
307
+ verify=ssl_verify,
306
308
  ) as stream:
307
309
  try:
308
310
  stream.raise_for_status()