eodag 3.2.1__py3-none-any.whl → 3.3.1__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 +2 -1
- eodag/api/search_result.py +5 -8
- eodag/config.py +6 -0
- eodag/plugins/authentication/token.py +102 -50
- eodag/plugins/download/http.py +46 -17
- eodag/plugins/search/base.py +9 -7
- eodag/plugins/search/build_search_result.py +269 -83
- eodag/plugins/search/qssearch.py +15 -14
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/providers.yml +57 -10
- eodag/rest/errors.py +18 -3
- eodag/types/queryables.py +3 -0
- eodag/utils/__init__.py +7 -1
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/METADATA +2 -2
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/RECORD +19 -19
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/WHEEL +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/entry_points.txt +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/licenses/LICENSE +0 -0
- {eodag-3.2.1.dist-info → eodag-3.3.1.dist-info}/top_level.txt +0 -0
eodag/api/core.py
CHANGED
|
@@ -1599,6 +1599,7 @@ class EODataAccessGateway:
|
|
|
1599
1599
|
if kwargs.get("raise_errors"):
|
|
1600
1600
|
raise
|
|
1601
1601
|
logger.warning(e)
|
|
1602
|
+
results.errors.append((plugin.provider, e))
|
|
1602
1603
|
continue
|
|
1603
1604
|
|
|
1604
1605
|
# try using crunch to get unique result
|
|
@@ -1622,7 +1623,7 @@ class EODataAccessGateway:
|
|
|
1622
1623
|
"Several products found for this id (%s). You may try searching using more selective criteria.",
|
|
1623
1624
|
results,
|
|
1624
1625
|
)
|
|
1625
|
-
return SearchResult([], 0)
|
|
1626
|
+
return SearchResult([], 0, results.errors)
|
|
1626
1627
|
|
|
1627
1628
|
def _fetch_external_product_type(self, provider: str, product_type: str):
|
|
1628
1629
|
plugins = self._plugins_manager.get_search_plugins(provider=provider)
|
eodag/api/search_result.py
CHANGED
|
@@ -36,7 +36,7 @@ if TYPE_CHECKING:
|
|
|
36
36
|
from eodag.plugins.crunch.base import Crunch
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
class SearchResult(UserList):
|
|
39
|
+
class SearchResult(UserList[EOProduct]):
|
|
40
40
|
"""An object representing a collection of :class:`~eodag.api.product._product.EOProduct` resulting from a search.
|
|
41
41
|
|
|
42
42
|
:param products: A list of products resulting from a search
|
|
@@ -46,8 +46,6 @@ class SearchResult(UserList):
|
|
|
46
46
|
:ivar number_matched: Estimated total number of matching results
|
|
47
47
|
"""
|
|
48
48
|
|
|
49
|
-
data: list[EOProduct]
|
|
50
|
-
|
|
51
49
|
errors: Annotated[
|
|
52
50
|
list[tuple[str, Exception]], Doc("Tuple of provider name, exception")
|
|
53
51
|
]
|
|
@@ -56,11 +54,11 @@ class SearchResult(UserList):
|
|
|
56
54
|
self,
|
|
57
55
|
products: list[EOProduct],
|
|
58
56
|
number_matched: Optional[int] = None,
|
|
59
|
-
errors: list[tuple[str, Exception]] =
|
|
57
|
+
errors: Optional[list[tuple[str, Exception]]] = None,
|
|
60
58
|
) -> None:
|
|
61
59
|
super().__init__(products)
|
|
62
60
|
self.number_matched = number_matched
|
|
63
|
-
self.errors = errors
|
|
61
|
+
self.errors = errors if errors is not None else []
|
|
64
62
|
|
|
65
63
|
def crunch(self, cruncher: Crunch, **search_params: Any) -> SearchResult:
|
|
66
64
|
"""Do some crunching with the underlying EO products.
|
|
@@ -193,7 +191,7 @@ class SearchResult(UserList):
|
|
|
193
191
|
<details><summary style='color: grey; font-family: monospace;'>
|
|
194
192
|
{i} 
|
|
195
193
|
{type(p).__name__}(id=<span style='color: black;'>{
|
|
196
|
-
p.properties[
|
|
194
|
+
p.properties["id"]
|
|
197
195
|
}</span>, provider={p.provider})
|
|
198
196
|
</summary>
|
|
199
197
|
{p._repr_html_()}
|
|
@@ -214,13 +212,12 @@ class SearchResult(UserList):
|
|
|
214
212
|
return super().extend(other)
|
|
215
213
|
|
|
216
214
|
|
|
217
|
-
class RawSearchResult(UserList):
|
|
215
|
+
class RawSearchResult(UserList[dict[str, Any]]):
|
|
218
216
|
"""An object representing a collection of raw/unparsed search results obtained from a provider.
|
|
219
217
|
|
|
220
218
|
:param results: A list of raw/unparsed search results
|
|
221
219
|
"""
|
|
222
220
|
|
|
223
|
-
data: list[Any]
|
|
224
221
|
query_params: dict[str, Any]
|
|
225
222
|
product_type_def_params: dict[str, Any]
|
|
226
223
|
|
eodag/config.py
CHANGED
|
@@ -615,6 +615,12 @@ class PluginConfig(yaml.YAMLObject):
|
|
|
615
615
|
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
|
|
616
616
|
#: type of the token
|
|
617
617
|
token_type: str
|
|
618
|
+
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
|
|
619
|
+
#: key to get the expiration time of the token
|
|
620
|
+
token_expiration_key: str
|
|
621
|
+
#: :class:`~eodag.plugins.authentication.token.TokenAuth`
|
|
622
|
+
#: HTTP method to use
|
|
623
|
+
request_method: str
|
|
618
624
|
#: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
|
|
619
625
|
#: The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin configuration
|
|
620
626
|
#: used to retrieve subject token
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
+
from datetime import datetime, timedelta
|
|
22
|
+
from threading import Lock
|
|
21
23
|
from typing import TYPE_CHECKING, Any, Optional
|
|
22
24
|
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
23
25
|
|
|
@@ -72,8 +74,11 @@ class TokenAuth(Authentication):
|
|
|
72
74
|
key to get the access token in the response to the token request
|
|
73
75
|
* :attr:`~eodag.config.PluginConfig.refresh_token_key` (``str``): key to get the refresh
|
|
74
76
|
token in the response to the token request
|
|
77
|
+
* :attr:`~eodag.config.PluginConfig.token_expiration_key` (``str``): key to get expiration time of
|
|
78
|
+
the token (given in s)
|
|
75
79
|
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
|
|
76
80
|
should be verified in the requests; default: ``True``
|
|
81
|
+
* :attr:`~eodag.config.PluginConfig.request_method` (``str``): HTTP method to use; default: ``POST``
|
|
77
82
|
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
|
|
78
83
|
returned in case of an authentication error
|
|
79
84
|
* :attr:`~eodag.config.PluginConfig.req_data` (``dict[str, Any]``): if the credentials
|
|
@@ -91,6 +96,20 @@ class TokenAuth(Authentication):
|
|
|
91
96
|
super(TokenAuth, self).__init__(provider, config)
|
|
92
97
|
self.token = ""
|
|
93
98
|
self.refresh_token = ""
|
|
99
|
+
self.token_expiration = datetime.now()
|
|
100
|
+
self.auth_lock = Lock()
|
|
101
|
+
|
|
102
|
+
def __getstate__(self):
|
|
103
|
+
"""Exclude attributes that can't be pickled from serialization."""
|
|
104
|
+
state = dict(self.__dict__)
|
|
105
|
+
del state["auth_lock"]
|
|
106
|
+
return state
|
|
107
|
+
|
|
108
|
+
def __setstate__(self, state):
|
|
109
|
+
"""Exclude attributes that can't be pickled from deserialization."""
|
|
110
|
+
self.__dict__.update(state)
|
|
111
|
+
# Init them manually
|
|
112
|
+
self.auth_lock = Lock()
|
|
94
113
|
|
|
95
114
|
def validate_config_credentials(self) -> None:
|
|
96
115
|
"""Validate configured credentials"""
|
|
@@ -130,55 +149,72 @@ class TokenAuth(Authentication):
|
|
|
130
149
|
|
|
131
150
|
def authenticate(self) -> AuthBase:
|
|
132
151
|
"""Authenticate"""
|
|
133
|
-
self.validate_config_credentials()
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
153
|
+
# Use a thread lock to avoid several threads requesting the token at the same time
|
|
154
|
+
with self.auth_lock:
|
|
155
|
+
|
|
156
|
+
self.validate_config_credentials()
|
|
157
|
+
if self.token and self.token_expiration > datetime.now():
|
|
158
|
+
logger.debug("using existing access token")
|
|
159
|
+
return RequestsTokenAuth(
|
|
160
|
+
self.token, "header", headers=getattr(self.config, "headers", {})
|
|
161
|
+
)
|
|
162
|
+
s = requests.Session()
|
|
163
|
+
try:
|
|
164
|
+
# First get the token
|
|
165
|
+
response = self._token_request(session=s)
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
except requests.exceptions.Timeout as exc:
|
|
168
|
+
raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
|
|
169
|
+
except RequestException as e:
|
|
170
|
+
response_text = getattr(e.response, "text", "").strip()
|
|
171
|
+
# check if error is identified as auth_error in provider conf
|
|
172
|
+
auth_errors = getattr(self.config, "auth_error_code", [None])
|
|
173
|
+
if not isinstance(auth_errors, list):
|
|
174
|
+
auth_errors = [auth_errors]
|
|
175
|
+
if (
|
|
176
|
+
e.response is not None
|
|
177
|
+
and getattr(e.response, "status_code", None)
|
|
178
|
+
and e.response.status_code in auth_errors
|
|
179
|
+
):
|
|
180
|
+
raise AuthenticationError(
|
|
181
|
+
f"Please check your credentials for {self.provider}.",
|
|
182
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
183
|
+
response_text,
|
|
184
|
+
) from e
|
|
185
|
+
# other error
|
|
186
|
+
else:
|
|
187
|
+
raise AuthenticationError(
|
|
188
|
+
"Could no get authentication token", str(e), response_text
|
|
189
|
+
) from e
|
|
166
190
|
else:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
191
|
+
if getattr(self.config, "token_type", "text") == "json":
|
|
192
|
+
token = response.json()[self.config.token_key]
|
|
193
|
+
else:
|
|
194
|
+
token = response.text
|
|
195
|
+
self.token = token
|
|
196
|
+
if getattr(self.config, "refresh_token_key", None):
|
|
197
|
+
self.refresh_token = response.json()[self.config.refresh_token_key]
|
|
198
|
+
if getattr(self.config, "token_expiration_key", None):
|
|
199
|
+
expiration_time = response.json()[self.config.token_expiration_key]
|
|
200
|
+
self.token_expiration = datetime.now() + timedelta(
|
|
201
|
+
seconds=expiration_time
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if not hasattr(self.config, "headers"):
|
|
205
|
+
raise MisconfiguredError(
|
|
206
|
+
f"Missing headers configuration for {self}"
|
|
207
|
+
)
|
|
208
|
+
# Return auth class set with obtained token
|
|
209
|
+
return RequestsTokenAuth(
|
|
210
|
+
token, "header", headers=getattr(self.config, "headers", {})
|
|
211
|
+
)
|
|
177
212
|
|
|
178
213
|
def _token_request(
|
|
179
214
|
self,
|
|
180
215
|
session: requests.Session,
|
|
181
216
|
) -> requests.Response:
|
|
217
|
+
|
|
182
218
|
retry_total = getattr(self.config, "retry_total", REQ_RETRY_TOTAL)
|
|
183
219
|
retry_backoff_factor = getattr(
|
|
184
220
|
self.config, "retry_backoff_factor", REQ_RETRY_BACKOFF_FACTOR
|
|
@@ -202,14 +238,36 @@ class TokenAuth(Authentication):
|
|
|
202
238
|
# append headers to req if some are specified in config
|
|
203
239
|
req_kwargs: dict[str, Any] = {"headers": dict(headers, **USER_AGENT)}
|
|
204
240
|
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
241
|
+
method = getattr(self.config, "request_method", "POST")
|
|
242
|
+
|
|
243
|
+
def set_request_data(call_refresh: bool) -> None:
|
|
244
|
+
"""Set the request data contents for POST requests"""
|
|
245
|
+
if method != "POST":
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
# append req_data to credentials if specified in config
|
|
249
|
+
data = dict(getattr(self.config, "req_data", {}), **self.config.credentials)
|
|
250
|
+
|
|
251
|
+
# when refreshing the token, we pass only the client_id/secret if present,
|
|
252
|
+
# not other parameters (username/password, scope, ...)
|
|
253
|
+
if call_refresh:
|
|
254
|
+
data = {
|
|
255
|
+
k: v for k, v in data.items() if k in ["client_id", "client_secret"]
|
|
256
|
+
}
|
|
257
|
+
# the grant type is always refresh_token
|
|
258
|
+
data["grant_type"] = "refresh_token"
|
|
259
|
+
# and we add the old refresh token value to the request
|
|
260
|
+
data["refresh_token"] = self.refresh_token
|
|
261
|
+
|
|
262
|
+
req_kwargs["data"] = data
|
|
205
263
|
|
|
206
264
|
if self.refresh_token:
|
|
207
265
|
logger.debug("fetching access token with refresh token")
|
|
208
266
|
session.mount(self.config.refresh_uri, HTTPAdapter(max_retries=retries))
|
|
267
|
+
set_request_data(call_refresh=True)
|
|
209
268
|
try:
|
|
210
269
|
response = session.post(
|
|
211
270
|
self.config.refresh_uri,
|
|
212
|
-
data={"refresh_token": self.refresh_token},
|
|
213
271
|
timeout=HTTP_REQ_TIMEOUT,
|
|
214
272
|
verify=ssl_verify,
|
|
215
273
|
**req_kwargs,
|
|
@@ -222,14 +280,8 @@ class TokenAuth(Authentication):
|
|
|
222
280
|
logger.debug("fetching access token from %s", self.config.auth_uri)
|
|
223
281
|
# append headers to req if some are specified in config
|
|
224
282
|
session.mount(self.config.auth_uri, HTTPAdapter(max_retries=retries))
|
|
225
|
-
method = getattr(self.config, "request_method", "POST")
|
|
226
283
|
|
|
227
|
-
|
|
228
|
-
if method == "POST":
|
|
229
|
-
# append req_data to credentials if specified in config
|
|
230
|
-
req_kwargs["data"] = dict(
|
|
231
|
-
getattr(self.config, "req_data", {}), **self.config.credentials
|
|
232
|
-
)
|
|
284
|
+
set_request_data(call_refresh=False)
|
|
233
285
|
|
|
234
286
|
# credentials as auth tuple if possible
|
|
235
287
|
req_kwargs["auth"] = (
|
eodag/plugins/download/http.py
CHANGED
|
@@ -216,7 +216,7 @@ class HTTPDownload(Download):
|
|
|
216
216
|
product.properties["storageStatus"] = STAGING_STATUS
|
|
217
217
|
except RequestException as e:
|
|
218
218
|
self._check_auth_exception(e)
|
|
219
|
-
msg = f
|
|
219
|
+
msg = f"{product.properties['title']} could not be ordered"
|
|
220
220
|
if e.response is not None and e.response.status_code == 400:
|
|
221
221
|
raise ValidationError.from_error(e, msg) from e
|
|
222
222
|
else:
|
|
@@ -255,6 +255,16 @@ class HTTPDownload(Download):
|
|
|
255
255
|
product.properties.update(
|
|
256
256
|
{k: v for k, v in properties_update.items() if v != NOT_AVAILABLE}
|
|
257
257
|
)
|
|
258
|
+
# the job id becomes the product id for EcmwfSearch products
|
|
259
|
+
if "ORDERABLE" in product.properties.get("id", ""):
|
|
260
|
+
product.properties["id"] = product.properties.get(
|
|
261
|
+
"orderId", product.properties["id"]
|
|
262
|
+
)
|
|
263
|
+
product.properties["title"] = (
|
|
264
|
+
(product.product_type or product.provider).upper()
|
|
265
|
+
+ "_"
|
|
266
|
+
+ product.properties["id"]
|
|
267
|
+
)
|
|
258
268
|
if "downloadLink" in product.properties:
|
|
259
269
|
product.remote_location = product.location = product.properties[
|
|
260
270
|
"downloadLink"
|
|
@@ -390,7 +400,10 @@ class HTTPDownload(Download):
|
|
|
390
400
|
# success and no need to get status response content
|
|
391
401
|
skip_parsing_status_response = True
|
|
392
402
|
except RequestException as e:
|
|
393
|
-
msg =
|
|
403
|
+
msg = (
|
|
404
|
+
f"{product.properties.get('title') or product.properties.get('id') or product} "
|
|
405
|
+
"order status could not be checked"
|
|
406
|
+
)
|
|
394
407
|
if e.response is not None and e.response.status_code == 400:
|
|
395
408
|
raise ValidationError.from_error(e, msg) from e
|
|
396
409
|
else:
|
|
@@ -426,9 +439,14 @@ class HTTPDownload(Download):
|
|
|
426
439
|
f"{product.properties['title']} order status: {status_percent}"
|
|
427
440
|
)
|
|
428
441
|
|
|
429
|
-
|
|
442
|
+
product.properties.update(
|
|
443
|
+
{k: v for k, v in status_dict.items() if v != NOT_AVAILABLE}
|
|
444
|
+
)
|
|
445
|
+
|
|
430
446
|
product.properties["orderStatus"] = status_dict.get("status")
|
|
431
447
|
|
|
448
|
+
status_message = status_dict.get("message")
|
|
449
|
+
|
|
432
450
|
# handle status error
|
|
433
451
|
errors: dict[str, Any] = status_config.get("error", {})
|
|
434
452
|
if errors and errors.items() <= status_dict.items():
|
|
@@ -436,13 +454,14 @@ class HTTPDownload(Download):
|
|
|
436
454
|
f"Provider {product.provider} returned: {status_dict.get('error_message', status_message)}"
|
|
437
455
|
)
|
|
438
456
|
|
|
457
|
+
product.properties["storageStatus"] = STAGING_STATUS
|
|
458
|
+
|
|
439
459
|
success_status: dict[str, Any] = status_config.get("success", {}).get("status")
|
|
440
460
|
# if not success
|
|
441
461
|
if (success_status and success_status != status_dict.get("status")) or (
|
|
442
462
|
success_code and success_code != response.status_code
|
|
443
463
|
):
|
|
444
|
-
|
|
445
|
-
raise error
|
|
464
|
+
return None
|
|
446
465
|
|
|
447
466
|
product.properties["storageStatus"] = ONLINE_STATUS
|
|
448
467
|
|
|
@@ -461,7 +480,11 @@ class HTTPDownload(Download):
|
|
|
461
480
|
product.properties["title"],
|
|
462
481
|
e,
|
|
463
482
|
)
|
|
464
|
-
|
|
483
|
+
msg = f"{product.properties['title']} order status could not be checked"
|
|
484
|
+
if e.response is not None and e.response.status_code == 400:
|
|
485
|
+
raise ValidationError.from_error(e, msg) from e
|
|
486
|
+
else:
|
|
487
|
+
raise DownloadError.from_error(e, msg) from e
|
|
465
488
|
|
|
466
489
|
result_type = config_on_success.get("result_type", "json")
|
|
467
490
|
result_entry = config_on_success.get("results_entry")
|
|
@@ -626,6 +649,8 @@ class HTTPDownload(Download):
|
|
|
626
649
|
if fs_path is not None:
|
|
627
650
|
ext = Path(product.filename).suffix
|
|
628
651
|
path = Path(fs_path).with_suffix(ext)
|
|
652
|
+
if "ORDERABLE" in path.stem and product.properties.get("title"):
|
|
653
|
+
path = path.with_stem(sanitize(product.properties["title"]))
|
|
629
654
|
|
|
630
655
|
with open(path, "wb") as fhandle:
|
|
631
656
|
for chunk in chunk_iterator:
|
|
@@ -961,17 +986,21 @@ class HTTPDownload(Download):
|
|
|
961
986
|
auth = None
|
|
962
987
|
|
|
963
988
|
s = requests.Session()
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
989
|
+
try:
|
|
990
|
+
self.stream = s.request(
|
|
991
|
+
req_method,
|
|
992
|
+
req_url,
|
|
993
|
+
stream=True,
|
|
994
|
+
auth=auth,
|
|
995
|
+
params=params,
|
|
996
|
+
headers=USER_AGENT,
|
|
997
|
+
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
998
|
+
verify=ssl_verify,
|
|
999
|
+
**req_kwargs,
|
|
1000
|
+
)
|
|
1001
|
+
except requests.exceptions.MissingSchema:
|
|
1002
|
+
# location is not a valid url -> product is not available yet
|
|
1003
|
+
raise NotAvailableError("Product is not available yet")
|
|
975
1004
|
try:
|
|
976
1005
|
self.stream.raise_for_status()
|
|
977
1006
|
except requests.exceptions.Timeout as exc:
|
eodag/plugins/search/base.py
CHANGED
|
@@ -337,7 +337,9 @@ class Search(PluginTopic):
|
|
|
337
337
|
try:
|
|
338
338
|
filters["productType"] = product_type
|
|
339
339
|
queryables = self.discover_queryables(**{**default_values, **filters}) or {}
|
|
340
|
-
except NotImplementedError:
|
|
340
|
+
except NotImplementedError as e:
|
|
341
|
+
if str(e):
|
|
342
|
+
logger.debug(str(e))
|
|
341
343
|
queryables = self.queryables_from_metadata_mapping(product_type, alias)
|
|
342
344
|
|
|
343
345
|
return QueryablesDict(**queryables)
|
|
@@ -381,12 +383,12 @@ class Search(PluginTopic):
|
|
|
381
383
|
for pt in available_product_types:
|
|
382
384
|
self.config.product_type_config = product_type_configs[pt]
|
|
383
385
|
pt_queryables = self._get_product_type_queryables(pt, None, filters)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
386
|
+
all_queryables.update(pt_queryables)
|
|
387
|
+
# reset defaults because they may vary between product types
|
|
388
|
+
for k, v in all_queryables.items():
|
|
389
|
+
v.__metadata__[0].default = getattr(
|
|
390
|
+
Queryables.model_fields.get(k, Field(None)), "default", None
|
|
391
|
+
)
|
|
390
392
|
return QueryablesDict(
|
|
391
393
|
additional_properties=True,
|
|
392
394
|
additional_information=additional_info,
|