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
@@ -19,8 +19,8 @@ from __future__ import annotations
19
19
 
20
20
  import logging
21
21
  import time
22
- from datetime import datetime, timedelta
23
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
22
+ from datetime import datetime, timedelta, timezone
23
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast
24
24
 
25
25
  import requests
26
26
 
@@ -30,10 +30,11 @@ from eodag.api.product.metadata_mapping import (
30
30
  mtd_cfg_as_conversion_and_querypath,
31
31
  properties_from_json,
32
32
  )
33
+ from eodag.plugins.search import PreparedSearch
33
34
  from eodag.plugins.search.base import Search
34
- from eodag.rest.stac import DEFAULT_MISSION_START_DATE
35
35
  from eodag.utils import (
36
36
  DEFAULT_ITEMS_PER_PAGE,
37
+ DEFAULT_MISSION_START_DATE,
37
38
  DEFAULT_PAGE,
38
39
  GENERIC_PRODUCT_TYPE,
39
40
  HTTP_REQ_TIMEOUT,
@@ -41,7 +42,12 @@ from eodag.utils import (
41
42
  deepcopy,
42
43
  string_to_jsonpath,
43
44
  )
44
- from eodag.utils.exceptions import NotAvailableError, RequestError, TimeOutError
45
+ from eodag.utils.exceptions import (
46
+ NotAvailableError,
47
+ RequestError,
48
+ TimeOutError,
49
+ ValidationError,
50
+ )
45
51
 
46
52
  if TYPE_CHECKING:
47
53
  from eodag.config import PluginConfig
@@ -57,6 +63,8 @@ class DataRequestSearch(Search):
57
63
  - if finished - fetch the result of the job
58
64
  """
59
65
 
66
+ data_request_id: Optional[str]
67
+
60
68
  def __init__(self, provider: str, config: PluginConfig) -> None:
61
69
  super(DataRequestSearch, self).__init__(provider, config)
62
70
  self.config.__dict__.setdefault("result_type", "json")
@@ -101,14 +109,13 @@ class DataRequestSearch(Search):
101
109
  self.config.pagination["next_page_url_key_path"] = string_to_jsonpath(
102
110
  self.config.pagination.get("next_page_url_key_path", None)
103
111
  )
104
- self.download_info = {}
112
+ self.download_info: Dict[str, Any] = {}
105
113
  self.data_request_id = None
106
114
 
107
- def discover_product_types(self) -> Optional[Dict[str, Any]]:
115
+ def discover_product_types(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
108
116
  """Fetch product types is disabled for `DataRequestSearch`
109
117
 
110
118
  :returns: empty dict
111
- :rtype: (optional) dict
112
119
  """
113
120
  return None
114
121
 
@@ -119,26 +126,30 @@ class DataRequestSearch(Search):
119
126
 
120
127
  def query(
121
128
  self,
122
- product_type: Optional[str] = None,
123
- items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
124
- page: int = DEFAULT_PAGE,
125
- count: bool = True,
129
+ prep: PreparedSearch = PreparedSearch(),
126
130
  **kwargs: Any,
127
131
  ) -> Tuple[List[EOProduct], Optional[int]]:
128
132
  """
129
133
  performs the search for a provider where several steps are required to fetch the data
130
134
  """
135
+ if kwargs.get("sort_by"):
136
+ raise ValidationError(f"{self.provider} does not support sorting feature")
137
+
131
138
  product_type = kwargs.get("productType", None)
139
+
140
+ if product_type is None:
141
+ raise ValidationError("Required productType is missing")
142
+
132
143
  # replace "product_type" to "providerProductType" in search args if exists
133
144
  # for compatibility with DataRequestSearch method
134
145
  if kwargs.get("product_type"):
135
146
  kwargs["providerProductType"] = kwargs.pop("product_type", None)
136
- provider_product_type = self._map_product_type(product_type or "")
147
+ provider_product_type = cast(str, self._map_product_type(product_type or ""))
137
148
  keywords = {k: v for k, v in kwargs.items() if k != "auth" and v is not None}
138
149
 
139
150
  if provider_product_type and provider_product_type != GENERIC_PRODUCT_TYPE:
140
151
  keywords["productType"] = provider_product_type
141
- elif product_type:
152
+ else:
142
153
  keywords["productType"] = product_type
143
154
 
144
155
  # provider product type specific conf
@@ -185,7 +196,7 @@ class DataRequestSearch(Search):
185
196
  if not keywords.get("completionTimeFromAscendingNode", None):
186
197
  keywords["completionTimeFromAscendingNode"] = getattr(
187
198
  self.config, "product_type_config", {}
188
- ).get("missionEndDate", datetime.utcnow().isoformat())
199
+ ).get("missionEndDate", datetime.now(timezone.utc).isoformat())
189
200
 
190
201
  # ask for data_request_id if not set (it must exist when iterating over pages)
191
202
  if not self.data_request_id:
@@ -246,24 +257,27 @@ class DataRequestSearch(Search):
246
257
  self, product_type: str, eodag_product_type: str, **kwargs: Any
247
258
  ) -> str:
248
259
  headers = getattr(self.auth, "headers", USER_AGENT)
260
+ ssl_verify = getattr(self.config.ssl_verify, "ssl_verify", True)
249
261
  try:
250
262
  url = self.config.data_request_url
251
- request_body = format_query_params(
252
- eodag_product_type, self.config, **kwargs
253
- )
263
+ request_body = format_query_params(eodag_product_type, self.config, kwargs)
254
264
  logger.debug(
255
265
  f"Sending search job request to {url} with {str(request_body)}"
256
266
  )
257
267
  request_job = requests.post(
258
- url, json=request_body, headers=headers, timeout=HTTP_REQ_TIMEOUT
268
+ url,
269
+ json=request_body,
270
+ headers=headers,
271
+ timeout=HTTP_REQ_TIMEOUT,
272
+ verify=ssl_verify,
259
273
  )
260
274
  request_job.raise_for_status()
261
275
  except requests.exceptions.Timeout as exc:
262
276
  raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
263
277
  except requests.RequestException as e:
264
- raise RequestError(
265
- f"search job for product_type {product_type} could not be created: {str(e)}, {request_job.text}"
266
- )
278
+ raise RequestError.from_error(
279
+ e, f"search job for product_type {product_type} could not be created"
280
+ ) from e
267
281
  else:
268
282
  logger.info("search job for product_type %s created", product_type)
269
283
  return request_job.json()["jobId"]
@@ -271,28 +285,35 @@ class DataRequestSearch(Search):
271
285
  def _cancel_request(self, data_request_id: str) -> None:
272
286
  logger.info("deleting request job %s", data_request_id)
273
287
  delete_url = f"{self.config.data_request_url}/{data_request_id}"
288
+ headers = getattr(self.auth, "headers", USER_AGENT)
274
289
  try:
275
290
  delete_resp = requests.delete(
276
- delete_url, headers=self.auth.headers, timeout=HTTP_REQ_TIMEOUT
291
+ delete_url, headers=headers, timeout=HTTP_REQ_TIMEOUT
277
292
  )
278
293
  delete_resp.raise_for_status()
279
294
  except requests.exceptions.Timeout as exc:
280
295
  raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
281
296
  except requests.RequestException as e:
282
- raise RequestError(f"_cancel_request failed: {str(e)}")
297
+ raise RequestError.from_error(e, "_cancel_request failed") from e
283
298
 
284
299
  def _check_request_status(self, data_request_id: str) -> bool:
285
300
  logger.debug("checking status of request job %s", data_request_id)
286
301
  status_url = self.config.status_url + data_request_id
302
+ headers = getattr(self.auth, "headers", USER_AGENT)
303
+ ssl_verify = getattr(self.config, "ssl_verify", True)
304
+
287
305
  try:
288
306
  status_resp = requests.get(
289
- status_url, headers=self.auth.headers, timeout=HTTP_REQ_TIMEOUT
307
+ status_url,
308
+ headers=headers,
309
+ timeout=HTTP_REQ_TIMEOUT,
310
+ verify=ssl_verify,
290
311
  )
291
312
  status_resp.raise_for_status()
292
313
  except requests.exceptions.Timeout as exc:
293
314
  raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
294
315
  except requests.RequestException as e:
295
- raise RequestError(f"_check_request_status failed: {str(e)}")
316
+ raise RequestError.from_error(e, "_check_request_status failed") from e
296
317
  else:
297
318
  status_data = status_resp.json()
298
319
  if "status_code" in status_data and status_data["status_code"] in [
@@ -300,7 +321,9 @@ class DataRequestSearch(Search):
300
321
  404,
301
322
  ]:
302
323
  logger.error(f"_check_request_status failed: {status_data}")
303
- raise RequestError("authentication token expired during request")
324
+ error = RequestError("authentication token expired during request")
325
+ error.status_code = status_data["status_code"]
326
+ raise error
304
327
  if status_data["status"] == "failed":
305
328
  logger.error(f"_check_request_status failed: {status_data}")
306
329
  raise RequestError(
@@ -315,9 +338,11 @@ class DataRequestSearch(Search):
315
338
  url = self.config.result_url.format(
316
339
  jobId=data_request_id, items_per_page=items_per_page, page=page
317
340
  )
341
+ ssl_verify = getattr(self.config, "ssl_verify", True)
342
+ headers = getattr(self.auth, "headers", USER_AGENT)
318
343
  try:
319
344
  return requests.get(
320
- url, headers=self.auth.headers, timeout=HTTP_REQ_TIMEOUT
345
+ url, headers=headers, timeout=HTTP_REQ_TIMEOUT, verify=ssl_verify
321
346
  ).json()
322
347
  except requests.exceptions.Timeout as exc:
323
348
  raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
@@ -362,8 +387,11 @@ class DataRequestSearch(Search):
362
387
  total_items_nb_key_path = string_to_jsonpath(
363
388
  self.config.pagination["total_items_nb_key_path"]
364
389
  )
365
- if len(total_items_nb_key_path.find(results)) > 0:
366
- total_items_nb = total_items_nb_key_path.find(results)[0].value
390
+ found_total_items_nb_paths = total_items_nb_key_path.find(results)
391
+ if found_total_items_nb_paths and not isinstance(
392
+ found_total_items_nb_paths, int
393
+ ):
394
+ total_items_nb = found_total_items_nb_paths[0].value
367
395
  else:
368
396
  total_items_nb = 0
369
397
  for p in products:
@@ -399,7 +427,10 @@ class DataRequestSearch(Search):
399
427
  path = string_to_jsonpath(custom_filters["filter_attribute"])
400
428
  indexes = custom_filters["indexes"].split("-")
401
429
  for record in results:
402
- filter_param = path.find(record)[0].value
430
+ found_paths = path.find(record)
431
+ if not found_paths or isinstance(found_paths, int):
432
+ continue
433
+ filter_param = found_paths[0].value
403
434
  filter_value = filter_param[int(indexes[0]) : int(indexes[1])]
404
435
  filter_clause = "'" + filter_value + "' " + custom_filters["filter_clause"]
405
436
  if eval(filter_clause):