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.
Files changed (87) hide show
  1. eodag/api/core.py +174 -138
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +117 -90
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +86 -92
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +42 -22
  15. eodag/plugins/apis/usgs.py +17 -16
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +22 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +6 -7
  32. eodag/plugins/download/aws.py +146 -87
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +90 -46
  40. eodag/plugins/search/build_search_result.py +1048 -361
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +99 -258
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +4 -4
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +1134 -325
  50. eodag/resources/providers.yml +906 -2006
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +10 -9
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +42 -31
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +141 -32
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +153 -51
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +231 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
  81. eodag-3.1.0.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
@@ -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, Dict, List, Optional, Union
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` (``[Dict[str, str]]``): order request 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, Dict[str, str]]] = None,
97
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
97
98
  progress_callback: Optional[ProgressCallback] = None,
98
- wait: int = DEFAULT_DOWNLOAD_WAIT,
99
- timeout: int = DEFAULT_DOWNLOAD_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.order_download(product=product, auth=auth)
134
+ self.http_download_plugin._order(product=product, auth=auth)
134
135
 
135
- @self._download_retry(product, wait, timeout)
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.order_download_status(
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: List[int] = [
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: Dict[str, List[ProviderConfig]]
77
+ product_type_to_provider_config_map: dict[str, list[ProviderConfig]]
88
78
 
89
- skipped_plugins: List[str]
79
+ skipped_plugins: list[str]
90
80
 
91
- def __init__(self, providers_config: Dict[str, ProviderConfig]) -> None:
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[Dict[str, ProviderConfig]] = None
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: Dict[Tuple[str, str, str], Any] = {}
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[List[ProviderConfig]]
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, Dict[str, str]]]:
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: Type[PluginTopic],
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
@@ -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, Dict, List, Optional, Union
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, Dict[str, str]]] = None
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: Dict[str, Any] = field(init=False, repr=False)
50
+ query_params: dict[str, Any] = field(init=False, repr=False)
50
51
  query_string: str = field(init=False, repr=False)
51
- search_urls: List[str] = field(init=False, repr=False)
52
- product_type_def_params: Dict[str, Any] = field(init=False, repr=False)
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)
@@ -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, Dict, List, Optional, Tuple, Union
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, Dict[str, str]]
64
+ auth: Union[AuthBase, S3SessionKwargs]
64
65
  next_page_url: Optional[str]
65
- next_page_query_obj: Optional[Dict[str, Any]]
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: Dict[str, Any] = DEFAULT_METADATA_MAPPING.copy()
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
- ) -> Tuple[List[EOProduct], Optional[int]]:
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[Dict[str, Any]]:
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[Dict[str, Annotated[Any, FieldInfo]]]:
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
- ) -> Dict[str, Annotated[Any, FieldInfo]]:
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: Dict[str, Annotated[Any, FieldInfo]] = {}
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, **kwargs: Any
153
- ) -> Dict[str, Any]:
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], **kwargs
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
- ) -> Dict[str, Union[str, List[str]]]:
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: Dict[str, Any]) -> Optional[SortByList]:
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
- ) -> Tuple[str, Dict[str, List[Dict[str, str]]]]:
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: Dict[str, Any] = {}
252
+ sort_by_qp: dict[str, Any] = {}
251
253
 
252
- provider_sort_by_tuples_used: List[Tuple[str, str]] = []
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: Tuple[str, str] = (
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: Dict[str, Any] = orjson.loads(
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: Dict[str, Any],
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
- ) -> Dict[str, Annotated[Any, FieldInfo]]:
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
- default_values: Dict[str, Any] = deepcopy(
343
- getattr(self.config, "products", {}).get(product_type, {})
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
- default_values.pop("metadata_mapping", None)
346
-
347
- queryables: Dict[str, Annotated[Any, FieldInfo]] = {}
348
- try:
349
- queryables = self.discover_queryables(**{**default_values, **filters}) or {}
350
- except NotImplementedError:
351
- pass
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
- metadata_mapping: Dict[str, Any] = deepcopy(
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