eodag 3.0.1__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 +174 -138
- 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 +117 -90
- eodag/api/search_result.py +13 -23
- eodag/cli.py +26 -5
- eodag/config.py +86 -92
- eodag/plugins/apis/base.py +1 -1
- eodag/plugins/apis/ecmwf.py +42 -22
- eodag/plugins/apis/usgs.py +17 -16
- eodag/plugins/authentication/aws_auth.py +16 -13
- eodag/plugins/authentication/base.py +5 -3
- eodag/plugins/authentication/header.py +3 -3
- eodag/plugins/authentication/keycloak.py +4 -4
- eodag/plugins/authentication/oauth.py +7 -3
- eodag/plugins/authentication/openid_connect.py +22 -16
- eodag/plugins/authentication/sas_auth.py +4 -4
- eodag/plugins/authentication/token.py +41 -10
- eodag/plugins/authentication/token_exchange.py +1 -1
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +4 -4
- eodag/plugins/crunch/filter_date.py +4 -4
- eodag/plugins/crunch/filter_latest_intersect.py +6 -6
- eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
- eodag/plugins/crunch/filter_overlap.py +4 -4
- eodag/plugins/crunch/filter_property.py +6 -7
- eodag/plugins/download/aws.py +146 -87
- eodag/plugins/download/base.py +38 -56
- eodag/plugins/download/creodias_s3.py +29 -0
- eodag/plugins/download/http.py +173 -183
- eodag/plugins/download/s3rest.py +10 -11
- eodag/plugins/manager.py +10 -20
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +90 -46
- eodag/plugins/search/build_search_result.py +1048 -361
- eodag/plugins/search/cop_marine.py +22 -12
- eodag/plugins/search/creodias_s3.py +9 -73
- eodag/plugins/search/csw.py +11 -11
- eodag/plugins/search/data_request_search.py +19 -18
- eodag/plugins/search/qssearch.py +99 -258
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +4 -4
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1134 -325
- eodag/resources/providers.yml +906 -2006
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +10 -9
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -3
- eodag/rest/core.py +112 -82
- eodag/rest/errors.py +5 -5
- eodag/rest/server.py +33 -14
- eodag/rest/stac.py +41 -38
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +29 -23
- eodag/rest/types/queryables.py +42 -31
- eodag/rest/types/stac_search.py +15 -25
- 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 +141 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +183 -72
- eodag/types/search_args.py +4 -4
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +153 -51
- eodag/utils/exceptions.py +28 -21
- eodag/utils/import_system.py +2 -2
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -13
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +10 -10
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
- eodag/utils/constraints.py +0 -244
- eodag-3.0.1.dist-info/RECORD +0 -109
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/plugins/download/s3rest.py
CHANGED
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
22
|
import os.path
|
|
23
|
-
from typing import TYPE_CHECKING,
|
|
23
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
24
24
|
from xml.dom import minidom
|
|
25
25
|
from xml.parsers.expat import ExpatError
|
|
26
26
|
|
|
@@ -54,6 +54,7 @@ from eodag.utils.exceptions import (
|
|
|
54
54
|
if TYPE_CHECKING:
|
|
55
55
|
from eodag.api.product import EOProduct
|
|
56
56
|
from eodag.config import PluginConfig
|
|
57
|
+
from eodag.types import S3SessionKwargs
|
|
57
58
|
from eodag.types.download_args import DownloadConf
|
|
58
59
|
from eodag.utils import Unpack
|
|
59
60
|
|
|
@@ -78,7 +79,7 @@ class S3RestDownload(Download):
|
|
|
78
79
|
* :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): whether order is enabled
|
|
79
80
|
or not if product is `OFFLINE`
|
|
80
81
|
* :attr:`~eodag.config.PluginConfig.order_method` (``str``) HTTP request method, ``GET`` (default) or ``POST``
|
|
81
|
-
* :attr:`~eodag.config.PluginConfig.order_headers` (``[
|
|
82
|
+
* :attr:`~eodag.config.PluginConfig.order_headers` (``[dict[str, str]]``): order request headers
|
|
82
83
|
* :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
|
|
83
84
|
a typed dictionary containing the key :attr:`~eodag.config.PluginConfig.OrderOnResponse.metadata_mapping`
|
|
84
85
|
which can be used to add new product properties based on the data in response to the order request
|
|
@@ -93,10 +94,10 @@ class S3RestDownload(Download):
|
|
|
93
94
|
def download(
|
|
94
95
|
self,
|
|
95
96
|
product: EOProduct,
|
|
96
|
-
auth: Optional[Union[AuthBase,
|
|
97
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
97
98
|
progress_callback: Optional[ProgressCallback] = None,
|
|
98
|
-
wait:
|
|
99
|
-
timeout:
|
|
99
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
100
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
100
101
|
**kwargs: Unpack[DownloadConf],
|
|
101
102
|
) -> Optional[str]:
|
|
102
103
|
"""Download method for S3 REST API.
|
|
@@ -130,9 +131,9 @@ class S3RestDownload(Download):
|
|
|
130
131
|
and "storageStatus" in product.properties
|
|
131
132
|
and product.properties["storageStatus"] != ONLINE_STATUS
|
|
132
133
|
):
|
|
133
|
-
self.http_download_plugin.
|
|
134
|
+
self.http_download_plugin._order(product=product, auth=auth)
|
|
134
135
|
|
|
135
|
-
@self.
|
|
136
|
+
@self._order_download_retry(product, wait, timeout)
|
|
136
137
|
def download_request(
|
|
137
138
|
product: EOProduct,
|
|
138
139
|
auth: AuthBase,
|
|
@@ -142,9 +143,7 @@ class S3RestDownload(Download):
|
|
|
142
143
|
):
|
|
143
144
|
# check order status
|
|
144
145
|
if product.properties.get("orderStatusLink", None):
|
|
145
|
-
self.http_download_plugin.
|
|
146
|
-
product=product, auth=auth
|
|
147
|
-
)
|
|
146
|
+
self.http_download_plugin._order_status(product=product, auth=auth)
|
|
148
147
|
|
|
149
148
|
# get bucket urls
|
|
150
149
|
bucket_name, prefix = get_bucket_name_and_prefix(
|
|
@@ -272,7 +271,7 @@ class S3RestDownload(Download):
|
|
|
272
271
|
os.remove(record_filename)
|
|
273
272
|
|
|
274
273
|
# total size for progress_callback
|
|
275
|
-
size_list:
|
|
274
|
+
size_list: list[int] = [
|
|
276
275
|
int(node.firstChild.nodeValue) # type: ignore[attr-defined]
|
|
277
276
|
for node in xmldoc.getElementsByTagName("Size")
|
|
278
277
|
if node.firstChild is not None
|
eodag/plugins/manager.py
CHANGED
|
@@ -21,18 +21,7 @@ import logging
|
|
|
21
21
|
import re
|
|
22
22
|
from operator import attrgetter
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from typing import
|
|
25
|
-
TYPE_CHECKING,
|
|
26
|
-
Any,
|
|
27
|
-
Dict,
|
|
28
|
-
Iterator,
|
|
29
|
-
List,
|
|
30
|
-
Optional,
|
|
31
|
-
Tuple,
|
|
32
|
-
Type,
|
|
33
|
-
Union,
|
|
34
|
-
cast,
|
|
35
|
-
)
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Iterator, Optional, Union, cast
|
|
36
25
|
|
|
37
26
|
import pkg_resources
|
|
38
27
|
|
|
@@ -61,6 +50,7 @@ if TYPE_CHECKING:
|
|
|
61
50
|
from eodag.api.product import EOProduct
|
|
62
51
|
from eodag.config import PluginConfig, ProviderConfig
|
|
63
52
|
from eodag.plugins.base import PluginTopic
|
|
53
|
+
from eodag.types import S3SessionKwargs
|
|
64
54
|
|
|
65
55
|
|
|
66
56
|
logger = logging.getLogger("eodag.plugins.manager")
|
|
@@ -84,11 +74,11 @@ class PluginManager:
|
|
|
84
74
|
|
|
85
75
|
supported_topics = set(PLUGINS_TOPICS_KEYS)
|
|
86
76
|
|
|
87
|
-
product_type_to_provider_config_map:
|
|
77
|
+
product_type_to_provider_config_map: dict[str, list[ProviderConfig]]
|
|
88
78
|
|
|
89
|
-
skipped_plugins:
|
|
79
|
+
skipped_plugins: list[str]
|
|
90
80
|
|
|
91
|
-
def __init__(self, providers_config:
|
|
81
|
+
def __init__(self, providers_config: dict[str, ProviderConfig]) -> None:
|
|
92
82
|
self.skipped_plugins = []
|
|
93
83
|
self.providers_config = providers_config
|
|
94
84
|
# Load all the plugins. This will make all plugin classes of a particular
|
|
@@ -144,14 +134,14 @@ class PluginManager:
|
|
|
144
134
|
self.rebuild()
|
|
145
135
|
|
|
146
136
|
def rebuild(
|
|
147
|
-
self, providers_config: Optional[
|
|
137
|
+
self, providers_config: Optional[dict[str, ProviderConfig]] = None
|
|
148
138
|
) -> None:
|
|
149
139
|
"""(Re)Build plugin manager mapping and cache"""
|
|
150
140
|
if providers_config is not None:
|
|
151
141
|
self.providers_config = providers_config
|
|
152
142
|
|
|
153
143
|
self.build_product_type_to_provider_config_map()
|
|
154
|
-
self._built_plugins_cache:
|
|
144
|
+
self._built_plugins_cache: dict[tuple[str, str, str], Any] = {}
|
|
155
145
|
|
|
156
146
|
def build_product_type_to_provider_config_map(self) -> None:
|
|
157
147
|
"""Build mapping conf between product types and providers"""
|
|
@@ -211,7 +201,7 @@ class PluginManager:
|
|
|
211
201
|
)
|
|
212
202
|
return plugin
|
|
213
203
|
|
|
214
|
-
configs: Optional[
|
|
204
|
+
configs: Optional[list[ProviderConfig]]
|
|
215
205
|
if product_type:
|
|
216
206
|
configs = self.product_type_to_provider_config_map.get(product_type)
|
|
217
207
|
if not configs:
|
|
@@ -378,7 +368,7 @@ class PluginManager:
|
|
|
378
368
|
provider: str,
|
|
379
369
|
matching_url: Optional[str] = None,
|
|
380
370
|
matching_conf: Optional[PluginConfig] = None,
|
|
381
|
-
) -> Optional[Union[AuthBase,
|
|
371
|
+
) -> Optional[Union[AuthBase, S3SessionKwargs]]:
|
|
382
372
|
"""Authenticate and return the authenticated object for the first matching
|
|
383
373
|
authentication plugin
|
|
384
374
|
|
|
@@ -447,7 +437,7 @@ class PluginManager:
|
|
|
447
437
|
self,
|
|
448
438
|
provider: str,
|
|
449
439
|
plugin_conf: PluginConfig,
|
|
450
|
-
topic_class:
|
|
440
|
+
topic_class: type[PluginTopic],
|
|
451
441
|
) -> Union[Api, Search, Download, Authentication, Crunch]:
|
|
452
442
|
"""Build the plugin of the given topic with the given plugin configuration and
|
|
453
443
|
registered as the given provider
|
eodag/plugins/search/__init__.py
CHANGED
|
@@ -24,11 +24,12 @@ from typing import TYPE_CHECKING
|
|
|
24
24
|
from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
|
-
from typing import Any,
|
|
27
|
+
from typing import Any, Optional, Union
|
|
28
28
|
|
|
29
29
|
from requests.auth import AuthBase
|
|
30
30
|
|
|
31
31
|
from eodag.plugins.authentication.base import Authentication
|
|
32
|
+
from eodag.types import S3SessionKwargs
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@dataclass
|
|
@@ -38,7 +39,7 @@ class PreparedSearch:
|
|
|
38
39
|
product_type: Optional[str] = None
|
|
39
40
|
page: Optional[int] = DEFAULT_PAGE
|
|
40
41
|
items_per_page: Optional[int] = DEFAULT_ITEMS_PER_PAGE
|
|
41
|
-
auth: Optional[Union[AuthBase,
|
|
42
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None
|
|
42
43
|
auth_plugin: Optional[Authentication] = None
|
|
43
44
|
count: bool = True
|
|
44
45
|
url: Optional[str] = None
|
|
@@ -46,9 +47,9 @@ class PreparedSearch:
|
|
|
46
47
|
exception_message: Optional[str] = None
|
|
47
48
|
|
|
48
49
|
need_count: bool = field(init=False, repr=False)
|
|
49
|
-
query_params:
|
|
50
|
+
query_params: dict[str, Any] = field(init=False, repr=False)
|
|
50
51
|
query_string: str = field(init=False, repr=False)
|
|
51
|
-
search_urls:
|
|
52
|
-
product_type_def_params:
|
|
52
|
+
search_urls: list[str] = field(init=False, repr=False)
|
|
53
|
+
product_type_def_params: dict[str, Any] = field(init=False, repr=False)
|
|
53
54
|
total_items_nb: int = field(init=False, repr=False)
|
|
54
55
|
sort_by_qs: str = field(init=False, repr=False)
|
eodag/plugins/search/base.py
CHANGED
|
@@ -31,7 +31,7 @@ from eodag.api.product.metadata_mapping import (
|
|
|
31
31
|
from eodag.plugins.base import PluginTopic
|
|
32
32
|
from eodag.plugins.search import PreparedSearch
|
|
33
33
|
from eodag.types import model_fields_to_annotated
|
|
34
|
-
from eodag.types.queryables import Queryables
|
|
34
|
+
from eodag.types.queryables import Queryables, QueryablesDict
|
|
35
35
|
from eodag.types.search_args import SortByList
|
|
36
36
|
from eodag.utils import (
|
|
37
37
|
GENERIC_PRODUCT_TYPE,
|
|
@@ -43,12 +43,13 @@ from eodag.utils import (
|
|
|
43
43
|
from eodag.utils.exceptions import ValidationError
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING:
|
|
46
|
-
from typing import Any,
|
|
46
|
+
from typing import Any, Optional, Union
|
|
47
47
|
|
|
48
48
|
from requests.auth import AuthBase
|
|
49
49
|
|
|
50
50
|
from eodag.api.product import EOProduct
|
|
51
51
|
from eodag.config import PluginConfig
|
|
52
|
+
from eodag.types import S3SessionKwargs
|
|
52
53
|
|
|
53
54
|
logger = logging.getLogger("eodag.search.base")
|
|
54
55
|
|
|
@@ -60,9 +61,9 @@ class Search(PluginTopic):
|
|
|
60
61
|
:param config: An EODAG plugin configuration
|
|
61
62
|
"""
|
|
62
63
|
|
|
63
|
-
auth: Union[AuthBase,
|
|
64
|
+
auth: Union[AuthBase, S3SessionKwargs]
|
|
64
65
|
next_page_url: Optional[str]
|
|
65
|
-
next_page_query_obj: Optional[
|
|
66
|
+
next_page_query_obj: Optional[dict[str, Any]]
|
|
66
67
|
total_items_nb: int
|
|
67
68
|
need_count: bool
|
|
68
69
|
_request: Any # needed by deprecated load_stac_items
|
|
@@ -71,7 +72,7 @@ class Search(PluginTopic):
|
|
|
71
72
|
super(Search, self).__init__(provider, config)
|
|
72
73
|
# Prepare the metadata mapping
|
|
73
74
|
# Do a shallow copy, the structure is flat enough for this to be sufficient
|
|
74
|
-
metas:
|
|
75
|
+
metas: dict[str, Any] = DEFAULT_METADATA_MAPPING.copy()
|
|
75
76
|
# Update the defaults with the mapping value. This will add any new key
|
|
76
77
|
# added by the provider mapping that is not in the default metadata
|
|
77
78
|
if self.config.metadata_mapping:
|
|
@@ -90,7 +91,7 @@ class Search(PluginTopic):
|
|
|
90
91
|
self,
|
|
91
92
|
prep: PreparedSearch = PreparedSearch(),
|
|
92
93
|
**kwargs: Any,
|
|
93
|
-
) ->
|
|
94
|
+
) -> tuple[list[EOProduct], Optional[int]]:
|
|
94
95
|
"""Implementation of how the products must be searched goes here.
|
|
95
96
|
|
|
96
97
|
This method must return a tuple with (1) a list of :class:`~eodag.api.product._product.EOProduct` instances
|
|
@@ -99,13 +100,13 @@ class Search(PluginTopic):
|
|
|
99
100
|
"""
|
|
100
101
|
raise NotImplementedError("A Search plugin must implement a method named query")
|
|
101
102
|
|
|
102
|
-
def discover_product_types(self, **kwargs: Any) -> Optional[
|
|
103
|
+
def discover_product_types(self, **kwargs: Any) -> Optional[dict[str, Any]]:
|
|
103
104
|
"""Fetch product types list from provider using `discover_product_types` conf"""
|
|
104
105
|
return None
|
|
105
106
|
|
|
106
107
|
def discover_queryables(
|
|
107
108
|
self, **kwargs: Any
|
|
108
|
-
) -> Optional[
|
|
109
|
+
) -> Optional[dict[str, Annotated[Any, FieldInfo]]]:
|
|
109
110
|
"""Fetch queryables list from provider using :attr:`~eodag.config.PluginConfig.discover_queryables` conf
|
|
110
111
|
|
|
111
112
|
:param kwargs: additional filters for queryables (``productType`` and other search
|
|
@@ -118,7 +119,7 @@ class Search(PluginTopic):
|
|
|
118
119
|
|
|
119
120
|
def _get_defaults_as_queryables(
|
|
120
121
|
self, product_type: str
|
|
121
|
-
) ->
|
|
122
|
+
) -> dict[str, Annotated[Any, FieldInfo]]:
|
|
122
123
|
"""
|
|
123
124
|
Return given product type default settings as queryables
|
|
124
125
|
|
|
@@ -128,7 +129,7 @@ class Search(PluginTopic):
|
|
|
128
129
|
defaults = deepcopy(self.config.products.get(product_type, {}))
|
|
129
130
|
defaults.pop("metadata_mapping", None)
|
|
130
131
|
|
|
131
|
-
queryables:
|
|
132
|
+
queryables: dict[str, Annotated[Any, FieldInfo]] = {}
|
|
132
133
|
for parameter, value in defaults.items():
|
|
133
134
|
queryables[parameter] = Annotated[type(value), Field(default=value)]
|
|
134
135
|
return queryables
|
|
@@ -149,8 +150,8 @@ class Search(PluginTopic):
|
|
|
149
150
|
)
|
|
150
151
|
|
|
151
152
|
def get_product_type_def_params(
|
|
152
|
-
self, product_type: str,
|
|
153
|
-
) ->
|
|
153
|
+
self, product_type: str, format_variables: Optional[dict[str, Any]] = None
|
|
154
|
+
) -> dict[str, Any]:
|
|
154
155
|
"""Get the provider product type definition parameters and specific settings
|
|
155
156
|
|
|
156
157
|
:param product_type: the desired product type
|
|
@@ -170,7 +171,8 @@ class Search(PluginTopic):
|
|
|
170
171
|
return {
|
|
171
172
|
k: v
|
|
172
173
|
for k, v in format_dict_items(
|
|
173
|
-
self.config.products[GENERIC_PRODUCT_TYPE],
|
|
174
|
+
self.config.products[GENERIC_PRODUCT_TYPE],
|
|
175
|
+
**(format_variables or {}),
|
|
174
176
|
).items()
|
|
175
177
|
if v
|
|
176
178
|
}
|
|
@@ -200,7 +202,7 @@ class Search(PluginTopic):
|
|
|
200
202
|
|
|
201
203
|
def get_metadata_mapping(
|
|
202
204
|
self, product_type: Optional[str] = None
|
|
203
|
-
) ->
|
|
205
|
+
) -> dict[str, Union[str, list[str]]]:
|
|
204
206
|
"""Get the plugin metadata mapping configuration (product type specific if exists)
|
|
205
207
|
|
|
206
208
|
:param product_type: the desired product type
|
|
@@ -212,7 +214,7 @@ class Search(PluginTopic):
|
|
|
212
214
|
)
|
|
213
215
|
return self.config.metadata_mapping
|
|
214
216
|
|
|
215
|
-
def get_sort_by_arg(self, kwargs:
|
|
217
|
+
def get_sort_by_arg(self, kwargs: dict[str, Any]) -> Optional[SortByList]:
|
|
216
218
|
"""Extract the ``sort_by`` argument from the kwargs or the provider default sort configuration
|
|
217
219
|
|
|
218
220
|
:param kwargs: Search arguments
|
|
@@ -233,7 +235,7 @@ class Search(PluginTopic):
|
|
|
233
235
|
|
|
234
236
|
def build_sort_by(
|
|
235
237
|
self, sort_by_arg: SortByList
|
|
236
|
-
) ->
|
|
238
|
+
) -> tuple[str, dict[str, list[dict[str, str]]]]:
|
|
237
239
|
"""Build the sorting part of the query string or body by transforming
|
|
238
240
|
the ``sort_by`` argument into a provider-specific string or dictionary
|
|
239
241
|
|
|
@@ -247,9 +249,9 @@ class Search(PluginTopic):
|
|
|
247
249
|
sort_by_arg = list(dict.fromkeys(sort_by_arg))
|
|
248
250
|
|
|
249
251
|
sort_by_qs: str = ""
|
|
250
|
-
sort_by_qp:
|
|
252
|
+
sort_by_qp: dict[str, Any] = {}
|
|
251
253
|
|
|
252
|
-
provider_sort_by_tuples_used:
|
|
254
|
+
provider_sort_by_tuples_used: list[tuple[str, str]] = []
|
|
253
255
|
for eodag_sort_by_tuple in sort_by_arg:
|
|
254
256
|
eodag_sort_param = eodag_sort_by_tuple[0]
|
|
255
257
|
provider_sort_param = self.config.sort["sort_param_mapping"].get(
|
|
@@ -282,7 +284,7 @@ class Search(PluginTopic):
|
|
|
282
284
|
if eodag_sort_order == "ASC"
|
|
283
285
|
else self.config.sort["sort_order_mapping"]["descending"]
|
|
284
286
|
)
|
|
285
|
-
provider_sort_by_tuple:
|
|
287
|
+
provider_sort_by_tuple: tuple[str, str] = (
|
|
286
288
|
provider_sort_param,
|
|
287
289
|
provider_sort_order,
|
|
288
290
|
)
|
|
@@ -315,7 +317,7 @@ class Search(PluginTopic):
|
|
|
315
317
|
sort_order=provider_sort_by_tuple[1],
|
|
316
318
|
)
|
|
317
319
|
try:
|
|
318
|
-
parsed_sort_by_tpl_dict:
|
|
320
|
+
parsed_sort_by_tpl_dict: dict[str, Any] = orjson.loads(
|
|
319
321
|
parsed_sort_by_tpl
|
|
320
322
|
)
|
|
321
323
|
sort_by_qp = update_nested_dict(
|
|
@@ -325,35 +327,87 @@ class Search(PluginTopic):
|
|
|
325
327
|
sort_by_qs += parsed_sort_by_tpl
|
|
326
328
|
return (sort_by_qs, sort_by_qp)
|
|
327
329
|
|
|
330
|
+
def _get_product_type_queryables(
|
|
331
|
+
self, product_type: Optional[str], alias: Optional[str], filters: dict[str, Any]
|
|
332
|
+
) -> QueryablesDict:
|
|
333
|
+
default_values: dict[str, Any] = deepcopy(
|
|
334
|
+
getattr(self.config, "products", {}).get(product_type, {})
|
|
335
|
+
)
|
|
336
|
+
default_values.pop("metadata_mapping", None)
|
|
337
|
+
try:
|
|
338
|
+
filters["productType"] = product_type
|
|
339
|
+
queryables = self.discover_queryables(**{**default_values, **filters}) or {}
|
|
340
|
+
except NotImplementedError:
|
|
341
|
+
queryables = self.queryables_from_metadata_mapping(product_type, alias)
|
|
342
|
+
|
|
343
|
+
return QueryablesDict(**queryables)
|
|
344
|
+
|
|
328
345
|
def list_queryables(
|
|
329
346
|
self,
|
|
330
|
-
filters:
|
|
347
|
+
filters: dict[str, Any],
|
|
348
|
+
available_product_types: list[Any],
|
|
349
|
+
product_type_configs: dict[str, dict[str, Any]],
|
|
331
350
|
product_type: Optional[str] = None,
|
|
332
|
-
|
|
351
|
+
alias: Optional[str] = None,
|
|
352
|
+
) -> QueryablesDict:
|
|
333
353
|
"""
|
|
334
354
|
Get queryables
|
|
335
355
|
|
|
336
356
|
:param filters: Additional filters for queryables.
|
|
357
|
+
:param available_product_types: list of available product types
|
|
358
|
+
:param product_type_configs: dict containing the product type information for all used product types
|
|
337
359
|
:param product_type: (optional) The product type.
|
|
360
|
+
:param alias: (optional) alias of the product type
|
|
338
361
|
|
|
339
362
|
:return: A dictionary containing the queryable properties, associating parameters to their
|
|
340
363
|
annotated type.
|
|
341
364
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
365
|
+
additional_info = (
|
|
366
|
+
"Please select a product type to get the possible values of the parameters!"
|
|
367
|
+
if not product_type
|
|
368
|
+
else ""
|
|
344
369
|
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
370
|
+
if product_type or getattr(self.config, "discover_queryables", {}).get(
|
|
371
|
+
"fetch_url", ""
|
|
372
|
+
):
|
|
373
|
+
if product_type:
|
|
374
|
+
self.config.product_type_config = product_type_configs[product_type]
|
|
375
|
+
queryables = self._get_product_type_queryables(product_type, alias, filters)
|
|
376
|
+
queryables.additional_information = additional_info
|
|
377
|
+
|
|
378
|
+
return queryables
|
|
379
|
+
else:
|
|
380
|
+
all_queryables: dict[str, Any] = {}
|
|
381
|
+
for pt in available_product_types:
|
|
382
|
+
self.config.product_type_config = product_type_configs[pt]
|
|
383
|
+
pt_queryables = self._get_product_type_queryables(pt, None, filters)
|
|
384
|
+
# only use key and type because values and defaults will vary between product types
|
|
385
|
+
pt_queryables_neutral = {
|
|
386
|
+
k: Annotated[v.__args__[0], Field(default=None)]
|
|
387
|
+
for k, v in pt_queryables.items()
|
|
388
|
+
}
|
|
389
|
+
all_queryables.update(pt_queryables_neutral)
|
|
390
|
+
return QueryablesDict(
|
|
391
|
+
additional_properties=True,
|
|
392
|
+
additional_information=additional_info,
|
|
393
|
+
**all_queryables,
|
|
394
|
+
)
|
|
352
395
|
|
|
353
|
-
|
|
396
|
+
def queryables_from_metadata_mapping(
|
|
397
|
+
self, product_type: Optional[str] = None, alias: Optional[str] = None
|
|
398
|
+
) -> dict[str, Annotated[Any, FieldInfo]]:
|
|
399
|
+
"""
|
|
400
|
+
Extract queryable parameters from product type metadata mapping.
|
|
401
|
+
:param product_type: product type id (optional)
|
|
402
|
+
:param alias: (optional) alias of the product type
|
|
403
|
+
:returns: dict of annotated queryables
|
|
404
|
+
"""
|
|
405
|
+
metadata_mapping: dict[str, Any] = deepcopy(
|
|
354
406
|
self.get_metadata_mapping(product_type)
|
|
355
407
|
)
|
|
356
408
|
|
|
409
|
+
queryables: dict[str, Annotated[Any, FieldInfo]] = {}
|
|
410
|
+
|
|
357
411
|
for param in list(metadata_mapping.keys()):
|
|
358
412
|
if NOT_MAPPED in metadata_mapping[param] or not isinstance(
|
|
359
413
|
metadata_mapping[param], list
|
|
@@ -363,28 +417,18 @@ class Search(PluginTopic):
|
|
|
363
417
|
eodag_queryables = copy_deepcopy(
|
|
364
418
|
model_fields_to_annotated(Queryables.model_fields)
|
|
365
419
|
)
|
|
420
|
+
# add default value for product type
|
|
421
|
+
if alias:
|
|
422
|
+
eodag_queryables.pop("productType")
|
|
423
|
+
eodag_queryables["productType"] = Annotated[str, Field(default=alias)]
|
|
366
424
|
for k, v in eodag_queryables.items():
|
|
367
425
|
eodag_queryable_field_info = (
|
|
368
426
|
get_args(v)[1] if len(get_args(v)) > 1 else None
|
|
369
427
|
)
|
|
370
428
|
if not isinstance(eodag_queryable_field_info, FieldInfo):
|
|
371
429
|
continue
|
|
372
|
-
# keep default field info of eodag queryables
|
|
373
|
-
if k in filters and k in queryables:
|
|
374
|
-
queryable_field_info = (
|
|
375
|
-
get_args(queryables[k])[1]
|
|
376
|
-
if len(get_args(queryables[k])) > 1
|
|
377
|
-
else None
|
|
378
|
-
)
|
|
379
|
-
if not isinstance(queryable_field_info, FieldInfo):
|
|
380
|
-
continue
|
|
381
|
-
queryable_field_info.default = filters[k]
|
|
382
|
-
continue
|
|
383
|
-
if k in queryables:
|
|
384
|
-
continue
|
|
385
430
|
if eodag_queryable_field_info.is_required() or (
|
|
386
431
|
(eodag_queryable_field_info.alias or k) in metadata_mapping
|
|
387
432
|
):
|
|
388
433
|
queryables[k] = v
|
|
389
|
-
|
|
390
434
|
return queryables
|