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.
Files changed (75) hide show
  1. eodag/__init__.py +6 -1
  2. eodag/api/collection.py +353 -0
  3. eodag/api/core.py +606 -641
  4. eodag/api/product/__init__.py +3 -3
  5. eodag/api/product/_product.py +74 -56
  6. eodag/api/product/drivers/__init__.py +4 -46
  7. eodag/api/product/drivers/base.py +0 -28
  8. eodag/api/product/metadata_mapping.py +178 -216
  9. eodag/api/search_result.py +156 -15
  10. eodag/cli.py +83 -403
  11. eodag/config.py +81 -51
  12. eodag/plugins/apis/base.py +2 -2
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +55 -40
  15. eodag/plugins/authentication/base.py +1 -3
  16. eodag/plugins/crunch/filter_date.py +3 -3
  17. eodag/plugins/crunch/filter_latest_intersect.py +2 -2
  18. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
  19. eodag/plugins/download/aws.py +46 -42
  20. eodag/plugins/download/base.py +13 -14
  21. eodag/plugins/download/http.py +65 -65
  22. eodag/plugins/manager.py +28 -29
  23. eodag/plugins/search/__init__.py +6 -4
  24. eodag/plugins/search/base.py +131 -80
  25. eodag/plugins/search/build_search_result.py +245 -173
  26. eodag/plugins/search/cop_marine.py +87 -56
  27. eodag/plugins/search/csw.py +47 -37
  28. eodag/plugins/search/qssearch.py +653 -429
  29. eodag/plugins/search/stac_list_assets.py +1 -1
  30. eodag/plugins/search/static_stac_search.py +43 -44
  31. eodag/resources/{product_types.yml → collections.yml} +2594 -2453
  32. eodag/resources/ext_collections.json +1 -1
  33. eodag/resources/ext_product_types.json +1 -1
  34. eodag/resources/providers.yml +2706 -2733
  35. eodag/resources/stac_provider.yml +50 -92
  36. eodag/resources/user_conf_template.yml +9 -0
  37. eodag/types/__init__.py +2 -0
  38. eodag/types/queryables.py +70 -91
  39. eodag/types/search_args.py +1 -1
  40. eodag/utils/__init__.py +97 -21
  41. eodag/utils/dates.py +0 -12
  42. eodag/utils/exceptions.py +6 -6
  43. eodag/utils/free_text_search.py +3 -3
  44. eodag/utils/repr.py +2 -0
  45. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/METADATA +13 -99
  46. eodag-4.0.0a2.dist-info/RECORD +93 -0
  47. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/entry_points.txt +0 -4
  48. eodag/plugins/authentication/oauth.py +0 -60
  49. eodag/plugins/download/creodias_s3.py +0 -71
  50. eodag/plugins/download/s3rest.py +0 -351
  51. eodag/plugins/search/data_request_search.py +0 -565
  52. eodag/resources/stac.yml +0 -294
  53. eodag/resources/stac_api.yml +0 -2105
  54. eodag/rest/__init__.py +0 -24
  55. eodag/rest/cache.py +0 -70
  56. eodag/rest/config.py +0 -67
  57. eodag/rest/constants.py +0 -26
  58. eodag/rest/core.py +0 -764
  59. eodag/rest/errors.py +0 -210
  60. eodag/rest/server.py +0 -604
  61. eodag/rest/server.wsgi +0 -6
  62. eodag/rest/stac.py +0 -1032
  63. eodag/rest/templates/README +0 -1
  64. eodag/rest/types/__init__.py +0 -18
  65. eodag/rest/types/collections_search.py +0 -44
  66. eodag/rest/types/eodag_search.py +0 -386
  67. eodag/rest/types/queryables.py +0 -174
  68. eodag/rest/types/stac_search.py +0 -272
  69. eodag/rest/utils/__init__.py +0 -207
  70. eodag/rest/utils/cql_evaluate.py +0 -119
  71. eodag/rest/utils/rfc3339.py +0 -64
  72. eodag-3.10.1.dist-info/RECORD +0 -116
  73. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/WHEEL +0 -0
  74. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/licenses/LICENSE +0 -0
  75. {eodag-3.10.1.dist-info → eodag-4.0.0a2.dist-info}/top_level.txt +0 -0
@@ -19,9 +19,11 @@ from __future__ import annotations
19
19
 
20
20
  import logging
21
21
  from collections import UserList
22
- from typing import TYPE_CHECKING, Annotated, Any, Iterable, Optional, Union
22
+ from typing import TYPE_CHECKING, Annotated, Any, Iterable, Iterator, Optional, Union
23
23
 
24
- from shapely.geometry import GeometryCollection, shape
24
+ from shapely.geometry import GeometryCollection
25
+ from shapely.geometry import mapping as shapely_mapping
26
+ from shapely.geometry import shape
25
27
  from typing_extensions import Doc
26
28
 
27
29
  from eodag.api.product import EOProduct, unregistered_product_from_item
@@ -36,6 +38,7 @@ from eodag.utils.exceptions import MisconfiguredError
36
38
  if TYPE_CHECKING:
37
39
  from shapely.geometry.base import BaseGeometry
38
40
 
41
+ from eodag.api.core import EODataAccessGateway
39
42
  from eodag.plugins.crunch.base import Crunch
40
43
  from eodag.plugins.manager import PluginManager
41
44
 
@@ -48,6 +51,11 @@ class SearchResult(UserList[EOProduct]):
48
51
 
49
52
  :param products: A list of products resulting from a search
50
53
  :param number_matched: (optional) the estimated total number of matching results
54
+ :param errors: (optional) stored errors encountered during the search. Tuple of (provider name, exception)
55
+ :param search_params: (optional) search parameters stored to use in pagination
56
+ :param next_page_token: (optional) next page token value to use in pagination
57
+ :param next_page_token_key: (optional) next page token key to use in pagination
58
+ :param raise_errors: (optional) whether to raise errors encountered during the search
51
59
 
52
60
  :cvar data: List of products
53
61
  :ivar number_matched: Estimated total number of matching results
@@ -62,10 +70,19 @@ class SearchResult(UserList[EOProduct]):
62
70
  products: list[EOProduct],
63
71
  number_matched: Optional[int] = None,
64
72
  errors: Optional[list[tuple[str, Exception]]] = None,
73
+ search_params: Optional[dict[str, Any]] = None,
74
+ next_page_token: Optional[str] = None,
75
+ next_page_token_key: Optional[str] = None,
76
+ raise_errors: Optional[bool] = False,
65
77
  ) -> None:
66
78
  super().__init__(products)
67
79
  self.number_matched = number_matched
68
80
  self.errors = errors if errors is not None else []
81
+ self.search_params = search_params
82
+ self.next_page_token = next_page_token
83
+ self.next_page_token_key = next_page_token_key
84
+ self.raise_errors = raise_errors
85
+ self._dag: Optional["EODataAccessGateway"] = None
69
86
 
70
87
  def crunch(self, cruncher: Crunch, **search_params: Any) -> SearchResult:
71
88
  """Do some crunching with the underlying EO products.
@@ -140,7 +157,7 @@ class SearchResult(UserList[EOProduct]):
140
157
  Use cruncher :class:`~eodag.plugins.crunch.filter_property.FilterProperty`,
141
158
  filter for online products.
142
159
  """
143
- return self.filter_property(storageStatus="ONLINE")
160
+ return self.filter_property(**{"order:status": "succeeded"})
144
161
 
145
162
  @staticmethod
146
163
  def from_geojson(feature_collection: dict[str, Any]) -> SearchResult:
@@ -149,18 +166,46 @@ class SearchResult(UserList[EOProduct]):
149
166
  :param feature_collection: A collection representing a search result.
150
167
  :returns: An eodag representation of a search result
151
168
  """
169
+
170
+ products = [
171
+ EOProduct.from_geojson(feature)
172
+ for feature in feature_collection.get("features", [])
173
+ ]
174
+ props = feature_collection.get("metadata", {}) or {}
175
+
176
+ eodag_search_params = props.get("eodag:search_params", {})
177
+ if eodag_search_params and eodag_search_params.get("geometry"):
178
+ eodag_search_params["geometry"] = shape(eodag_search_params["geometry"])
179
+
152
180
  return SearchResult(
153
- [
154
- EOProduct.from_geojson(feature)
155
- for feature in feature_collection["features"]
156
- ]
181
+ products=products,
182
+ number_matched=props.get("eodag:number_matched"),
183
+ next_page_token=props.get("eodag:next_page_token"),
184
+ next_page_token_key=props.get("eodag:next_page_token_key"),
185
+ search_params=eodag_search_params or None,
186
+ raise_errors=props.get("eodag:raise_errors"),
157
187
  )
158
188
 
159
189
  def as_geojson_object(self) -> dict[str, Any]:
160
190
  """GeoJSON representation of SearchResult"""
191
+
192
+ geojson_search_params = {} | (self.search_params or {})
193
+ # search_params shapely geometry to wkt
194
+ if self.search_params and self.search_params.get("geometry"):
195
+ geojson_search_params["geometry"] = shapely_mapping(
196
+ self.search_params["geometry"]
197
+ )
198
+
161
199
  return {
162
200
  "type": "FeatureCollection",
163
201
  "features": [product.as_dict() for product in self],
202
+ "metadata": {
203
+ "eodag:number_matched": self.number_matched,
204
+ "eodag:next_page_token": self.next_page_token,
205
+ "eodag:next_page_token_key": self.next_page_token_key,
206
+ "eodag:search_params": geojson_search_params or None,
207
+ "eodag:raise_errors": self.raise_errors,
208
+ },
164
209
  }
165
210
 
166
211
  def as_shapely_geometry_object(self) -> GeometryCollection:
@@ -218,6 +263,93 @@ class SearchResult(UserList[EOProduct]):
218
263
 
219
264
  return super().extend(other)
220
265
 
266
+ def next_page(self, update: bool = True) -> Iterator[SearchResult]:
267
+ """
268
+ Retrieve and iterate over the next pages of search results, if available.
269
+
270
+ This method uses the current search parameters and next page token to request
271
+ additional results from the provider. If ``update`` is ``True``, the current ``SearchResult``
272
+ instance is updated with new products and pagination information as pages are fetched.
273
+
274
+ :param update: If ``True``, update the current ``SearchResult`` with new results.
275
+ :returns: An iterator yielding ``SearchResult`` objects for each subsequent page.
276
+
277
+ Example:
278
+
279
+ >>> first_page = SearchResult([]) # result of a search
280
+ >>> for new_results in first_page.next_page():
281
+ ... continue # do something with new_results
282
+ """
283
+
284
+ def get_next_page(current):
285
+ if current.search_params is None:
286
+ current.search_params = {}
287
+ # Update the next_page_token in the search params
288
+ current.search_params["next_page_token"] = current.next_page_token
289
+ current.search_params["next_page_token_key"] = current.next_page_token_key
290
+ # Ensure the provider is in the search params
291
+ if "provider" in current.search_params:
292
+ current.search_params["provider"] = current.search_params.get(
293
+ "provider", None
294
+ )
295
+ elif current.data and hasattr(current.data[-1], "provider"):
296
+ current.search_params["provider"] = current.data[-1].provider
297
+ search_plugins, search_kwargs = current._dag._prepare_search(
298
+ **current.search_params
299
+ )
300
+ # If number_matched was provided, ensure it is passed to the next search
301
+ if current.number_matched:
302
+ search_kwargs["number_matched"] = current.number_matched
303
+ for i, search_plugin in enumerate(search_plugins):
304
+ # validate no needed for next pages
305
+ search_kwargs["validate"] = False
306
+ return current._dag._do_search(
307
+ search_plugin,
308
+ raise_errors=self.raise_errors,
309
+ **search_kwargs,
310
+ )
311
+
312
+ # Do not iterate if there is no next page token
313
+ # or if the current one returned less than the maximum number of items asked for.
314
+ if self.next_page_token is None:
315
+ return
316
+
317
+ new_results = get_next_page(self)
318
+ old_results = self
319
+
320
+ while new_results.data:
321
+ # The products between two iterations are compared. If they
322
+ # are actually the same product, it means the iteration failed at
323
+ # progressing for some reason.
324
+ if (
325
+ old_results.data[0].properties["id"]
326
+ == new_results.data[0].properties["id"]
327
+ ):
328
+ logger.warning(
329
+ "Iterate over pages: stop iterating since the next page "
330
+ "appears to have the same products as in the previous one. "
331
+ "This provider may not implement pagination.",
332
+ )
333
+ break
334
+ if update:
335
+ self.data += new_results.data
336
+ self.search_params = new_results.search_params
337
+ self.next_page_token = new_results.next_page_token
338
+ self.next_page_token_key = new_results.next_page_token_key
339
+ yield new_results
340
+ # Stop iterating if there is no next page token
341
+ # or if the current one returned less than the maximum number of items asked for.
342
+ if (
343
+ new_results.next_page_token is None
344
+ or len(new_results) < new_results.search_params["items_per_page"]
345
+ ):
346
+ break
347
+ old_results = new_results
348
+ new_results = get_next_page(new_results)
349
+ if not new_results:
350
+ break
351
+ return
352
+
221
353
  @classmethod
222
354
  def _from_stac_item(
223
355
  cls, feature: dict[str, Any], plugins_manager: PluginManager
@@ -260,7 +392,7 @@ def _import_stac_item_from_eodag_server(
260
392
  assets = {
261
393
  k: v["alternate"]["origin"]
262
394
  for k, v in feature.get("assets", {}).items()
263
- if k not in ("thumbnail", "downloadLink")
395
+ if k not in ("thumbnail", "downloadLink", "eodag:download_link")
264
396
  and "origin" in v.get("alternate", {})
265
397
  }
266
398
  if assets:
@@ -274,6 +406,12 @@ def _import_stac_item_from_eodag_server(
274
406
  .get("alternate", {})
275
407
  .get("origin", {})
276
408
  .get("href")
409
+ ) or (
410
+ feature.get("assets", {})
411
+ .get("eodag:download_link", {})
412
+ .get("alternate", {})
413
+ .get("origin", {})
414
+ .get("href")
277
415
  )
278
416
  if download_link:
279
417
  updated_item["assets"] = {}
@@ -325,15 +463,15 @@ def _import_stac_item_from_known_provider(
325
463
  )
326
464
  eo_product = products[0]
327
465
 
328
- configured_pts = [
466
+ configured_cols = [
329
467
  k
330
468
  for k, v in search_plugin.config.products.items()
331
- if v.get("productType") == feature.get("collection")
469
+ if v.get("_collection") == feature.get("collection")
332
470
  ]
333
- if len(configured_pts) > 0:
334
- eo_product.product_type = configured_pts[0]
471
+ if len(configured_cols) > 0:
472
+ eo_product.collection = configured_cols[0]
335
473
  else:
336
- eo_product.product_type = feature.get("collection")
474
+ eo_product.collection = feature.get("collection")
337
475
 
338
476
  eo_product._register_downloader_from_manager(plugins_manager)
339
477
  imported_products.append(eo_product)
@@ -359,7 +497,7 @@ def _import_stac_item_from_unknown_provider(
359
497
  except MisconfiguredError:
360
498
  pass
361
499
  if eo_product is not None:
362
- eo_product.product_type = feature.get("collection")
500
+ eo_product.collection = feature.get("collection")
363
501
  eo_product._register_downloader_from_manager(plugins_manager)
364
502
  return SearchResult([eo_product])
365
503
  else:
@@ -373,7 +511,10 @@ class RawSearchResult(UserList[dict[str, Any]]):
373
511
  """
374
512
 
375
513
  query_params: dict[str, Any]
376
- product_type_def_params: dict[str, Any]
514
+ collection_def_params: dict[str, Any]
515
+ search_params: dict[str, Any]
516
+ next_page_token: Optional[str] = None
517
+ next_page_token_key: Optional[str] = None
377
518
 
378
519
  def __init__(self, results: list[Any]) -> None:
379
520
  super(RawSearchResult, self).__init__(results)