eodag 3.0.0b3__py3-none-any.whl → 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eodag/api/core.py +347 -247
- eodag/api/product/_assets.py +44 -15
- eodag/api/product/_product.py +58 -47
- eodag/api/product/drivers/__init__.py +81 -4
- eodag/api/product/drivers/base.py +65 -4
- eodag/api/product/drivers/generic.py +65 -0
- eodag/api/product/drivers/sentinel1.py +97 -0
- eodag/api/product/drivers/sentinel2.py +95 -0
- eodag/api/product/metadata_mapping.py +129 -93
- eodag/api/search_result.py +28 -12
- eodag/cli.py +61 -24
- eodag/config.py +457 -167
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +53 -23
- eodag/plugins/apis/usgs.py +41 -17
- eodag/plugins/authentication/aws_auth.py +30 -18
- eodag/plugins/authentication/base.py +14 -3
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +14 -6
- eodag/plugins/authentication/keycloak.py +44 -25
- eodag/plugins/authentication/oauth.py +18 -4
- eodag/plugins/authentication/openid_connect.py +192 -171
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +22 -5
- eodag/plugins/authentication/token.py +95 -17
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +8 -5
- eodag/plugins/crunch/filter_date.py +9 -6
- eodag/plugins/crunch/filter_latest_intersect.py +9 -8
- eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
- eodag/plugins/crunch/filter_overlap.py +9 -11
- eodag/plugins/crunch/filter_property.py +10 -10
- eodag/plugins/download/aws.py +181 -105
- eodag/plugins/download/base.py +49 -67
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +247 -223
- eodag/plugins/download/s3rest.py +29 -28
- eodag/plugins/manager.py +176 -41
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +123 -60
- eodag/plugins/search/build_search_result.py +1046 -355
- eodag/plugins/search/cop_marine.py +132 -39
- eodag/plugins/search/creodias_s3.py +19 -68
- eodag/plugins/search/csw.py +48 -8
- eodag/plugins/search/data_request_search.py +124 -23
- eodag/plugins/search/qssearch.py +531 -310
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +23 -24
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1295 -355
- eodag/resources/providers.yml +1819 -3010
- eodag/resources/stac.yml +3 -163
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +115 -99
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -4
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +157 -117
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +57 -339
- eodag/rest/stac.py +133 -581
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +41 -30
- eodag/rest/types/queryables.py +42 -32
- eodag/rest/types/stac_search.py +15 -16
- eodag/rest/utils/__init__.py +14 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +153 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +4 -4
- eodag/types/queryables.py +183 -73
- eodag/types/search_args.py +6 -6
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +228 -106
- eodag/utils/exceptions.py +47 -26
- eodag/utils/import_system.py +2 -2
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -15
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +11 -11
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/api/core.py
CHANGED
|
@@ -17,29 +17,34 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import datetime
|
|
20
21
|
import logging
|
|
21
22
|
import os
|
|
22
23
|
import re
|
|
23
24
|
import shutil
|
|
24
25
|
import tempfile
|
|
26
|
+
from importlib.metadata import version
|
|
27
|
+
from importlib.resources import files as res_files
|
|
25
28
|
from operator import itemgetter
|
|
26
|
-
from typing import TYPE_CHECKING, Any,
|
|
29
|
+
from typing import TYPE_CHECKING, Any, Iterator, Optional, Union
|
|
27
30
|
|
|
28
31
|
import geojson
|
|
29
|
-
import pkg_resources
|
|
30
32
|
import yaml.parser
|
|
31
|
-
from pkg_resources import resource_filename
|
|
32
|
-
from pydantic.fields import FieldInfo
|
|
33
33
|
from whoosh import analysis, fields
|
|
34
34
|
from whoosh.fields import Schema
|
|
35
|
-
from whoosh.index import
|
|
35
|
+
from whoosh.index import exists_in, open_dir
|
|
36
36
|
from whoosh.qparser import QueryParser
|
|
37
37
|
|
|
38
|
-
from eodag.api.product.metadata_mapping import
|
|
38
|
+
from eodag.api.product.metadata_mapping import (
|
|
39
|
+
ONLINE_STATUS,
|
|
40
|
+
mtd_cfg_as_conversion_and_querypath,
|
|
41
|
+
)
|
|
39
42
|
from eodag.api.search_result import SearchResult
|
|
40
43
|
from eodag.config import (
|
|
44
|
+
PLUGINS_TOPICS_KEYS,
|
|
41
45
|
PluginConfig,
|
|
42
46
|
SimpleYamlProxyConfig,
|
|
47
|
+
credentials_in_auth,
|
|
43
48
|
get_ext_product_types_conf,
|
|
44
49
|
load_default_config,
|
|
45
50
|
load_stac_provider_config,
|
|
@@ -48,13 +53,15 @@ from eodag.config import (
|
|
|
48
53
|
override_config_from_file,
|
|
49
54
|
override_config_from_mapping,
|
|
50
55
|
provider_config_init,
|
|
56
|
+
share_credentials,
|
|
51
57
|
)
|
|
52
58
|
from eodag.plugins.manager import PluginManager
|
|
53
59
|
from eodag.plugins.search import PreparedSearch
|
|
54
|
-
from eodag.plugins.search.build_search_result import
|
|
60
|
+
from eodag.plugins.search.build_search_result import MeteoblueSearch
|
|
61
|
+
from eodag.plugins.search.qssearch import PostJsonSearch
|
|
55
62
|
from eodag.types import model_fields_to_annotated
|
|
56
|
-
from eodag.types.queryables import CommonQueryables
|
|
57
|
-
from eodag.types.whoosh import EODAGQueryParser
|
|
63
|
+
from eodag.types.queryables import CommonQueryables, QueryablesDict
|
|
64
|
+
from eodag.types.whoosh import EODAGQueryParser, create_in
|
|
58
65
|
from eodag.utils import (
|
|
59
66
|
DEFAULT_DOWNLOAD_TIMEOUT,
|
|
60
67
|
DEFAULT_DOWNLOAD_WAIT,
|
|
@@ -65,7 +72,6 @@ from eodag.utils import (
|
|
|
65
72
|
HTTP_REQ_TIMEOUT,
|
|
66
73
|
MockResponse,
|
|
67
74
|
_deprecated,
|
|
68
|
-
copy_deepcopy,
|
|
69
75
|
get_geometry_from_various,
|
|
70
76
|
makedirs,
|
|
71
77
|
obj_md5sum,
|
|
@@ -76,7 +82,6 @@ from eodag.utils import (
|
|
|
76
82
|
from eodag.utils.exceptions import (
|
|
77
83
|
AuthenticationError,
|
|
78
84
|
EodagError,
|
|
79
|
-
MisconfiguredError,
|
|
80
85
|
NoMatchingProductType,
|
|
81
86
|
PluginImplementationError,
|
|
82
87
|
RequestError,
|
|
@@ -96,7 +101,7 @@ if TYPE_CHECKING:
|
|
|
96
101
|
from eodag.plugins.search.base import Search
|
|
97
102
|
from eodag.types import ProviderSortables
|
|
98
103
|
from eodag.types.download_args import DownloadConf
|
|
99
|
-
from eodag.utils import
|
|
104
|
+
from eodag.utils import DownloadedCallback, ProgressCallback, Unpack
|
|
100
105
|
|
|
101
106
|
logger = logging.getLogger("eodag.core")
|
|
102
107
|
|
|
@@ -114,8 +119,8 @@ class EODataAccessGateway:
|
|
|
114
119
|
user_conf_file_path: Optional[str] = None,
|
|
115
120
|
locations_conf_path: Optional[str] = None,
|
|
116
121
|
) -> None:
|
|
117
|
-
product_types_config_path =
|
|
118
|
-
"eodag"
|
|
122
|
+
product_types_config_path = os.getenv("EODAG_PRODUCT_TYPES_CFG_FILE") or str(
|
|
123
|
+
res_files("eodag") / "resources" / "product_types.yml"
|
|
119
124
|
)
|
|
120
125
|
self.product_types_config = SimpleYamlProxyConfig(product_types_config_path)
|
|
121
126
|
self.product_types_config_md5 = obj_md5sum(self.product_types_config.source)
|
|
@@ -156,8 +161,8 @@ class EODataAccessGateway:
|
|
|
156
161
|
user_conf_file_path = standard_configuration_path
|
|
157
162
|
if not os.path.isfile(standard_configuration_path):
|
|
158
163
|
shutil.copy(
|
|
159
|
-
|
|
160
|
-
"eodag"
|
|
164
|
+
str(
|
|
165
|
+
res_files("eodag") / "resources" / "user_conf_template.yml"
|
|
161
166
|
),
|
|
162
167
|
standard_configuration_path,
|
|
163
168
|
)
|
|
@@ -166,16 +171,21 @@ class EODataAccessGateway:
|
|
|
166
171
|
# Second level override: From environment variables
|
|
167
172
|
override_config_from_env(self.providers_config)
|
|
168
173
|
|
|
174
|
+
# share credentials between updated plugins confs
|
|
175
|
+
share_credentials(self.providers_config)
|
|
176
|
+
|
|
169
177
|
# init updated providers conf
|
|
170
|
-
stac_provider_config = load_stac_provider_config()
|
|
171
178
|
for provider in self.providers_config.keys():
|
|
172
|
-
provider_config_init(
|
|
179
|
+
provider_config_init(
|
|
180
|
+
self.providers_config[provider],
|
|
181
|
+
load_stac_provider_config(),
|
|
182
|
+
)
|
|
173
183
|
|
|
174
184
|
# re-build _plugins_manager using up-to-date providers_config
|
|
175
185
|
self._plugins_manager.rebuild(self.providers_config)
|
|
176
186
|
|
|
177
187
|
# store pruned providers configs
|
|
178
|
-
self._pruned_providers_config:
|
|
188
|
+
self._pruned_providers_config: dict[str, Any] = {}
|
|
179
189
|
# filter out providers needing auth that have no credentials set
|
|
180
190
|
self._prune_providers_list()
|
|
181
191
|
|
|
@@ -193,13 +203,13 @@ class EODataAccessGateway:
|
|
|
193
203
|
locations_conf_path = os.path.join(self.conf_dir, "locations.yml")
|
|
194
204
|
if not os.path.isfile(locations_conf_path):
|
|
195
205
|
# copy locations conf file and replace path example
|
|
196
|
-
locations_conf_template =
|
|
197
|
-
"eodag"
|
|
198
|
-
os.path.join("resources", "locations_conf_template.yml"),
|
|
206
|
+
locations_conf_template = str(
|
|
207
|
+
res_files("eodag") / "resources" / "locations_conf_template.yml"
|
|
199
208
|
)
|
|
200
|
-
with
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
with (
|
|
210
|
+
open(locations_conf_template) as infile,
|
|
211
|
+
open(locations_conf_path, "w") as outfile,
|
|
212
|
+
):
|
|
203
213
|
# The template contains paths in the form of:
|
|
204
214
|
# /path/to/locations/file.shp
|
|
205
215
|
path_template = "/path/to/locations/"
|
|
@@ -211,15 +221,14 @@ class EODataAccessGateway:
|
|
|
211
221
|
outfile.write(line)
|
|
212
222
|
# copy sample shapefile dir
|
|
213
223
|
shutil.copytree(
|
|
214
|
-
|
|
224
|
+
str(res_files("eodag") / "resources" / "shp"),
|
|
215
225
|
os.path.join(self.conf_dir, "shp"),
|
|
216
226
|
)
|
|
217
227
|
self.set_locations_conf(locations_conf_path)
|
|
218
|
-
self.search_errors: Set = set()
|
|
219
228
|
|
|
220
229
|
def get_version(self) -> str:
|
|
221
230
|
"""Get eodag package version"""
|
|
222
|
-
return
|
|
231
|
+
return version("eodag")
|
|
223
232
|
|
|
224
233
|
def build_index(self) -> None:
|
|
225
234
|
"""Build a `Whoosh <https://whoosh.readthedocs.io/en/latest/index.html>`_
|
|
@@ -296,13 +305,18 @@ class EODataAccessGateway:
|
|
|
296
305
|
product_type, **{"md5": self.product_types_config_md5}
|
|
297
306
|
)
|
|
298
307
|
# add to index
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
308
|
+
try:
|
|
309
|
+
ix_writer.add_document(
|
|
310
|
+
**{
|
|
311
|
+
k: v
|
|
312
|
+
for k, v in versioned_product_type.items()
|
|
313
|
+
if k in product_types_schema.names()
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
except TypeError as e:
|
|
317
|
+
logger.error(
|
|
318
|
+
f"Cannot write product type {product_type['ID']} into index. e={e} product_type={product_type}"
|
|
319
|
+
)
|
|
306
320
|
ix_writer.commit()
|
|
307
321
|
|
|
308
322
|
def set_preferred_provider(self, provider: str) -> None:
|
|
@@ -320,7 +334,7 @@ class EODataAccessGateway:
|
|
|
320
334
|
new_priority = max_priority + 1
|
|
321
335
|
self._plugins_manager.set_priority(provider, new_priority)
|
|
322
336
|
|
|
323
|
-
def get_preferred_provider(self) ->
|
|
337
|
+
def get_preferred_provider(self) -> tuple[str, int]:
|
|
324
338
|
"""Get the provider currently set as the preferred one for searching
|
|
325
339
|
products, along with its priority.
|
|
326
340
|
|
|
@@ -333,14 +347,24 @@ class EODataAccessGateway:
|
|
|
333
347
|
preferred, priority = max(providers_with_priority, key=itemgetter(1))
|
|
334
348
|
return preferred, priority
|
|
335
349
|
|
|
336
|
-
def update_providers_config(
|
|
350
|
+
def update_providers_config(
|
|
351
|
+
self,
|
|
352
|
+
yaml_conf: Optional[str] = None,
|
|
353
|
+
dict_conf: Optional[dict[str, Any]] = None,
|
|
354
|
+
) -> None:
|
|
337
355
|
"""Update providers configuration with given input.
|
|
338
356
|
Can be used to add a provider to existing configuration or update
|
|
339
357
|
an existing one.
|
|
340
358
|
|
|
341
359
|
:param yaml_conf: YAML formated provider configuration
|
|
360
|
+
:param dict_conf: provider configuration as dictionary in place of ``yaml_conf``
|
|
342
361
|
"""
|
|
343
|
-
|
|
362
|
+
if dict_conf is not None:
|
|
363
|
+
conf_update = dict_conf
|
|
364
|
+
elif yaml_conf is not None:
|
|
365
|
+
conf_update = yaml.safe_load(yaml_conf)
|
|
366
|
+
else:
|
|
367
|
+
return None
|
|
344
368
|
|
|
345
369
|
# restore the pruned configuration
|
|
346
370
|
for provider in list(self._pruned_providers_config.keys()):
|
|
@@ -355,9 +379,14 @@ class EODataAccessGateway:
|
|
|
355
379
|
|
|
356
380
|
override_config_from_mapping(self.providers_config, conf_update)
|
|
357
381
|
|
|
358
|
-
|
|
382
|
+
# share credentials between updated plugins confs
|
|
383
|
+
share_credentials(self.providers_config)
|
|
384
|
+
|
|
359
385
|
for provider in conf_update.keys():
|
|
360
|
-
provider_config_init(
|
|
386
|
+
provider_config_init(
|
|
387
|
+
self.providers_config[provider],
|
|
388
|
+
load_stac_provider_config(),
|
|
389
|
+
)
|
|
361
390
|
setattr(self.providers_config[provider], "product_types_fetched", False)
|
|
362
391
|
# re-create _plugins_manager using up-to-date providers_config
|
|
363
392
|
self._plugins_manager.build_product_type_to_provider_config_map()
|
|
@@ -367,12 +396,12 @@ class EODataAccessGateway:
|
|
|
367
396
|
name: str,
|
|
368
397
|
url: Optional[str] = None,
|
|
369
398
|
priority: Optional[int] = None,
|
|
370
|
-
search:
|
|
371
|
-
products:
|
|
399
|
+
search: dict[str, Any] = {"type": "StacSearch"},
|
|
400
|
+
products: dict[str, Any] = {
|
|
372
401
|
GENERIC_PRODUCT_TYPE: {"productType": "{productType}"}
|
|
373
402
|
},
|
|
374
|
-
download:
|
|
375
|
-
**kwargs:
|
|
403
|
+
download: dict[str, Any] = {"type": "HTTPDownload", "auth_error_code": 401},
|
|
404
|
+
**kwargs: dict[str, Any],
|
|
376
405
|
):
|
|
377
406
|
"""Adds a new provider.
|
|
378
407
|
|
|
@@ -391,7 +420,7 @@ class EODataAccessGateway:
|
|
|
391
420
|
:param download: Download :class:`~eodag.config.PluginConfig` mapping
|
|
392
421
|
:param kwargs: Additional :class:`~eodag.config.ProviderConfig` mapping
|
|
393
422
|
"""
|
|
394
|
-
conf_dict:
|
|
423
|
+
conf_dict: dict[str, Any] = {
|
|
395
424
|
name: {
|
|
396
425
|
"url": url,
|
|
397
426
|
"search": {"type": "StacSearch", **search},
|
|
@@ -419,14 +448,11 @@ class EODataAccessGateway:
|
|
|
419
448
|
|
|
420
449
|
# api plugin usage: remove unneeded search/download/auth plugin conf
|
|
421
450
|
if conf_dict[name].get("api"):
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
451
|
+
for k in PLUGINS_TOPICS_KEYS:
|
|
452
|
+
if k != "api":
|
|
453
|
+
conf_dict[name].pop(k, None)
|
|
425
454
|
|
|
426
|
-
|
|
427
|
-
provider_config_init(self.providers_config[name], load_stac_provider_config())
|
|
428
|
-
setattr(self.providers_config[name], "product_types_fetched", False)
|
|
429
|
-
self._plugins_manager.build_product_type_to_provider_config_map()
|
|
455
|
+
self.update_providers_config(dict_conf=conf_dict)
|
|
430
456
|
|
|
431
457
|
if priority is None:
|
|
432
458
|
self.set_preferred_provider(name)
|
|
@@ -452,12 +478,7 @@ class EODataAccessGateway:
|
|
|
452
478
|
|
|
453
479
|
# check authentication
|
|
454
480
|
if hasattr(conf, "api") and getattr(conf.api, "need_auth", False):
|
|
455
|
-
credentials_exist =
|
|
456
|
-
[
|
|
457
|
-
cred is not None
|
|
458
|
-
for cred in getattr(conf.api, "credentials", {}).values()
|
|
459
|
-
]
|
|
460
|
-
)
|
|
481
|
+
credentials_exist = credentials_in_auth(conf.api)
|
|
461
482
|
if not credentials_exist:
|
|
462
483
|
# credentials needed but not found
|
|
463
484
|
self._pruned_providers_config[provider] = self.providers_config.pop(
|
|
@@ -469,7 +490,7 @@ class EODataAccessGateway:
|
|
|
469
490
|
provider,
|
|
470
491
|
)
|
|
471
492
|
elif hasattr(conf, "search") and getattr(conf.search, "need_auth", False):
|
|
472
|
-
if not hasattr(conf, "auth"):
|
|
493
|
+
if not hasattr(conf, "auth") and not hasattr(conf, "search_auth"):
|
|
473
494
|
# credentials needed but no auth plugin was found
|
|
474
495
|
self._pruned_providers_config[provider] = self.providers_config.pop(
|
|
475
496
|
provider
|
|
@@ -480,11 +501,13 @@ class EODataAccessGateway:
|
|
|
480
501
|
provider,
|
|
481
502
|
)
|
|
482
503
|
continue
|
|
483
|
-
credentials_exist =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
504
|
+
credentials_exist = (
|
|
505
|
+
hasattr(conf, "search_auth")
|
|
506
|
+
and credentials_in_auth(conf.search_auth)
|
|
507
|
+
) or (
|
|
508
|
+
not hasattr(conf, "search_auth")
|
|
509
|
+
and hasattr(conf, "auth")
|
|
510
|
+
and credentials_in_auth(conf.auth)
|
|
488
511
|
)
|
|
489
512
|
if not credentials_exist:
|
|
490
513
|
# credentials needed but not found
|
|
@@ -541,7 +564,7 @@ class EODataAccessGateway:
|
|
|
541
564
|
main_locations_config = locations_config[main_key]
|
|
542
565
|
|
|
543
566
|
logger.info("Locations configuration loaded from %s" % locations_conf_path)
|
|
544
|
-
self.locations_config:
|
|
567
|
+
self.locations_config: list[dict[str, Any]] = main_locations_config
|
|
545
568
|
else:
|
|
546
569
|
logger.info(
|
|
547
570
|
"Could not load locations configuration from %s" % locations_conf_path
|
|
@@ -550,7 +573,7 @@ class EODataAccessGateway:
|
|
|
550
573
|
|
|
551
574
|
def list_product_types(
|
|
552
575
|
self, provider: Optional[str] = None, fetch_providers: bool = True
|
|
553
|
-
) ->
|
|
576
|
+
) -> list[dict[str, Any]]:
|
|
554
577
|
"""Lists supported product types.
|
|
555
578
|
|
|
556
579
|
:param provider: (optional) The name of a provider that must support the product
|
|
@@ -564,7 +587,7 @@ class EODataAccessGateway:
|
|
|
564
587
|
# First, update product types list if possible
|
|
565
588
|
self.fetch_product_types_list(provider=provider)
|
|
566
589
|
|
|
567
|
-
product_types:
|
|
590
|
+
product_types: list[dict[str, Any]] = []
|
|
568
591
|
|
|
569
592
|
providers_configs = (
|
|
570
593
|
list(self.providers_config.values())
|
|
@@ -599,21 +622,32 @@ class EODataAccessGateway:
|
|
|
599
622
|
def fetch_product_types_list(self, provider: Optional[str] = None) -> None:
|
|
600
623
|
"""Fetch product types list and update if needed
|
|
601
624
|
|
|
602
|
-
:param provider:
|
|
603
|
-
should be updated. Defaults to all providers (None value).
|
|
625
|
+
:param provider: The name of a provider or provider-group for which product types
|
|
626
|
+
list should be updated. Defaults to all providers (None value).
|
|
604
627
|
"""
|
|
628
|
+
providers_to_fetch = list(self.providers_config.keys())
|
|
629
|
+
# check if some providers are grouped under a group name which is not a provider name
|
|
605
630
|
if provider is not None and provider not in self.providers_config:
|
|
606
|
-
|
|
631
|
+
providers_to_fetch = [
|
|
632
|
+
p
|
|
633
|
+
for p, pconf in self.providers_config.items()
|
|
634
|
+
if provider == getattr(pconf, "group", None)
|
|
635
|
+
]
|
|
636
|
+
if providers_to_fetch:
|
|
637
|
+
logger.info(
|
|
638
|
+
f"Fetch product types for {provider} group: {', '.join(providers_to_fetch)}"
|
|
639
|
+
)
|
|
640
|
+
else:
|
|
641
|
+
return None
|
|
642
|
+
elif provider is not None:
|
|
643
|
+
providers_to_fetch = [provider]
|
|
607
644
|
|
|
608
645
|
# providers discovery confs that are fetchable
|
|
609
|
-
providers_discovery_configs_fetchable:
|
|
646
|
+
providers_discovery_configs_fetchable: dict[str, Any] = {}
|
|
610
647
|
# check if any provider has not already been fetched for product types
|
|
611
648
|
already_fetched = True
|
|
612
|
-
for provider_to_fetch
|
|
613
|
-
|
|
614
|
-
if provider
|
|
615
|
-
else self.providers_config.items()
|
|
616
|
-
):
|
|
649
|
+
for provider_to_fetch in providers_to_fetch:
|
|
650
|
+
provider_config = self.providers_config[provider_to_fetch]
|
|
617
651
|
# get discovery conf
|
|
618
652
|
if hasattr(provider_config, "search"):
|
|
619
653
|
provider_search_config = provider_config.search
|
|
@@ -732,29 +766,40 @@ class EODataAccessGateway:
|
|
|
732
766
|
|
|
733
767
|
def discover_product_types(
|
|
734
768
|
self, provider: Optional[str] = None
|
|
735
|
-
) -> Optional[
|
|
769
|
+
) -> Optional[dict[str, Any]]:
|
|
736
770
|
"""Fetch providers for product types
|
|
737
771
|
|
|
738
|
-
:param provider:
|
|
739
|
-
providers (None value).
|
|
772
|
+
:param provider: The name of a provider or provider-group to fetch. Defaults to
|
|
773
|
+
all providers (None value).
|
|
740
774
|
:returns: external product types configuration
|
|
741
775
|
"""
|
|
742
|
-
|
|
776
|
+
grouped_providers = [
|
|
777
|
+
p
|
|
778
|
+
for p, provider_config in self.providers_config.items()
|
|
779
|
+
if provider == getattr(provider_config, "group", None)
|
|
780
|
+
]
|
|
781
|
+
if provider and provider not in self.providers_config and grouped_providers:
|
|
782
|
+
logger.info(
|
|
783
|
+
f"Discover product types for {provider} group: {', '.join(grouped_providers)}"
|
|
784
|
+
)
|
|
785
|
+
elif provider and provider not in self.providers_config:
|
|
743
786
|
raise UnsupportedProvider(
|
|
744
787
|
f"The requested provider is not (yet) supported: {provider}"
|
|
745
788
|
)
|
|
746
|
-
ext_product_types_conf:
|
|
789
|
+
ext_product_types_conf: dict[str, Any] = {}
|
|
747
790
|
providers_to_fetch = [
|
|
748
791
|
p
|
|
749
792
|
for p in (
|
|
750
793
|
[
|
|
751
|
-
|
|
794
|
+
p
|
|
795
|
+
for p in self.providers_config
|
|
796
|
+
if p in grouped_providers + [provider]
|
|
752
797
|
]
|
|
753
798
|
if provider
|
|
754
799
|
else self.available_providers()
|
|
755
800
|
)
|
|
756
801
|
]
|
|
757
|
-
kwargs:
|
|
802
|
+
kwargs: dict[str, Any] = {}
|
|
758
803
|
for provider in providers_to_fetch:
|
|
759
804
|
if hasattr(self.providers_config[provider], "search"):
|
|
760
805
|
search_plugin_config = self.providers_config[provider].search
|
|
@@ -762,7 +807,9 @@ class EODataAccessGateway:
|
|
|
762
807
|
search_plugin_config = self.providers_config[provider].api
|
|
763
808
|
else:
|
|
764
809
|
return None
|
|
765
|
-
if getattr(search_plugin_config, "discover_product_types",
|
|
810
|
+
if getattr(search_plugin_config, "discover_product_types", {}).get(
|
|
811
|
+
"fetch_url", None
|
|
812
|
+
):
|
|
766
813
|
search_plugin: Union[Search, Api] = next(
|
|
767
814
|
self._plugins_manager.get_search_plugins(provider=provider)
|
|
768
815
|
)
|
|
@@ -773,23 +820,15 @@ class EODataAccessGateway:
|
|
|
773
820
|
continue
|
|
774
821
|
# append auth to search plugin if needed
|
|
775
822
|
if getattr(search_plugin.config, "need_auth", False):
|
|
776
|
-
|
|
777
|
-
search_plugin.provider
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
getattr(auth_plugin, "authenticate", None)
|
|
823
|
+
if auth := self._plugins_manager.get_auth(
|
|
824
|
+
search_plugin.provider,
|
|
825
|
+
getattr(search_plugin.config, "api_endpoint", None),
|
|
826
|
+
search_plugin.config,
|
|
781
827
|
):
|
|
782
|
-
|
|
783
|
-
kwargs["auth"] = auth_plugin.authenticate()
|
|
784
|
-
except (AuthenticationError, MisconfiguredError) as e:
|
|
785
|
-
logger.warning(
|
|
786
|
-
f"Could not authenticate on {provider}: {str(e)}"
|
|
787
|
-
)
|
|
788
|
-
ext_product_types_conf[provider] = None
|
|
789
|
-
continue
|
|
828
|
+
kwargs["auth"] = auth
|
|
790
829
|
else:
|
|
791
|
-
logger.
|
|
792
|
-
f"Could not authenticate on {provider}
|
|
830
|
+
logger.debug(
|
|
831
|
+
f"Could not authenticate on {provider} for product types discovery"
|
|
793
832
|
)
|
|
794
833
|
ext_product_types_conf[provider] = None
|
|
795
834
|
continue
|
|
@@ -801,7 +840,7 @@ class EODataAccessGateway:
|
|
|
801
840
|
return sort_dict(ext_product_types_conf)
|
|
802
841
|
|
|
803
842
|
def update_product_types_list(
|
|
804
|
-
self, ext_product_types_conf:
|
|
843
|
+
self, ext_product_types_conf: dict[str, Optional[dict[str, dict[str, Any]]]]
|
|
805
844
|
) -> None:
|
|
806
845
|
"""Update eodag product types list
|
|
807
846
|
|
|
@@ -815,7 +854,9 @@ class EODataAccessGateway:
|
|
|
815
854
|
) or getattr(self.providers_config[provider], "api", None)
|
|
816
855
|
if search_plugin_config is None:
|
|
817
856
|
continue
|
|
818
|
-
if not
|
|
857
|
+
if not getattr(
|
|
858
|
+
search_plugin_config, "discover_product_types", {}
|
|
859
|
+
).get("fetch_url", None):
|
|
819
860
|
# conf has been updated and provider product types are no more discoverable
|
|
820
861
|
continue
|
|
821
862
|
provider_products_config = (
|
|
@@ -827,7 +868,7 @@ class EODataAccessGateway:
|
|
|
827
868
|
provider,
|
|
828
869
|
)
|
|
829
870
|
continue
|
|
830
|
-
new_product_types:
|
|
871
|
+
new_product_types: list[str] = []
|
|
831
872
|
for (
|
|
832
873
|
new_product_type,
|
|
833
874
|
new_product_type_conf,
|
|
@@ -874,7 +915,7 @@ class EODataAccessGateway:
|
|
|
874
915
|
new_product_types.append(new_product_type)
|
|
875
916
|
if new_product_types:
|
|
876
917
|
logger.debug(
|
|
877
|
-
f"Added
|
|
918
|
+
f"Added {len(new_product_types)} product types for {provider}"
|
|
878
919
|
)
|
|
879
920
|
|
|
880
921
|
elif provider not in self.providers_config:
|
|
@@ -890,7 +931,7 @@ class EODataAccessGateway:
|
|
|
890
931
|
|
|
891
932
|
def available_providers(
|
|
892
933
|
self, product_type: Optional[str] = None, by_group: bool = False
|
|
893
|
-
) ->
|
|
934
|
+
) -> list[str]:
|
|
894
935
|
"""Gives the sorted list of the available providers or groups
|
|
895
936
|
|
|
896
937
|
The providers or groups are sorted first by their priority level in descending order,
|
|
@@ -917,7 +958,7 @@ class EODataAccessGateway:
|
|
|
917
958
|
|
|
918
959
|
# If by_group is True, keep only the highest priority for each group
|
|
919
960
|
if by_group:
|
|
920
|
-
group_priority:
|
|
961
|
+
group_priority: dict[str, int] = {}
|
|
921
962
|
for name, priority in providers:
|
|
922
963
|
if name not in group_priority or priority > group_priority[name]:
|
|
923
964
|
group_priority[name] = priority
|
|
@@ -984,7 +1025,7 @@ class EODataAccessGateway:
|
|
|
984
1025
|
missionStartDate: Optional[str] = None,
|
|
985
1026
|
missionEndDate: Optional[str] = None,
|
|
986
1027
|
**kwargs: Any,
|
|
987
|
-
) ->
|
|
1028
|
+
) -> list[str]:
|
|
988
1029
|
"""
|
|
989
1030
|
Find EODAG product type IDs that best match a set of search parameters.
|
|
990
1031
|
|
|
@@ -1042,24 +1083,32 @@ class EODataAccessGateway:
|
|
|
1042
1083
|
query = p.parse(text)
|
|
1043
1084
|
results = searcher.search(query, limit=None)
|
|
1044
1085
|
|
|
1045
|
-
guesses:
|
|
1086
|
+
guesses: list[dict[str, str]] = [dict(r) for r in results or []]
|
|
1046
1087
|
|
|
1047
1088
|
# datetime filtering
|
|
1048
1089
|
if missionStartDate or missionEndDate:
|
|
1090
|
+
min_aware = datetime.datetime.min.replace(tzinfo=datetime.timezone.utc)
|
|
1091
|
+
max_aware = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
|
|
1049
1092
|
guesses = [
|
|
1050
1093
|
g
|
|
1051
1094
|
for g in guesses
|
|
1052
1095
|
if (
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1096
|
+
max(
|
|
1097
|
+
rfc3339_str_to_datetime(missionStartDate)
|
|
1098
|
+
if missionStartDate
|
|
1099
|
+
else min_aware,
|
|
1100
|
+
rfc3339_str_to_datetime(g["missionStartDate"])
|
|
1101
|
+
if g.get("missionStartDate")
|
|
1102
|
+
else min_aware,
|
|
1103
|
+
)
|
|
1104
|
+
<= min(
|
|
1105
|
+
rfc3339_str_to_datetime(missionEndDate)
|
|
1106
|
+
if missionEndDate
|
|
1107
|
+
else max_aware,
|
|
1108
|
+
rfc3339_str_to_datetime(g["missionEndDate"])
|
|
1109
|
+
if g.get("missionEndDate")
|
|
1110
|
+
else max_aware,
|
|
1111
|
+
)
|
|
1063
1112
|
)
|
|
1064
1113
|
]
|
|
1065
1114
|
|
|
@@ -1075,8 +1124,8 @@ class EODataAccessGateway:
|
|
|
1075
1124
|
raise_errors: bool = False,
|
|
1076
1125
|
start: Optional[str] = None,
|
|
1077
1126
|
end: Optional[str] = None,
|
|
1078
|
-
geom: Optional[Union[str,
|
|
1079
|
-
locations: Optional[
|
|
1127
|
+
geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
|
|
1128
|
+
locations: Optional[dict[str, str]] = None,
|
|
1080
1129
|
provider: Optional[str] = None,
|
|
1081
1130
|
count: bool = False,
|
|
1082
1131
|
**kwargs: Any,
|
|
@@ -1155,7 +1204,7 @@ class EODataAccessGateway:
|
|
|
1155
1204
|
items_per_page=items_per_page,
|
|
1156
1205
|
)
|
|
1157
1206
|
|
|
1158
|
-
|
|
1207
|
+
errors: list[tuple[str, Exception]] = []
|
|
1159
1208
|
# Loop over available providers and return the first non-empty results
|
|
1160
1209
|
for i, search_plugin in enumerate(search_plugins):
|
|
1161
1210
|
search_plugin.clear()
|
|
@@ -1165,25 +1214,27 @@ class EODataAccessGateway:
|
|
|
1165
1214
|
raise_errors=raise_errors,
|
|
1166
1215
|
**search_kwargs,
|
|
1167
1216
|
)
|
|
1217
|
+
errors.extend(search_results.errors)
|
|
1168
1218
|
if len(search_results) == 0 and i < len(search_plugins) - 1:
|
|
1169
1219
|
logger.warning(
|
|
1170
1220
|
f"No result could be obtained from provider {search_plugin.provider}, "
|
|
1171
1221
|
"we will try to get the data from another provider",
|
|
1172
1222
|
)
|
|
1173
1223
|
elif len(search_results) > 0:
|
|
1224
|
+
search_results.errors = errors
|
|
1174
1225
|
return search_results
|
|
1175
1226
|
|
|
1176
1227
|
if i > 1:
|
|
1177
1228
|
logger.error("No result could be obtained from any available provider")
|
|
1178
|
-
return SearchResult([], 0) if count else SearchResult([])
|
|
1229
|
+
return SearchResult([], 0, errors) if count else SearchResult([], errors=errors)
|
|
1179
1230
|
|
|
1180
1231
|
def search_iter_page(
|
|
1181
1232
|
self,
|
|
1182
1233
|
items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
|
|
1183
1234
|
start: Optional[str] = None,
|
|
1184
1235
|
end: Optional[str] = None,
|
|
1185
|
-
geom: Optional[Union[str,
|
|
1186
|
-
locations: Optional[
|
|
1236
|
+
geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
|
|
1237
|
+
locations: Optional[dict[str, str]] = None,
|
|
1187
1238
|
**kwargs: Any,
|
|
1188
1239
|
) -> Iterator[SearchResult]:
|
|
1189
1240
|
"""Iterate over the pages of a products search.
|
|
@@ -1359,8 +1410,8 @@ class EODataAccessGateway:
|
|
|
1359
1410
|
items_per_page: Optional[int] = None,
|
|
1360
1411
|
start: Optional[str] = None,
|
|
1361
1412
|
end: Optional[str] = None,
|
|
1362
|
-
geom: Optional[Union[str,
|
|
1363
|
-
locations: Optional[
|
|
1413
|
+
geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
|
|
1414
|
+
locations: Optional[dict[str, str]] = None,
|
|
1364
1415
|
**kwargs: Any,
|
|
1365
1416
|
) -> SearchResult:
|
|
1366
1417
|
"""Search and return all the products matching the search criteria.
|
|
@@ -1435,7 +1486,7 @@ class EODataAccessGateway:
|
|
|
1435
1486
|
)
|
|
1436
1487
|
or DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1437
1488
|
)
|
|
1438
|
-
logger.
|
|
1489
|
+
logger.info(
|
|
1439
1490
|
"Searching for all the products with provider %s and a maximum of %s "
|
|
1440
1491
|
"items per page.",
|
|
1441
1492
|
search_plugin.provider,
|
|
@@ -1503,7 +1554,7 @@ class EODataAccessGateway:
|
|
|
1503
1554
|
try:
|
|
1504
1555
|
product_type = self.get_product_type_from_alias(product_type)
|
|
1505
1556
|
except NoMatchingProductType:
|
|
1506
|
-
logger.
|
|
1557
|
+
logger.debug("product type %s not found", product_type)
|
|
1507
1558
|
get_search_plugins_kwargs = dict(provider=provider, product_type=product_type)
|
|
1508
1559
|
search_plugins = self._plugins_manager.get_search_plugins(
|
|
1509
1560
|
**get_search_plugins_kwargs
|
|
@@ -1526,7 +1577,7 @@ class EODataAccessGateway:
|
|
|
1526
1577
|
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1527
1578
|
)
|
|
1528
1579
|
kwargs.update(items_per_page=items_per_page)
|
|
1529
|
-
if isinstance(plugin,
|
|
1580
|
+
if isinstance(plugin, PostJsonSearch):
|
|
1530
1581
|
kwargs.update(
|
|
1531
1582
|
items_per_page=items_per_page,
|
|
1532
1583
|
_dc_qs=_dc_qs,
|
|
@@ -1544,9 +1595,10 @@ class EODataAccessGateway:
|
|
|
1544
1595
|
**kwargs,
|
|
1545
1596
|
):
|
|
1546
1597
|
results.data.extend(page_results.data)
|
|
1547
|
-
except Exception:
|
|
1598
|
+
except Exception as e:
|
|
1548
1599
|
if kwargs.get("raise_errors"):
|
|
1549
1600
|
raise
|
|
1601
|
+
logger.warning(e)
|
|
1550
1602
|
continue
|
|
1551
1603
|
|
|
1552
1604
|
# try using crunch to get unique result
|
|
@@ -1580,20 +1632,16 @@ class EODataAccessGateway:
|
|
|
1580
1632
|
if not getattr(plugin.config, "discover_product_types", {}).get("fetch_url"):
|
|
1581
1633
|
return None
|
|
1582
1634
|
|
|
1583
|
-
kwargs:
|
|
1635
|
+
kwargs: dict[str, Any] = {"productType": product_type}
|
|
1584
1636
|
|
|
1585
1637
|
# append auth if needed
|
|
1586
1638
|
if getattr(plugin.config, "need_auth", False):
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
else:
|
|
1594
|
-
logger.warning(
|
|
1595
|
-
f"Could not authenticate on {provider} using {auth_plugin} plugin"
|
|
1596
|
-
)
|
|
1639
|
+
if auth := self._plugins_manager.get_auth(
|
|
1640
|
+
plugin.provider,
|
|
1641
|
+
getattr(plugin.config, "api_endpoint", None),
|
|
1642
|
+
plugin.config,
|
|
1643
|
+
):
|
|
1644
|
+
kwargs["auth"] = auth
|
|
1597
1645
|
|
|
1598
1646
|
product_type_config = plugin.discover_product_types(**kwargs)
|
|
1599
1647
|
self.update_product_types_list({provider: product_type_config})
|
|
@@ -1602,11 +1650,11 @@ class EODataAccessGateway:
|
|
|
1602
1650
|
self,
|
|
1603
1651
|
start: Optional[str] = None,
|
|
1604
1652
|
end: Optional[str] = None,
|
|
1605
|
-
geom: Optional[Union[str,
|
|
1606
|
-
locations: Optional[
|
|
1653
|
+
geom: Optional[Union[str, dict[str, float], BaseGeometry]] = None,
|
|
1654
|
+
locations: Optional[dict[str, str]] = None,
|
|
1607
1655
|
provider: Optional[str] = None,
|
|
1608
1656
|
**kwargs: Any,
|
|
1609
|
-
) ->
|
|
1657
|
+
) -> tuple[list[Union[Search, Api]], dict[str, Any]]:
|
|
1610
1658
|
"""Internal method to prepare the search kwargs and get the search plugins.
|
|
1611
1659
|
|
|
1612
1660
|
Product query:
|
|
@@ -1714,16 +1762,16 @@ class EODataAccessGateway:
|
|
|
1714
1762
|
|
|
1715
1763
|
preferred_provider = self.get_preferred_provider()[0]
|
|
1716
1764
|
|
|
1717
|
-
search_plugins:
|
|
1765
|
+
search_plugins: list[Union[Search, Api]] = []
|
|
1718
1766
|
for plugin in self._plugins_manager.get_search_plugins(
|
|
1719
1767
|
product_type=product_type, provider=provider
|
|
1720
1768
|
):
|
|
1721
|
-
# exclude
|
|
1769
|
+
# exclude MeteoblueSearch plugins from search fallback for unknown product_type
|
|
1722
1770
|
if (
|
|
1723
1771
|
provider != plugin.provider
|
|
1724
1772
|
and preferred_provider != plugin.provider
|
|
1725
1773
|
and product_type not in self.product_types_config
|
|
1726
|
-
and isinstance(plugin,
|
|
1774
|
+
and isinstance(plugin, MeteoblueSearch)
|
|
1727
1775
|
):
|
|
1728
1776
|
continue
|
|
1729
1777
|
search_plugins.append(plugin)
|
|
@@ -1732,12 +1780,10 @@ class EODataAccessGateway:
|
|
|
1732
1780
|
provider = preferred_provider
|
|
1733
1781
|
providers = [plugin.provider for plugin in search_plugins]
|
|
1734
1782
|
if provider not in providers:
|
|
1735
|
-
logger.
|
|
1736
|
-
"Product type '%s' is not available with provider '%s'.
|
|
1737
|
-
"Searching it on provider '%s' instead.",
|
|
1783
|
+
logger.debug(
|
|
1784
|
+
"Product type '%s' is not available with preferred provider '%s'.",
|
|
1738
1785
|
product_type,
|
|
1739
1786
|
provider,
|
|
1740
|
-
search_plugins[0].provider,
|
|
1741
1787
|
)
|
|
1742
1788
|
else:
|
|
1743
1789
|
provider_plugin = list(
|
|
@@ -1745,35 +1791,10 @@ class EODataAccessGateway:
|
|
|
1745
1791
|
)[0]
|
|
1746
1792
|
search_plugins.remove(provider_plugin)
|
|
1747
1793
|
search_plugins.insert(0, provider_plugin)
|
|
1748
|
-
logger.info(
|
|
1749
|
-
"Searching product type '%s' on provider: %s",
|
|
1750
|
-
product_type,
|
|
1751
|
-
search_plugins[0].provider,
|
|
1752
|
-
)
|
|
1753
1794
|
# Add product_types_config to plugin config. This dict contains product
|
|
1754
1795
|
# type metadata that will also be stored in each product's properties.
|
|
1755
1796
|
for search_plugin in search_plugins:
|
|
1756
|
-
|
|
1757
|
-
search_plugin.config.product_type_config = dict(
|
|
1758
|
-
[
|
|
1759
|
-
p
|
|
1760
|
-
for p in self.list_product_types(
|
|
1761
|
-
search_plugin.provider, fetch_providers=False
|
|
1762
|
-
)
|
|
1763
|
-
if p["_id"] == product_type
|
|
1764
|
-
][0],
|
|
1765
|
-
**{"productType": product_type},
|
|
1766
|
-
)
|
|
1767
|
-
# If the product isn't in the catalog, it's a generic product type.
|
|
1768
|
-
except IndexError:
|
|
1769
|
-
# Construct the GENERIC_PRODUCT_TYPE metadata
|
|
1770
|
-
search_plugin.config.product_type_config = dict(
|
|
1771
|
-
ID=GENERIC_PRODUCT_TYPE,
|
|
1772
|
-
**self.product_types_config[GENERIC_PRODUCT_TYPE],
|
|
1773
|
-
productType=product_type,
|
|
1774
|
-
)
|
|
1775
|
-
# Remove the ID since this is equal to productType.
|
|
1776
|
-
search_plugin.config.product_type_config.pop("ID", None)
|
|
1797
|
+
self._attach_product_type_config(search_plugin, product_type)
|
|
1777
1798
|
|
|
1778
1799
|
return search_plugins, kwargs
|
|
1779
1800
|
|
|
@@ -1793,6 +1814,7 @@ class EODataAccessGateway:
|
|
|
1793
1814
|
:param kwargs: Some other criteria that will be used to do the search
|
|
1794
1815
|
:returns: A collection of EO products matching the criteria
|
|
1795
1816
|
"""
|
|
1817
|
+
logger.info("Searching on provider %s", search_plugin.provider)
|
|
1796
1818
|
max_items_per_page = getattr(search_plugin.config, "pagination", {}).get(
|
|
1797
1819
|
"max_items_per_page", DEFAULT_MAX_ITEMS_PER_PAGE
|
|
1798
1820
|
)
|
|
@@ -1810,19 +1832,23 @@ class EODataAccessGateway:
|
|
|
1810
1832
|
max_items_per_page,
|
|
1811
1833
|
)
|
|
1812
1834
|
|
|
1813
|
-
|
|
1814
|
-
auth_plugin = self._plugins_manager.get_auth_plugin(search_plugin.provider)
|
|
1815
|
-
can_authenticate = callable(getattr(auth_plugin, "authenticate", None))
|
|
1816
|
-
|
|
1817
|
-
results: List[EOProduct] = []
|
|
1835
|
+
results: list[EOProduct] = []
|
|
1818
1836
|
total_results: Optional[int] = 0 if count else None
|
|
1819
1837
|
|
|
1838
|
+
errors: list[tuple[str, Exception]] = []
|
|
1839
|
+
|
|
1820
1840
|
try:
|
|
1821
1841
|
prep = PreparedSearch(count=count)
|
|
1822
|
-
if need_auth and auth_plugin and can_authenticate:
|
|
1823
|
-
prep.auth = auth_plugin.authenticate()
|
|
1824
1842
|
|
|
1825
|
-
|
|
1843
|
+
# append auth if needed
|
|
1844
|
+
if getattr(search_plugin.config, "need_auth", False):
|
|
1845
|
+
if auth := self._plugins_manager.get_auth(
|
|
1846
|
+
search_plugin.provider,
|
|
1847
|
+
getattr(search_plugin.config, "api_endpoint", None),
|
|
1848
|
+
search_plugin.config,
|
|
1849
|
+
):
|
|
1850
|
+
prep.auth = auth
|
|
1851
|
+
|
|
1826
1852
|
prep.page = kwargs.pop("page", None)
|
|
1827
1853
|
prep.items_per_page = kwargs.pop("items_per_page", None)
|
|
1828
1854
|
|
|
@@ -1876,12 +1902,31 @@ class EODataAccessGateway:
|
|
|
1876
1902
|
eo_product.product_type
|
|
1877
1903
|
)
|
|
1878
1904
|
except NoMatchingProductType:
|
|
1879
|
-
logger.
|
|
1905
|
+
logger.debug("product type %s not found", eo_product.product_type)
|
|
1880
1906
|
|
|
1881
1907
|
if eo_product.search_intersection is not None:
|
|
1882
1908
|
download_plugin = self._plugins_manager.get_download_plugin(
|
|
1883
1909
|
eo_product
|
|
1884
1910
|
)
|
|
1911
|
+
if len(eo_product.assets) > 0:
|
|
1912
|
+
matching_url = next(iter(eo_product.assets.values()))["href"]
|
|
1913
|
+
elif eo_product.properties.get("storageStatus") != ONLINE_STATUS:
|
|
1914
|
+
matching_url = eo_product.properties.get(
|
|
1915
|
+
"orderLink"
|
|
1916
|
+
) or eo_product.properties.get("downloadLink")
|
|
1917
|
+
else:
|
|
1918
|
+
matching_url = eo_product.properties.get("downloadLink")
|
|
1919
|
+
|
|
1920
|
+
try:
|
|
1921
|
+
auth_plugin = next(
|
|
1922
|
+
self._plugins_manager.get_auth_plugins(
|
|
1923
|
+
search_plugin.provider,
|
|
1924
|
+
matching_url=matching_url,
|
|
1925
|
+
matching_conf=download_plugin.config,
|
|
1926
|
+
)
|
|
1927
|
+
)
|
|
1928
|
+
except StopIteration:
|
|
1929
|
+
auth_plugin = None
|
|
1885
1930
|
eo_product.register_downloader(download_plugin, auth_plugin)
|
|
1886
1931
|
|
|
1887
1932
|
results.extend(res)
|
|
@@ -1911,13 +1956,6 @@ class EODataAccessGateway:
|
|
|
1911
1956
|
"the total number of products matching the search criteria"
|
|
1912
1957
|
)
|
|
1913
1958
|
except Exception as e:
|
|
1914
|
-
log_msg = f"No result from provider '{search_plugin.provider}' due to an error during search."
|
|
1915
|
-
if not raise_errors:
|
|
1916
|
-
log_msg += " Raise verbosity of log messages for details"
|
|
1917
|
-
logger.info(log_msg)
|
|
1918
|
-
# keep only the message from exception args
|
|
1919
|
-
if len(e.args) > 1:
|
|
1920
|
-
e.args = (e.args[0],)
|
|
1921
1959
|
if raise_errors:
|
|
1922
1960
|
# Raise the error, letting the application wrapping eodag know that
|
|
1923
1961
|
# something went bad. This way it will be able to decide what to do next
|
|
@@ -1927,8 +1965,8 @@ class EODataAccessGateway:
|
|
|
1927
1965
|
"Error while searching on provider %s (ignored):",
|
|
1928
1966
|
search_plugin.provider,
|
|
1929
1967
|
)
|
|
1930
|
-
|
|
1931
|
-
return SearchResult(results, total_results)
|
|
1968
|
+
errors.append((search_plugin.provider, e))
|
|
1969
|
+
return SearchResult(results, total_results, errors)
|
|
1932
1970
|
|
|
1933
1971
|
def crunch(self, results: SearchResult, **kwargs: Any) -> SearchResult:
|
|
1934
1972
|
"""Apply the filters given through the keyword arguments to the results
|
|
@@ -1945,7 +1983,7 @@ class EODataAccessGateway:
|
|
|
1945
1983
|
return results
|
|
1946
1984
|
|
|
1947
1985
|
@staticmethod
|
|
1948
|
-
def group_by_extent(searches:
|
|
1986
|
+
def group_by_extent(searches: list[SearchResult]) -> list[SearchResult]:
|
|
1949
1987
|
"""Combines multiple SearchResults and return a list of SearchResults grouped
|
|
1950
1988
|
by extent (i.e. bounding box).
|
|
1951
1989
|
|
|
@@ -1954,7 +1992,7 @@ class EODataAccessGateway:
|
|
|
1954
1992
|
"""
|
|
1955
1993
|
# Dict with extents as keys, each extent being defined by a str
|
|
1956
1994
|
# "{minx}{miny}{maxx}{maxy}" (each float rounded to 2 dec).
|
|
1957
|
-
products_grouped_by_extent:
|
|
1995
|
+
products_grouped_by_extent: dict[str, Any] = {}
|
|
1958
1996
|
|
|
1959
1997
|
for search in searches:
|
|
1960
1998
|
for product in search:
|
|
@@ -1973,16 +2011,16 @@ class EODataAccessGateway:
|
|
|
1973
2011
|
search_result: SearchResult,
|
|
1974
2012
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
1975
2013
|
progress_callback: Optional[ProgressCallback] = None,
|
|
1976
|
-
wait:
|
|
1977
|
-
timeout:
|
|
2014
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
2015
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1978
2016
|
**kwargs: Unpack[DownloadConf],
|
|
1979
|
-
) ->
|
|
2017
|
+
) -> list[str]:
|
|
1980
2018
|
"""Download all products resulting from a search.
|
|
1981
2019
|
|
|
1982
2020
|
:param search_result: A collection of EO products resulting from a search
|
|
1983
2021
|
:param downloaded_callback: (optional) A method or a callable object which takes
|
|
1984
2022
|
as parameter the ``product``. You can use the base class
|
|
1985
|
-
:class:`~eodag.
|
|
2023
|
+
:class:`~eodag.utils.DownloadedCallback` and override
|
|
1986
2024
|
its ``__call__`` method. Will be called each time a product
|
|
1987
2025
|
finishes downloading
|
|
1988
2026
|
:param progress_callback: (optional) A method or a callable object
|
|
@@ -2061,12 +2099,12 @@ class EODataAccessGateway:
|
|
|
2061
2099
|
products = self.deserialize(filename)
|
|
2062
2100
|
for i, product in enumerate(products):
|
|
2063
2101
|
if product.downloader is None:
|
|
2102
|
+
downloader = self._plugins_manager.get_download_plugin(product)
|
|
2064
2103
|
auth = product.downloader_auth
|
|
2065
2104
|
if auth is None:
|
|
2066
|
-
auth = self._plugins_manager.get_auth_plugin(product
|
|
2067
|
-
products[i].register_downloader(
|
|
2068
|
-
|
|
2069
|
-
)
|
|
2105
|
+
auth = self._plugins_manager.get_auth_plugin(downloader, product)
|
|
2106
|
+
products[i].register_downloader(downloader, auth)
|
|
2107
|
+
|
|
2070
2108
|
return products
|
|
2071
2109
|
|
|
2072
2110
|
@_deprecated(
|
|
@@ -2090,7 +2128,7 @@ class EODataAccessGateway:
|
|
|
2090
2128
|
|
|
2091
2129
|
:param filename: A filename containing features encoded as a geojson
|
|
2092
2130
|
:param recursive: (optional) Browse recursively in child nodes if True
|
|
2093
|
-
:param max_connections: (optional) Maximum number of connections for HTTP requests
|
|
2131
|
+
:param max_connections: (optional) Maximum number of connections for concurrent HTTP requests
|
|
2094
2132
|
:param provider: (optional) Data provider
|
|
2095
2133
|
:param productType: (optional) Data product type
|
|
2096
2134
|
:param timeout: (optional) Timeout in seconds for each internal HTTP request
|
|
@@ -2136,8 +2174,8 @@ class EODataAccessGateway:
|
|
|
2136
2174
|
self,
|
|
2137
2175
|
product: EOProduct,
|
|
2138
2176
|
progress_callback: Optional[ProgressCallback] = None,
|
|
2139
|
-
wait:
|
|
2140
|
-
timeout:
|
|
2177
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
2178
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
2141
2179
|
**kwargs: Unpack[DownloadConf],
|
|
2142
2180
|
) -> str:
|
|
2143
2181
|
"""Download a single product.
|
|
@@ -2197,12 +2235,11 @@ class EODataAccessGateway:
|
|
|
2197
2235
|
|
|
2198
2236
|
def _setup_downloader(self, product: EOProduct) -> None:
|
|
2199
2237
|
if product.downloader is None:
|
|
2238
|
+
downloader = self._plugins_manager.get_download_plugin(product)
|
|
2200
2239
|
auth = product.downloader_auth
|
|
2201
2240
|
if auth is None:
|
|
2202
|
-
auth = self._plugins_manager.get_auth_plugin(product
|
|
2203
|
-
product.register_downloader(
|
|
2204
|
-
self._plugins_manager.get_download_plugin(product), auth
|
|
2205
|
-
)
|
|
2241
|
+
auth = self._plugins_manager.get_auth_plugin(downloader, product)
|
|
2242
|
+
product.register_downloader(downloader, auth)
|
|
2206
2243
|
|
|
2207
2244
|
def get_cruncher(self, name: str, **options: Any) -> Crunch:
|
|
2208
2245
|
"""Build a crunch plugin from a configuration
|
|
@@ -2216,79 +2253,115 @@ class EODataAccessGateway:
|
|
|
2216
2253
|
return self._plugins_manager.get_crunch_plugin(name, **plugin_conf)
|
|
2217
2254
|
|
|
2218
2255
|
def list_queryables(
|
|
2219
|
-
self,
|
|
2220
|
-
|
|
2256
|
+
self,
|
|
2257
|
+
provider: Optional[str] = None,
|
|
2258
|
+
fetch_providers: bool = True,
|
|
2259
|
+
**kwargs: Any,
|
|
2260
|
+
) -> QueryablesDict:
|
|
2221
2261
|
"""Fetch the queryable properties for a given product type and/or provider.
|
|
2222
2262
|
|
|
2223
2263
|
:param provider: (optional) The provider.
|
|
2264
|
+
:param fetch_providers: If new product types should be fetched from the providers; default: True
|
|
2224
2265
|
:param kwargs: additional filters for queryables (`productType` or other search
|
|
2225
2266
|
arguments)
|
|
2226
2267
|
|
|
2227
2268
|
:raises UnsupportedProductType: If the specified product type is not available for the
|
|
2228
2269
|
provider.
|
|
2229
2270
|
|
|
2230
|
-
:returns: A
|
|
2231
|
-
parameters to their annotated type
|
|
2271
|
+
:returns: A :class:`~eodag.api.product.queryables.QuerybalesDict` containing the EODAG queryable
|
|
2272
|
+
properties, associating parameters to their annotated type, and a additional_properties attribute
|
|
2232
2273
|
"""
|
|
2233
|
-
|
|
2274
|
+
# only fetch providers if product type is not found
|
|
2275
|
+
available_product_types: list[str] = [
|
|
2234
2276
|
pt["ID"]
|
|
2235
2277
|
for pt in self.list_product_types(provider=provider, fetch_providers=False)
|
|
2236
2278
|
]
|
|
2237
|
-
product_type = kwargs.get("productType")
|
|
2279
|
+
product_type: Optional[str] = kwargs.get("productType")
|
|
2280
|
+
pt_alias: Optional[str] = product_type
|
|
2238
2281
|
|
|
2239
2282
|
if product_type:
|
|
2283
|
+
if product_type not in available_product_types:
|
|
2284
|
+
if fetch_providers:
|
|
2285
|
+
# fetch providers and try again
|
|
2286
|
+
available_product_types = [
|
|
2287
|
+
pt["ID"]
|
|
2288
|
+
for pt in self.list_product_types(
|
|
2289
|
+
provider=provider, fetch_providers=True
|
|
2290
|
+
)
|
|
2291
|
+
]
|
|
2292
|
+
raise UnsupportedProductType(f"{product_type} is not available.")
|
|
2240
2293
|
try:
|
|
2241
2294
|
kwargs["productType"] = product_type = self.get_product_type_from_alias(
|
|
2242
2295
|
product_type
|
|
2243
2296
|
)
|
|
2244
2297
|
except NoMatchingProductType as e:
|
|
2245
|
-
raise UnsupportedProductType(f"{product_type} is not available") from e
|
|
2246
|
-
|
|
2247
|
-
if product_type and product_type not in available_product_types:
|
|
2248
|
-
self.fetch_product_types_list()
|
|
2298
|
+
raise UnsupportedProductType(f"{product_type} is not available.") from e
|
|
2249
2299
|
|
|
2250
2300
|
if not provider and not product_type:
|
|
2251
|
-
return
|
|
2301
|
+
return QueryablesDict(
|
|
2302
|
+
additional_properties=True,
|
|
2303
|
+
**model_fields_to_annotated(CommonQueryables.model_fields),
|
|
2304
|
+
)
|
|
2252
2305
|
|
|
2253
|
-
|
|
2306
|
+
additional_properties = False
|
|
2307
|
+
additional_information = []
|
|
2308
|
+
queryable_properties: dict[str, Any] = {}
|
|
2254
2309
|
|
|
2255
2310
|
for plugin in self._plugins_manager.get_search_plugins(product_type, provider):
|
|
2311
|
+
# attach product type config
|
|
2312
|
+
product_type_configs: dict[str, Any] = {}
|
|
2313
|
+
if product_type:
|
|
2314
|
+
self._attach_product_type_config(plugin, product_type)
|
|
2315
|
+
product_type_configs[product_type] = plugin.config.product_type_config
|
|
2316
|
+
else:
|
|
2317
|
+
for pt in available_product_types:
|
|
2318
|
+
self._attach_product_type_config(plugin, pt)
|
|
2319
|
+
product_type_configs[pt] = plugin.config.product_type_config
|
|
2320
|
+
|
|
2321
|
+
# authenticate if required
|
|
2256
2322
|
if getattr(plugin.config, "need_auth", False) and (
|
|
2257
|
-
auth := self._plugins_manager.get_auth_plugin(plugin
|
|
2323
|
+
auth := self._plugins_manager.get_auth_plugin(plugin)
|
|
2258
2324
|
):
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
)
|
|
2267
|
-
queryables = {
|
|
2268
|
-
k: v
|
|
2269
|
-
for k, v in list(providers_queryables.values())[0].items()
|
|
2270
|
-
if k in queryable_keys
|
|
2271
|
-
}
|
|
2325
|
+
try:
|
|
2326
|
+
plugin.auth = auth.authenticate()
|
|
2327
|
+
except AuthenticationError:
|
|
2328
|
+
logger.debug(
|
|
2329
|
+
"queryables from provider %s could not be fetched due to an authentication error",
|
|
2330
|
+
plugin.provider,
|
|
2331
|
+
)
|
|
2272
2332
|
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2333
|
+
plugin_queryables = plugin.list_queryables(
|
|
2334
|
+
kwargs,
|
|
2335
|
+
available_product_types,
|
|
2336
|
+
product_type_configs,
|
|
2337
|
+
product_type,
|
|
2338
|
+
pt_alias,
|
|
2339
|
+
)
|
|
2278
2340
|
|
|
2279
|
-
|
|
2341
|
+
if plugin_queryables.additional_information:
|
|
2342
|
+
additional_information.append(
|
|
2343
|
+
f"{plugin.provider}: {plugin_queryables.additional_information}"
|
|
2344
|
+
)
|
|
2345
|
+
queryable_properties = {**plugin_queryables, **queryable_properties}
|
|
2346
|
+
additional_properties = (
|
|
2347
|
+
additional_properties or plugin_queryables.additional_properties
|
|
2348
|
+
)
|
|
2280
2349
|
|
|
2281
|
-
return
|
|
2350
|
+
return QueryablesDict(
|
|
2351
|
+
additional_properties=additional_properties,
|
|
2352
|
+
additional_information=" | ".join(additional_information),
|
|
2353
|
+
**queryable_properties,
|
|
2354
|
+
)
|
|
2282
2355
|
|
|
2283
|
-
def available_sortables(self) ->
|
|
2356
|
+
def available_sortables(self) -> dict[str, Optional[ProviderSortables]]:
|
|
2284
2357
|
"""For each provider, gives its available sortable parameter(s) and its maximum
|
|
2285
2358
|
number of them if it supports the sorting feature, otherwise gives None.
|
|
2286
2359
|
|
|
2287
|
-
:returns: A
|
|
2360
|
+
:returns: A dictionary with providers as keys and dictionary of sortable parameter(s) and
|
|
2288
2361
|
its (their) maximum number as value(s).
|
|
2289
2362
|
:raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
|
|
2290
2363
|
"""
|
|
2291
|
-
sortables:
|
|
2364
|
+
sortables: dict[str, Optional[ProviderSortables]] = {}
|
|
2292
2365
|
provider_search_plugins = self._plugins_manager.get_search_plugins()
|
|
2293
2366
|
for provider_search_plugin in provider_search_plugins:
|
|
2294
2367
|
provider = provider_search_plugin.provider
|
|
@@ -2311,3 +2384,30 @@ class EODataAccessGateway:
|
|
|
2311
2384
|
],
|
|
2312
2385
|
}
|
|
2313
2386
|
return sortables
|
|
2387
|
+
|
|
2388
|
+
def _attach_product_type_config(self, plugin: Search, product_type: str) -> None:
|
|
2389
|
+
"""
|
|
2390
|
+
Attach product_types_config to plugin config. This dict contains product
|
|
2391
|
+
type metadata that will also be stored in each product's properties.
|
|
2392
|
+
"""
|
|
2393
|
+
try:
|
|
2394
|
+
plugin.config.product_type_config = dict(
|
|
2395
|
+
[
|
|
2396
|
+
p
|
|
2397
|
+
for p in self.list_product_types(
|
|
2398
|
+
plugin.provider, fetch_providers=False
|
|
2399
|
+
)
|
|
2400
|
+
if p["_id"] == product_type
|
|
2401
|
+
][0],
|
|
2402
|
+
**{"productType": product_type},
|
|
2403
|
+
)
|
|
2404
|
+
# If the product isn't in the catalog, it's a generic product type.
|
|
2405
|
+
except IndexError:
|
|
2406
|
+
# Construct the GENERIC_PRODUCT_TYPE metadata
|
|
2407
|
+
plugin.config.product_type_config = dict(
|
|
2408
|
+
ID=GENERIC_PRODUCT_TYPE,
|
|
2409
|
+
**self.product_types_config[GENERIC_PRODUCT_TYPE],
|
|
2410
|
+
productType=product_type,
|
|
2411
|
+
)
|
|
2412
|
+
# Remove the ID since this is equal to productType.
|
|
2413
|
+
plugin.config.product_type_config.pop("ID", None)
|