eodag 3.0.0b3__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 (94) hide show
  1. eodag/api/core.py +347 -247
  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 +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {eodag-3.0.0b3.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
 
@@ -62,23 +63,28 @@ logger = logging.getLogger("eodag.download.s3rest")
62
63
 
63
64
  class S3RestDownload(Download):
64
65
  """Http download on S3-like object storage location
65
- for example using Mundi REST API (free account)
66
+
67
+ For example using Mundi REST API (free account)
66
68
  https://mundiwebservices.com/keystoneapi/uploads/documents/CWS-DATA-MUT-087-EN-Mundi_Download_v1.1.pdf#page=13
67
69
 
68
- Re-use AwsDownload bucket some handling methods
70
+ Re-use AwsDownload bucket and some handling methods
69
71
 
70
72
  :param provider: provider name
71
73
  :param config: Download plugin configuration:
72
74
 
73
- * ``config.base_uri`` (str) - default endpoint url
74
- * ``config.extract`` (bool) - (optional) extract downloaded archive or not
75
- * ``config.auth_error_code`` (int) - (optional) authentication error code
76
- * ``config.bucket_path_level`` (int) - (optional) bucket location index in path.split('/')
77
- * ``config.order_enabled`` (bool) - (optional) wether order is enabled or not if product is `OFFLINE`
78
- * ``config.order_method`` (str) - (optional) HTTP request method, GET (default) or POST
79
- * ``config.order_headers`` (dict) - (optional) order request headers
80
- * ``config.order_on_response`` (dict) - (optional) edit or add new product properties
81
- * ``config.order_status`` (:class:`~eodag.config.PluginConfig.OrderStatus`) - Order status handling
75
+ * :attr:`~eodag.config.PluginConfig.base_uri` (``str``) (**mandatory**): default endpoint url
76
+ * :attr:`~eodag.config.PluginConfig.extract` (``bool``): extract downloaded archive or not
77
+ * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): authentication error code
78
+ * :attr:`~eodag.config.PluginConfig.bucket_path_level` (``int``): bucket location index in ``path.split('/')``
79
+ * :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): whether order is enabled
80
+ or not if product is `OFFLINE`
81
+ * :attr:`~eodag.config.PluginConfig.order_method` (``str``) HTTP request method, ``GET`` (default) or ``POST``
82
+ * :attr:`~eodag.config.PluginConfig.order_headers` (``[dict[str, str]]``): order request headers
83
+ * :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
84
+ a typed dictionary containing the key :attr:`~eodag.config.PluginConfig.OrderOnResponse.metadata_mapping`
85
+ which can be used to add new product properties based on the data in response to the order request
86
+ * :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
87
+ Order status handling
82
88
  """
83
89
 
84
90
  def __init__(self, provider: str, config: PluginConfig) -> None:
@@ -88,10 +94,10 @@ class S3RestDownload(Download):
88
94
  def download(
89
95
  self,
90
96
  product: EOProduct,
91
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
97
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
92
98
  progress_callback: Optional[ProgressCallback] = None,
93
- wait: int = DEFAULT_DOWNLOAD_WAIT,
94
- timeout: int = DEFAULT_DOWNLOAD_TIMEOUT,
99
+ wait: float = DEFAULT_DOWNLOAD_WAIT,
100
+ timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
95
101
  **kwargs: Unpack[DownloadConf],
96
102
  ) -> Optional[str]:
97
103
  """Download method for S3 REST API.
@@ -125,9 +131,9 @@ class S3RestDownload(Download):
125
131
  and "storageStatus" in product.properties
126
132
  and product.properties["storageStatus"] != ONLINE_STATUS
127
133
  ):
128
- self.http_download_plugin.order_download(product=product, auth=auth)
134
+ self.http_download_plugin._order(product=product, auth=auth)
129
135
 
130
- @self._download_retry(product, wait, timeout)
136
+ @self._order_download_retry(product, wait, timeout)
131
137
  def download_request(
132
138
  product: EOProduct,
133
139
  auth: AuthBase,
@@ -137,9 +143,7 @@ class S3RestDownload(Download):
137
143
  ):
138
144
  # check order status
139
145
  if product.properties.get("orderStatusLink", None):
140
- self.http_download_plugin.order_download_status(
141
- product=product, auth=auth
142
- )
146
+ self.http_download_plugin._order_status(product=product, auth=auth)
143
147
 
144
148
  # get bucket urls
145
149
  bucket_name, prefix = get_bucket_name_and_prefix(
@@ -189,12 +193,9 @@ class S3RestDownload(Download):
189
193
  auth_errors = [auth_errors]
190
194
  if err.response and err.response.status_code in auth_errors:
191
195
  raise AuthenticationError(
192
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
193
- % (
194
- err.response.status_code,
195
- err.response.text.strip(),
196
- self.provider,
197
- )
196
+ f"Please check your credentials for {self.provider}.",
197
+ f"HTTP Error {err.response.status_code} returned.",
198
+ err.response.text.strip(),
198
199
  )
199
200
  # product not available
200
201
  elif (
@@ -225,7 +226,7 @@ class S3RestDownload(Download):
225
226
  self.__class__.__name__,
226
227
  bucket_contents.text,
227
228
  )
228
- raise RequestError(str(err))
229
+ raise RequestError.from_error(err) from err
229
230
  try:
230
231
  xmldoc = minidom.parseString(bucket_contents.text)
231
232
  except ExpatError as err:
@@ -270,7 +271,7 @@ class S3RestDownload(Download):
270
271
  os.remove(record_filename)
271
272
 
272
273
  # total size for progress_callback
273
- size_list: List[int] = [
274
+ size_list: list[int] = [
274
275
  int(node.firstChild.nodeValue) # type: ignore[attr-defined]
275
276
  for node in xmldoc.getElementsByTagName("Size")
276
277
  if node.firstChild is not None
eodag/plugins/manager.py CHANGED
@@ -18,37 +18,39 @@
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
- from typing import (
24
- TYPE_CHECKING,
25
- Any,
26
- Dict,
27
- Iterator,
28
- List,
29
- Optional,
30
- Tuple,
31
- Type,
32
- Union,
33
- cast,
34
- )
24
+ from typing import TYPE_CHECKING, Any, Iterator, Optional, Union, cast
35
25
 
36
26
  import pkg_resources
37
27
 
38
- from eodag.config import load_config, merge_configs
28
+ from eodag.config import (
29
+ AUTH_TOPIC_KEYS,
30
+ PLUGINS_TOPICS_KEYS,
31
+ load_config,
32
+ merge_configs,
33
+ )
39
34
  from eodag.plugins.apis.base import Api
40
35
  from eodag.plugins.authentication.base import Authentication
41
36
  from eodag.plugins.base import EODAGPluginMount
42
37
  from eodag.plugins.crunch.base import Crunch
43
38
  from eodag.plugins.download.base import Download
44
39
  from eodag.plugins.search.base import Search
45
- from eodag.utils import GENERIC_PRODUCT_TYPE
46
- from eodag.utils.exceptions import MisconfiguredError, UnsupportedProvider
40
+ from eodag.utils import GENERIC_PRODUCT_TYPE, deepcopy, dict_md5sum
41
+ from eodag.utils.exceptions import (
42
+ AuthenticationError,
43
+ MisconfiguredError,
44
+ UnsupportedProvider,
45
+ )
47
46
 
48
47
  if TYPE_CHECKING:
48
+ from requests.auth import AuthBase
49
+
49
50
  from eodag.api.product import EOProduct
50
51
  from eodag.config import PluginConfig, ProviderConfig
51
52
  from eodag.plugins.base import PluginTopic
53
+ from eodag.types import S3SessionKwargs
52
54
 
53
55
 
54
56
  logger = logging.getLogger("eodag.plugins.manager")
@@ -70,13 +72,13 @@ class PluginManager:
70
72
  supported by ``eodag``
71
73
  """
72
74
 
73
- supported_topics = {"search", "download", "crunch", "auth", "api"}
75
+ supported_topics = set(PLUGINS_TOPICS_KEYS)
74
76
 
75
- product_type_to_provider_config_map: Dict[str, List[ProviderConfig]]
77
+ product_type_to_provider_config_map: dict[str, list[ProviderConfig]]
76
78
 
77
- skipped_plugins: List[str]
79
+ skipped_plugins: list[str]
78
80
 
79
- def __init__(self, providers_config: Dict[str, ProviderConfig]) -> None:
81
+ def __init__(self, providers_config: dict[str, ProviderConfig]) -> None:
80
82
  self.skipped_plugins = []
81
83
  self.providers_config = providers_config
82
84
  # Load all the plugins. This will make all plugin classes of a particular
@@ -132,14 +134,14 @@ class PluginManager:
132
134
  self.rebuild()
133
135
 
134
136
  def rebuild(
135
- self, providers_config: Optional[Dict[str, ProviderConfig]] = None
137
+ self, providers_config: Optional[dict[str, ProviderConfig]] = None
136
138
  ) -> None:
137
139
  """(Re)Build plugin manager mapping and cache"""
138
140
  if providers_config is not None:
139
141
  self.providers_config = providers_config
140
142
 
141
143
  self.build_product_type_to_provider_config_map()
142
- self._built_plugins_cache: Dict[Tuple[str, str], Any] = {}
144
+ self._built_plugins_cache: dict[tuple[str, str, str], Any] = {}
143
145
 
144
146
  def build_product_type_to_provider_config_map(self) -> None:
145
147
  """Build mapping conf between product types and providers"""
@@ -199,7 +201,7 @@ class PluginManager:
199
201
  )
200
202
  return plugin
201
203
 
202
- configs: Optional[List[ProviderConfig]]
204
+ configs: Optional[list[ProviderConfig]]
203
205
  if product_type:
204
206
  configs = self.product_type_to_provider_config_map.get(product_type)
205
207
  if not configs:
@@ -249,25 +251,146 @@ class PluginManager:
249
251
  )
250
252
  return plugin
251
253
 
252
- def get_auth_plugin(self, provider: str) -> Optional[Authentication]:
254
+ def get_auth_plugin(
255
+ self, associated_plugin: PluginTopic, product: Optional[EOProduct] = None
256
+ ) -> Optional[Authentication]:
257
+ """Build and return the authentication plugin associated to the given
258
+ search/download plugin
259
+
260
+ .. versionchanged:: v3.0.0
261
+ ``get_auth_plugin()`` now needs ``associated_plugin`` instead of ``provider``
262
+ as argument.
263
+
264
+ :param associated_plugin: The search/download plugin to which the authentication
265
+ plugin is linked
266
+ :param product: The product to download. ``None`` for search authentication
267
+ :returns: The Authentication plugin
268
+ """
269
+ # matching url from product to download
270
+ if product is not None and len(product.assets) > 0:
271
+ matching_url = next(iter(product.assets.values()))["href"]
272
+ elif product is not None:
273
+ matching_url = product.properties.get(
274
+ "downloadLink"
275
+ ) or product.properties.get("orderLink")
276
+ else:
277
+ # search auth
278
+ matching_url = getattr(associated_plugin.config, "api_endpoint", None)
279
+
280
+ try:
281
+ auth_plugin = next(
282
+ self.get_auth_plugins(
283
+ associated_plugin.provider,
284
+ matching_url=matching_url,
285
+ matching_conf=associated_plugin.config,
286
+ )
287
+ )
288
+ except StopIteration:
289
+ auth_plugin = None
290
+ return auth_plugin
291
+
292
+ def get_auth_plugins(
293
+ self,
294
+ provider: str,
295
+ matching_url: Optional[str] = None,
296
+ matching_conf: Optional[PluginConfig] = None,
297
+ ) -> Iterator[Authentication]:
253
298
  """Build and return the authentication plugin for the given product_type and
254
299
  provider
255
300
 
256
301
  :param provider: The provider for which to get the authentication plugin
257
- :returns: The Authentication plugin for the provider
302
+ :param matching_url: url to compare with plugin matching_url pattern
303
+ :param matching_conf: configuration to compare with plugin matching_conf
304
+ :returns: All the Authentication plugins for the given criteria
258
305
  """
259
- plugin_conf = self.providers_config[provider]
260
- auth: Optional[PluginConfig] = getattr(plugin_conf, "auth", None)
261
- if not auth:
262
- # We guess the plugin being built is of type Api, therefore no need
263
- # for an Auth plugin.
264
- return None
265
- auth.priority = plugin_conf.priority
266
- plugin = cast(
267
- Authentication,
268
- self._build_plugin(provider, auth, Authentication),
269
- )
270
- return plugin
306
+ auth_conf: Optional[PluginConfig] = None
307
+
308
+ def _is_auth_plugin_matching(
309
+ auth_conf: PluginConfig,
310
+ matching_url: Optional[str],
311
+ matching_conf: Optional[PluginConfig],
312
+ ) -> bool:
313
+ plugin_matching_conf = getattr(auth_conf, "matching_conf", {})
314
+ if matching_conf:
315
+ if (
316
+ plugin_matching_conf
317
+ and matching_conf.__dict__.items() >= plugin_matching_conf.items()
318
+ ):
319
+ # conf matches
320
+ return True
321
+ plugin_matching_url = getattr(auth_conf, "matching_url", None)
322
+ if matching_url:
323
+ if plugin_matching_url and re.match(
324
+ rf"{plugin_matching_url}", matching_url
325
+ ):
326
+ # url matches
327
+ return True
328
+ # no match
329
+ return False
330
+
331
+ # providers configs with given provider at first
332
+ sorted_providers_config = deepcopy(self.providers_config)
333
+ sorted_providers_config = {
334
+ provider: sorted_providers_config.pop(provider),
335
+ **sorted_providers_config,
336
+ }
337
+
338
+ for plugin_provider, provider_conf in sorted_providers_config.items():
339
+ for key in AUTH_TOPIC_KEYS:
340
+ auth_conf = getattr(provider_conf, key, None)
341
+ if auth_conf is None:
342
+ continue
343
+ # plugin without configured match criteria: only works for given provider
344
+ unconfigured_match = (
345
+ True
346
+ if (
347
+ not getattr(auth_conf, "matching_conf", {})
348
+ and not getattr(auth_conf, "matching_url", None)
349
+ and provider == plugin_provider
350
+ )
351
+ else False
352
+ )
353
+
354
+ if unconfigured_match or _is_auth_plugin_matching(
355
+ auth_conf, matching_url, matching_conf
356
+ ):
357
+ auth_conf.priority = provider_conf.priority
358
+ plugin = cast(
359
+ Authentication,
360
+ self._build_plugin(plugin_provider, auth_conf, Authentication),
361
+ )
362
+ yield plugin
363
+ else:
364
+ continue
365
+
366
+ def get_auth(
367
+ self,
368
+ provider: str,
369
+ matching_url: Optional[str] = None,
370
+ matching_conf: Optional[PluginConfig] = None,
371
+ ) -> Optional[Union[AuthBase, S3SessionKwargs]]:
372
+ """Authenticate and return the authenticated object for the first matching
373
+ authentication plugin
374
+
375
+ :param provider: The provider for which to get the authentication plugin
376
+ :param matching_url: url to compare with plugin matching_url pattern
377
+ :param matching_conf: configuration to compare with plugin matching_conf
378
+ :returns: All the Authentication plugins for the given criteria
379
+ """
380
+ for auth_plugin in self.get_auth_plugins(provider, matching_url, matching_conf):
381
+ if auth_plugin and callable(getattr(auth_plugin, "authenticate", None)):
382
+ try:
383
+ auth = auth_plugin.authenticate()
384
+ return auth
385
+ except (AuthenticationError, MisconfiguredError) as e:
386
+ logger.debug(f"Could not authenticate on {provider}: {str(e)}")
387
+ continue
388
+ else:
389
+ logger.debug(
390
+ f"Could not authenticate on {provider} using {auth_plugin} plugin"
391
+ )
392
+ continue
393
+ return None
271
394
 
272
395
  @staticmethod
273
396
  def get_crunch_plugin(name: str, **options: Any) -> Crunch:
@@ -304,15 +427,17 @@ class PluginManager:
304
427
  # Sort the provider configs, taking into account the new priority order
305
428
  provider_configs.sort(key=attrgetter("priority"), reverse=True)
306
429
  # Update the priority of already built plugins of the given provider
307
- for provider_name, topic_class in self._built_plugins_cache:
430
+ for provider_name, topic_class, auth_match_md5 in self._built_plugins_cache:
308
431
  if provider_name == provider:
309
- self._built_plugins_cache[(provider, topic_class)].priority = priority
432
+ self._built_plugins_cache[
433
+ (provider, topic_class, auth_match_md5)
434
+ ].priority = priority
310
435
 
311
436
  def _build_plugin(
312
437
  self,
313
438
  provider: str,
314
439
  plugin_conf: PluginConfig,
315
- topic_class: Type[PluginTopic],
440
+ topic_class: type[PluginTopic],
316
441
  ) -> Union[Api, Search, Download, Authentication, Crunch]:
317
442
  """Build the plugin of the given topic with the given plugin configuration and
318
443
  registered as the given provider
@@ -325,8 +450,16 @@ class PluginManager:
325
450
  :class:`~eodag.plugin.authentication.Authentication` or
326
451
  :class:`~eodag.plugin.crunch.Crunch`
327
452
  """
453
+ # md5 hash to helps identifying an auth plugin within several for a given provider
454
+ # (each has distinct matching settings)
455
+ auth_match_md5 = dict_md5sum(
456
+ {
457
+ "matching_url": getattr(plugin_conf, "matching_url", None),
458
+ "matching_conf": getattr(plugin_conf, "matching_conf", None),
459
+ }
460
+ )
328
461
  cached_instance = self._built_plugins_cache.setdefault(
329
- (provider, topic_class.__name__), None
462
+ (provider, topic_class.__name__, auth_match_md5), None
330
463
  )
331
464
  if cached_instance is not None:
332
465
  return cached_instance
@@ -336,5 +469,7 @@ class PluginManager:
336
469
  plugin: Union[Api, Search, Download, Authentication, Crunch] = plugin_class(
337
470
  provider, plugin_conf
338
471
  )
339
- self._built_plugins_cache[(provider, topic_class.__name__)] = plugin
472
+ self._built_plugins_cache[
473
+ (provider, topic_class.__name__, auth_match_md5)
474
+ ] = plugin
340
475
  return plugin
@@ -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)