eodag 2.12.0__py3-none-any.whl → 3.0.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 (93) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +654 -538
  3. eodag/api/product/__init__.py +12 -2
  4. eodag/api/product/_assets.py +59 -16
  5. eodag/api/product/_product.py +100 -93
  6. eodag/api/product/drivers/__init__.py +7 -2
  7. eodag/api/product/drivers/base.py +0 -3
  8. eodag/api/product/metadata_mapping.py +192 -96
  9. eodag/api/search_result.py +69 -10
  10. eodag/cli.py +55 -25
  11. eodag/config.py +391 -116
  12. eodag/plugins/apis/base.py +11 -165
  13. eodag/plugins/apis/ecmwf.py +36 -25
  14. eodag/plugins/apis/usgs.py +80 -35
  15. eodag/plugins/authentication/aws_auth.py +13 -4
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +2 -2
  18. eodag/plugins/authentication/header.py +31 -6
  19. eodag/plugins/authentication/keycloak.py +17 -84
  20. eodag/plugins/authentication/oauth.py +3 -3
  21. eodag/plugins/authentication/openid_connect.py +268 -49
  22. eodag/plugins/authentication/qsauth.py +4 -1
  23. eodag/plugins/authentication/sas_auth.py +9 -2
  24. eodag/plugins/authentication/token.py +98 -47
  25. eodag/plugins/authentication/token_exchange.py +122 -0
  26. eodag/plugins/crunch/base.py +3 -1
  27. eodag/plugins/crunch/filter_date.py +3 -9
  28. eodag/plugins/crunch/filter_latest_intersect.py +0 -3
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
  30. eodag/plugins/crunch/filter_overlap.py +4 -8
  31. eodag/plugins/crunch/filter_property.py +5 -11
  32. eodag/plugins/download/aws.py +149 -185
  33. eodag/plugins/download/base.py +88 -97
  34. eodag/plugins/download/creodias_s3.py +1 -1
  35. eodag/plugins/download/http.py +638 -310
  36. eodag/plugins/download/s3rest.py +47 -45
  37. eodag/plugins/manager.py +228 -88
  38. eodag/plugins/search/__init__.py +36 -0
  39. eodag/plugins/search/base.py +239 -30
  40. eodag/plugins/search/build_search_result.py +382 -37
  41. eodag/plugins/search/cop_marine.py +441 -0
  42. eodag/plugins/search/creodias_s3.py +25 -20
  43. eodag/plugins/search/csw.py +5 -7
  44. eodag/plugins/search/data_request_search.py +61 -30
  45. eodag/plugins/search/qssearch.py +713 -255
  46. eodag/plugins/search/static_stac_search.py +106 -40
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +1921 -34
  49. eodag/resources/providers.yml +4091 -3655
  50. eodag/resources/stac.yml +50 -216
  51. eodag/resources/stac_api.yml +71 -25
  52. eodag/resources/stac_provider.yml +5 -0
  53. eodag/resources/user_conf_template.yml +89 -32
  54. eodag/rest/__init__.py +6 -0
  55. eodag/rest/cache.py +70 -0
  56. eodag/rest/config.py +68 -0
  57. eodag/rest/constants.py +26 -0
  58. eodag/rest/core.py +735 -0
  59. eodag/rest/errors.py +178 -0
  60. eodag/rest/server.py +264 -431
  61. eodag/rest/stac.py +442 -836
  62. eodag/rest/types/collections_search.py +44 -0
  63. eodag/rest/types/eodag_search.py +238 -47
  64. eodag/rest/types/queryables.py +164 -0
  65. eodag/rest/types/stac_search.py +273 -0
  66. eodag/rest/utils/__init__.py +216 -0
  67. eodag/rest/utils/cql_evaluate.py +119 -0
  68. eodag/rest/utils/rfc3339.py +64 -0
  69. eodag/types/__init__.py +106 -10
  70. eodag/types/bbox.py +15 -14
  71. eodag/types/download_args.py +40 -0
  72. eodag/types/search_args.py +57 -7
  73. eodag/types/whoosh.py +79 -0
  74. eodag/utils/__init__.py +110 -91
  75. eodag/utils/constraints.py +37 -45
  76. eodag/utils/exceptions.py +39 -22
  77. eodag/utils/import_system.py +0 -4
  78. eodag/utils/logging.py +37 -80
  79. eodag/utils/notebook.py +4 -4
  80. eodag/utils/repr.py +113 -0
  81. eodag/utils/requests.py +128 -0
  82. eodag/utils/rest.py +100 -0
  83. eodag/utils/stac_reader.py +93 -21
  84. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/METADATA +88 -53
  85. eodag-3.0.0.dist-info/RECORD +109 -0
  86. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/WHEEL +1 -1
  87. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/entry_points.txt +7 -5
  88. eodag/plugins/apis/cds.py +0 -540
  89. eodag/rest/types/stac_queryables.py +0 -134
  90. eodag/rest/utils.py +0 -1133
  91. eodag-2.12.0.dist-info/RECORD +0 -94
  92. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/LICENSE +0 -0
  93. {eodag-2.12.0.dist-info → eodag-3.0.0.dist-info}/top_level.txt +0 -0
eodag/plugins/manager.py CHANGED
@@ -18,6 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import logging
21
+ import re
21
22
  from operator import attrgetter
22
23
  from pathlib import Path
23
24
  from typing import (
@@ -35,17 +36,28 @@ from typing import (
35
36
 
36
37
  import pkg_resources
37
38
 
38
- from eodag.config import load_config, merge_configs
39
+ from eodag.config import (
40
+ AUTH_TOPIC_KEYS,
41
+ PLUGINS_TOPICS_KEYS,
42
+ load_config,
43
+ merge_configs,
44
+ )
39
45
  from eodag.plugins.apis.base import Api
40
46
  from eodag.plugins.authentication.base import Authentication
41
47
  from eodag.plugins.base import EODAGPluginMount
42
48
  from eodag.plugins.crunch.base import Crunch
43
49
  from eodag.plugins.download.base import Download
44
50
  from eodag.plugins.search.base import Search
45
- from eodag.utils import GENERIC_PRODUCT_TYPE
46
- from eodag.utils.exceptions import UnsupportedProvider
51
+ from eodag.utils import GENERIC_PRODUCT_TYPE, deepcopy, dict_md5sum
52
+ from eodag.utils.exceptions import (
53
+ AuthenticationError,
54
+ MisconfiguredError,
55
+ UnsupportedProvider,
56
+ )
47
57
 
48
58
  if TYPE_CHECKING:
59
+ from requests.auth import AuthBase
60
+
49
61
  from eodag.api.product import EOProduct
50
62
  from eodag.config import PluginConfig, ProviderConfig
51
63
  from eodag.plugins.base import PluginTopic
@@ -68,12 +80,16 @@ class PluginManager:
68
80
 
69
81
  :param providers_config: The configuration with all information about the providers
70
82
  supported by ``eodag``
71
- :type providers_config: dict[str, :class:`~eodag.config.ProviderConfig`]
72
83
  """
73
84
 
74
- supported_topics = {"search", "download", "crunch", "auth", "api"}
85
+ supported_topics = set(PLUGINS_TOPICS_KEYS)
86
+
87
+ product_type_to_provider_config_map: Dict[str, List[ProviderConfig]]
88
+
89
+ skipped_plugins: List[str]
75
90
 
76
91
  def __init__(self, providers_config: Dict[str, ProviderConfig]) -> None:
92
+ self.skipped_plugins = []
77
93
  self.providers_config = providers_config
78
94
  # Load all the plugins. This will make all plugin classes of a particular
79
95
  # type to be available in the base plugin class's 'plugins' attribute.
@@ -90,6 +106,13 @@ class PluginManager:
90
106
  ):
91
107
  try:
92
108
  entry_point.load()
109
+ except pkg_resources.DistributionNotFound:
110
+ logger.debug(
111
+ "%s plugin skipped, eodag[%s] or eodag[all] needed",
112
+ entry_point.name,
113
+ ",".join(entry_point.extras),
114
+ )
115
+ self.skipped_plugins.append(entry_point.name)
93
116
  except ImportError:
94
117
  import traceback as tb
95
118
 
@@ -99,7 +122,11 @@ class PluginManager:
99
122
  "Check that the plugin module (%s) is importable",
100
123
  entry_point.module_name,
101
124
  )
102
- if entry_point.dist and entry_point.dist.key != "eodag":
125
+ if (
126
+ entry_point.dist
127
+ and entry_point.dist.key != "eodag"
128
+ and entry_point.dist.location is not None
129
+ ):
103
130
  # use plugin providers if any
104
131
  plugin_providers_config_path = [
105
132
  str(x)
@@ -116,23 +143,25 @@ class PluginManager:
116
143
  self.providers_config = plugin_providers_config
117
144
  self.rebuild()
118
145
 
119
- def rebuild(self, providers_config=None):
146
+ def rebuild(
147
+ self, providers_config: Optional[Dict[str, ProviderConfig]] = None
148
+ ) -> None:
120
149
  """(Re)Build plugin manager mapping and cache"""
121
150
  if providers_config is not None:
122
151
  self.providers_config = providers_config
123
152
 
124
153
  self.build_product_type_to_provider_config_map()
125
- self._built_plugins_cache: Dict[Tuple[str, str], Any] = {}
154
+ self._built_plugins_cache: Dict[Tuple[str, str, str], Any] = {}
126
155
 
127
156
  def build_product_type_to_provider_config_map(self) -> None:
128
157
  """Build mapping conf between product types and providers"""
129
- self.product_type_to_provider_config_map: Dict[str, List[ProviderConfig]] = {}
158
+ self.product_type_to_provider_config_map = {}
130
159
  for provider in list(self.providers_config):
131
160
  provider_config = self.providers_config[provider]
132
161
  if not hasattr(provider_config, "products") or not provider_config.products:
133
162
  logger.info(
134
- "%s: provider has no product configured and will be skipped"
135
- % provider
163
+ "%s: provider has no product configured and will be skipped",
164
+ provider,
136
165
  )
137
166
  self.providers_config.pop(provider)
138
167
  continue
@@ -158,103 +187,211 @@ class PluginManager:
158
187
 
159
188
  :param product_type: (optional) The product type that the constructed plugins
160
189
  must support
161
- :type product_type: str
162
- :param provider: (optional) The provider on which to get the search plugin
163
- :type provider: str
190
+ :param provider: (optional) The provider or the provider group on which to get
191
+ the search plugins
164
192
  :returns: All the plugins supporting the product type, one by one (a generator
165
193
  object)
166
- :rtype: types.GeneratorType(:class:`~eodag.plugins.search.Search` or :class:`~eodag.plugins.download.Api`)
194
+ or :class:`~eodag.plugins.download.Api`)
167
195
  :raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
168
- :raises: :class:`~eodag.utils.exceptions.UnsupportedProductType`
169
196
  """
170
197
 
171
198
  def get_plugin() -> Union[Search, Api]:
172
199
  plugin: Union[Search, Api]
173
- try:
200
+ if search := getattr(config, "search", None):
174
201
  config.search.products = config.products
175
202
  config.search.priority = config.priority
176
- plugin = cast(
177
- Search, self._build_plugin(config.name, config.search, Search)
178
- )
179
- except AttributeError:
203
+ plugin = cast(Search, self._build_plugin(config.name, search, Search))
204
+ elif api := getattr(config, "api", None):
180
205
  config.api.products = config.products
181
206
  config.api.priority = config.priority
182
- plugin = cast(Api, self._build_plugin(config.name, config.api, Api))
207
+ plugin = cast(Api, self._build_plugin(config.name, api, Api))
208
+ else:
209
+ raise MisconfiguredError(
210
+ f"No search plugin configureed for {config.name}."
211
+ )
183
212
  return plugin
184
213
 
185
- if provider is not None:
186
- try:
187
- config = self.providers_config[provider]
188
- except KeyError:
189
- raise UnsupportedProvider
190
- yield get_plugin()
191
- # Signal the end of iteration as we already have what we wanted (see PEP-479)
192
- return
193
-
194
- if product_type is None:
195
- for config in sorted(
196
- self.providers_config.values(), key=attrgetter("priority"), reverse=True
197
- ):
198
- yield get_plugin()
199
- # Signal the end of iteration as we already have what we wanted (see PEP-479)
200
- return
201
- try:
202
- for config in self.product_type_to_provider_config_map[product_type]:
203
- yield get_plugin()
204
- except KeyError:
205
- logger.info(
206
- "UnsupportedProductType: %s, using generic settings", product_type
214
+ configs: Optional[List[ProviderConfig]]
215
+ if product_type:
216
+ configs = self.product_type_to_provider_config_map.get(product_type)
217
+ if not configs:
218
+ logger.info(
219
+ "UnsupportedProductType: %s, using generic settings", product_type
220
+ )
221
+ configs = self.product_type_to_provider_config_map[GENERIC_PRODUCT_TYPE]
222
+ else:
223
+ configs = list(self.providers_config.values())
224
+
225
+ if provider:
226
+ configs = [
227
+ c for c in configs if provider in [getattr(c, "group", None), c.name]
228
+ ]
229
+
230
+ if not configs and product_type:
231
+ raise UnsupportedProvider(
232
+ f"{provider} is not (yet) supported for {product_type}"
207
233
  )
208
- for config in self.product_type_to_provider_config_map[
209
- GENERIC_PRODUCT_TYPE
210
- ]:
211
- yield get_plugin()
234
+ if not configs:
235
+ raise UnsupportedProvider(f"{provider} is not (yet) supported")
236
+
237
+ for config in sorted(configs, key=attrgetter("priority"), reverse=True):
238
+ yield get_plugin()
212
239
 
213
240
  def get_download_plugin(self, product: EOProduct) -> Union[Download, Api]:
214
241
  """Build and return the download plugin capable of downloading the given
215
242
  product.
216
243
 
217
244
  :param product: The product to get a download plugin for
218
- :type product: :class:`~eodag.api.product._product.EOProduct`
219
245
  :returns: The download plugin capable of downloading the product
220
- :rtype: :class:`~eodag.plugins.download.Download` or :class:`~eodag.plugins.download.Api`
221
246
  """
222
247
  plugin_conf = self.providers_config[product.provider]
223
- try:
248
+ if download := getattr(plugin_conf, "download", None):
224
249
  plugin_conf.download.priority = plugin_conf.priority
225
250
  plugin = cast(
226
251
  Download,
227
- self._build_plugin(product.provider, plugin_conf.download, Download),
252
+ self._build_plugin(product.provider, download, Download),
228
253
  )
229
- return plugin
230
- except AttributeError:
254
+ elif api := getattr(plugin_conf, "api", None):
255
+ plugin_conf.api.products = plugin_conf.products
231
256
  plugin_conf.api.priority = plugin_conf.priority
232
- plugin = cast(
233
- Api, self._build_plugin(product.provider, plugin_conf.api, Api)
257
+ plugin = cast(Api, self._build_plugin(product.provider, api, Api))
258
+ else:
259
+ raise MisconfiguredError(
260
+ f"No download plugin configured for provider {plugin_conf.name}."
234
261
  )
235
- return plugin
262
+ return plugin
263
+
264
+ def get_auth_plugin(
265
+ self, associated_plugin: PluginTopic, product: Optional[EOProduct] = None
266
+ ) -> Optional[Authentication]:
267
+ """Build and return the authentication plugin associated to the given
268
+ search/download plugin
269
+
270
+ .. versionchanged:: v3.0.0
271
+ ``get_auth_plugin()`` now needs ``associated_plugin`` instead of ``provider``
272
+ as argument.
236
273
 
237
- def get_auth_plugin(self, provider: str) -> Optional[Authentication]:
274
+ :param associated_plugin: The search/download plugin to which the authentication
275
+ plugin is linked
276
+ :param product: The product to download. ``None`` for search authentication
277
+ :returns: The Authentication plugin
278
+ """
279
+ # matching url from product to download
280
+ if product is not None and len(product.assets) > 0:
281
+ matching_url = next(iter(product.assets.values()))["href"]
282
+ elif product is not None:
283
+ matching_url = product.properties.get(
284
+ "downloadLink"
285
+ ) or product.properties.get("orderLink")
286
+ else:
287
+ # search auth
288
+ matching_url = getattr(associated_plugin.config, "api_endpoint", None)
289
+
290
+ try:
291
+ auth_plugin = next(
292
+ self.get_auth_plugins(
293
+ associated_plugin.provider,
294
+ matching_url=matching_url,
295
+ matching_conf=associated_plugin.config,
296
+ )
297
+ )
298
+ except StopIteration:
299
+ auth_plugin = None
300
+ return auth_plugin
301
+
302
+ def get_auth_plugins(
303
+ self,
304
+ provider: str,
305
+ matching_url: Optional[str] = None,
306
+ matching_conf: Optional[PluginConfig] = None,
307
+ ) -> Iterator[Authentication]:
238
308
  """Build and return the authentication plugin for the given product_type and
239
309
  provider
240
310
 
241
311
  :param provider: The provider for which to get the authentication plugin
242
- :type provider: str
243
- :returns: The Authentication plugin for the provider
244
- :rtype: :class:`~eodag.plugins.authentication.Authentication`
312
+ :param matching_url: url to compare with plugin matching_url pattern
313
+ :param matching_conf: configuration to compare with plugin matching_conf
314
+ :returns: All the Authentication plugins for the given criteria
245
315
  """
246
- plugin_conf = self.providers_config[provider]
247
- try:
248
- plugin_conf.auth.priority = plugin_conf.priority
249
- plugin = cast(
250
- Authentication,
251
- self._build_plugin(provider, plugin_conf.auth, Authentication),
252
- )
253
- return plugin
254
- except AttributeError:
255
- # We guess the plugin being built is of type Api, therefore no need
256
- # for an Auth plugin.
257
- return None
316
+ auth_conf: Optional[PluginConfig] = None
317
+
318
+ def _is_auth_plugin_matching(
319
+ auth_conf: PluginConfig,
320
+ matching_url: Optional[str],
321
+ matching_conf: Optional[PluginConfig],
322
+ ) -> bool:
323
+ plugin_matching_conf = getattr(auth_conf, "matching_conf", {})
324
+ if matching_conf:
325
+ if (
326
+ plugin_matching_conf
327
+ and matching_conf.__dict__.items() >= plugin_matching_conf.items()
328
+ ):
329
+ # conf matches
330
+ return True
331
+ plugin_matching_url = getattr(auth_conf, "matching_url", None)
332
+ if matching_url:
333
+ if plugin_matching_url and re.match(
334
+ rf"{plugin_matching_url}", matching_url
335
+ ):
336
+ # url matches
337
+ return True
338
+ if not plugin_matching_conf and not plugin_matching_url:
339
+ # plugin without match criteria
340
+ return True
341
+ else:
342
+ # no match
343
+ return False
344
+
345
+ # providers configs with given provider at first
346
+ sorted_providers_config = deepcopy(self.providers_config)
347
+ sorted_providers_config = {
348
+ provider: sorted_providers_config.pop(provider),
349
+ **sorted_providers_config,
350
+ }
351
+
352
+ for plugin_provider, provider_conf in sorted_providers_config.items():
353
+ for key in AUTH_TOPIC_KEYS:
354
+ auth_conf = getattr(provider_conf, key, None)
355
+ if auth_conf is None:
356
+ continue
357
+ if _is_auth_plugin_matching(auth_conf, matching_url, matching_conf):
358
+ auth_conf.priority = provider_conf.priority
359
+ plugin = cast(
360
+ Authentication,
361
+ self._build_plugin(plugin_provider, auth_conf, Authentication),
362
+ )
363
+ yield plugin
364
+ else:
365
+ continue
366
+
367
+ def get_auth(
368
+ self,
369
+ provider: str,
370
+ matching_url: Optional[str] = None,
371
+ matching_conf: Optional[PluginConfig] = None,
372
+ ) -> Optional[Union[AuthBase, Dict[str, str]]]:
373
+ """Authenticate and return the authenticated object for the first matching
374
+ authentication plugin
375
+
376
+ :param provider: The provider for which to get the authentication plugin
377
+ :param matching_url: url to compare with plugin matching_url pattern
378
+ :param matching_conf: configuration to compare with plugin matching_conf
379
+ :returns: All the Authentication plugins for the given criteria
380
+ """
381
+ for auth_plugin in self.get_auth_plugins(provider, matching_url, matching_conf):
382
+ if auth_plugin and callable(getattr(auth_plugin, "authenticate", None)):
383
+ try:
384
+ auth = auth_plugin.authenticate()
385
+ return auth
386
+ except (AuthenticationError, MisconfiguredError) as e:
387
+ logger.debug(f"Could not authenticate on {provider}: {str(e)}")
388
+ continue
389
+ else:
390
+ logger.debug(
391
+ f"Could not authenticate on {provider} using {auth_plugin} plugin"
392
+ )
393
+ continue
394
+ return None
258
395
 
259
396
  @staticmethod
260
397
  def get_crunch_plugin(name: str, **options: Any) -> Crunch:
@@ -262,14 +399,11 @@ class PluginManager:
262
399
  it with the `options`
263
400
 
264
401
  :param name: The name of the Crunch plugin to instantiate
265
- :type name: str
266
402
  :param options: The configuration parameters of the cruncher
267
- :type options: dict
268
403
  :returns: The cruncher named `name`
269
- :rtype: :class:`~eodag.plugins.crunch.Crunch`
270
404
  """
271
- Klass = Crunch.get_plugin_by_class_name(name)
272
- return Klass(options)
405
+ klass = Crunch.get_plugin_by_class_name(name)
406
+ return klass(options)
273
407
 
274
408
  def sort_providers(self) -> None:
275
409
  """Sort providers taking into account current priority order"""
@@ -280,9 +414,7 @@ class PluginManager:
280
414
  """Set the priority of the given provider
281
415
 
282
416
  :param provider: The provider which is assigned the priority
283
- :type provider: str
284
417
  :param priority: The priority to assign to the provider
285
- :type priority: int
286
418
  """
287
419
  # Update the priority in the configurations so that it is taken into account
288
420
  # when a plugin of this provider is latterly built
@@ -296,9 +428,11 @@ class PluginManager:
296
428
  # Sort the provider configs, taking into account the new priority order
297
429
  provider_configs.sort(key=attrgetter("priority"), reverse=True)
298
430
  # Update the priority of already built plugins of the given provider
299
- for provider_name, topic_class in self._built_plugins_cache:
431
+ for provider_name, topic_class, auth_match_md5 in self._built_plugins_cache:
300
432
  if provider_name == provider:
301
- self._built_plugins_cache[(provider, topic_class)].priority = priority
433
+ self._built_plugins_cache[
434
+ (provider, topic_class, auth_match_md5)
435
+ ].priority = priority
302
436
 
303
437
  def _build_plugin(
304
438
  self,
@@ -310,19 +444,23 @@ class PluginManager:
310
444
  registered as the given provider
311
445
 
312
446
  :param provider: The provider for which to build the plugin
313
- :type provider: str
314
447
  :param plugin_conf: The configuration of the plugin to be built
315
- :type plugin_conf: :class:`~eodag.config.PluginConfig`
316
448
  :param topic_class: The type of plugin to build
317
- :type topic_class: :class:`~eodag.plugin.base.PluginTopic`
318
449
  :returns: The built plugin
319
- :rtype: :class:`~eodag.plugin.search.Search` or
320
450
  :class:`~eodag.plugin.download.Download` or
321
451
  :class:`~eodag.plugin.authentication.Authentication` or
322
452
  :class:`~eodag.plugin.crunch.Crunch`
323
453
  """
454
+ # md5 hash to helps identifying an auth plugin within several for a given provider
455
+ # (each has distinct matching settings)
456
+ auth_match_md5 = dict_md5sum(
457
+ {
458
+ "matching_url": getattr(plugin_conf, "matching_url", None),
459
+ "matching_conf": getattr(plugin_conf, "matching_conf", None),
460
+ }
461
+ )
324
462
  cached_instance = self._built_plugins_cache.setdefault(
325
- (provider, topic_class.__name__), None
463
+ (provider, topic_class.__name__, auth_match_md5), None
326
464
  )
327
465
  if cached_instance is not None:
328
466
  return cached_instance
@@ -332,5 +470,7 @@ class PluginManager:
332
470
  plugin: Union[Api, Search, Download, Authentication, Crunch] = plugin_class(
333
471
  provider, plugin_conf
334
472
  )
335
- self._built_plugins_cache[(provider, topic_class.__name__)] = plugin
473
+ self._built_plugins_cache[
474
+ (provider, topic_class.__name__, auth_match_md5)
475
+ ] = plugin
336
476
  return plugin
@@ -16,3 +16,39 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
  """EODAG search package"""
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass, field
22
+ from typing import TYPE_CHECKING
23
+
24
+ from eodag.utils import DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE
25
+
26
+ if TYPE_CHECKING:
27
+ from typing import Any, Dict, List, Optional, Union
28
+
29
+ from requests.auth import AuthBase
30
+
31
+ from eodag.plugins.authentication.base import Authentication
32
+
33
+
34
+ @dataclass
35
+ class PreparedSearch:
36
+ """An object collecting needed information for search."""
37
+
38
+ product_type: Optional[str] = None
39
+ page: Optional[int] = DEFAULT_PAGE
40
+ items_per_page: Optional[int] = DEFAULT_ITEMS_PER_PAGE
41
+ auth: Optional[Union[AuthBase, Dict[str, str]]] = None
42
+ auth_plugin: Optional[Authentication] = None
43
+ count: bool = True
44
+ url: Optional[str] = None
45
+ info_message: Optional[str] = None
46
+ exception_message: Optional[str] = None
47
+
48
+ need_count: bool = field(init=False, repr=False)
49
+ query_params: Dict[str, Any] = field(init=False, repr=False)
50
+ 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)
53
+ total_items_nb: int = field(init=False, repr=False)
54
+ sort_by_qs: str = field(init=False, repr=False)