eodag 3.5.1__py3-none-any.whl → 3.7.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.
- eodag/api/core.py +29 -14
- eodag/api/product/__init__.py +30 -0
- eodag/api/product/metadata_mapping.py +20 -3
- eodag/api/search_result.py +155 -1
- eodag/cli.py +60 -44
- eodag/config.py +7 -6
- eodag/plugins/authentication/openid_connect.py +1 -2
- eodag/plugins/download/aws.py +145 -178
- eodag/plugins/download/base.py +18 -5
- eodag/plugins/download/creodias_s3.py +10 -5
- eodag/plugins/download/http.py +14 -6
- eodag/plugins/download/s3rest.py +1 -2
- eodag/plugins/manager.py +1 -1
- eodag/plugins/search/base.py +34 -4
- eodag/plugins/search/build_search_result.py +11 -6
- eodag/plugins/search/cop_marine.py +2 -0
- eodag/plugins/search/qssearch.py +48 -26
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +100 -153
- eodag/resources/providers.yml +58 -325
- eodag/resources/stac.yml +1 -2
- eodag/resources/user_conf_template.yml +0 -11
- eodag/rest/core.py +5 -9
- eodag/rest/server.py +9 -7
- eodag/utils/__init__.py +48 -27
- eodag/utils/exceptions.py +4 -0
- eodag/utils/s3.py +605 -65
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/METADATA +10 -10
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/RECORD +33 -33
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/WHEEL +0 -0
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/entry_points.txt +0 -0
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.5.1.dist-info → eodag-3.7.0.dist-info}/top_level.txt +0 -0
eodag/api/core.py
CHANGED
|
@@ -69,6 +69,7 @@ from eodag.utils import (
|
|
|
69
69
|
DEFAULT_MAX_ITEMS_PER_PAGE,
|
|
70
70
|
DEFAULT_PAGE,
|
|
71
71
|
GENERIC_PRODUCT_TYPE,
|
|
72
|
+
GENERIC_STAC_PROVIDER,
|
|
72
73
|
HTTP_REQ_TIMEOUT,
|
|
73
74
|
MockResponse,
|
|
74
75
|
_deprecated,
|
|
@@ -2006,20 +2007,6 @@ class EODataAccessGateway:
|
|
|
2006
2007
|
nb_res,
|
|
2007
2008
|
search_plugin.provider,
|
|
2008
2009
|
)
|
|
2009
|
-
# Hitting for instance
|
|
2010
|
-
# https://theia.cnes.fr/atdistrib/resto2/api/collections/SENTINEL2/
|
|
2011
|
-
# search.json?startDate=2019-03-01&completionDate=2019-06-15
|
|
2012
|
-
# &processingLevel=LEVEL2A&maxRecords=1&page=1
|
|
2013
|
-
# returns a number (properties.totalResults) that is the number of
|
|
2014
|
-
# products in the collection (here SENTINEL2) instead of the estimated
|
|
2015
|
-
# total number of products matching the search criteria (start/end date).
|
|
2016
|
-
# Remove this warning when this is fixed upstream by THEIA.
|
|
2017
|
-
if search_plugin.provider == "theia":
|
|
2018
|
-
logger.warning(
|
|
2019
|
-
"Results found on provider 'theia' is the total number of products "
|
|
2020
|
-
"available in the searched collection (e.g. SENTINEL2) instead of "
|
|
2021
|
-
"the total number of products matching the search criteria"
|
|
2022
|
-
)
|
|
2023
2010
|
except Exception as e:
|
|
2024
2011
|
if raise_errors:
|
|
2025
2012
|
# Raise the error, letting the application wrapping eodag know that
|
|
@@ -2476,3 +2463,31 @@ class EODataAccessGateway:
|
|
|
2476
2463
|
)
|
|
2477
2464
|
# Remove the ID since this is equal to productType.
|
|
2478
2465
|
plugin.config.product_type_config.pop("ID", None)
|
|
2466
|
+
|
|
2467
|
+
def import_stac_items(self, items_urls: list[str]) -> SearchResult:
|
|
2468
|
+
"""Import STAC items from a list of URLs and convert them to SearchResult.
|
|
2469
|
+
|
|
2470
|
+
- Origin provider and download links will be set if item comes from an EODAG
|
|
2471
|
+
server.
|
|
2472
|
+
- If item comes from a known EODAG provider, result will be registered to it,
|
|
2473
|
+
ready to download and its metadata normalized.
|
|
2474
|
+
- If item comes from an unknown provider, a generic STAC provider will be used.
|
|
2475
|
+
|
|
2476
|
+
:param items_urls: A list of STAC items URLs to import
|
|
2477
|
+
:returns: A SearchResult containing the imported STAC items
|
|
2478
|
+
"""
|
|
2479
|
+
json_items = []
|
|
2480
|
+
for item_url in items_urls:
|
|
2481
|
+
json_items.extend(fetch_stac_items(item_url))
|
|
2482
|
+
|
|
2483
|
+
# add a generic STAC provider that might be needed to handle the items
|
|
2484
|
+
self.add_provider(GENERIC_STAC_PROVIDER)
|
|
2485
|
+
|
|
2486
|
+
results = SearchResult([])
|
|
2487
|
+
for json_item in json_items:
|
|
2488
|
+
if search_result := SearchResult._from_stac_item(
|
|
2489
|
+
json_item, self._plugins_manager
|
|
2490
|
+
):
|
|
2491
|
+
results.extend(search_result)
|
|
2492
|
+
|
|
2493
|
+
return results
|
eodag/api/product/__init__.py
CHANGED
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
#
|
|
19
19
|
"""EODAG product package"""
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from eodag.plugins.manager import PluginManager
|
|
25
|
+
|
|
20
26
|
try:
|
|
21
27
|
# import from eodag-cube if installed
|
|
22
28
|
from eodag_cube.api.product import ( # pyright: ignore[reportMissingImports]
|
|
@@ -30,3 +36,27 @@ except ImportError:
|
|
|
30
36
|
|
|
31
37
|
# exportable content
|
|
32
38
|
__all__ = ["Asset", "AssetsDict", "EOProduct"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def unregistered_product_from_item(
|
|
42
|
+
feature: dict[str, Any], provider: str, plugins_manager: "PluginManager"
|
|
43
|
+
) -> Optional[EOProduct]:
|
|
44
|
+
"""Create an EOProduct from a STAC item, map its metadata, but without registering its plugins.
|
|
45
|
+
|
|
46
|
+
:param feature: The STAC item to convert into an EOProduct.
|
|
47
|
+
:param provider: The associated provider from which configuration should be used for mapping.
|
|
48
|
+
:param plugins_manager: The plugins manager instance to use for retrieving search plugins.
|
|
49
|
+
:returns: An EOProduct instance if the item can be normalized, otherwise None.
|
|
50
|
+
"""
|
|
51
|
+
for search_plugin in plugins_manager.get_search_plugins(provider=provider):
|
|
52
|
+
if hasattr(search_plugin, "normalize_results"):
|
|
53
|
+
products = search_plugin.normalize_results([feature])
|
|
54
|
+
if len(products) > 0:
|
|
55
|
+
# properties cleanup
|
|
56
|
+
for prop in ("start_datetime", "end_datetime"):
|
|
57
|
+
products[0].properties.pop(prop, None)
|
|
58
|
+
# set product type if not already set
|
|
59
|
+
if products[0].product_type is None:
|
|
60
|
+
products[0].product_type = products[0].properties.get("productType")
|
|
61
|
+
return products[0]
|
|
62
|
+
return None
|
|
@@ -219,7 +219,7 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
219
219
|
elif value is not None:
|
|
220
220
|
converted = self.custom_converter(value)
|
|
221
221
|
else:
|
|
222
|
-
converted =
|
|
222
|
+
converted = None
|
|
223
223
|
# Clear this state variable in case the same converter is used to
|
|
224
224
|
# resolve other named arguments
|
|
225
225
|
self.custom_converter = None
|
|
@@ -374,6 +374,18 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
374
374
|
def convert_to_geojson(value: Any) -> str:
|
|
375
375
|
return geojson.dumps(value)
|
|
376
376
|
|
|
377
|
+
@staticmethod
|
|
378
|
+
def convert_to_geojson_polytope(
|
|
379
|
+
value: BaseGeometry,
|
|
380
|
+
) -> Union[dict[Any, Any], str]:
|
|
381
|
+
# ECMWF Polytope uses non-geojson structure for features
|
|
382
|
+
if isinstance(value, Polygon):
|
|
383
|
+
return {
|
|
384
|
+
"type": "polygon",
|
|
385
|
+
"shape": [[y, x] for x, y in value.exterior.coords],
|
|
386
|
+
}
|
|
387
|
+
raise ValidationError("to_geojson_polytope only accepts shapely Polygon")
|
|
388
|
+
|
|
377
389
|
@staticmethod
|
|
378
390
|
def convert_from_ewkt(ewkt_string: str) -> Union[BaseGeometry, str]:
|
|
379
391
|
"""Convert EWKT (Extended Well-Known text) to shapely geometry"""
|
|
@@ -488,10 +500,14 @@ def format_metadata(search_param: str, *args: Any, **kwargs: Any) -> str:
|
|
|
488
500
|
|
|
489
501
|
@staticmethod
|
|
490
502
|
def convert_get_group_name(string: str, pattern: str) -> str:
|
|
503
|
+
sanitized_pattern = pattern.replace(" ", "_SPACE_")
|
|
491
504
|
try:
|
|
492
|
-
match = re.search(
|
|
505
|
+
match = re.search(sanitized_pattern, str(string))
|
|
493
506
|
if match:
|
|
494
|
-
|
|
507
|
+
if result := match.lastgroup:
|
|
508
|
+
return result.replace("_SPACE_", " ")
|
|
509
|
+
else:
|
|
510
|
+
return NOT_AVAILABLE
|
|
495
511
|
except AttributeError:
|
|
496
512
|
pass
|
|
497
513
|
logger.warning(
|
|
@@ -1342,6 +1358,7 @@ def format_query_params(
|
|
|
1342
1358
|
formatted_query_param = remove_str_array_quotes(
|
|
1343
1359
|
formatted_query_param
|
|
1344
1360
|
)
|
|
1361
|
+
|
|
1345
1362
|
# json query string (for POST request)
|
|
1346
1363
|
update_nested_dict(
|
|
1347
1364
|
query_params,
|
eodag/api/search_result.py
CHANGED
|
@@ -17,23 +17,30 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import logging
|
|
20
21
|
from collections import UserList
|
|
21
22
|
from typing import TYPE_CHECKING, Annotated, Any, Iterable, Optional, Union
|
|
22
23
|
|
|
23
24
|
from shapely.geometry import GeometryCollection, shape
|
|
24
25
|
from typing_extensions import Doc
|
|
25
26
|
|
|
26
|
-
from eodag.api.product import EOProduct
|
|
27
|
+
from eodag.api.product import EOProduct, unregistered_product_from_item
|
|
27
28
|
from eodag.plugins.crunch.filter_date import FilterDate
|
|
28
29
|
from eodag.plugins.crunch.filter_latest_intersect import FilterLatestIntersect
|
|
29
30
|
from eodag.plugins.crunch.filter_latest_tpl_name import FilterLatestByName
|
|
30
31
|
from eodag.plugins.crunch.filter_overlap import FilterOverlap
|
|
31
32
|
from eodag.plugins.crunch.filter_property import FilterProperty
|
|
33
|
+
from eodag.utils import GENERIC_STAC_PROVIDER, STAC_SEARCH_PLUGINS
|
|
34
|
+
from eodag.utils.exceptions import MisconfiguredError
|
|
32
35
|
|
|
33
36
|
if TYPE_CHECKING:
|
|
34
37
|
from shapely.geometry.base import BaseGeometry
|
|
35
38
|
|
|
36
39
|
from eodag.plugins.crunch.base import Crunch
|
|
40
|
+
from eodag.plugins.manager import PluginManager
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger("eodag.search_result")
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
class SearchResult(UserList[EOProduct]):
|
|
@@ -211,6 +218,153 @@ class SearchResult(UserList[EOProduct]):
|
|
|
211
218
|
|
|
212
219
|
return super().extend(other)
|
|
213
220
|
|
|
221
|
+
@classmethod
|
|
222
|
+
def _from_stac_item(
|
|
223
|
+
cls, feature: dict[str, Any], plugins_manager: PluginManager
|
|
224
|
+
) -> SearchResult:
|
|
225
|
+
"""Create a SearchResult from a STAC item.
|
|
226
|
+
|
|
227
|
+
:param feature: A STAC item as a dictionary
|
|
228
|
+
:param plugins_manager: The EODAG plugin manager instance
|
|
229
|
+
:returns: A SearchResult containing the EOProduct(s) created from the STAC item
|
|
230
|
+
"""
|
|
231
|
+
# Try importing from EODAG Server
|
|
232
|
+
if results := _import_stac_item_from_eodag_server(feature, plugins_manager):
|
|
233
|
+
return results
|
|
234
|
+
|
|
235
|
+
# try importing from a known STAC provider
|
|
236
|
+
if results := _import_stac_item_from_known_provider(feature, plugins_manager):
|
|
237
|
+
return results
|
|
238
|
+
|
|
239
|
+
# try importing from an unknown STAC provider
|
|
240
|
+
return _import_stac_item_from_unknown_provider(feature, plugins_manager)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _import_stac_item_from_eodag_server(
|
|
244
|
+
feature: dict[str, Any], plugins_manager: PluginManager
|
|
245
|
+
) -> Optional[SearchResult]:
|
|
246
|
+
"""Import a STAC item from EODAG Server.
|
|
247
|
+
|
|
248
|
+
:param feature: A STAC item as a dictionary
|
|
249
|
+
:param plugins_manager: The EODAG plugin manager instance
|
|
250
|
+
:returns: A SearchResult containing the EOProduct(s) created from the STAC item
|
|
251
|
+
"""
|
|
252
|
+
provider = None
|
|
253
|
+
if backends := feature["properties"].get("federation:backends"):
|
|
254
|
+
provider = backends[0]
|
|
255
|
+
elif providers := feature["properties"].get("providers"):
|
|
256
|
+
provider = providers[0].get("name")
|
|
257
|
+
if provider is not None:
|
|
258
|
+
logger.debug("Trying to import STAC item from EODAG Server")
|
|
259
|
+
# assets coming from a STAC provider
|
|
260
|
+
assets = {
|
|
261
|
+
k: v["alternate"]["origin"]
|
|
262
|
+
for k, v in feature.get("assets", {}).items()
|
|
263
|
+
if k not in ("thumbnail", "downloadLink")
|
|
264
|
+
and "origin" in v.get("alternate", {})
|
|
265
|
+
}
|
|
266
|
+
if assets:
|
|
267
|
+
updated_item = {**feature, **{"assets": assets}}
|
|
268
|
+
else:
|
|
269
|
+
# item coming from a non-STAC provider
|
|
270
|
+
updated_item = {**feature}
|
|
271
|
+
download_link = (
|
|
272
|
+
feature.get("assets", {})
|
|
273
|
+
.get("downloadLink", {})
|
|
274
|
+
.get("alternate", {})
|
|
275
|
+
.get("origin", {})
|
|
276
|
+
.get("href")
|
|
277
|
+
)
|
|
278
|
+
if download_link:
|
|
279
|
+
updated_item["assets"] = {}
|
|
280
|
+
updated_item["links"] = [{"rel": "self", "href": download_link}]
|
|
281
|
+
else:
|
|
282
|
+
updated_item = {}
|
|
283
|
+
try:
|
|
284
|
+
eo_product = unregistered_product_from_item(
|
|
285
|
+
updated_item, GENERIC_STAC_PROVIDER, plugins_manager
|
|
286
|
+
)
|
|
287
|
+
except MisconfiguredError:
|
|
288
|
+
eo_product = None
|
|
289
|
+
if eo_product is not None:
|
|
290
|
+
eo_product.provider = provider
|
|
291
|
+
eo_product._register_downloader_from_manager(plugins_manager)
|
|
292
|
+
return SearchResult([eo_product])
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _import_stac_item_from_known_provider(
|
|
297
|
+
feature: dict[str, Any], plugins_manager: PluginManager
|
|
298
|
+
) -> Optional[SearchResult]:
|
|
299
|
+
"""Import a STAC item from an already-configured STAC provider.
|
|
300
|
+
|
|
301
|
+
:param feature: A STAC item as a dictionary
|
|
302
|
+
:param plugins_manager: The EODAG plugin manager instance
|
|
303
|
+
:returns: A SearchResult containing the EOProduct(s) created from the STAC item
|
|
304
|
+
"""
|
|
305
|
+
item_hrefs = [f for f in feature.get("links", []) if f.get("rel") == "self"]
|
|
306
|
+
item_href = item_hrefs[0]["href"] if len(item_hrefs) > 0 else None
|
|
307
|
+
imported_products = SearchResult([])
|
|
308
|
+
for search_plugin in plugins_manager.get_search_plugins():
|
|
309
|
+
# only try STAC search plugins
|
|
310
|
+
if (
|
|
311
|
+
search_plugin.config.type in STAC_SEARCH_PLUGINS
|
|
312
|
+
and search_plugin.provider != GENERIC_STAC_PROVIDER
|
|
313
|
+
and hasattr(search_plugin, "normalize_results")
|
|
314
|
+
):
|
|
315
|
+
provider_base_url = search_plugin.config.api_endpoint.removesuffix(
|
|
316
|
+
"/search"
|
|
317
|
+
)
|
|
318
|
+
# compare the item href with the provider base URL
|
|
319
|
+
if item_href and item_href.startswith(provider_base_url):
|
|
320
|
+
products = search_plugin.normalize_results([feature])
|
|
321
|
+
if len(products) == 0 or len(products[0].assets) == 0:
|
|
322
|
+
continue
|
|
323
|
+
logger.debug(
|
|
324
|
+
"Trying to import STAC item from %s", search_plugin.provider
|
|
325
|
+
)
|
|
326
|
+
eo_product = products[0]
|
|
327
|
+
|
|
328
|
+
configured_pts = [
|
|
329
|
+
k
|
|
330
|
+
for k, v in search_plugin.config.products.items()
|
|
331
|
+
if v.get("productType") == feature.get("collection")
|
|
332
|
+
]
|
|
333
|
+
if len(configured_pts) > 0:
|
|
334
|
+
eo_product.product_type = configured_pts[0]
|
|
335
|
+
else:
|
|
336
|
+
eo_product.product_type = feature.get("collection")
|
|
337
|
+
|
|
338
|
+
eo_product._register_downloader_from_manager(plugins_manager)
|
|
339
|
+
imported_products.append(eo_product)
|
|
340
|
+
if len(imported_products) > 0:
|
|
341
|
+
return imported_products
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _import_stac_item_from_unknown_provider(
|
|
346
|
+
feature: dict[str, Any], plugins_manager: PluginManager
|
|
347
|
+
) -> SearchResult:
|
|
348
|
+
"""Import a STAC item from an unknown STAC provider.
|
|
349
|
+
|
|
350
|
+
:param feature: A STAC item as a dictionary
|
|
351
|
+
:param plugins_manager: The EODAG plugin manager instance
|
|
352
|
+
:returns: A SearchResult containing the EOProduct(s) created from the STAC item
|
|
353
|
+
"""
|
|
354
|
+
try:
|
|
355
|
+
logger.debug("Trying to import STAC item from unknown provider")
|
|
356
|
+
eo_product = unregistered_product_from_item(
|
|
357
|
+
feature, GENERIC_STAC_PROVIDER, plugins_manager
|
|
358
|
+
)
|
|
359
|
+
except MisconfiguredError:
|
|
360
|
+
pass
|
|
361
|
+
if eo_product is not None:
|
|
362
|
+
eo_product.product_type = feature.get("collection")
|
|
363
|
+
eo_product._register_downloader_from_manager(plugins_manager)
|
|
364
|
+
return SearchResult([eo_product])
|
|
365
|
+
else:
|
|
366
|
+
return SearchResult([])
|
|
367
|
+
|
|
214
368
|
|
|
215
369
|
class RawSearchResult(UserList[dict[str, Any]]):
|
|
216
370
|
"""An object representing a collection of raw/unparsed search results obtained from a provider.
|
eodag/cli.py
CHANGED
|
@@ -49,11 +49,12 @@ import sys
|
|
|
49
49
|
import textwrap
|
|
50
50
|
from importlib.metadata import metadata
|
|
51
51
|
from typing import TYPE_CHECKING, Any, Mapping
|
|
52
|
+
from urllib.parse import parse_qs
|
|
52
53
|
|
|
53
54
|
import click
|
|
54
55
|
|
|
55
|
-
from eodag.api.core import EODataAccessGateway
|
|
56
|
-
from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
|
|
56
|
+
from eodag.api.core import EODataAccessGateway, SearchResult
|
|
57
|
+
from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
|
|
57
58
|
from eodag.utils.exceptions import NoMatchingProductType, UnsupportedProvider
|
|
58
59
|
from eodag.utils.logging import setup_logging
|
|
59
60
|
|
|
@@ -109,14 +110,15 @@ class MutuallyExclusiveOption(click.Option):
|
|
|
109
110
|
"""Raise error or use parent handle_parse_result()"""
|
|
110
111
|
if self.mutually_exclusive.intersection(opts) and self.name in opts:
|
|
111
112
|
raise click.UsageError(
|
|
112
|
-
"Illegal usage: `{}` is mutually exclusive with "
|
|
113
|
-
|
|
113
|
+
"Illegal usage: `{}` is mutually exclusive with arguments `{}`.".format(
|
|
114
|
+
self.name, ", ".join(self.mutually_exclusive)
|
|
115
|
+
)
|
|
114
116
|
)
|
|
115
117
|
|
|
116
118
|
return super(MutuallyExclusiveOption, self).handle_parse_result(ctx, opts, args)
|
|
117
119
|
|
|
118
120
|
|
|
119
|
-
@click.group()
|
|
121
|
+
@click.group(chain=True)
|
|
120
122
|
@click.option(
|
|
121
123
|
"-v",
|
|
122
124
|
"--verbose",
|
|
@@ -212,6 +214,18 @@ def version() -> None:
|
|
|
212
214
|
"-S", "--sensorType", help="Search for products matching this type of sensor"
|
|
213
215
|
)
|
|
214
216
|
@click.option("--id", help="Search for the product identified by this id")
|
|
217
|
+
@click.option(
|
|
218
|
+
"--locations",
|
|
219
|
+
type=str,
|
|
220
|
+
help="Custom query-string argument(s) to select locations. "
|
|
221
|
+
"Format :'key1=value1&key2=value2'. Example: --locations country=FRA&continent=Africa",
|
|
222
|
+
)
|
|
223
|
+
@click.option(
|
|
224
|
+
"-q",
|
|
225
|
+
"--query",
|
|
226
|
+
type=str,
|
|
227
|
+
help="Custom query-string argument(s). Format :'key1=value1&key2=value2'",
|
|
228
|
+
)
|
|
215
229
|
@click.option(
|
|
216
230
|
"--cruncher",
|
|
217
231
|
type=click.Choice(CRUNCHERS),
|
|
@@ -262,19 +276,9 @@ def version() -> None:
|
|
|
262
276
|
@click.option(
|
|
263
277
|
"--count",
|
|
264
278
|
is_flag=True,
|
|
265
|
-
help="
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
"--locations",
|
|
269
|
-
type=str,
|
|
270
|
-
help="Custom query-string argument(s) to select locations. "
|
|
271
|
-
"Format :'key1=value1&key2=value2'. Example: --locations country=FRA&continent=Africa",
|
|
272
|
-
)
|
|
273
|
-
@click.option(
|
|
274
|
-
"-q",
|
|
275
|
-
"--query",
|
|
276
|
-
type=str,
|
|
277
|
-
help="Custom query-string argument(s). Format :'key1=value1&key2=value2'",
|
|
279
|
+
help="Make a count request together with search (Enabling count will significantly "
|
|
280
|
+
"slow down search requests for some providers, and might be unavailable for some"
|
|
281
|
+
"others).",
|
|
278
282
|
)
|
|
279
283
|
@click.pass_context
|
|
280
284
|
def search_crunch(ctx: Context, **kwargs: Any) -> None:
|
|
@@ -407,6 +411,7 @@ def search_crunch(ctx: Context, **kwargs: Any) -> None:
|
|
|
407
411
|
storage_filepath += ".geojson"
|
|
408
412
|
result_storage = gateway.serialize(results, filename=storage_filepath)
|
|
409
413
|
click.echo("Results stored at '{}'".format(result_storage))
|
|
414
|
+
ctx.obj["search_results"] = results
|
|
410
415
|
|
|
411
416
|
|
|
412
417
|
@eodag.command(name="list", help="List supported product types")
|
|
@@ -528,12 +533,26 @@ def discover_pt(ctx: Context, **kwargs: Any) -> None:
|
|
|
528
533
|
click.echo("Results stored at '{}'".format(storage_filepath))
|
|
529
534
|
|
|
530
535
|
|
|
531
|
-
@eodag.command(
|
|
536
|
+
@eodag.command(
|
|
537
|
+
help="""Download a list of products from a serialized search result or STAC items URLs/paths
|
|
538
|
+
|
|
539
|
+
Examples:
|
|
540
|
+
|
|
541
|
+
eodag download --search-results /path/to/search_results.geojson
|
|
542
|
+
|
|
543
|
+
eodag download --stac-item https://example.com/stac/item1.json --stac-item /path/to/item2.json
|
|
544
|
+
""",
|
|
545
|
+
)
|
|
532
546
|
@click.option(
|
|
533
547
|
"--search-results",
|
|
534
548
|
type=click.Path(exists=True, dir_okay=False),
|
|
535
549
|
help="Path to a serialized search result",
|
|
536
550
|
)
|
|
551
|
+
@click.option(
|
|
552
|
+
"--stac-item",
|
|
553
|
+
multiple=True,
|
|
554
|
+
help="URL/path of a STAC item to download (multiple values accepted)",
|
|
555
|
+
)
|
|
537
556
|
@click.option(
|
|
538
557
|
"-f",
|
|
539
558
|
"--conf",
|
|
@@ -546,13 +565,20 @@ def discover_pt(ctx: Context, **kwargs: Any) -> None:
|
|
|
546
565
|
show_default=False,
|
|
547
566
|
help="Download only quicklooks of products instead full set of files",
|
|
548
567
|
)
|
|
568
|
+
@click.option(
|
|
569
|
+
"--output-dir",
|
|
570
|
+
type=click.Path(dir_okay=True, file_okay=False),
|
|
571
|
+
help="Products or quicklooks download directory (Default: local temporary directory)",
|
|
572
|
+
)
|
|
549
573
|
@click.pass_context
|
|
550
574
|
def download(ctx: Context, **kwargs: Any) -> None:
|
|
551
575
|
"""Download a bunch of products from a serialized search result"""
|
|
552
576
|
search_result_path = kwargs.pop("search_results")
|
|
553
|
-
|
|
577
|
+
stac_items = kwargs.pop("stac_item")
|
|
578
|
+
search_results = ctx.obj.get("search_results")
|
|
579
|
+
if not search_result_path and not stac_items and search_results is None:
|
|
554
580
|
with click.Context(download) as ctx:
|
|
555
|
-
click.echo("Nothing to do (no search results file provided)")
|
|
581
|
+
click.echo("Nothing to do (no search results file or stac item provided)")
|
|
556
582
|
click.echo(download.get_help(ctx))
|
|
557
583
|
sys.exit(1)
|
|
558
584
|
setup_logging(verbose=ctx.obj["verbosity"])
|
|
@@ -561,25 +587,24 @@ def download(ctx: Context, **kwargs: Any) -> None:
|
|
|
561
587
|
conf_file = click.format_filename(conf_file)
|
|
562
588
|
|
|
563
589
|
satim_api = EODataAccessGateway(user_conf_file_path=conf_file)
|
|
564
|
-
search_results = satim_api.deserialize(search_result_path)
|
|
565
590
|
|
|
591
|
+
search_results = search_results or SearchResult([])
|
|
592
|
+
if search_result_path:
|
|
593
|
+
search_results.extend(satim_api.deserialize_and_register(search_result_path))
|
|
594
|
+
if stac_items:
|
|
595
|
+
search_results.extend(satim_api.import_stac_items(list(stac_items)))
|
|
596
|
+
|
|
597
|
+
output_dir = kwargs.pop("output_dir")
|
|
566
598
|
get_quicklooks = kwargs.pop("quicklooks")
|
|
599
|
+
|
|
567
600
|
if get_quicklooks:
|
|
601
|
+
# Download only quicklooks
|
|
568
602
|
click.echo(
|
|
569
603
|
"Flag 'quicklooks' specified, downloading only quicklooks of products"
|
|
570
604
|
)
|
|
571
605
|
|
|
572
606
|
for idx, product in enumerate(search_results):
|
|
573
|
-
|
|
574
|
-
downloader = satim_api._plugins_manager.get_download_plugin(product)
|
|
575
|
-
auth = product.downloader_auth
|
|
576
|
-
if auth is None:
|
|
577
|
-
auth = satim_api._plugins_manager.get_auth_plugin(
|
|
578
|
-
downloader, product
|
|
579
|
-
)
|
|
580
|
-
search_results[idx].register_downloader(downloader, auth)
|
|
581
|
-
|
|
582
|
-
downloaded_file = product.get_quicklook()
|
|
607
|
+
downloaded_file = product.get_quicklook(output_dir=output_dir)
|
|
583
608
|
if not downloaded_file:
|
|
584
609
|
click.echo(
|
|
585
610
|
"A quicklook may have been downloaded but we cannot locate it. "
|
|
@@ -589,18 +614,8 @@ def download(ctx: Context, **kwargs: Any) -> None:
|
|
|
589
614
|
click.echo("Downloaded {}".format(downloaded_file))
|
|
590
615
|
|
|
591
616
|
else:
|
|
592
|
-
#
|
|
593
|
-
|
|
594
|
-
if product.downloader is None:
|
|
595
|
-
downloader = satim_api._plugins_manager.get_download_plugin(product)
|
|
596
|
-
auth = product.downloader_auth
|
|
597
|
-
if auth is None:
|
|
598
|
-
auth = satim_api._plugins_manager.get_auth_plugin(
|
|
599
|
-
downloader, product
|
|
600
|
-
)
|
|
601
|
-
search_results[idx].register_downloader(downloader, auth)
|
|
602
|
-
|
|
603
|
-
downloaded_files = satim_api.download_all(search_results)
|
|
617
|
+
# Download products
|
|
618
|
+
downloaded_files = satim_api.download_all(search_results, output_dir=output_dir)
|
|
604
619
|
if downloaded_files and len(downloaded_files) > 0:
|
|
605
620
|
for downloaded_file in downloaded_files:
|
|
606
621
|
if downloaded_file is None:
|
|
@@ -674,6 +689,7 @@ def serve_rest(
|
|
|
674
689
|
setup_logging(verbose=ctx.obj["verbosity"])
|
|
675
690
|
try:
|
|
676
691
|
import uvicorn
|
|
692
|
+
import uvicorn.config
|
|
677
693
|
except ImportError:
|
|
678
694
|
raise ImportError(
|
|
679
695
|
"Feature not available, please install eodag[server] or eodag[all]"
|
eodag/config.py
CHANGED
|
@@ -46,6 +46,7 @@ from jsonpath_ng import JSONPath
|
|
|
46
46
|
from eodag.api.product.metadata_mapping import mtd_cfg_as_conversion_and_querypath
|
|
47
47
|
from eodag.utils import (
|
|
48
48
|
HTTP_REQ_TIMEOUT,
|
|
49
|
+
STAC_SEARCH_PLUGINS,
|
|
49
50
|
USER_AGENT,
|
|
50
51
|
cached_yaml_load,
|
|
51
52
|
cached_yaml_load_all,
|
|
@@ -451,6 +452,11 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
451
452
|
discover_queryables: PluginConfig.DiscoverQueryables
|
|
452
453
|
#: :class:`~eodag.plugins.search.base.Search` The mapping between eodag metadata and the plugin specific metadata
|
|
453
454
|
metadata_mapping: dict[str, Union[str, list[str]]]
|
|
455
|
+
#: :class:`~eodag.plugins.search.base.Search` :attr:`~eodag.config.PluginConfig.metadata_mapping` got from the given
|
|
456
|
+
#: product type
|
|
457
|
+
metadata_mapping_from_product: str
|
|
458
|
+
#: :class:`~eodag.plugins.search.base.Search` A mapping for the metadata of individual assets
|
|
459
|
+
assets_mapping: dict[str, dict[str, Any]]
|
|
454
460
|
#: :class:`~eodag.plugins.search.base.Search` Parameters to remove from queryables
|
|
455
461
|
remove_from_queryables: list[str]
|
|
456
462
|
#: :class:`~eodag.plugins.search.base.Search` Parameters to be passed as is in the search url query string
|
|
@@ -819,12 +825,7 @@ def provider_config_init(
|
|
|
819
825
|
if (
|
|
820
826
|
stac_search_default_conf is not None
|
|
821
827
|
and provider_config.search
|
|
822
|
-
and provider_config.search.type
|
|
823
|
-
in [
|
|
824
|
-
"StacSearch",
|
|
825
|
-
"StacListAssets",
|
|
826
|
-
"StaticStacSearch",
|
|
827
|
-
]
|
|
828
|
+
and provider_config.search.type in STAC_SEARCH_PLUGINS
|
|
828
829
|
):
|
|
829
830
|
# search config set to stac defaults overriden with provider config
|
|
830
831
|
per_provider_stac_provider_config = deepcopy(stac_search_default_conf)
|
|
@@ -23,6 +23,7 @@ import string
|
|
|
23
23
|
from datetime import datetime, timedelta, timezone
|
|
24
24
|
from random import SystemRandom
|
|
25
25
|
from typing import TYPE_CHECKING, Any, Optional
|
|
26
|
+
from urllib.parse import parse_qs, urlparse
|
|
26
27
|
|
|
27
28
|
import jwt
|
|
28
29
|
import requests
|
|
@@ -34,9 +35,7 @@ from eodag.utils import (
|
|
|
34
35
|
DEFAULT_TOKEN_EXPIRATION_MARGIN,
|
|
35
36
|
HTTP_REQ_TIMEOUT,
|
|
36
37
|
USER_AGENT,
|
|
37
|
-
parse_qs,
|
|
38
38
|
repeatfunc,
|
|
39
|
-
urlparse,
|
|
40
39
|
)
|
|
41
40
|
from eodag.utils.exceptions import (
|
|
42
41
|
AuthenticationError,
|