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/api/search_result.py
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
466
|
+
configured_cols = [
|
|
329
467
|
k
|
|
330
468
|
for k, v in search_plugin.config.products.items()
|
|
331
|
-
if v.get("
|
|
469
|
+
if v.get("_collection") == feature.get("collection")
|
|
332
470
|
]
|
|
333
|
-
if len(
|
|
334
|
-
eo_product.
|
|
471
|
+
if len(configured_cols) > 0:
|
|
472
|
+
eo_product.collection = configured_cols[0]
|
|
335
473
|
else:
|
|
336
|
-
eo_product.
|
|
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.
|
|
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
|
-
|
|
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)
|