eodag 2.12.1__py3-none-any.whl → 3.0.0b2__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 +440 -321
- eodag/api/product/__init__.py +5 -1
- eodag/api/product/_assets.py +57 -2
- eodag/api/product/_product.py +89 -68
- eodag/api/product/metadata_mapping.py +181 -66
- eodag/api/search_result.py +48 -1
- eodag/cli.py +20 -6
- eodag/config.py +95 -6
- eodag/plugins/apis/base.py +8 -165
- 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 +74 -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/repr.py +113 -0
- eodag/utils/requests.py +138 -0
- eodag/utils/rest.py +104 -0
- eodag/utils/stac_reader.py +100 -16
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/METADATA +65 -44
- eodag-3.0.0b2.dist-info/RECORD +110 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/WHEEL +1 -1
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/entry_points.txt +6 -5
- eodag/plugins/apis/cds.py +0 -540
- eodag/rest/utils.py +0 -1133
- eodag-2.12.1.dist-info/RECORD +0 -94
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.dist-info}/LICENSE +0 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0b2.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
|
|
@@ -1338,8 +1445,12 @@ class EODataAccessGateway:
|
|
|
1338
1445
|
start=start, end=end, geom=geom, locations=locations, **kwargs
|
|
1339
1446
|
)
|
|
1340
1447
|
for i, search_plugin in enumerate(search_plugins):
|
|
1341
|
-
itp =
|
|
1342
|
-
|
|
1448
|
+
itp = (
|
|
1449
|
+
items_per_page
|
|
1450
|
+
or getattr(search_plugin.config, "pagination", {}).get(
|
|
1451
|
+
"max_items_per_page"
|
|
1452
|
+
)
|
|
1453
|
+
or DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1343
1454
|
)
|
|
1344
1455
|
logger.debug(
|
|
1345
1456
|
"Searching for all the products with provider %s and a maximum of %s "
|
|
@@ -1385,7 +1496,7 @@ class EODataAccessGateway:
|
|
|
1385
1496
|
|
|
1386
1497
|
def _search_by_id(
|
|
1387
1498
|
self, uid: str, provider: Optional[str] = None, **kwargs: Any
|
|
1388
|
-
) ->
|
|
1499
|
+
) -> SearchResult:
|
|
1389
1500
|
"""Internal method that enables searching a product by its id.
|
|
1390
1501
|
|
|
1391
1502
|
Keeps requesting providers until a result matching the id is supplied. The
|
|
@@ -1405,9 +1516,8 @@ class EODataAccessGateway:
|
|
|
1405
1516
|
:type provider: str
|
|
1406
1517
|
:param kwargs: Search criteria to help finding the right product
|
|
1407
1518
|
: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)
|
|
1519
|
+
:returns: A search result with one EO product or None at all
|
|
1520
|
+
:rtype: :class:`~eodag.api.search_result.SearchResult`
|
|
1411
1521
|
"""
|
|
1412
1522
|
product_type = kwargs.get("productType", None)
|
|
1413
1523
|
if product_type is not None:
|
|
@@ -1422,16 +1532,51 @@ class EODataAccessGateway:
|
|
|
1422
1532
|
# datacube query string
|
|
1423
1533
|
_dc_qs = kwargs.pop("_dc_qs", None)
|
|
1424
1534
|
|
|
1535
|
+
results = SearchResult([])
|
|
1536
|
+
|
|
1425
1537
|
for plugin in search_plugins:
|
|
1426
1538
|
logger.info(
|
|
1427
1539
|
"Searching product with id '%s' on provider: %s", uid, plugin.provider
|
|
1428
1540
|
)
|
|
1429
1541
|
logger.debug("Using plugin class for search: %s", plugin.__class__.__name__)
|
|
1430
1542
|
plugin.clear()
|
|
1543
|
+
|
|
1544
|
+
# adds maximal pagination to be able to do a search-all + crunch if more
|
|
1545
|
+
# than one result are returned
|
|
1546
|
+
items_per_page = plugin.config.pagination.get(
|
|
1547
|
+
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1548
|
+
)
|
|
1549
|
+
kwargs.update(items_per_page=items_per_page)
|
|
1431
1550
|
if isinstance(plugin, BuildPostSearchResult):
|
|
1432
|
-
|
|
1551
|
+
kwargs.update(
|
|
1552
|
+
items_per_page=items_per_page,
|
|
1553
|
+
_dc_qs=_dc_qs,
|
|
1554
|
+
)
|
|
1433
1555
|
else:
|
|
1434
|
-
|
|
1556
|
+
kwargs.update(
|
|
1557
|
+
items_per_page=items_per_page,
|
|
1558
|
+
)
|
|
1559
|
+
|
|
1560
|
+
try:
|
|
1561
|
+
# if more than one results are found, try getting them all and then filter using crunch
|
|
1562
|
+
for page_results in self.search_iter_page_plugin(
|
|
1563
|
+
search_plugin=plugin,
|
|
1564
|
+
id=uid,
|
|
1565
|
+
**kwargs,
|
|
1566
|
+
):
|
|
1567
|
+
results.data.extend(page_results.data)
|
|
1568
|
+
except Exception:
|
|
1569
|
+
if kwargs.get("raise_errors"):
|
|
1570
|
+
raise
|
|
1571
|
+
continue
|
|
1572
|
+
|
|
1573
|
+
# try using crunch to get unique result
|
|
1574
|
+
if (
|
|
1575
|
+
len(results) > 1
|
|
1576
|
+
and len(filtered := results.filter_property(id=uid)) == 1
|
|
1577
|
+
):
|
|
1578
|
+
results = filtered
|
|
1579
|
+
|
|
1435
1580
|
if len(results) == 1:
|
|
1436
1581
|
if not results[0].product_type:
|
|
1437
1582
|
# guess product type from properties
|
|
@@ -1439,20 +1584,36 @@ class EODataAccessGateway:
|
|
|
1439
1584
|
results[0].product_type = guesses[0]
|
|
1440
1585
|
# reset driver
|
|
1441
1586
|
results[0].driver = results[0].get_driver()
|
|
1442
|
-
|
|
1587
|
+
results.number_matched = 1
|
|
1588
|
+
return results
|
|
1443
1589
|
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
1590
|
logger.info(
|
|
1452
1591
|
"Several products found for this id (%s). You may try searching using more selective criteria.",
|
|
1453
1592
|
results,
|
|
1454
1593
|
)
|
|
1455
|
-
return SearchResult([]
|
|
1594
|
+
return SearchResult([], 0)
|
|
1595
|
+
|
|
1596
|
+
def _fetch_external_product_type(self, provider: str, product_type: str):
|
|
1597
|
+
plugins = self._plugins_manager.get_search_plugins(provider=provider)
|
|
1598
|
+
plugin = next(plugins)
|
|
1599
|
+
|
|
1600
|
+
kwargs: Dict[str, Any] = {"productType": product_type}
|
|
1601
|
+
|
|
1602
|
+
# append auth if needed
|
|
1603
|
+
if getattr(plugin.config, "need_auth", False):
|
|
1604
|
+
auth_plugin = self._plugins_manager.get_auth_plugin(plugin.provider)
|
|
1605
|
+
if auth_plugin and callable(getattr(auth_plugin, "authenticate", None)):
|
|
1606
|
+
try:
|
|
1607
|
+
kwargs["auth"] = auth_plugin.authenticate()
|
|
1608
|
+
except (AuthenticationError, MisconfiguredError) as e:
|
|
1609
|
+
logger.warning(f"Could not authenticate on {provider}: {str(e)}")
|
|
1610
|
+
else:
|
|
1611
|
+
logger.warning(
|
|
1612
|
+
f"Could not authenticate on {provider} using {auth_plugin} plugin"
|
|
1613
|
+
)
|
|
1614
|
+
|
|
1615
|
+
product_type_config = plugin.discover_product_types(**kwargs)
|
|
1616
|
+
self.update_product_types_list({provider: product_type_config})
|
|
1456
1617
|
|
|
1457
1618
|
def _prepare_search(
|
|
1458
1619
|
self,
|
|
@@ -1532,7 +1693,7 @@ class EODataAccessGateway:
|
|
|
1532
1693
|
try:
|
|
1533
1694
|
product_type = self.get_product_type_from_alias(product_type)
|
|
1534
1695
|
except NoMatchingProductType:
|
|
1535
|
-
logger.
|
|
1696
|
+
logger.info("unknown product type " + product_type)
|
|
1536
1697
|
kwargs["productType"] = product_type
|
|
1537
1698
|
|
|
1538
1699
|
if start is not None:
|
|
@@ -1567,7 +1728,16 @@ class EODataAccessGateway:
|
|
|
1567
1728
|
logger.debug(
|
|
1568
1729
|
f"Fetching external product types sources to find {product_type} product type"
|
|
1569
1730
|
)
|
|
1570
|
-
|
|
1731
|
+
if provider:
|
|
1732
|
+
# Try to get specific product type from external provider
|
|
1733
|
+
self._fetch_external_product_type(provider, product_type)
|
|
1734
|
+
if (
|
|
1735
|
+
not provider
|
|
1736
|
+
or product_type
|
|
1737
|
+
not in self._plugins_manager.product_type_to_provider_config_map.keys()
|
|
1738
|
+
):
|
|
1739
|
+
# no provider or still not found -> fetch all external product types
|
|
1740
|
+
self.fetch_product_types_list()
|
|
1571
1741
|
|
|
1572
1742
|
preferred_provider = self.get_preferred_provider()[0]
|
|
1573
1743
|
|
|
@@ -1617,8 +1787,7 @@ class EODataAccessGateway:
|
|
|
1617
1787
|
for p in self.list_product_types(
|
|
1618
1788
|
search_plugin.provider, fetch_providers=False
|
|
1619
1789
|
)
|
|
1620
|
-
if p["
|
|
1621
|
-
or ("_id" in p and p["_id"] == product_type)
|
|
1790
|
+
if p["_id"] == product_type
|
|
1622
1791
|
][0],
|
|
1623
1792
|
**{"productType": product_type},
|
|
1624
1793
|
)
|
|
@@ -1638,10 +1807,10 @@ class EODataAccessGateway:
|
|
|
1638
1807
|
def _do_search(
|
|
1639
1808
|
self,
|
|
1640
1809
|
search_plugin: Union[Search, Api],
|
|
1641
|
-
count: bool =
|
|
1810
|
+
count: bool = False,
|
|
1642
1811
|
raise_errors: bool = False,
|
|
1643
1812
|
**kwargs: Any,
|
|
1644
|
-
) ->
|
|
1813
|
+
) -> SearchResult:
|
|
1645
1814
|
"""Internal method that performs a search on a given provider.
|
|
1646
1815
|
|
|
1647
1816
|
:param search_plugin: A search plugin
|
|
@@ -1653,14 +1822,16 @@ class EODataAccessGateway:
|
|
|
1653
1822
|
:type raise_errors: bool
|
|
1654
1823
|
:param kwargs: Some other criteria that will be used to do the search
|
|
1655
1824
|
:type kwargs: Any
|
|
1656
|
-
:returns: A collection of EO products matching the criteria
|
|
1657
|
-
number of results found if count is True else None
|
|
1825
|
+
:returns: A collection of EO products matching the criteria
|
|
1658
1826
|
:rtype: tuple(:class:`~eodag.api.search_result.SearchResult`, int or None)
|
|
1659
1827
|
"""
|
|
1660
1828
|
max_items_per_page = getattr(search_plugin.config, "pagination", {}).get(
|
|
1661
1829
|
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1662
1830
|
)
|
|
1663
|
-
if
|
|
1831
|
+
if (
|
|
1832
|
+
kwargs.get("items_per_page", DEFAULT_ITEMS_PER_PAGE) > max_items_per_page
|
|
1833
|
+
and max_items_per_page > 0
|
|
1834
|
+
):
|
|
1664
1835
|
logger.warning(
|
|
1665
1836
|
"EODAG believes that you might have asked for more products/items "
|
|
1666
1837
|
"than the maximum allowed by '%s': %s > %s. Try to lower "
|
|
@@ -1676,51 +1847,18 @@ class EODataAccessGateway:
|
|
|
1676
1847
|
can_authenticate = callable(getattr(auth_plugin, "authenticate", None))
|
|
1677
1848
|
|
|
1678
1849
|
results: List[EOProduct] = []
|
|
1679
|
-
total_results = 0
|
|
1850
|
+
total_results: Optional[int] = 0 if count else None
|
|
1680
1851
|
|
|
1681
1852
|
try:
|
|
1853
|
+
prep = PreparedSearch(count=count)
|
|
1682
1854
|
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
|
|
1855
|
+
prep.auth = auth_plugin.authenticate()
|
|
1856
|
+
|
|
1857
|
+
prep.auth_plugin = auth_plugin
|
|
1858
|
+
prep.page = kwargs.pop("page", None)
|
|
1859
|
+
prep.items_per_page = kwargs.pop("items_per_page", None)
|
|
1860
|
+
|
|
1861
|
+
res, nb_res = search_plugin.query(prep, **kwargs)
|
|
1724
1862
|
|
|
1725
1863
|
if not isinstance(res, list):
|
|
1726
1864
|
raise PluginImplementationError(
|
|
@@ -1744,7 +1882,6 @@ class EODataAccessGateway:
|
|
|
1744
1882
|
try:
|
|
1745
1883
|
guesses = self.guess_product_type(
|
|
1746
1884
|
**{
|
|
1747
|
-
# k:str(v) for k,v in eo_product.properties.items()
|
|
1748
1885
|
k: pattern.sub("", str(v).upper())
|
|
1749
1886
|
for k, v in eo_product.properties.items()
|
|
1750
1887
|
if k
|
|
@@ -1779,8 +1916,12 @@ class EODataAccessGateway:
|
|
|
1779
1916
|
eo_product.register_downloader(download_plugin, auth_plugin)
|
|
1780
1917
|
|
|
1781
1918
|
results.extend(res)
|
|
1782
|
-
total_results =
|
|
1783
|
-
|
|
1919
|
+
total_results = (
|
|
1920
|
+
None
|
|
1921
|
+
if (nb_res is None or total_results is None)
|
|
1922
|
+
else total_results + nb_res
|
|
1923
|
+
)
|
|
1924
|
+
if count and nb_res is not None:
|
|
1784
1925
|
logger.info(
|
|
1785
1926
|
"Found %s result(s) on provider '%s'",
|
|
1786
1927
|
nb_res,
|
|
@@ -1805,6 +1946,9 @@ class EODataAccessGateway:
|
|
|
1805
1946
|
if not raise_errors:
|
|
1806
1947
|
log_msg += " Raise verbosity of log messages for details"
|
|
1807
1948
|
logger.info(log_msg)
|
|
1949
|
+
# keep only the message from exception args
|
|
1950
|
+
if len(e.args) > 1:
|
|
1951
|
+
e.args = (e.args[0],)
|
|
1808
1952
|
if raise_errors:
|
|
1809
1953
|
# Raise the error, letting the application wrapping eodag know that
|
|
1810
1954
|
# something went bad. This way it will be able to decide what to do next
|
|
@@ -1815,7 +1959,7 @@ class EODataAccessGateway:
|
|
|
1815
1959
|
search_plugin.provider,
|
|
1816
1960
|
)
|
|
1817
1961
|
self.search_errors.add((search_plugin.provider, e))
|
|
1818
|
-
return SearchResult(results
|
|
1962
|
+
return SearchResult(results, total_results)
|
|
1819
1963
|
|
|
1820
1964
|
def crunch(self, results: SearchResult, **kwargs: Any) -> SearchResult:
|
|
1821
1965
|
"""Apply the filters given through the keyword arguments to the results
|
|
@@ -1865,7 +2009,7 @@ class EODataAccessGateway:
|
|
|
1865
2009
|
progress_callback: Optional[ProgressCallback] = None,
|
|
1866
2010
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
1867
2011
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1868
|
-
**kwargs:
|
|
2012
|
+
**kwargs: Unpack[DownloadConf],
|
|
1869
2013
|
) -> List[str]:
|
|
1870
2014
|
"""Download all products resulting from a search.
|
|
1871
2015
|
|
|
@@ -1978,6 +2122,7 @@ class EODataAccessGateway:
|
|
|
1978
2122
|
provider: Optional[str] = None,
|
|
1979
2123
|
productType: Optional[str] = None,
|
|
1980
2124
|
timeout: int = HTTP_REQ_TIMEOUT,
|
|
2125
|
+
ssl_verify: bool = True,
|
|
1981
2126
|
**kwargs: Any,
|
|
1982
2127
|
) -> SearchResult:
|
|
1983
2128
|
"""Loads STAC items from a geojson file / STAC catalog or collection, and convert to SearchResult.
|
|
@@ -2011,6 +2156,7 @@ class EODataAccessGateway:
|
|
|
2011
2156
|
recursive=recursive,
|
|
2012
2157
|
max_connections=max_connections,
|
|
2013
2158
|
timeout=timeout,
|
|
2159
|
+
ssl_verify=ssl_verify,
|
|
2014
2160
|
)
|
|
2015
2161
|
feature_collection = geojson.FeatureCollection(features)
|
|
2016
2162
|
|
|
@@ -2027,12 +2173,14 @@ class EODataAccessGateway:
|
|
|
2027
2173
|
)
|
|
2028
2174
|
)
|
|
2029
2175
|
|
|
2030
|
-
|
|
2176
|
+
search_result = self.search(
|
|
2177
|
+
productType=productType, provider=provider, **kwargs
|
|
2178
|
+
)
|
|
2031
2179
|
|
|
2032
2180
|
# restore plugin._request
|
|
2033
2181
|
plugin._request = plugin_request
|
|
2034
2182
|
|
|
2035
|
-
return
|
|
2183
|
+
return search_result
|
|
2036
2184
|
|
|
2037
2185
|
def download(
|
|
2038
2186
|
self,
|
|
@@ -2040,7 +2188,7 @@ class EODataAccessGateway:
|
|
|
2040
2188
|
progress_callback: Optional[ProgressCallback] = None,
|
|
2041
2189
|
wait: int = DEFAULT_DOWNLOAD_WAIT,
|
|
2042
2190
|
timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
2043
|
-
**kwargs:
|
|
2191
|
+
**kwargs: Unpack[DownloadConf],
|
|
2044
2192
|
) -> str:
|
|
2045
2193
|
"""Download a single product.
|
|
2046
2194
|
|
|
@@ -2120,9 +2268,7 @@ class EODataAccessGateway:
|
|
|
2120
2268
|
return self._plugins_manager.get_crunch_plugin(name, **plugin_conf)
|
|
2121
2269
|
|
|
2122
2270
|
def list_queryables(
|
|
2123
|
-
self,
|
|
2124
|
-
provider: Optional[str] = None,
|
|
2125
|
-
**kwargs: Any,
|
|
2271
|
+
self, provider: Optional[str] = None, **kwargs: Any
|
|
2126
2272
|
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
2127
2273
|
"""Fetch the queryable properties for a given product type and/or provider.
|
|
2128
2274
|
|
|
@@ -2131,113 +2277,53 @@ class EODataAccessGateway:
|
|
|
2131
2277
|
:param kwargs: additional filters for queryables (`productType` or other search
|
|
2132
2278
|
arguments)
|
|
2133
2279
|
:type kwargs: Any
|
|
2280
|
+
|
|
2281
|
+
:raises UnsupportedProductType: If the specified product type is not available for the
|
|
2282
|
+
provider.
|
|
2283
|
+
|
|
2134
2284
|
:returns: A dict containing the EODAG queryable properties, associating
|
|
2135
2285
|
parameters to their annotated type
|
|
2136
2286
|
:rtype: Dict[str, Annotated[Any, FieldInfo]]
|
|
2137
2287
|
"""
|
|
2138
|
-
# unknown product type
|
|
2139
2288
|
available_product_types = [
|
|
2140
|
-
pt["ID"]
|
|
2289
|
+
pt["ID"]
|
|
2290
|
+
for pt in self.list_product_types(provider=provider, fetch_providers=False)
|
|
2141
2291
|
]
|
|
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
|
-
)
|
|
2292
|
+
product_type = kwargs.get("productType")
|
|
2160
2293
|
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
queryables_keys & queryables.keys()
|
|
2166
|
-
if queryables_keys
|
|
2167
|
-
else queryables.keys()
|
|
2294
|
+
if product_type:
|
|
2295
|
+
try:
|
|
2296
|
+
kwargs["productType"] = product_type = self.get_product_type_from_alias(
|
|
2297
|
+
product_type
|
|
2168
2298
|
)
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
for k, v in providers_available_queryables.popitem()[1].items()
|
|
2172
|
-
if k in queryables_keys
|
|
2173
|
-
}
|
|
2299
|
+
except NoMatchingProductType as e:
|
|
2300
|
+
raise UnsupportedProductType(f"{product_type} is not available") from e
|
|
2174
2301
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
)
|
|
2302
|
+
if product_type and product_type not in available_product_types:
|
|
2303
|
+
self.fetch_product_types_list()
|
|
2178
2304
|
|
|
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
|
|
2305
|
+
if not provider and not product_type:
|
|
2185
2306
|
return model_fields_to_annotated(CommonQueryables.model_fields)
|
|
2186
2307
|
|
|
2187
|
-
|
|
2308
|
+
providers_queryables: Dict[str, Dict[str, Annotated[Any, FieldInfo]]] = {}
|
|
2188
2309
|
|
|
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}"
|
|
2310
|
+
for plugin in self._plugins_manager.get_search_plugins(product_type, provider):
|
|
2311
|
+
if getattr(plugin.config, "need_auth", False) and (
|
|
2312
|
+
auth := self._plugins_manager.get_auth_plugin(plugin.provider)
|
|
2313
|
+
):
|
|
2314
|
+
plugin.auth = auth.authenticate()
|
|
2315
|
+
providers_queryables[plugin.provider] = plugin.list_queryables(
|
|
2316
|
+
filters=kwargs, product_type=product_type
|
|
2199
2317
|
)
|
|
2200
2318
|
|
|
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", {})
|
|
2319
|
+
queryable_keys: Set[str] = set.intersection( # type: ignore
|
|
2320
|
+
*[set(q.keys()) for q in providers_queryables.values()]
|
|
2208
2321
|
)
|
|
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])
|
|
2322
|
+
queryables = {
|
|
2323
|
+
k: v
|
|
2324
|
+
for k, v in list(providers_queryables.values())[0].items()
|
|
2325
|
+
if k in queryable_keys
|
|
2326
|
+
}
|
|
2241
2327
|
|
|
2242
2328
|
# always keep at least CommonQueryables
|
|
2243
2329
|
common_queryables = copy_deepcopy(CommonQueryables.model_fields)
|
|
@@ -2245,6 +2331,39 @@ class EODataAccessGateway:
|
|
|
2245
2331
|
if key in kwargs:
|
|
2246
2332
|
queryable.default = kwargs[key]
|
|
2247
2333
|
|
|
2248
|
-
|
|
2334
|
+
queryables.update(model_fields_to_annotated(common_queryables))
|
|
2335
|
+
|
|
2336
|
+
return queryables
|
|
2337
|
+
|
|
2338
|
+
def available_sortables(self) -> Dict[str, Optional[ProviderSortables]]:
|
|
2339
|
+
"""For each provider, gives its available sortable parameter(s) and its maximum
|
|
2340
|
+
number of them if it supports the sorting feature, otherwise gives None.
|
|
2249
2341
|
|
|
2250
|
-
|
|
2342
|
+
:returns: A dictionnary with providers as keys and dictionnary of sortable parameter(s) and
|
|
2343
|
+
its (their) maximum number as value(s).
|
|
2344
|
+
:rtype: dict
|
|
2345
|
+
:raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
|
|
2346
|
+
"""
|
|
2347
|
+
sortables: Dict[str, Optional[ProviderSortables]] = {}
|
|
2348
|
+
provider_search_plugins = self._plugins_manager.get_search_plugins()
|
|
2349
|
+
for provider_search_plugin in provider_search_plugins:
|
|
2350
|
+
provider = provider_search_plugin.provider
|
|
2351
|
+
if not hasattr(provider_search_plugin.config, "sort"):
|
|
2352
|
+
sortables[provider] = None
|
|
2353
|
+
continue
|
|
2354
|
+
sortable_params = list(
|
|
2355
|
+
provider_search_plugin.config.sort.get("sort_param_mapping", {}).keys()
|
|
2356
|
+
)
|
|
2357
|
+
if not provider_search_plugin.config.sort.get("max_sort_params"):
|
|
2358
|
+
sortables[provider] = {
|
|
2359
|
+
"sortables": sortable_params,
|
|
2360
|
+
"max_sort_params": None,
|
|
2361
|
+
}
|
|
2362
|
+
continue
|
|
2363
|
+
sortables[provider] = {
|
|
2364
|
+
"sortables": sortable_params,
|
|
2365
|
+
"max_sort_params": provider_search_plugin.config.sort[
|
|
2366
|
+
"max_sort_params"
|
|
2367
|
+
],
|
|
2368
|
+
}
|
|
2369
|
+
return sortables
|