eodag 2.12.0__py3-none-any.whl → 3.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. eodag/api/core.py +434 -319
  2. eodag/api/product/__init__.py +5 -1
  3. eodag/api/product/_assets.py +7 -2
  4. eodag/api/product/_product.py +46 -68
  5. eodag/api/product/metadata_mapping.py +181 -66
  6. eodag/api/search_result.py +21 -1
  7. eodag/cli.py +20 -6
  8. eodag/config.py +95 -6
  9. eodag/plugins/apis/base.py +8 -162
  10. eodag/plugins/apis/ecmwf.py +36 -24
  11. eodag/plugins/apis/usgs.py +40 -24
  12. eodag/plugins/authentication/aws_auth.py +2 -2
  13. eodag/plugins/authentication/header.py +31 -6
  14. eodag/plugins/authentication/keycloak.py +13 -84
  15. eodag/plugins/authentication/oauth.py +3 -3
  16. eodag/plugins/authentication/openid_connect.py +256 -46
  17. eodag/plugins/authentication/qsauth.py +3 -0
  18. eodag/plugins/authentication/sas_auth.py +8 -1
  19. eodag/plugins/authentication/token.py +92 -46
  20. eodag/plugins/authentication/token_exchange.py +120 -0
  21. eodag/plugins/download/aws.py +86 -91
  22. eodag/plugins/download/base.py +72 -40
  23. eodag/plugins/download/http.py +607 -264
  24. eodag/plugins/download/s3rest.py +28 -15
  25. eodag/plugins/manager.py +73 -57
  26. eodag/plugins/search/__init__.py +36 -0
  27. eodag/plugins/search/base.py +225 -18
  28. eodag/plugins/search/build_search_result.py +389 -32
  29. eodag/plugins/search/cop_marine.py +378 -0
  30. eodag/plugins/search/creodias_s3.py +15 -14
  31. eodag/plugins/search/csw.py +5 -7
  32. eodag/plugins/search/data_request_search.py +44 -20
  33. eodag/plugins/search/qssearch.py +508 -203
  34. eodag/plugins/search/static_stac_search.py +99 -36
  35. eodag/resources/constraints/climate-dt.json +13 -0
  36. eodag/resources/constraints/extremes-dt.json +8 -0
  37. eodag/resources/ext_product_types.json +1 -1
  38. eodag/resources/product_types.yml +1897 -34
  39. eodag/resources/providers.yml +3539 -3277
  40. eodag/resources/stac.yml +48 -54
  41. eodag/resources/stac_api.yml +71 -25
  42. eodag/resources/stac_provider.yml +5 -0
  43. eodag/resources/user_conf_template.yml +51 -3
  44. eodag/rest/__init__.py +6 -0
  45. eodag/rest/cache.py +70 -0
  46. eodag/rest/config.py +68 -0
  47. eodag/rest/constants.py +27 -0
  48. eodag/rest/core.py +757 -0
  49. eodag/rest/server.py +397 -258
  50. eodag/rest/stac.py +438 -307
  51. eodag/rest/types/collections_search.py +44 -0
  52. eodag/rest/types/eodag_search.py +232 -43
  53. eodag/rest/types/{stac_queryables.py → queryables.py} +81 -43
  54. eodag/rest/types/stac_search.py +277 -0
  55. eodag/rest/utils/__init__.py +216 -0
  56. eodag/rest/utils/cql_evaluate.py +119 -0
  57. eodag/rest/utils/rfc3339.py +65 -0
  58. eodag/types/__init__.py +99 -9
  59. eodag/types/bbox.py +15 -14
  60. eodag/types/download_args.py +31 -0
  61. eodag/types/search_args.py +58 -7
  62. eodag/types/whoosh.py +81 -0
  63. eodag/utils/__init__.py +72 -9
  64. eodag/utils/constraints.py +37 -37
  65. eodag/utils/exceptions.py +23 -17
  66. eodag/utils/requests.py +138 -0
  67. eodag/utils/rest.py +104 -0
  68. eodag/utils/stac_reader.py +100 -16
  69. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/METADATA +64 -44
  70. eodag-3.0.0b1.dist-info/RECORD +109 -0
  71. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/WHEEL +1 -1
  72. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/entry_points.txt +6 -5
  73. eodag/plugins/apis/cds.py +0 -540
  74. eodag/rest/utils.py +0 -1133
  75. eodag-2.12.0.dist-info/RECORD +0 -94
  76. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/LICENSE +0 -0
  77. {eodag-2.12.0.dist-info → eodag-3.0.0b1.dist-info}/top_level.txt +0 -0
eodag/plugins/apis/cds.py DELETED
@@ -1,540 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Copyright 2022, CS GROUP - France, https://www.csgroup.eu/
3
- #
4
- # This file is part of EODAG project
5
- # https://www.github.com/CS-SI/EODAG
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
- from __future__ import annotations
19
-
20
- import logging
21
- from datetime import datetime, timedelta
22
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union, cast
23
- from urllib.parse import unquote_plus
24
-
25
- import cdsapi
26
- import geojson
27
- import requests
28
- from dateutil.parser import isoparse
29
- from pydantic import create_model
30
- from pydantic.fields import FieldInfo
31
- from typing_extensions import get_args
32
-
33
- from eodag.api.product._assets import Asset
34
- from eodag.api.product.metadata_mapping import (
35
- get_queryable_from_provider,
36
- mtd_cfg_as_conversion_and_querypath,
37
- )
38
- from eodag.plugins.apis.base import Api
39
- from eodag.plugins.download.http import HTTPDownload
40
- from eodag.plugins.search.base import Search
41
- from eodag.plugins.search.build_search_result import BuildPostSearchResult
42
- from eodag.rest.stac import DEFAULT_MISSION_START_DATE
43
- from eodag.types import json_field_definition_to_python, model_fields_to_annotated
44
- from eodag.types.queryables import CommonQueryables
45
- from eodag.utils import (
46
- DEFAULT_DOWNLOAD_TIMEOUT,
47
- DEFAULT_DOWNLOAD_WAIT,
48
- DEFAULT_ITEMS_PER_PAGE,
49
- DEFAULT_PAGE,
50
- Annotated,
51
- datetime_range,
52
- deepcopy,
53
- get_geometry_from_various,
54
- path_to_uri,
55
- urlencode,
56
- urlsplit,
57
- )
58
- from eodag.utils.constraints import (
59
- fetch_constraints,
60
- get_constraint_queryables_with_additional_params,
61
- )
62
- from eodag.utils.exceptions import (
63
- AuthenticationError,
64
- DownloadError,
65
- RequestError,
66
- ValidationError,
67
- )
68
- from eodag.utils.logging import get_logging_verbose
69
-
70
- if TYPE_CHECKING:
71
- from eodag.api.product import EOProduct
72
- from eodag.api.search_result import SearchResult
73
- from eodag.config import PluginConfig
74
- from eodag.utils import DownloadedCallback, ProgressCallback
75
-
76
- logger = logging.getLogger("eodag.apis.cds")
77
-
78
- CDS_KNOWN_FORMATS = {"grib": "grib", "netcdf": "nc"}
79
-
80
-
81
- class CdsApi(HTTPDownload, Api, BuildPostSearchResult):
82
- """A plugin that enables to build download-request and download data on CDS API.
83
-
84
- Builds a single ready-to-download :class:`~eodag.api.product._product.EOProduct`
85
- during the search stage.
86
-
87
- This class inherits from :class:`~eodag.plugins.apis.base.Api` for compatibility,
88
- :class:`~eodag.plugins.download.base.Download` for download methods, and
89
- :class:`~eodag.plugins.search.qssearch.QueryStringSearch` for metadata-mapping and
90
- query build methods.
91
- """
92
-
93
- def __init__(self, provider: str, config: PluginConfig) -> None:
94
- # init self.config.metadata_mapping using Search Base plugin
95
- Search.__init__(self, provider, config)
96
-
97
- # needed by QueryStringSearch.build_query_string / format_free_text_search
98
- self.config.__dict__.setdefault("free_text_search_operations", {})
99
- # needed for compatibility
100
- self.config.__dict__.setdefault("pagination", {"next_page_query_obj": "{{}}"})
101
-
102
- # parse jsonpath on init: product type specific metadata-mapping
103
- for product_type in self.config.products.keys():
104
- if "metadata_mapping" in self.config.products[product_type].keys():
105
- self.config.products[product_type][
106
- "metadata_mapping"
107
- ] = mtd_cfg_as_conversion_and_querypath(
108
- self.config.products[product_type]["metadata_mapping"]
109
- )
110
- # Complete and ready to use product type specific metadata-mapping
111
- product_type_metadata_mapping = deepcopy(self.config.metadata_mapping)
112
-
113
- # update config using provider product type definition metadata_mapping
114
- # from another product
115
- other_product_for_mapping = cast(
116
- str,
117
- self.config.products[product_type].get(
118
- "metadata_mapping_from_product", ""
119
- ),
120
- )
121
- if other_product_for_mapping:
122
- other_product_type_def_params = self.get_product_type_def_params(
123
- other_product_for_mapping, # **kwargs
124
- )
125
- product_type_metadata_mapping.update(
126
- other_product_type_def_params.get("metadata_mapping", {})
127
- )
128
- # from current product
129
- product_type_metadata_mapping.update(
130
- self.config.products[product_type]["metadata_mapping"]
131
- )
132
-
133
- self.config.products[product_type][
134
- "metadata_mapping"
135
- ] = product_type_metadata_mapping
136
-
137
- def get_product_type_cfg(self, key: str, default: Any = None) -> Any:
138
- """
139
- Get the value of a configuration option specific to the current product type.
140
-
141
- This method retrieves the value of a configuration option from the
142
- `_product_type_config` attribute. If the option is not found, the provided
143
- default value is returned.
144
-
145
- :param key: The configuration option key.
146
- :type key: str
147
- :param default: The default value to be returned if the option is not found (default is None).
148
- :type default: Any
149
-
150
- :return: The value of the specified configuration option or the default value.
151
- :rtype: Any
152
- """
153
- product_type_cfg = getattr(self.config, "product_type_config", {})
154
- non_none_cfg = {k: v for k, v in product_type_cfg.items() if v}
155
-
156
- return non_none_cfg.get(key, default)
157
-
158
- def _preprocess_search_params(self, params: Dict[Any]) -> None:
159
- """Preprocess search parameters before making a request to the CDS API.
160
-
161
- This method is responsible for checking and updating the provided search parameters
162
- to ensure that required parameters like 'productType', 'startTimeFromAscendingNode',
163
- 'completionTimeFromAscendingNode', and 'geometry' are properly set. If not specified
164
- in the input parameters, default values or values from the configuration are used.
165
-
166
- :param params: Search parameters to be preprocessed.
167
- :type params: dict
168
- """
169
- _dc_qs = params.get("_dc_qs", None)
170
- if _dc_qs is not None:
171
- # if available, update search params using datacube query-string
172
- _dc_qp = geojson.loads(unquote_plus(unquote_plus(_dc_qs)))
173
- if "/" in _dc_qp.get("date", ""):
174
- (
175
- params["startTimeFromAscendingNode"],
176
- params["completionTimeFromAscendingNode"],
177
- ) = _dc_qp["date"].split("/")
178
- elif _dc_qp.get("date", None):
179
- params["startTimeFromAscendingNode"] = params[
180
- "completionTimeFromAscendingNode"
181
- ] = _dc_qp["date"]
182
-
183
- if "/" in _dc_qp.get("area", ""):
184
- params["geometry"] = _dc_qp["area"].split("/")
185
-
186
- non_none_params = {k: v for k, v in params.items() if v}
187
-
188
- # productType
189
- dataset = params.get("dataset", None)
190
- params["productType"] = non_none_params.get("productType", dataset)
191
-
192
- # dates
193
- mission_start_dt = datetime.fromisoformat(
194
- self.get_product_type_cfg(
195
- "missionStartDate", DEFAULT_MISSION_START_DATE
196
- ).replace(
197
- "Z", "+00:00"
198
- ) # before 3.11
199
- )
200
-
201
- default_end_from_cfg = self.config.products.get(params["productType"], {}).get(
202
- "_default_end_date", None
203
- )
204
- default_end_str = (
205
- default_end_from_cfg
206
- or (
207
- datetime.utcnow()
208
- if params.get("startTimeFromAscendingNode")
209
- else mission_start_dt + timedelta(days=1)
210
- ).isoformat()
211
- )
212
-
213
- params["startTimeFromAscendingNode"] = non_none_params.get(
214
- "startTimeFromAscendingNode", mission_start_dt.isoformat()
215
- )
216
- params["completionTimeFromAscendingNode"] = non_none_params.get(
217
- "completionTimeFromAscendingNode", default_end_str
218
- )
219
-
220
- # temporary _date parameter mixing start & end
221
- end_date = isoparse(params["completionTimeFromAscendingNode"]) + timedelta(
222
- days=-1
223
- )
224
- params[
225
- "_date"
226
- ] = f"{params['startTimeFromAscendingNode']}/{end_date.isoformat()}"
227
-
228
- # geometry
229
- if "geometry" in params:
230
- params["geometry"] = get_geometry_from_various(geometry=params["geometry"])
231
-
232
- def build_query_string(
233
- self, product_type: str, **kwargs: Any
234
- ) -> Tuple[Dict[str, Any], str]:
235
- """Build The query string using the search parameters"""
236
- qp, _ = BuildPostSearchResult.build_query_string(
237
- self, product_type=product_type, **kwargs
238
- )
239
- if "_date" in qp:
240
- qp.update(qp.pop("_date", {}))
241
-
242
- return qp, urlencode(qp, doseq=True, quote_via=lambda x, *_args, **_kwargs: x)
243
-
244
- def do_search(self, *args: Any, **kwargs: Any) -> List[Dict[str, Any]]:
245
- """Should perform the actual search request."""
246
- return [{}]
247
-
248
- def query(
249
- self,
250
- product_type: Optional[str] = None,
251
- items_per_page: int = DEFAULT_ITEMS_PER_PAGE,
252
- page: int = DEFAULT_PAGE,
253
- count: bool = True,
254
- **kwargs: Any,
255
- ) -> Tuple[List[EOProduct], Optional[int]]:
256
- """Build ready-to-download SearchResult"""
257
-
258
- self._preprocess_search_params(kwargs)
259
-
260
- return BuildPostSearchResult.query(
261
- self, items_per_page=items_per_page, page=page, count=count, **kwargs
262
- )
263
-
264
- def _get_cds_client(self, **auth_dict: Any) -> cdsapi.Client:
265
- """Returns cdsapi client."""
266
- # eodag logging info
267
- eodag_verbosity = get_logging_verbose()
268
- eodag_logger = logging.getLogger("eodag")
269
-
270
- client = cdsapi.Client(
271
- # disable cdsapi default logging and handle it on eodag side
272
- # until https://github.com/ecmwf/cdsapi/pull/47 is merged
273
- quiet=True,
274
- verify=True,
275
- **auth_dict,
276
- )
277
-
278
- if eodag_verbosity is None or eodag_verbosity == 1:
279
- client.logger.setLevel(logging.WARNING)
280
- elif eodag_verbosity == 2:
281
- client.logger.setLevel(logging.INFO)
282
- elif eodag_verbosity == 3:
283
- client.logger.setLevel(logging.DEBUG)
284
- else:
285
- client.logger.setLevel(logging.WARNING)
286
-
287
- if len(eodag_logger.handlers) > 0:
288
- client.logger.addHandler(eodag_logger.handlers[0])
289
-
290
- return client
291
-
292
- def authenticate(self) -> Dict[str, str]:
293
- """Returns information needed for auth
294
-
295
- :returns: {key, url} dictionary
296
- :rtype: dict
297
- :raises: :class:`~eodag.utils.exceptions.AuthenticationError`
298
- :raises: :class:`~eodag.utils.exceptions.RequestError`
299
- """
300
- # Get credentials from eodag or using cds conf
301
- uid = getattr(self.config, "credentials", {}).get("username", None)
302
- api_key = getattr(self.config, "credentials", {}).get("password", None)
303
- url = getattr(self.config, "api_endpoint", None)
304
- if not all([uid, api_key, url]):
305
- raise AuthenticationError("Missing authentication information")
306
-
307
- auth_dict: Dict[str, str] = {"key": f"{uid}:{api_key}", "url": url}
308
-
309
- client = self._get_cds_client(**auth_dict)
310
- try:
311
- client.status()
312
- logger.debug("Connection checked on CDS API")
313
- except requests.exceptions.ConnectionError as e:
314
- logger.error(e)
315
- raise RequestError(f"Could not connect to the CDS API '{url}'")
316
- except requests.exceptions.HTTPError as e:
317
- logger.error(e)
318
- raise RequestError("The CDS API has returned an unexpected error")
319
-
320
- return auth_dict
321
-
322
- def _prepare_download_link(self, product):
323
- """Update product download link with http url obtained from cds api"""
324
- # get download request dict from product.location/downloadLink url query string
325
- # separate url & parameters
326
- query_str = "".join(urlsplit(product.location).fragment.split("?", 1)[1:])
327
- download_request = geojson.loads(query_str)
328
-
329
- date_range = download_request.pop("date_range", False)
330
- if date_range:
331
- date = download_request.pop("date")
332
- start, end, *_ = date.split("/")
333
- _start = datetime.fromisoformat(start)
334
- _end = datetime.fromisoformat(end)
335
- d_range = [d for d in datetime_range(_start, _end)]
336
- download_request["year"] = [*{str(d.year) for d in d_range}]
337
- download_request["month"] = [*{str(d.month) for d in d_range}]
338
- download_request["day"] = [*{str(d.day) for d in d_range}]
339
-
340
- auth_dict = self.authenticate()
341
- dataset_name = download_request.pop("dataset")
342
-
343
- # Send download request to CDS web API
344
- logger.info(
345
- "Request download on CDS API: dataset=%s, request=%s",
346
- dataset_name,
347
- download_request,
348
- )
349
- try:
350
- client = self._get_cds_client(**auth_dict)
351
- result = client._api(
352
- "%s/resources/%s" % (client.url, dataset_name), download_request, "POST"
353
- )
354
- # update product download link through a new asset
355
- product.assets["data"] = Asset(product, "data", {"href": result.location})
356
- except Exception as e:
357
- logger.error(e)
358
- raise DownloadError(e)
359
-
360
- def download(
361
- self,
362
- product: EOProduct,
363
- auth: Optional[PluginConfig] = None,
364
- progress_callback: Optional[ProgressCallback] = None,
365
- wait: int = DEFAULT_DOWNLOAD_WAIT,
366
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
367
- **kwargs: Any,
368
- ) -> Optional[str]:
369
- """Download data from providers using CDS API"""
370
- product_format = product.properties.get("format", "grib")
371
- product_extension = CDS_KNOWN_FORMATS.get(product_format, product_format)
372
-
373
- # Prepare download
374
- fs_path, record_filename = self._prepare_download(
375
- product,
376
- progress_callback=progress_callback,
377
- outputs_extension=f".{product_extension}",
378
- **kwargs,
379
- )
380
-
381
- if not fs_path or not record_filename:
382
- if fs_path:
383
- product.location = path_to_uri(fs_path)
384
- return fs_path
385
-
386
- self._prepare_download_link(product)
387
-
388
- try:
389
- return super(CdsApi, self).download(
390
- product,
391
- progress_callback=progress_callback,
392
- **kwargs,
393
- )
394
- except Exception as e:
395
- logger.error(e)
396
- raise DownloadError(e)
397
-
398
- def _stream_download_dict(
399
- self,
400
- product: EOProduct,
401
- auth: Optional[PluginConfig] = None,
402
- progress_callback: Optional[ProgressCallback] = None,
403
- wait: int = DEFAULT_DOWNLOAD_WAIT,
404
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
405
- **kwargs: Union[str, bool, Dict[str, Any]],
406
- ) -> Dict[str, Any]:
407
- """Returns dictionnary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
408
- It contains a generator to streamed download chunks and the response headers."""
409
-
410
- self._prepare_download_link(product)
411
- return super(CdsApi, self)._stream_download_dict(
412
- product,
413
- auth=auth,
414
- progress_callback=progress_callback,
415
- wait=wait,
416
- timeout=timeout,
417
- **kwargs,
418
- )
419
-
420
- def download_all(
421
- self,
422
- products: SearchResult,
423
- auth: Optional[PluginConfig] = None,
424
- downloaded_callback: Optional[DownloadedCallback] = None,
425
- progress_callback: Optional[ProgressCallback] = None,
426
- wait: int = DEFAULT_DOWNLOAD_WAIT,
427
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
428
- **kwargs: Any,
429
- ):
430
- """
431
- Download all using parent (base plugin) method
432
- """
433
- return super(CdsApi, self).download_all(
434
- products,
435
- auth=auth,
436
- downloaded_callback=downloaded_callback,
437
- progress_callback=progress_callback,
438
- wait=wait,
439
- timeout=timeout,
440
- **kwargs,
441
- )
442
-
443
- def discover_queryables(
444
- self, **kwargs: Any
445
- ) -> Optional[Dict[str, Annotated[Any, FieldInfo]]]:
446
- """Fetch queryables list from provider using `discover_queryables` conf
447
-
448
- :param kwargs: additional filters for queryables (`productType` and other search
449
- arguments)
450
- :type kwargs: Any
451
- :returns: fetched queryable parameters dict
452
- :rtype: Optional[Dict[str, Annotated[Any, FieldInfo]]]
453
- """
454
- constraints_file_url = getattr(self.config, "constraints_file_url", "")
455
- if not constraints_file_url:
456
- return {}
457
- product_type = kwargs.pop("productType", None)
458
- if not product_type:
459
- return {}
460
-
461
- provider_product_type = self.config.products.get(product_type, {}).get(
462
- "dataset", None
463
- )
464
- user_provider_product_type = kwargs.pop("dataset", None)
465
- if (
466
- user_provider_product_type
467
- and user_provider_product_type != provider_product_type
468
- ):
469
- raise ValidationError(
470
- f"Cannot change dataset from {provider_product_type} to {user_provider_product_type}"
471
- )
472
-
473
- non_empty_kwargs = {k: v for k, v in kwargs.items() if v}
474
-
475
- if "{" in constraints_file_url:
476
- constraints_file_url = constraints_file_url.format(
477
- dataset=provider_product_type
478
- )
479
- constraints = fetch_constraints(constraints_file_url, self)
480
- if not constraints:
481
- return {}
482
-
483
- # defaults
484
- default_queryables = self.get_defaults_as_queryables(product_type)
485
- # remove dataset from queryables
486
- default_queryables.pop("dataset", None)
487
-
488
- constraint_params: Dict[str, Dict[str, Set[Any]]] = {}
489
- if len(kwargs) == 0:
490
- # get values from constraints without additional filters
491
- for constraint in constraints:
492
- for key in constraint.keys():
493
- if key in constraint_params:
494
- constraint_params[key]["enum"].update(constraint[key])
495
- else:
496
- constraint_params[key] = {}
497
- constraint_params[key]["enum"] = set(constraint[key])
498
- else:
499
- # get values from constraints with additional filters
500
- constraints_input_params = {k: v for k, v in non_empty_kwargs.items()}
501
- constraint_params = get_constraint_queryables_with_additional_params(
502
- constraints, constraints_input_params, self, product_type
503
- )
504
- # query params that are not in constraints but might be default queryables
505
- if len(constraint_params) == 1 and "not_available" in constraint_params:
506
- not_queryables = set()
507
- for constraint_param in constraint_params["not_available"]["enum"]:
508
- param = CommonQueryables.get_queryable_from_alias(constraint_param)
509
- if param in dict(
510
- CommonQueryables.model_fields, **default_queryables
511
- ):
512
- non_empty_kwargs.pop(constraint_param)
513
- else:
514
- not_queryables.add(constraint_param)
515
- if not_queryables:
516
- raise ValidationError(
517
- f"parameter(s) {str(not_queryables)} not queryable"
518
- )
519
- else:
520
- # get constraints again without common queryables
521
- constraint_params = (
522
- get_constraint_queryables_with_additional_params(
523
- constraints, non_empty_kwargs, self, product_type
524
- )
525
- )
526
-
527
- field_definitions = dict()
528
- for json_param, json_mtd in constraint_params.items():
529
- param = (
530
- get_queryable_from_provider(json_param, self.config.metadata_mapping)
531
- or json_param
532
- )
533
- default = kwargs.get(param, None)
534
- annotated_def = json_field_definition_to_python(
535
- json_mtd, default_value=default, required=True
536
- )
537
- field_definitions[param] = get_args(annotated_def)
538
-
539
- python_queryables = create_model("m", **field_definitions).model_fields
540
- return dict(default_queryables, **model_fields_to_annotated(python_queryables))