eodag 2.12.0__py3-none-any.whl → 3.0.0b1__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 +434 -319
- eodag/api/product/__init__.py +5 -1
- eodag/api/product/_assets.py +7 -2
- eodag/api/product/_product.py +46 -68
- eodag/api/product/metadata_mapping.py +181 -66
- eodag/api/search_result.py +21 -1
- eodag/cli.py +20 -6
- eodag/config.py +95 -6
- eodag/plugins/apis/base.py +8 -162
- eodag/plugins/apis/ecmwf.py +36 -24
- eodag/plugins/apis/usgs.py +40 -24
- eodag/plugins/authentication/aws_auth.py +2 -2
- eodag/plugins/authentication/header.py +31 -6
- eodag/plugins/authentication/keycloak.py +13 -84
- eodag/plugins/authentication/oauth.py +3 -3
- eodag/plugins/authentication/openid_connect.py +256 -46
- eodag/plugins/authentication/qsauth.py +3 -0
- eodag/plugins/authentication/sas_auth.py +8 -1
- eodag/plugins/authentication/token.py +92 -46
- eodag/plugins/authentication/token_exchange.py +120 -0
- eodag/plugins/download/aws.py +86 -91
- eodag/plugins/download/base.py +72 -40
- eodag/plugins/download/http.py +607 -264
- eodag/plugins/download/s3rest.py +28 -15
- eodag/plugins/manager.py +73 -57
- eodag/plugins/search/__init__.py +36 -0
- eodag/plugins/search/base.py +225 -18
- eodag/plugins/search/build_search_result.py +389 -32
- eodag/plugins/search/cop_marine.py +378 -0
- eodag/plugins/search/creodias_s3.py +15 -14
- eodag/plugins/search/csw.py +5 -7
- eodag/plugins/search/data_request_search.py +44 -20
- eodag/plugins/search/qssearch.py +508 -203
- eodag/plugins/search/static_stac_search.py +99 -36
- eodag/resources/constraints/climate-dt.json +13 -0
- eodag/resources/constraints/extremes-dt.json +8 -0
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1897 -34
- eodag/resources/providers.yml +3539 -3277
- eodag/resources/stac.yml +48 -54
- eodag/resources/stac_api.yml +71 -25
- eodag/resources/stac_provider.yml +5 -0
- eodag/resources/user_conf_template.yml +51 -3
- eodag/rest/__init__.py +6 -0
- eodag/rest/cache.py +70 -0
- eodag/rest/config.py +68 -0
- eodag/rest/constants.py +27 -0
- eodag/rest/core.py +757 -0
- eodag/rest/server.py +397 -258
- eodag/rest/stac.py +438 -307
- eodag/rest/types/collections_search.py +44 -0
- eodag/rest/types/eodag_search.py +232 -43
- eodag/rest/types/{stac_queryables.py → queryables.py} +81 -43
- eodag/rest/types/stac_search.py +277 -0
- eodag/rest/utils/__init__.py +216 -0
- eodag/rest/utils/cql_evaluate.py +119 -0
- eodag/rest/utils/rfc3339.py +65 -0
- eodag/types/__init__.py +99 -9
- eodag/types/bbox.py +15 -14
- eodag/types/download_args.py +31 -0
- eodag/types/search_args.py +58 -7
- eodag/types/whoosh.py +81 -0
- eodag/utils/__init__.py +72 -9
- eodag/utils/constraints.py +37 -37
- eodag/utils/exceptions.py +23 -17
- eodag/utils/requests.py +138 -0
- eodag/utils/rest.py +104 -0
- eodag/utils/stac_reader.py +100 -16
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/METADATA +64 -44
- eodag-3.0.0b1.dist-info/RECORD +109 -0
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/WHEEL +1 -1
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/entry_points.txt +6 -5
- eodag/plugins/apis/cds.py +0 -540
- eodag/rest/utils.py +0 -1133
- eodag-2.12.0.dist-info/RECORD +0 -94
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/LICENSE +0 -0
- {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/top_level.txt +0 -0
eodag/api/core.py
CHANGED
|
@@ -23,18 +23,7 @@ import re
|
|
|
23
23
|
import shutil
|
|
24
24
|
import tempfile
|
|
25
25
|
from operator import itemgetter
|
|
26
|
-
from typing import
|
|
27
|
-
TYPE_CHECKING,
|
|
28
|
-
AbstractSet,
|
|
29
|
-
Any,
|
|
30
|
-
Dict,
|
|
31
|
-
Iterator,
|
|
32
|
-
List,
|
|
33
|
-
Optional,
|
|
34
|
-
Set,
|
|
35
|
-
Tuple,
|
|
36
|
-
Union,
|
|
37
|
-
)
|
|
26
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Tuple, Union
|
|
38
27
|
|
|
39
28
|
import geojson
|
|
40
29
|
import pkg_resources
|
|
@@ -46,12 +35,10 @@ from whoosh.fields import Schema
|
|
|
46
35
|
from whoosh.index import create_in, exists_in, open_dir
|
|
47
36
|
from whoosh.qparser import QueryParser
|
|
48
37
|
|
|
49
|
-
from eodag.api.product.metadata_mapping import
|
|
50
|
-
NOT_MAPPED,
|
|
51
|
-
mtd_cfg_as_conversion_and_querypath,
|
|
52
|
-
)
|
|
38
|
+
from eodag.api.product.metadata_mapping import mtd_cfg_as_conversion_and_querypath
|
|
53
39
|
from eodag.api.search_result import SearchResult
|
|
54
40
|
from eodag.config import (
|
|
41
|
+
PluginConfig,
|
|
55
42
|
SimpleYamlProxyConfig,
|
|
56
43
|
get_ext_product_types_conf,
|
|
57
44
|
load_default_config,
|
|
@@ -63,9 +50,11 @@ from eodag.config import (
|
|
|
63
50
|
provider_config_init,
|
|
64
51
|
)
|
|
65
52
|
from eodag.plugins.manager import PluginManager
|
|
53
|
+
from eodag.plugins.search import PreparedSearch
|
|
66
54
|
from eodag.plugins.search.build_search_result import BuildPostSearchResult
|
|
67
55
|
from eodag.types import model_fields_to_annotated
|
|
68
|
-
from eodag.types.queryables import CommonQueryables
|
|
56
|
+
from eodag.types.queryables import CommonQueryables
|
|
57
|
+
from eodag.types.whoosh import EODAGQueryParser
|
|
69
58
|
from eodag.utils import (
|
|
70
59
|
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
71
60
|
DEFAULT_DOWNLOAD_WAIT,
|
|
@@ -78,7 +67,6 @@ from eodag.utils import (
|
|
|
78
67
|
_deprecated,
|
|
79
68
|
copy_deepcopy,
|
|
80
69
|
deepcopy,
|
|
81
|
-
get_args,
|
|
82
70
|
get_geometry_from_various,
|
|
83
71
|
makedirs,
|
|
84
72
|
obj_md5sum,
|
|
@@ -87,6 +75,7 @@ from eodag.utils import (
|
|
|
87
75
|
)
|
|
88
76
|
from eodag.utils.exceptions import (
|
|
89
77
|
AuthenticationError,
|
|
78
|
+
EodagError,
|
|
90
79
|
MisconfiguredError,
|
|
91
80
|
NoMatchingProductType,
|
|
92
81
|
PluginImplementationError,
|
|
@@ -94,6 +83,7 @@ from eodag.utils.exceptions import (
|
|
|
94
83
|
UnsupportedProductType,
|
|
95
84
|
UnsupportedProvider,
|
|
96
85
|
)
|
|
86
|
+
from eodag.utils.rest import rfc3339_str_to_datetime
|
|
97
87
|
from eodag.utils.stac_reader import fetch_stac_items
|
|
98
88
|
|
|
99
89
|
if TYPE_CHECKING:
|
|
@@ -104,7 +94,9 @@ if TYPE_CHECKING:
|
|
|
104
94
|
from eodag.plugins.apis.base import Api
|
|
105
95
|
from eodag.plugins.crunch.base import Crunch
|
|
106
96
|
from eodag.plugins.search.base import Search
|
|
107
|
-
from eodag.
|
|
97
|
+
from eodag.types import ProviderSortables
|
|
98
|
+
from eodag.types.download_args import DownloadConf
|
|
99
|
+
from eodag.utils import Annotated, DownloadedCallback, ProgressCallback, Unpack
|
|
108
100
|
|
|
109
101
|
logger = logging.getLogger("eodag.core")
|
|
110
102
|
|
|
@@ -247,7 +239,6 @@ class EODataAccessGateway:
|
|
|
247
239
|
if "unsupported pickle protocol" in str(ve):
|
|
248
240
|
logger.debug("Need to recreate whoosh .index: '%s'", ve)
|
|
249
241
|
create_index = True
|
|
250
|
-
shutil.rmtree(index_dir)
|
|
251
242
|
# Unexpected error
|
|
252
243
|
else:
|
|
253
244
|
logger.error(
|
|
@@ -261,13 +252,14 @@ class EODataAccessGateway:
|
|
|
261
252
|
if self._product_types_index is None:
|
|
262
253
|
logger.debug("Opening product types index in %s", index_dir)
|
|
263
254
|
self._product_types_index = open_dir(index_dir)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
|
|
256
|
+
with self._product_types_index.searcher() as searcher:
|
|
257
|
+
p = QueryParser("md5", self._product_types_index.schema, plugins=[])
|
|
258
|
+
query = p.parse(self.product_types_config_md5)
|
|
259
|
+
results = searcher.search(query, limit=1)
|
|
260
|
+
|
|
261
|
+
if not results:
|
|
262
|
+
create_index = True
|
|
271
263
|
logger.debug(
|
|
272
264
|
"Out-of-date product types index removed from %s", index_dir
|
|
273
265
|
)
|
|
@@ -284,9 +276,8 @@ class EODataAccessGateway:
|
|
|
284
276
|
)
|
|
285
277
|
|
|
286
278
|
product_types_schema = Schema(
|
|
287
|
-
ID=fields.
|
|
288
|
-
|
|
289
|
-
abstract=fields.STORED,
|
|
279
|
+
ID=fields.ID(stored=True),
|
|
280
|
+
abstract=fields.TEXT,
|
|
290
281
|
instrument=fields.IDLIST,
|
|
291
282
|
platform=fields.ID,
|
|
292
283
|
platformSerialIdentifier=fields.IDLIST,
|
|
@@ -294,10 +285,11 @@ class EODataAccessGateway:
|
|
|
294
285
|
sensorType=fields.ID,
|
|
295
286
|
md5=fields.ID,
|
|
296
287
|
license=fields.ID,
|
|
297
|
-
title=fields.
|
|
298
|
-
missionStartDate=fields.
|
|
299
|
-
missionEndDate=fields.
|
|
288
|
+
title=fields.TEXT,
|
|
289
|
+
missionStartDate=fields.STORED,
|
|
290
|
+
missionEndDate=fields.STORED,
|
|
300
291
|
keywords=fields.KEYWORD(analyzer=kw_analyzer),
|
|
292
|
+
stacCollection=fields.STORED,
|
|
301
293
|
)
|
|
302
294
|
self._product_types_index = create_in(index_dir, product_types_schema)
|
|
303
295
|
ix_writer = self._product_types_index.writer()
|
|
@@ -404,6 +396,7 @@ class EODataAccessGateway:
|
|
|
404
396
|
stac_provider_config = load_stac_provider_config()
|
|
405
397
|
for provider in conf_update.keys():
|
|
406
398
|
provider_config_init(self.providers_config[provider], stac_provider_config)
|
|
399
|
+
setattr(self.providers_config[provider], "product_types_fetched", False)
|
|
407
400
|
# re-create _plugins_manager using up-to-date providers_config
|
|
408
401
|
self._plugins_manager.build_product_type_to_provider_config_map()
|
|
409
402
|
|
|
@@ -413,6 +406,20 @@ class EODataAccessGateway:
|
|
|
413
406
|
for provider in list(self.providers_config.keys()):
|
|
414
407
|
conf = self.providers_config[provider]
|
|
415
408
|
|
|
409
|
+
# remove providers using skipped plugins
|
|
410
|
+
if [
|
|
411
|
+
v
|
|
412
|
+
for v in conf.__dict__.values()
|
|
413
|
+
if isinstance(v, PluginConfig)
|
|
414
|
+
and getattr(v, "type", None) in self._plugins_manager.skipped_plugins
|
|
415
|
+
]:
|
|
416
|
+
self.providers_config.pop(provider)
|
|
417
|
+
logger.debug(
|
|
418
|
+
f"{provider}: provider needing unavailable plugin has been removed"
|
|
419
|
+
)
|
|
420
|
+
continue
|
|
421
|
+
|
|
422
|
+
# check authentication
|
|
416
423
|
if hasattr(conf, "api") and getattr(conf.api, "need_auth", False):
|
|
417
424
|
credentials_exist = any(
|
|
418
425
|
[
|
|
@@ -427,7 +434,7 @@ class EODataAccessGateway:
|
|
|
427
434
|
)
|
|
428
435
|
update_needed = True
|
|
429
436
|
logger.info(
|
|
430
|
-
"%s: provider needing auth for search has been pruned because no
|
|
437
|
+
"%s: provider needing auth for search has been pruned because no credentials could be found",
|
|
431
438
|
provider,
|
|
432
439
|
)
|
|
433
440
|
elif hasattr(conf, "search") and getattr(conf.search, "need_auth", False):
|
|
@@ -455,7 +462,7 @@ class EODataAccessGateway:
|
|
|
455
462
|
)
|
|
456
463
|
update_needed = True
|
|
457
464
|
logger.info(
|
|
458
|
-
"%s: provider needing auth for search has been pruned because no
|
|
465
|
+
"%s: provider needing auth for search has been pruned because no credentials could be found",
|
|
459
466
|
provider,
|
|
460
467
|
)
|
|
461
468
|
elif not hasattr(conf, "api") and not hasattr(conf, "search"):
|
|
@@ -501,10 +508,10 @@ class EODataAccessGateway:
|
|
|
501
508
|
locations_config = load_yml_config(locations_conf_path)
|
|
502
509
|
|
|
503
510
|
main_key = next(iter(locations_config))
|
|
504
|
-
|
|
511
|
+
main_locations_config = locations_config[main_key]
|
|
505
512
|
|
|
506
513
|
logger.info("Locations configuration loaded from %s" % locations_conf_path)
|
|
507
|
-
self.locations_config: List[Dict[str, Any]] =
|
|
514
|
+
self.locations_config: List[Dict[str, Any]] = main_locations_config
|
|
508
515
|
else:
|
|
509
516
|
logger.info(
|
|
510
517
|
"Could not load locations configuration from %s" % locations_conf_path
|
|
@@ -531,35 +538,34 @@ class EODataAccessGateway:
|
|
|
531
538
|
self.fetch_product_types_list(provider=provider)
|
|
532
539
|
|
|
533
540
|
product_types: List[Dict[str, Any]] = []
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
product_types.append(product_type)
|
|
547
|
-
return sorted(product_types, key=itemgetter("ID"))
|
|
541
|
+
|
|
542
|
+
providers_configs = (
|
|
543
|
+
list(self.providers_config.values())
|
|
544
|
+
if not provider
|
|
545
|
+
else [
|
|
546
|
+
p
|
|
547
|
+
for p in self.providers_config.values()
|
|
548
|
+
if provider in [p.name, getattr(p, "group", None)]
|
|
549
|
+
]
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
if provider and not providers_configs:
|
|
548
553
|
raise UnsupportedProvider(
|
|
549
554
|
f"The requested provider is not (yet) supported: {provider}"
|
|
550
555
|
)
|
|
551
|
-
|
|
552
|
-
for
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
556
|
+
|
|
557
|
+
for p in providers_configs:
|
|
558
|
+
for product_type_id in p.products: # type: ignore
|
|
559
|
+
if product_type_id == GENERIC_PRODUCT_TYPE:
|
|
560
|
+
continue
|
|
561
|
+
config = self.product_types_config[product_type_id]
|
|
562
|
+
config["_id"] = product_type_id
|
|
563
|
+
if "alias" in config:
|
|
564
|
+
product_type_id = config["alias"]
|
|
565
|
+
product_type = {"ID": product_type_id, **config}
|
|
566
|
+
if product_type not in product_types:
|
|
567
|
+
product_types.append(product_type)
|
|
568
|
+
|
|
563
569
|
# Return the product_types sorted in lexicographic order of their ID
|
|
564
570
|
return sorted(product_types, key=itemgetter("ID"))
|
|
565
571
|
|
|
@@ -611,9 +617,8 @@ class EODataAccessGateway:
|
|
|
611
617
|
|
|
612
618
|
if not ext_product_types_conf:
|
|
613
619
|
# empty ext_product_types conf
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
**discover_kwargs
|
|
620
|
+
ext_product_types_conf = (
|
|
621
|
+
self.discover_product_types(provider=provider) or {}
|
|
617
622
|
)
|
|
618
623
|
|
|
619
624
|
# update eodag product types list with new conf
|
|
@@ -691,13 +696,13 @@ class EODataAccessGateway:
|
|
|
691
696
|
# providers not skipped here should be user-modified
|
|
692
697
|
# or not in ext_product_types_conf (if eodag system conf != eodag conf used for ext_product_types_conf)
|
|
693
698
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
699
|
+
if not already_fetched:
|
|
700
|
+
# discover product types for user configured provider
|
|
701
|
+
provider_ext_product_types_conf = (
|
|
702
|
+
self.discover_product_types(provider=provider) or {}
|
|
703
|
+
)
|
|
704
|
+
# update eodag product types list with new conf
|
|
705
|
+
self.update_product_types_list(provider_ext_product_types_conf)
|
|
701
706
|
|
|
702
707
|
def discover_product_types(
|
|
703
708
|
self, provider: Optional[str] = None
|
|
@@ -710,6 +715,10 @@ class EODataAccessGateway:
|
|
|
710
715
|
:returns: external product types configuration
|
|
711
716
|
:rtype: dict
|
|
712
717
|
"""
|
|
718
|
+
if provider and provider not in self.providers_config:
|
|
719
|
+
raise UnsupportedProvider(
|
|
720
|
+
f"The requested provider is not (yet) supported: {provider}"
|
|
721
|
+
)
|
|
713
722
|
ext_product_types_conf: Dict[str, Any] = {}
|
|
714
723
|
providers_to_fetch = [
|
|
715
724
|
p
|
|
@@ -721,6 +730,7 @@ class EODataAccessGateway:
|
|
|
721
730
|
else self.available_providers()
|
|
722
731
|
)
|
|
723
732
|
]
|
|
733
|
+
kwargs: Dict[str, Any] = {}
|
|
724
734
|
for provider in providers_to_fetch:
|
|
725
735
|
if hasattr(self.providers_config[provider], "search"):
|
|
726
736
|
search_plugin_config = self.providers_config[provider].search
|
|
@@ -737,9 +747,11 @@ class EODataAccessGateway:
|
|
|
737
747
|
auth_plugin = self._plugins_manager.get_auth_plugin(
|
|
738
748
|
search_plugin.provider
|
|
739
749
|
)
|
|
740
|
-
if
|
|
750
|
+
if auth_plugin and callable(
|
|
751
|
+
getattr(auth_plugin, "authenticate", None)
|
|
752
|
+
):
|
|
741
753
|
try:
|
|
742
|
-
|
|
754
|
+
kwargs["auth"] = auth_plugin.authenticate()
|
|
743
755
|
except (AuthenticationError, MisconfiguredError) as e:
|
|
744
756
|
logger.warning(
|
|
745
757
|
f"Could not authenticate on {provider}: {str(e)}"
|
|
@@ -753,9 +765,9 @@ class EODataAccessGateway:
|
|
|
753
765
|
ext_product_types_conf[provider] = None
|
|
754
766
|
continue
|
|
755
767
|
|
|
756
|
-
ext_product_types_conf[
|
|
757
|
-
|
|
758
|
-
|
|
768
|
+
ext_product_types_conf[provider] = search_plugin.discover_product_types(
|
|
769
|
+
**kwargs
|
|
770
|
+
)
|
|
759
771
|
|
|
760
772
|
return ext_product_types_conf
|
|
761
773
|
|
|
@@ -848,23 +860,49 @@ class EODataAccessGateway:
|
|
|
848
860
|
# rebuild index after product types list update
|
|
849
861
|
self.build_index()
|
|
850
862
|
|
|
851
|
-
def available_providers(
|
|
852
|
-
|
|
863
|
+
def available_providers(
|
|
864
|
+
self, product_type: Optional[str] = None, by_group: bool = False
|
|
865
|
+
) -> List[str]:
|
|
866
|
+
"""Gives the sorted list of the available providers or groups
|
|
867
|
+
|
|
868
|
+
The providers or groups are sorted first by their priority level in descending order,
|
|
869
|
+
and then alphabetically in ascending order for providers or groups with the same
|
|
870
|
+
priority level.
|
|
853
871
|
|
|
854
872
|
:param product_type: (optional) Only list providers configured for this product_type
|
|
855
|
-
:type product_type: str
|
|
856
|
-
:
|
|
857
|
-
|
|
873
|
+
:type product_type: Optional[str]
|
|
874
|
+
:param by_group: (optional) If set to True, list groups when available instead
|
|
875
|
+
of providers, mixed with other providers
|
|
876
|
+
:type by_group: bool
|
|
877
|
+
:returns: the sorted list of the available providers or groups
|
|
878
|
+
:rtype: List[str]
|
|
858
879
|
"""
|
|
859
880
|
|
|
860
881
|
if product_type:
|
|
861
|
-
|
|
862
|
-
k
|
|
882
|
+
providers = [
|
|
883
|
+
(v.group if by_group and hasattr(v, "group") else k, v.priority)
|
|
863
884
|
for k, v in self.providers_config.items()
|
|
864
885
|
if product_type in getattr(v, "products", {}).keys()
|
|
865
|
-
|
|
886
|
+
]
|
|
866
887
|
else:
|
|
867
|
-
|
|
888
|
+
providers = [
|
|
889
|
+
(v.group if by_group and hasattr(v, "group") else k, v.priority)
|
|
890
|
+
for k, v in self.providers_config.items()
|
|
891
|
+
]
|
|
892
|
+
|
|
893
|
+
# If by_group is True, keep only the highest priority for each group
|
|
894
|
+
if by_group:
|
|
895
|
+
group_priority: Dict[str, int] = {}
|
|
896
|
+
for name, priority in providers:
|
|
897
|
+
if name not in group_priority or priority > group_priority[name]:
|
|
898
|
+
group_priority[name] = priority
|
|
899
|
+
providers = list(group_priority.items())
|
|
900
|
+
|
|
901
|
+
# Sort by priority (descending) and then by name (ascending)
|
|
902
|
+
providers.sort(key=lambda x: (-x[1], x[0]))
|
|
903
|
+
|
|
904
|
+
# Return only the names of the providers or groups
|
|
905
|
+
return [name for name, _ in providers]
|
|
868
906
|
|
|
869
907
|
def get_product_type_from_alias(self, alias_or_id: str) -> str:
|
|
870
908
|
"""Return the ID of a product type by either its ID or alias
|
|
@@ -910,47 +948,116 @@ class EODataAccessGateway:
|
|
|
910
948
|
|
|
911
949
|
return self.product_types_config[product_type].get("alias", product_type)
|
|
912
950
|
|
|
913
|
-
def guess_product_type(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
:
|
|
917
|
-
:
|
|
918
|
-
:
|
|
951
|
+
def guess_product_type(
|
|
952
|
+
self,
|
|
953
|
+
free_text: Optional[str] = None,
|
|
954
|
+
intersect: bool = False,
|
|
955
|
+
instrument: Optional[str] = None,
|
|
956
|
+
platform: Optional[str] = None,
|
|
957
|
+
platformSerialIdentifier: Optional[str] = None,
|
|
958
|
+
processingLevel: Optional[str] = None,
|
|
959
|
+
sensorType: Optional[str] = None,
|
|
960
|
+
keywords: Optional[str] = None,
|
|
961
|
+
abstract: Optional[str] = None,
|
|
962
|
+
title: Optional[str] = None,
|
|
963
|
+
missionStartDate: Optional[str] = None,
|
|
964
|
+
missionEndDate: Optional[str] = None,
|
|
965
|
+
**kwargs: Any,
|
|
966
|
+
) -> List[str]:
|
|
967
|
+
"""
|
|
968
|
+
Find EODAG product type IDs that best match a set of search parameters.
|
|
969
|
+
|
|
970
|
+
See https://whoosh.readthedocs.io/en/latest/querylang.html#the-default-query-language
|
|
971
|
+
for syntax.
|
|
972
|
+
|
|
973
|
+
:param free_text: Whoosh-compatible free text search filter used to search
|
|
974
|
+
accross all the following parameters
|
|
975
|
+
:type free_text: Optional[str]
|
|
976
|
+
:param intersect: Join results for each parameter using INTERSECT instead of UNION.
|
|
977
|
+
:type intersect: bool
|
|
978
|
+
:param instrument: Instrument parameter.
|
|
979
|
+
:type instrument: Optional[str]
|
|
980
|
+
:param platform: Platform parameter.
|
|
981
|
+
:type platform: Optional[str]
|
|
982
|
+
:param platformSerialIdentifier: Platform serial identifier parameter.
|
|
983
|
+
:type platformSerialIdentifier: Optional[str]
|
|
984
|
+
:param processingLevel: Processing level parameter.
|
|
985
|
+
:type processingLevel: Optional[str]
|
|
986
|
+
:param sensorType: Sensor type parameter.
|
|
987
|
+
:type sensorType: Optional[str]
|
|
988
|
+
:param keywords: Keywords parameter.
|
|
989
|
+
:type keywords: Optional[str]
|
|
990
|
+
:param abstract: Abstract parameter.
|
|
991
|
+
:type abstract: Optional[str]
|
|
992
|
+
:param title: Title parameter.
|
|
993
|
+
:type title: Optional[str]
|
|
994
|
+
:param missionStartDate: start date for datetime filtering. Not used by free_text
|
|
995
|
+
:type missionStartDate: Optional[str]
|
|
996
|
+
:param missionEndDate: end date for datetime filtering. Not used by free_text
|
|
997
|
+
:type missionEndDate: Optional[str]
|
|
998
|
+
:returns: The best match for the given parameters.
|
|
999
|
+
:rtype: List[str]
|
|
919
1000
|
:raises: :class:`~eodag.utils.exceptions.NoMatchingProductType`
|
|
920
1001
|
"""
|
|
921
|
-
if kwargs.get("productType"
|
|
922
|
-
return [
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1002
|
+
if productType := kwargs.get("productType"):
|
|
1003
|
+
return [productType]
|
|
1004
|
+
|
|
1005
|
+
if not self._product_types_index:
|
|
1006
|
+
raise EodagError("Missing product types index")
|
|
1007
|
+
|
|
1008
|
+
filters = {
|
|
1009
|
+
"instrument": instrument,
|
|
1010
|
+
"platform": platform,
|
|
1011
|
+
"platformSerialIdentifier": platformSerialIdentifier,
|
|
1012
|
+
"processingLevel": processingLevel,
|
|
1013
|
+
"sensorType": sensorType,
|
|
1014
|
+
"keywords": keywords,
|
|
1015
|
+
"abstract": abstract,
|
|
1016
|
+
"title": title,
|
|
935
1017
|
}
|
|
1018
|
+
joint = " AND " if intersect else " OR "
|
|
1019
|
+
filters_text = joint.join(
|
|
1020
|
+
[f"{k}:({v})" for k, v in filters.items() if v is not None]
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
text = f"({free_text})" if free_text else ""
|
|
1024
|
+
if free_text and filters_text:
|
|
1025
|
+
text += joint
|
|
1026
|
+
if filters_text:
|
|
1027
|
+
text += f"({filters_text})"
|
|
1028
|
+
|
|
1029
|
+
if not text and (missionStartDate or missionEndDate):
|
|
1030
|
+
text = "*"
|
|
1031
|
+
|
|
936
1032
|
with self._product_types_index.searcher() as searcher:
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1033
|
+
p = EODAGQueryParser(list(filters.keys()), self._product_types_index.schema)
|
|
1034
|
+
query = p.parse(text)
|
|
1035
|
+
results = searcher.search(query, limit=None)
|
|
1036
|
+
|
|
1037
|
+
guesses: List[Dict[str, str]] = [dict(r) for r in results or []]
|
|
1038
|
+
|
|
1039
|
+
# datetime filtering
|
|
1040
|
+
if missionStartDate or missionEndDate:
|
|
1041
|
+
guesses = [
|
|
1042
|
+
g
|
|
1043
|
+
for g in guesses
|
|
1044
|
+
if (
|
|
1045
|
+
not missionEndDate
|
|
1046
|
+
or g.get("missionStartDate")
|
|
1047
|
+
and rfc3339_str_to_datetime(g["missionStartDate"])
|
|
1048
|
+
<= rfc3339_str_to_datetime(missionEndDate)
|
|
946
1049
|
)
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1050
|
+
and (
|
|
1051
|
+
not missionStartDate
|
|
1052
|
+
or g.get("missionEndDate")
|
|
1053
|
+
and rfc3339_str_to_datetime(g["missionEndDate"])
|
|
1054
|
+
>= rfc3339_str_to_datetime(missionStartDate)
|
|
1055
|
+
)
|
|
1056
|
+
]
|
|
1057
|
+
|
|
952
1058
|
if guesses:
|
|
953
|
-
return guesses
|
|
1059
|
+
return [g["ID"] for g in guesses or []]
|
|
1060
|
+
|
|
954
1061
|
raise NoMatchingProductType()
|
|
955
1062
|
|
|
956
1063
|
def search(
|
|
@@ -963,8 +1070,9 @@ class EODataAccessGateway:
|
|
|
963
1070
|
geom: Optional[Union[str, Dict[str, float], BaseGeometry]] = None,
|
|
964
1071
|
locations: Optional[Dict[str, str]] = None,
|
|
965
1072
|
provider: Optional[str] = None,
|
|
1073
|
+
count: bool = False,
|
|
966
1074
|
**kwargs: Any,
|
|
967
|
-
) ->
|
|
1075
|
+
) -> SearchResult:
|
|
968
1076
|
"""Look for products matching criteria on known providers.
|
|
969
1077
|
|
|
970
1078
|
The default behaviour is to look for products on the provider with the
|
|
@@ -1006,16 +1114,17 @@ class EODataAccessGateway:
|
|
|
1006
1114
|
'PA' such as Panama and Pakistan in the shapefile configured with
|
|
1007
1115
|
name=country and attr=ISO3
|
|
1008
1116
|
:type locations: dict
|
|
1009
|
-
:param kwargs: Some other criteria that will be used to do the search,
|
|
1010
|
-
using paramaters compatibles with the provider
|
|
1011
1117
|
:param provider: (optional) the provider to be used. If set, search fallback will be disabled.
|
|
1012
1118
|
If not set, the configured preferred provider will be used at first
|
|
1013
1119
|
before trying others until finding results.
|
|
1014
1120
|
:type provider: str
|
|
1121
|
+
:param count: (optional) Whether to run a query with a count request or not
|
|
1122
|
+
:type count: bool
|
|
1123
|
+
:param kwargs: Some other criteria that will be used to do the search,
|
|
1124
|
+
using paramaters compatibles with the provider
|
|
1015
1125
|
:type kwargs: Union[int, str, bool, dict]
|
|
1016
|
-
:returns: A collection of EO products matching the criteria
|
|
1017
|
-
|
|
1018
|
-
:rtype: tuple(:class:`~eodag.api.search_result.SearchResult`, int)
|
|
1126
|
+
:returns: A collection of EO products matching the criteria
|
|
1127
|
+
:rtype: :class:`~eodag.api.search_result.SearchResult`
|
|
1019
1128
|
|
|
1020
1129
|
.. note::
|
|
1021
1130
|
The search interfaces, which are implemented as plugins, are required to
|
|
@@ -1030,16 +1139,12 @@ class EODataAccessGateway:
|
|
|
1030
1139
|
provider=provider,
|
|
1031
1140
|
**kwargs,
|
|
1032
1141
|
)
|
|
1033
|
-
|
|
1034
1142
|
if search_kwargs.get("id"):
|
|
1035
|
-
# adds minimal pagination to be able to check only 1 product is returned
|
|
1036
|
-
search_kwargs.update(
|
|
1037
|
-
page=1,
|
|
1038
|
-
items_per_page=2,
|
|
1039
|
-
raise_errors=raise_errors,
|
|
1040
|
-
)
|
|
1041
1143
|
return self._search_by_id(
|
|
1042
|
-
search_kwargs.pop("id"),
|
|
1144
|
+
search_kwargs.pop("id"),
|
|
1145
|
+
provider=provider,
|
|
1146
|
+
raise_errors=raise_errors,
|
|
1147
|
+
**search_kwargs,
|
|
1043
1148
|
)
|
|
1044
1149
|
# remove datacube query string from kwargs which was only needed for search-by-id
|
|
1045
1150
|
search_kwargs.pop("_dc_qs", None)
|
|
@@ -1053,9 +1158,9 @@ class EODataAccessGateway:
|
|
|
1053
1158
|
# Loop over available providers and return the first non-empty results
|
|
1054
1159
|
for i, search_plugin in enumerate(search_plugins):
|
|
1055
1160
|
search_plugin.clear()
|
|
1056
|
-
search_results
|
|
1161
|
+
search_results = self._do_search(
|
|
1057
1162
|
search_plugin,
|
|
1058
|
-
count=
|
|
1163
|
+
count=count,
|
|
1059
1164
|
raise_errors=raise_errors,
|
|
1060
1165
|
**search_kwargs,
|
|
1061
1166
|
)
|
|
@@ -1065,10 +1170,11 @@ class EODataAccessGateway:
|
|
|
1065
1170
|
"we will try to get the data from another provider",
|
|
1066
1171
|
)
|
|
1067
1172
|
elif len(search_results) > 0:
|
|
1068
|
-
return search_results
|
|
1173
|
+
return search_results
|
|
1069
1174
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1175
|
+
if i > 1:
|
|
1176
|
+
logger.error("No result could be obtained from any available provider")
|
|
1177
|
+
return SearchResult([], 0) if count else SearchResult([])
|
|
1072
1178
|
|
|
1073
1179
|
def search_iter_page(
|
|
1074
1180
|
self,
|
|
@@ -1181,9 +1287,10 @@ class EODataAccessGateway:
|
|
|
1181
1287
|
pagination_config["next_page_query_obj"] = next_page_query_obj
|
|
1182
1288
|
logger.info("Iterate search over multiple pages: page #%s", iteration)
|
|
1183
1289
|
try:
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1290
|
+
# remove unwanted kwargs for _do_search
|
|
1291
|
+
kwargs.pop("count", None)
|
|
1292
|
+
kwargs.pop("raise_errors", None)
|
|
1293
|
+
search_result = self._do_search(
|
|
1187
1294
|
search_plugin, count=False, raise_errors=True, **kwargs
|
|
1188
1295
|
)
|
|
1189
1296
|
except Exception:
|
|
@@ -1222,12 +1329,12 @@ class EODataAccessGateway:
|
|
|
1222
1329
|
else:
|
|
1223
1330
|
search_plugin.next_page_query_obj = next_page_query_obj
|
|
1224
1331
|
|
|
1225
|
-
if len(
|
|
1332
|
+
if len(search_result) > 0:
|
|
1226
1333
|
# The first products between two iterations are compared. If they
|
|
1227
1334
|
# are actually the same product, it means the iteration failed at
|
|
1228
1335
|
# progressing for some reason. This is implemented as a workaround
|
|
1229
1336
|
# to some search plugins/providers not handling pagination.
|
|
1230
|
-
product =
|
|
1337
|
+
product = search_result[0]
|
|
1231
1338
|
if (
|
|
1232
1339
|
prev_product
|
|
1233
1340
|
and product.properties["id"] == prev_product.properties["id"]
|
|
@@ -1240,11 +1347,11 @@ class EODataAccessGateway:
|
|
|
1240
1347
|
)
|
|
1241
1348
|
last_page_with_products = iteration - 1
|
|
1242
1349
|
break
|
|
1243
|
-
yield
|
|
1350
|
+
yield search_result
|
|
1244
1351
|
prev_product = product
|
|
1245
1352
|
# Prevent a last search if the current one returned less than the
|
|
1246
1353
|
# maximum number of items asked for.
|
|
1247
|
-
if len(
|
|
1354
|
+
if len(search_result) < items_per_page:
|
|
1248
1355
|
last_page_with_products = iteration
|
|
1249
1356
|
break
|
|
1250
1357
|
else:
|
|
@@ -1319,7 +1426,7 @@ class EODataAccessGateway:
|
|
|
1319
1426
|
# of items_per_page if defined for the provider used.
|
|
1320
1427
|
try:
|
|
1321
1428
|
product_type = self.get_product_type_from_alias(
|
|
1322
|
-
|
|
1429
|
+
self.guess_product_type(**kwargs)[0]
|
|
1323
1430
|
)
|
|
1324
1431
|
except NoMatchingProductType:
|
|
1325
1432
|
product_type = GENERIC_PRODUCT_TYPE
|
|
@@ -1385,7 +1492,7 @@ class EODataAccessGateway:
|
|
|
1385
1492
|
|
|
1386
1493
|
def _search_by_id(
|
|
1387
1494
|
self, uid: str, provider: Optional[str] = None, **kwargs: Any
|
|
1388
|
-
) ->
|
|
1495
|
+
) -> SearchResult:
|
|
1389
1496
|
"""Internal method that enables searching a product by its id.
|
|
1390
1497
|
|
|
1391
1498
|
Keeps requesting providers until a result matching the id is supplied. The
|
|
@@ -1405,9 +1512,8 @@ class EODataAccessGateway:
|
|
|
1405
1512
|
:type provider: str
|
|
1406
1513
|
:param kwargs: Search criteria to help finding the right product
|
|
1407
1514
|
:type kwargs: Any
|
|
1408
|
-
:returns: A search result with one EO product or None at all
|
|
1409
|
-
|
|
1410
|
-
:rtype: tuple(:class:`~eodag.api.search_result.SearchResult`, int)
|
|
1515
|
+
:returns: A search result with one EO product or None at all
|
|
1516
|
+
:rtype: :class:`~eodag.api.search_result.SearchResult`
|
|
1411
1517
|
"""
|
|
1412
1518
|
product_type = kwargs.get("productType", None)
|
|
1413
1519
|
if product_type is not None:
|
|
@@ -1422,16 +1528,51 @@ class EODataAccessGateway:
|
|
|
1422
1528
|
# datacube query string
|
|
1423
1529
|
_dc_qs = kwargs.pop("_dc_qs", None)
|
|
1424
1530
|
|
|
1531
|
+
results = SearchResult([])
|
|
1532
|
+
|
|
1425
1533
|
for plugin in search_plugins:
|
|
1426
1534
|
logger.info(
|
|
1427
1535
|
"Searching product with id '%s' on provider: %s", uid, plugin.provider
|
|
1428
1536
|
)
|
|
1429
1537
|
logger.debug("Using plugin class for search: %s", plugin.__class__.__name__)
|
|
1430
1538
|
plugin.clear()
|
|
1539
|
+
|
|
1540
|
+
# adds maximal pagination to be able to do a search-all + crunch if more
|
|
1541
|
+
# than one result are returned
|
|
1542
|
+
items_per_page = plugin.config.pagination.get(
|
|
1543
|
+
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1544
|
+
)
|
|
1545
|
+
kwargs.update(items_per_page=items_per_page)
|
|
1431
1546
|
if isinstance(plugin, BuildPostSearchResult):
|
|
1432
|
-
|
|
1547
|
+
kwargs.update(
|
|
1548
|
+
items_per_page=items_per_page,
|
|
1549
|
+
_dc_qs=_dc_qs,
|
|
1550
|
+
)
|
|
1433
1551
|
else:
|
|
1434
|
-
|
|
1552
|
+
kwargs.update(
|
|
1553
|
+
items_per_page=items_per_page,
|
|
1554
|
+
)
|
|
1555
|
+
|
|
1556
|
+
try:
|
|
1557
|
+
# if more than one results are found, try getting them all and then filter using crunch
|
|
1558
|
+
for page_results in self.search_iter_page_plugin(
|
|
1559
|
+
search_plugin=plugin,
|
|
1560
|
+
id=uid,
|
|
1561
|
+
**kwargs,
|
|
1562
|
+
):
|
|
1563
|
+
results.data.extend(page_results.data)
|
|
1564
|
+
except Exception:
|
|
1565
|
+
if kwargs.get("raise_errors"):
|
|
1566
|
+
raise
|
|
1567
|
+
continue
|
|
1568
|
+
|
|
1569
|
+
# try using crunch to get unique result
|
|
1570
|
+
if (
|
|
1571
|
+
len(results) > 1
|
|
1572
|
+
and len(filtered := results.filter_property(id=uid)) == 1
|
|
1573
|
+
):
|
|
1574
|
+
results = filtered
|
|
1575
|
+
|
|
1435
1576
|
if len(results) == 1:
|
|
1436
1577
|
if not results[0].product_type:
|
|
1437
1578
|
# guess product type from properties
|
|
@@ -1439,20 +1580,36 @@ class EODataAccessGateway:
|
|
|
1439
1580
|
results[0].product_type = guesses[0]
|
|
1440
1581
|
# reset driver
|
|
1441
1582
|
results[0].driver = results[0].get_driver()
|
|
1442
|
-
|
|
1583
|
+
results.number_matched = 1
|
|
1584
|
+
return results
|
|
1443
1585
|
elif len(results) > 1:
|
|
1444
|
-
if getattr(plugin.config, "two_passes_id_search", False):
|
|
1445
|
-
# check if id of one product exactly matches id that was searched for
|
|
1446
|
-
# required if provider does not offer search by id and therefore other
|
|
1447
|
-
# parameters which might not given an exact result are used
|
|
1448
|
-
for result in results:
|
|
1449
|
-
if result.properties["id"] == uid.split(".")[0]:
|
|
1450
|
-
return [results[0]], 1
|
|
1451
1586
|
logger.info(
|
|
1452
1587
|
"Several products found for this id (%s). You may try searching using more selective criteria.",
|
|
1453
1588
|
results,
|
|
1454
1589
|
)
|
|
1455
|
-
return SearchResult([]
|
|
1590
|
+
return SearchResult([], 0)
|
|
1591
|
+
|
|
1592
|
+
def _fetch_external_product_type(self, provider: str, product_type: str):
|
|
1593
|
+
plugins = self._plugins_manager.get_search_plugins(provider=provider)
|
|
1594
|
+
plugin = next(plugins)
|
|
1595
|
+
|
|
1596
|
+
kwargs: Dict[str, Any] = {"productType": product_type}
|
|
1597
|
+
|
|
1598
|
+
# append auth if needed
|
|
1599
|
+
if getattr(plugin.config, "need_auth", False):
|
|
1600
|
+
auth_plugin = self._plugins_manager.get_auth_plugin(plugin.provider)
|
|
1601
|
+
if auth_plugin and callable(getattr(auth_plugin, "authenticate", None)):
|
|
1602
|
+
try:
|
|
1603
|
+
kwargs["auth"] = auth_plugin.authenticate()
|
|
1604
|
+
except (AuthenticationError, MisconfiguredError) as e:
|
|
1605
|
+
logger.warning(f"Could not authenticate on {provider}: {str(e)}")
|
|
1606
|
+
else:
|
|
1607
|
+
logger.warning(
|
|
1608
|
+
f"Could not authenticate on {provider} using {auth_plugin} plugin"
|
|
1609
|
+
)
|
|
1610
|
+
|
|
1611
|
+
product_type_config = plugin.discover_product_types(**kwargs)
|
|
1612
|
+
self.update_product_types_list({provider: product_type_config})
|
|
1456
1613
|
|
|
1457
1614
|
def _prepare_search(
|
|
1458
1615
|
self,
|
|
@@ -1532,7 +1689,7 @@ class EODataAccessGateway:
|
|
|
1532
1689
|
try:
|
|
1533
1690
|
product_type = self.get_product_type_from_alias(product_type)
|
|
1534
1691
|
except NoMatchingProductType:
|
|
1535
|
-
logger.
|
|
1692
|
+
logger.info("unknown product type " + product_type)
|
|
1536
1693
|
kwargs["productType"] = product_type
|
|
1537
1694
|
|
|
1538
1695
|
if start is not None:
|
|
@@ -1567,7 +1724,16 @@ class EODataAccessGateway:
|
|
|
1567
1724
|
logger.debug(
|
|
1568
1725
|
f"Fetching external product types sources to find {product_type} product type"
|
|
1569
1726
|
)
|
|
1570
|
-
|
|
1727
|
+
if provider:
|
|
1728
|
+
# Try to get specific product type from external provider
|
|
1729
|
+
self._fetch_external_product_type(provider, product_type)
|
|
1730
|
+
if (
|
|
1731
|
+
not provider
|
|
1732
|
+
or product_type
|
|
1733
|
+
not in self._plugins_manager.product_type_to_provider_config_map.keys()
|
|
1734
|
+
):
|
|
1735
|
+
# no provider or still not found -> fetch all external product types
|
|
1736
|
+
self.fetch_product_types_list()
|
|
1571
1737
|
|
|
1572
1738
|
preferred_provider = self.get_preferred_provider()[0]
|
|
1573
1739
|
|
|
@@ -1617,8 +1783,7 @@ class EODataAccessGateway:
|
|
|
1617
1783
|
for p in self.list_product_types(
|
|
1618
1784
|
search_plugin.provider, fetch_providers=False
|
|
1619
1785
|
)
|
|
1620
|
-
if p["
|
|
1621
|
-
or ("_id" in p and p["_id"] == product_type)
|
|
1786
|
+
if p["_id"] == product_type
|
|
1622
1787
|
][0],
|
|
1623
1788
|
**{"productType": product_type},
|
|
1624
1789
|
)
|
|
@@ -1638,10 +1803,10 @@ class EODataAccessGateway:
|
|
|
1638
1803
|
def _do_search(
|
|
1639
1804
|
self,
|
|
1640
1805
|
search_plugin: Union[Search, Api],
|
|
1641
|
-
count: bool =
|
|
1806
|
+
count: bool = False,
|
|
1642
1807
|
raise_errors: bool = False,
|
|
1643
1808
|
**kwargs: Any,
|
|
1644
|
-
) ->
|
|
1809
|
+
) -> SearchResult:
|
|
1645
1810
|
"""Internal method that performs a search on a given provider.
|
|
1646
1811
|
|
|
1647
1812
|
:param search_plugin: A search plugin
|
|
@@ -1653,14 +1818,16 @@ class EODataAccessGateway:
|
|
|
1653
1818
|
:type raise_errors: bool
|
|
1654
1819
|
:param kwargs: Some other criteria that will be used to do the search
|
|
1655
1820
|
:type kwargs: Any
|
|
1656
|
-
:returns: A collection of EO products matching the criteria
|
|
1657
|
-
number of results found if count is True else None
|
|
1821
|
+
:returns: A collection of EO products matching the criteria
|
|
1658
1822
|
:rtype: tuple(:class:`~eodag.api.search_result.SearchResult`, int or None)
|
|
1659
1823
|
"""
|
|
1660
1824
|
max_items_per_page = getattr(search_plugin.config, "pagination", {}).get(
|
|
1661
1825
|
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1662
1826
|
)
|
|
1663
|
-
if
|
|
1827
|
+
if (
|
|
1828
|
+
kwargs.get("items_per_page", DEFAULT_ITEMS_PER_PAGE) > max_items_per_page
|
|
1829
|
+
and max_items_per_page > 0
|
|
1830
|
+
):
|
|
1664
1831
|
logger.warning(
|
|
1665
1832
|
"EODAG believes that you might have asked for more products/items "
|
|
1666
1833
|
"than the maximum allowed by '%s': %s > %s. Try to lower "
|
|
@@ -1676,51 +1843,18 @@ class EODataAccessGateway:
|
|
|
1676
1843
|
can_authenticate = callable(getattr(auth_plugin, "authenticate", None))
|
|
1677
1844
|
|
|
1678
1845
|
results: List[EOProduct] = []
|
|
1679
|
-
total_results = 0
|
|
1846
|
+
total_results: Optional[int] = 0 if count else None
|
|
1680
1847
|
|
|
1681
1848
|
try:
|
|
1849
|
+
prep = PreparedSearch(count=count)
|
|
1682
1850
|
if need_auth and auth_plugin and can_authenticate:
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
page = kwargs.get("page")
|
|
1691
|
-
items_per_page = kwargs.get("items_per_page")
|
|
1692
|
-
if page and items_per_page and count:
|
|
1693
|
-
# Take into account the fact that a provider may not return the count of
|
|
1694
|
-
# products (in that case, fallback to using the length of the results it
|
|
1695
|
-
# returned and the page requested. As an example, check the result of
|
|
1696
|
-
# the following request (look for the value of properties.totalResults)
|
|
1697
|
-
# https://theia-landsat.cnes.fr/resto/api/collections/Landsat/search.json?
|
|
1698
|
-
# maxRecords=1&page=1
|
|
1699
|
-
if not nb_res:
|
|
1700
|
-
nb_res = len(res) * page
|
|
1701
|
-
|
|
1702
|
-
# Attempt to ensure a little bit more coherence. Some providers return
|
|
1703
|
-
# a fuzzy number of total results, meaning that you have to keep
|
|
1704
|
-
# requesting it until it has returned everything it has to know exactly
|
|
1705
|
-
# how many EO products they have in their stock. In that case, we need
|
|
1706
|
-
# to replace the returned number of results with the sum of the number
|
|
1707
|
-
# of items that were skipped so far and the length of the currently
|
|
1708
|
-
# retrieved items. We know there is an incoherence when the number of
|
|
1709
|
-
# skipped items is greater than the total number of items returned by
|
|
1710
|
-
# the plugin
|
|
1711
|
-
nb_skipped_items = items_per_page * (page - 1)
|
|
1712
|
-
nb_current_items = len(res)
|
|
1713
|
-
if nb_skipped_items > nb_res:
|
|
1714
|
-
if nb_res != 0:
|
|
1715
|
-
nb_res = nb_skipped_items + nb_current_items
|
|
1716
|
-
# This is for when the returned results is an empty list and the
|
|
1717
|
-
# number of results returned is incoherent with the observations.
|
|
1718
|
-
# In that case, we assume the total number of results is the number
|
|
1719
|
-
# of skipped results. By requesting a lower page than the current
|
|
1720
|
-
# one, a user can iteratively reach the last page of results for
|
|
1721
|
-
# these criteria on the provider.
|
|
1722
|
-
else:
|
|
1723
|
-
nb_res = nb_skipped_items
|
|
1851
|
+
prep.auth = auth_plugin.authenticate()
|
|
1852
|
+
|
|
1853
|
+
prep.auth_plugin = auth_plugin
|
|
1854
|
+
prep.page = kwargs.pop("page", None)
|
|
1855
|
+
prep.items_per_page = kwargs.pop("items_per_page", None)
|
|
1856
|
+
|
|
1857
|
+
res, nb_res = search_plugin.query(prep, **kwargs)
|
|
1724
1858
|
|
|
1725
1859
|
if not isinstance(res, list):
|
|
1726
1860
|
raise PluginImplementationError(
|
|
@@ -1744,7 +1878,6 @@ class EODataAccessGateway:
|
|
|
1744
1878
|
try:
|
|
1745
1879
|
guesses = self.guess_product_type(
|
|
1746
1880
|
**{
|
|
1747
|
-
# k:str(v) for k,v in eo_product.properties.items()
|
|
1748
1881
|
k: pattern.sub("", str(v).upper())
|
|
1749
1882
|
for k, v in eo_product.properties.items()
|
|
1750
1883
|
if k
|
|
@@ -1779,8 +1912,12 @@ class EODataAccessGateway:
|
|
|
1779
1912
|
eo_product.register_downloader(download_plugin, auth_plugin)
|
|
1780
1913
|
|
|
1781
1914
|
results.extend(res)
|
|
1782
|
-
total_results =
|
|
1783
|
-
|
|
1915
|
+
total_results = (
|
|
1916
|
+
None
|
|
1917
|
+
if (nb_res is None or total_results is None)
|
|
1918
|
+
else total_results + nb_res
|
|
1919
|
+
)
|
|
1920
|
+
if count and nb_res is not None:
|
|
1784
1921
|
logger.info(
|
|
1785
1922
|
"Found %s result(s) on provider '%s'",
|
|
1786
1923
|
nb_res,
|
|
@@ -1805,6 +1942,9 @@ class EODataAccessGateway:
|
|
|
1805
1942
|
if not raise_errors:
|
|
1806
1943
|
log_msg += " Raise verbosity of log messages for details"
|
|
1807
1944
|
logger.info(log_msg)
|
|
1945
|
+
# keep only the message from exception args
|
|
1946
|
+
if len(e.args) > 1:
|
|
1947
|
+
e.args = (e.args[0],)
|
|
1808
1948
|
if raise_errors:
|
|
1809
1949
|
# Raise the error, letting the application wrapping eodag know that
|
|
1810
1950
|
# something went bad. This way it will be able to decide what to do next
|
|
@@ -1815,7 +1955,7 @@ class EODataAccessGateway:
|
|
|
1815
1955
|
search_plugin.provider,
|
|
1816
1956
|
)
|
|
1817
1957
|
self.search_errors.add((search_plugin.provider, e))
|
|
1818
|
-
return SearchResult(results
|
|
1958
|
+
return SearchResult(results, total_results)
|
|
1819
1959
|
|
|
1820
1960
|
def crunch(self, results: SearchResult, **kwargs: Any) -> SearchResult:
|
|
1821
1961
|
"""Apply the filters given through the keyword arguments to the results
|
|
@@ -1865,7 +2005,7 @@ class EODataAccessGateway:
|
|
|
1865
2005
|
progress_callback: Optional[ProgressCallback] = None,
|
|
1866
2006
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
1867
2007
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1868
|
-
**kwargs:
|
|
2008
|
+
**kwargs: Unpack[DownloadConf],
|
|
1869
2009
|
) -> List[str]:
|
|
1870
2010
|
"""Download all products resulting from a search.
|
|
1871
2011
|
|
|
@@ -1978,6 +2118,7 @@ class EODataAccessGateway:
|
|
|
1978
2118
|
provider: Optional[str] = None,
|
|
1979
2119
|
productType: Optional[str] = None,
|
|
1980
2120
|
timeout: int = HTTP_REQ_TIMEOUT,
|
|
2121
|
+
ssl_verify: bool = True,
|
|
1981
2122
|
**kwargs: Any,
|
|
1982
2123
|
) -> SearchResult:
|
|
1983
2124
|
"""Loads STAC items from a geojson file / STAC catalog or collection, and convert to SearchResult.
|
|
@@ -2011,6 +2152,7 @@ class EODataAccessGateway:
|
|
|
2011
2152
|
recursive=recursive,
|
|
2012
2153
|
max_connections=max_connections,
|
|
2013
2154
|
timeout=timeout,
|
|
2155
|
+
ssl_verify=ssl_verify,
|
|
2014
2156
|
)
|
|
2015
2157
|
feature_collection = geojson.FeatureCollection(features)
|
|
2016
2158
|
|
|
@@ -2027,12 +2169,14 @@ class EODataAccessGateway:
|
|
|
2027
2169
|
)
|
|
2028
2170
|
)
|
|
2029
2171
|
|
|
2030
|
-
|
|
2172
|
+
search_result = self.search(
|
|
2173
|
+
productType=productType, provider=provider, **kwargs
|
|
2174
|
+
)
|
|
2031
2175
|
|
|
2032
2176
|
# restore plugin._request
|
|
2033
2177
|
plugin._request = plugin_request
|
|
2034
2178
|
|
|
2035
|
-
return
|
|
2179
|
+
return search_result
|
|
2036
2180
|
|
|
2037
2181
|
def download(
|
|
2038
2182
|
self,
|
|
@@ -2040,7 +2184,7 @@ class EODataAccessGateway:
|
|
|
2040
2184
|
progress_callback: Optional[ProgressCallback] = None,
|
|
2041
2185
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
2042
2186
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
2043
|
-
**kwargs:
|
|
2187
|
+
**kwargs: Unpack[DownloadConf],
|
|
2044
2188
|
) -> str:
|
|
2045
2189
|
"""Download a single product.
|
|
2046
2190
|
|
|
@@ -2120,9 +2264,7 @@ class EODataAccessGateway:
|
|
|
2120
2264
|
return self._plugins_manager.get_crunch_plugin(name, **plugin_conf)
|
|
2121
2265
|
|
|
2122
2266
|
def list_queryables(
|
|
2123
|
-
self,
|
|
2124
|
-
provider: Optional[str] = None,
|
|
2125
|
-
**kwargs: Any,
|
|
2267
|
+
self, provider: Optional[str] = None, **kwargs: Any
|
|
2126
2268
|
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
2127
2269
|
"""Fetch the queryable properties for a given product type and/or provider.
|
|
2128
2270
|
|
|
@@ -2131,113 +2273,53 @@ class EODataAccessGateway:
|
|
|
2131
2273
|
:param kwargs: additional filters for queryables (`productType` or other search
|
|
2132
2274
|
arguments)
|
|
2133
2275
|
:type kwargs: Any
|
|
2276
|
+
|
|
2277
|
+
:raises UnsupportedProductType: If the specified product type is not available for the
|
|
2278
|
+
provider.
|
|
2279
|
+
|
|
2134
2280
|
:returns: A dict containing the EODAG queryable properties, associating
|
|
2135
2281
|
parameters to their annotated type
|
|
2136
2282
|
:rtype: Dict[str, Annotated[Any, FieldInfo]]
|
|
2137
2283
|
"""
|
|
2138
|
-
# unknown product type
|
|
2139
2284
|
available_product_types = [
|
|
2140
|
-
pt["ID"]
|
|
2285
|
+
pt["ID"]
|
|
2286
|
+
for pt in self.list_product_types(provider=provider, fetch_providers=False)
|
|
2141
2287
|
]
|
|
2142
|
-
product_type = kwargs.get("productType"
|
|
2143
|
-
if product_type is not None and product_type not in available_product_types:
|
|
2144
|
-
self.fetch_product_types_list()
|
|
2145
|
-
|
|
2146
|
-
# dictionary of the queryable properties of the providers supporting the given product type
|
|
2147
|
-
providers_available_queryables: Dict[
|
|
2148
|
-
str, Dict[str, Annotated[Any, FieldInfo]]
|
|
2149
|
-
] = dict()
|
|
2150
|
-
|
|
2151
|
-
if provider is None and product_type is None:
|
|
2152
|
-
return model_fields_to_annotated(CommonQueryables.model_fields)
|
|
2153
|
-
elif provider is None:
|
|
2154
|
-
for plugin in self._plugins_manager.get_search_plugins(
|
|
2155
|
-
product_type, provider
|
|
2156
|
-
):
|
|
2157
|
-
providers_available_queryables[plugin.provider] = self.list_queryables(
|
|
2158
|
-
provider=plugin.provider, **kwargs
|
|
2159
|
-
)
|
|
2288
|
+
product_type = kwargs.get("productType")
|
|
2160
2289
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
queryables_keys & queryables.keys()
|
|
2166
|
-
if queryables_keys
|
|
2167
|
-
else queryables.keys()
|
|
2290
|
+
if product_type:
|
|
2291
|
+
try:
|
|
2292
|
+
kwargs["productType"] = product_type = self.get_product_type_from_alias(
|
|
2293
|
+
product_type
|
|
2168
2294
|
)
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
for k, v in providers_available_queryables.popitem()[1].items()
|
|
2172
|
-
if k in queryables_keys
|
|
2173
|
-
}
|
|
2295
|
+
except NoMatchingProductType as e:
|
|
2296
|
+
raise UnsupportedProductType(f"{product_type} is not available") from e
|
|
2174
2297
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
)
|
|
2298
|
+
if product_type and product_type not in available_product_types:
|
|
2299
|
+
self.fetch_product_types_list()
|
|
2178
2300
|
|
|
2179
|
-
|
|
2180
|
-
plugin = next(
|
|
2181
|
-
self._plugins_manager.get_search_plugins(product_type, provider)
|
|
2182
|
-
)
|
|
2183
|
-
except StopIteration:
|
|
2184
|
-
# return default queryables if no plugin is found
|
|
2301
|
+
if not provider and not product_type:
|
|
2185
2302
|
return model_fields_to_annotated(CommonQueryables.model_fields)
|
|
2186
2303
|
|
|
2187
|
-
|
|
2304
|
+
providers_queryables: Dict[str, Dict[str, Annotated[Any, FieldInfo]]] = {}
|
|
2188
2305
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
elif product_type and product_type not in plugin.config.products.keys():
|
|
2197
|
-
raise UnsupportedProductType(
|
|
2198
|
-
f"{product_type} is not available for provider {provider}"
|
|
2306
|
+
for plugin in self._plugins_manager.get_search_plugins(product_type, provider):
|
|
2307
|
+
if getattr(plugin.config, "need_auth", False) and (
|
|
2308
|
+
auth := self._plugins_manager.get_auth_plugin(plugin.provider)
|
|
2309
|
+
):
|
|
2310
|
+
plugin.auth = auth.authenticate()
|
|
2311
|
+
providers_queryables[plugin.provider] = plugin.list_queryables(
|
|
2312
|
+
filters=kwargs, product_type=product_type
|
|
2199
2313
|
)
|
|
2200
2314
|
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
# product_type-specific metadata-mapping
|
|
2204
|
-
metadata_mapping.update(
|
|
2205
|
-
getattr(plugin.config, "products", {})
|
|
2206
|
-
.get(product_type, {})
|
|
2207
|
-
.get("metadata_mapping", {})
|
|
2315
|
+
queryable_keys: Set[str] = set.intersection( # type: ignore
|
|
2316
|
+
*[set(q.keys()) for q in providers_queryables.values()]
|
|
2208
2317
|
)
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
default_values.pop("metadata_mapping", None)
|
|
2215
|
-
kwargs = dict(default_values, **kwargs)
|
|
2216
|
-
|
|
2217
|
-
# remove not mapped parameters or non-queryables
|
|
2218
|
-
for param in list(metadata_mapping.keys()):
|
|
2219
|
-
if NOT_MAPPED in metadata_mapping[param] or not isinstance(
|
|
2220
|
-
metadata_mapping[param], list
|
|
2221
|
-
):
|
|
2222
|
-
del metadata_mapping[param]
|
|
2223
|
-
|
|
2224
|
-
for key, value in all_queryables.items():
|
|
2225
|
-
annotated_args = get_args(value)
|
|
2226
|
-
if len(annotated_args) < 1:
|
|
2227
|
-
continue
|
|
2228
|
-
field_info = annotated_args[1]
|
|
2229
|
-
if not isinstance(field_info, FieldInfo):
|
|
2230
|
-
continue
|
|
2231
|
-
if key in kwargs:
|
|
2232
|
-
field_info.default = kwargs[key]
|
|
2233
|
-
if field_info.is_required() or (
|
|
2234
|
-
(field_info.alias or key) in metadata_mapping
|
|
2235
|
-
):
|
|
2236
|
-
providers_available_queryables[plugin.provider][key] = value
|
|
2237
|
-
|
|
2238
|
-
provider_queryables = plugin.discover_queryables(**kwargs) or dict()
|
|
2239
|
-
# use EODAG configured queryables by default
|
|
2240
|
-
provider_queryables.update(providers_available_queryables[provider])
|
|
2318
|
+
queryables = {
|
|
2319
|
+
k: v
|
|
2320
|
+
for k, v in list(providers_queryables.values())[0].items()
|
|
2321
|
+
if k in queryable_keys
|
|
2322
|
+
}
|
|
2241
2323
|
|
|
2242
2324
|
# always keep at least CommonQueryables
|
|
2243
2325
|
common_queryables = copy_deepcopy(CommonQueryables.model_fields)
|
|
@@ -2245,6 +2327,39 @@ class EODataAccessGateway:
|
|
|
2245
2327
|
if key in kwargs:
|
|
2246
2328
|
queryable.default = kwargs[key]
|
|
2247
2329
|
|
|
2248
|
-
|
|
2330
|
+
queryables.update(model_fields_to_annotated(common_queryables))
|
|
2331
|
+
|
|
2332
|
+
return queryables
|
|
2333
|
+
|
|
2334
|
+
def available_sortables(self) -> Dict[str, Optional[ProviderSortables]]:
|
|
2335
|
+
"""For each provider, gives its available sortable parameter(s) and its maximum
|
|
2336
|
+
number of them if it supports the sorting feature, otherwise gives None.
|
|
2249
2337
|
|
|
2250
|
-
|
|
2338
|
+
:returns: A dictionnary with providers as keys and dictionnary of sortable parameter(s) and
|
|
2339
|
+
its (their) maximum number as value(s).
|
|
2340
|
+
:rtype: dict
|
|
2341
|
+
:raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
|
|
2342
|
+
"""
|
|
2343
|
+
sortables: Dict[str, Optional[ProviderSortables]] = {}
|
|
2344
|
+
provider_search_plugins = self._plugins_manager.get_search_plugins()
|
|
2345
|
+
for provider_search_plugin in provider_search_plugins:
|
|
2346
|
+
provider = provider_search_plugin.provider
|
|
2347
|
+
if not hasattr(provider_search_plugin.config, "sort"):
|
|
2348
|
+
sortables[provider] = None
|
|
2349
|
+
continue
|
|
2350
|
+
sortable_params = list(
|
|
2351
|
+
provider_search_plugin.config.sort.get("sort_param_mapping", {}).keys()
|
|
2352
|
+
)
|
|
2353
|
+
if not provider_search_plugin.config.sort.get("max_sort_params"):
|
|
2354
|
+
sortables[provider] = {
|
|
2355
|
+
"sortables": sortable_params,
|
|
2356
|
+
"max_sort_params": None,
|
|
2357
|
+
}
|
|
2358
|
+
continue
|
|
2359
|
+
sortables[provider] = {
|
|
2360
|
+
"sortables": sortable_params,
|
|
2361
|
+
"max_sort_params": provider_search_plugin.config.sort[
|
|
2362
|
+
"max_sort_params"
|
|
2363
|
+
],
|
|
2364
|
+
}
|
|
2365
|
+
return sortables
|