eodag 3.0.0b2__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +295 -287
  3. eodag/api/product/__init__.py +10 -4
  4. eodag/api/product/_assets.py +2 -14
  5. eodag/api/product/_product.py +16 -30
  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 +12 -31
  9. eodag/api/search_result.py +33 -12
  10. eodag/cli.py +35 -19
  11. eodag/config.py +455 -155
  12. eodag/plugins/apis/base.py +13 -7
  13. eodag/plugins/apis/ecmwf.py +16 -7
  14. eodag/plugins/apis/usgs.py +68 -16
  15. eodag/plugins/authentication/aws_auth.py +25 -7
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +14 -3
  18. eodag/plugins/authentication/header.py +12 -4
  19. eodag/plugins/authentication/keycloak.py +41 -22
  20. eodag/plugins/authentication/oauth.py +11 -1
  21. eodag/plugins/authentication/openid_connect.py +183 -167
  22. eodag/plugins/authentication/qsauth.py +12 -4
  23. eodag/plugins/authentication/sas_auth.py +19 -2
  24. eodag/plugins/authentication/token.py +59 -11
  25. eodag/plugins/authentication/token_exchange.py +19 -19
  26. eodag/plugins/crunch/base.py +7 -2
  27. eodag/plugins/crunch/filter_date.py +8 -11
  28. eodag/plugins/crunch/filter_latest_intersect.py +5 -7
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
  30. eodag/plugins/crunch/filter_overlap.py +9 -15
  31. eodag/plugins/crunch/filter_property.py +9 -14
  32. eodag/plugins/download/aws.py +84 -99
  33. eodag/plugins/download/base.py +36 -77
  34. eodag/plugins/download/creodias_s3.py +11 -2
  35. eodag/plugins/download/http.py +134 -109
  36. eodag/plugins/download/s3rest.py +37 -43
  37. eodag/plugins/manager.py +173 -41
  38. eodag/plugins/search/__init__.py +9 -9
  39. eodag/plugins/search/base.py +35 -35
  40. eodag/plugins/search/build_search_result.py +55 -64
  41. eodag/plugins/search/cop_marine.py +113 -32
  42. eodag/plugins/search/creodias_s3.py +20 -8
  43. eodag/plugins/search/csw.py +41 -1
  44. eodag/plugins/search/data_request_search.py +119 -14
  45. eodag/plugins/search/qssearch.py +619 -197
  46. eodag/plugins/search/static_stac_search.py +25 -23
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +211 -56
  49. eodag/resources/providers.yml +1762 -1809
  50. eodag/resources/stac.yml +3 -163
  51. eodag/resources/user_conf_template.yml +134 -119
  52. eodag/rest/config.py +1 -2
  53. eodag/rest/constants.py +0 -1
  54. eodag/rest/core.py +70 -92
  55. eodag/rest/errors.py +181 -0
  56. eodag/rest/server.py +24 -330
  57. eodag/rest/stac.py +105 -630
  58. eodag/rest/types/eodag_search.py +17 -15
  59. eodag/rest/types/queryables.py +5 -14
  60. eodag/rest/types/stac_search.py +18 -13
  61. eodag/rest/utils/rfc3339.py +0 -1
  62. eodag/types/__init__.py +24 -6
  63. eodag/types/download_args.py +14 -5
  64. eodag/types/queryables.py +1 -2
  65. eodag/types/search_args.py +10 -11
  66. eodag/types/whoosh.py +0 -2
  67. eodag/utils/__init__.py +97 -136
  68. eodag/utils/constraints.py +0 -8
  69. eodag/utils/exceptions.py +23 -9
  70. eodag/utils/import_system.py +0 -4
  71. eodag/utils/logging.py +37 -80
  72. eodag/utils/notebook.py +4 -4
  73. eodag/utils/requests.py +13 -23
  74. eodag/utils/rest.py +0 -4
  75. eodag/utils/stac_reader.py +3 -15
  76. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
  77. eodag-3.0.1.dist-info/RECORD +109 -0
  78. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
  79. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
  80. eodag/resources/constraints/climate-dt.json +0 -13
  81. eodag/resources/constraints/extremes-dt.json +0 -8
  82. eodag-3.0.0b2.dist-info/RECORD +0 -110
  83. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
  84. {eodag-3.0.0b2.dist-info → eodag-3.0.1.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, Optional, Union
23
+ from typing import TYPE_CHECKING, Dict, List, Optional, Union
24
24
  from xml.dom import minidom
25
25
  from xml.parsers.expat import ExpatError
26
26
 
@@ -62,26 +62,28 @@ logger = logging.getLogger("eodag.download.s3rest")
62
62
 
63
63
  class S3RestDownload(Download):
64
64
  """Http download on S3-like object storage location
65
- for example using Mundi REST API (free account)
65
+
66
+ For example using Mundi REST API (free account)
66
67
  https://mundiwebservices.com/keystoneapi/uploads/documents/CWS-DATA-MUT-087-EN-Mundi_Download_v1.1.pdf#page=13
67
68
 
68
- Re-use AwsDownload bucket some handling methods
69
+ Re-use AwsDownload bucket and some handling methods
69
70
 
70
71
  :param provider: provider name
71
- :type provider: str
72
72
  :param config: Download plugin configuration:
73
73
 
74
- * ``config.base_uri`` (str) - default endpoint url
75
- * ``config.extract`` (bool) - (optional) extract downloaded archive or not
76
- * ``config.auth_error_code`` (int) - (optional) authentication error code
77
- * ``config.bucket_path_level`` (int) - (optional) bucket location index in path.split('/')
78
- * ``config.order_enabled`` (bool) - (optional) wether order is enabled or not if product is `OFFLINE`
79
- * ``config.order_method`` (str) - (optional) HTTP request method, GET (default) or POST
80
- * ``config.order_headers`` (dict) - (optional) order request headers
81
- * ``config.order_on_response`` (dict) - (optional) edit or add new product properties
82
- * ``config.order_status`` (:class:`~eodag.config.PluginConfig.OrderStatus`) - Order status handling
83
-
84
- :type config: :class:`~eodag.config.PluginConfig`
74
+ * :attr:`~eodag.config.PluginConfig.base_uri` (``str``) (**mandatory**): default endpoint url
75
+ * :attr:`~eodag.config.PluginConfig.extract` (``bool``): extract downloaded archive or not
76
+ * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): authentication error code
77
+ * :attr:`~eodag.config.PluginConfig.bucket_path_level` (``int``): bucket location index in ``path.split('/')``
78
+ * :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): whether order is enabled
79
+ or not if product is `OFFLINE`
80
+ * :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_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
83
+ a typed dictionary containing the key :attr:`~eodag.config.PluginConfig.OrderOnResponse.metadata_mapping`
84
+ which can be used to add new product properties based on the data in response to the order request
85
+ * :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
86
+ Order status handling
85
87
  """
86
88
 
87
89
  def __init__(self, provider: str, config: PluginConfig) -> None:
@@ -100,22 +102,17 @@ class S3RestDownload(Download):
100
102
  """Download method for S3 REST API.
101
103
 
102
104
  :param product: The EO product to download
103
- :type product: :class:`~eodag.api.product._product.EOProduct`
104
105
  :param auth: (optional) authenticated object
105
- :type auth: Optional[Union[AuthBase, Dict[str, str]]]
106
106
  :param progress_callback: (optional) A method or a callable object
107
107
  which takes a current size and a maximum
108
108
  size as inputs and handle progress bar
109
109
  creation and update to give the user a
110
110
  feedback on the download progress
111
- :type progress_callback: :class:`~eodag.utils.ProgressCallback` or None
112
- :param kwargs: `outputs_prefix` (str), `extract` (bool), `delete_archive` (bool)
111
+ :param kwargs: `output_dir` (str), `extract` (bool), `delete_archive` (bool)
113
112
  and `dl_url_params` (dict) can be provided as additional kwargs
114
113
  and will override any other values defined in a configuration
115
114
  file or with environment variables.
116
- :type kwargs: Union[str, bool, dict]
117
115
  :returns: The absolute path to the downloaded product in the local filesystem
118
- :rtype: str
119
116
  """
120
117
  if auth is not None and not isinstance(auth, AuthBase):
121
118
  raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
@@ -133,7 +130,7 @@ class S3RestDownload(Download):
133
130
  and "storageStatus" in product.properties
134
131
  and product.properties["storageStatus"] != ONLINE_STATUS
135
132
  ):
136
- self.http_download_plugin.orderDownload(product=product, auth=auth)
133
+ self.http_download_plugin.order_download(product=product, auth=auth)
137
134
 
138
135
  @self._download_retry(product, wait, timeout)
139
136
  def download_request(
@@ -145,7 +142,7 @@ class S3RestDownload(Download):
145
142
  ):
146
143
  # check order status
147
144
  if product.properties.get("orderStatusLink", None):
148
- self.http_download_plugin.orderDownloadStatus(
145
+ self.http_download_plugin.order_download_status(
149
146
  product=product, auth=auth
150
147
  )
151
148
 
@@ -153,6 +150,8 @@ class S3RestDownload(Download):
153
150
  bucket_name, prefix = get_bucket_name_and_prefix(
154
151
  url=product.location, bucket_path_level=self.config.bucket_path_level
155
152
  )
153
+ if prefix is None:
154
+ raise DownloadError(f"Could not extract prefix from {product.location}")
156
155
 
157
156
  if (
158
157
  bucket_name is None
@@ -195,12 +194,9 @@ class S3RestDownload(Download):
195
194
  auth_errors = [auth_errors]
196
195
  if err.response and err.response.status_code in auth_errors:
197
196
  raise AuthenticationError(
198
- "HTTP Error %s returned, %s\nPlease check your credentials for %s"
199
- % (
200
- err.response.status_code,
201
- err.response.text.strip(),
202
- self.provider,
203
- )
197
+ f"Please check your credentials for {self.provider}.",
198
+ f"HTTP Error {err.response.status_code} returned.",
199
+ err.response.text.strip(),
204
200
  )
205
201
  # product not available
206
202
  elif (
@@ -231,7 +227,7 @@ class S3RestDownload(Download):
231
227
  self.__class__.__name__,
232
228
  bucket_contents.text,
233
229
  )
234
- raise RequestError(str(err))
230
+ raise RequestError.from_error(err) from err
235
231
  try:
236
232
  xmldoc = minidom.parseString(bucket_contents.text)
237
233
  except ExpatError as err:
@@ -243,14 +239,12 @@ class S3RestDownload(Download):
243
239
  logger.warning("Could not load any content from %s", nodes_list_url)
244
240
 
245
241
  # destination product path
246
- outputs_prefix = (
247
- kwargs.pop("outputs_prefix", None) or self.config.outputs_prefix
248
- )
249
- abs_outputs_prefix = os.path.abspath(outputs_prefix)
250
- product_local_path = os.path.join(abs_outputs_prefix, prefix.split("/")[-1])
242
+ output_dir = kwargs.pop("output_dir", None) or self.config.output_dir
243
+ abs_output_dir = os.path.abspath(output_dir)
244
+ product_local_path = os.path.join(abs_output_dir, prefix.split("/")[-1])
251
245
 
252
246
  # .downloaded cache record directory
253
- download_records_dir = os.path.join(abs_outputs_prefix, ".downloaded")
247
+ download_records_dir = os.path.join(abs_output_dir, ".downloaded")
254
248
  try:
255
249
  os.makedirs(download_records_dir)
256
250
  except OSError as exc:
@@ -278,18 +272,18 @@ class S3RestDownload(Download):
278
272
  os.remove(record_filename)
279
273
 
280
274
  # total size for progress_callback
281
- total_size = sum(
282
- [
283
- int(node.firstChild.nodeValue)
284
- for node in xmldoc.getElementsByTagName("Size")
285
- ]
286
- )
275
+ size_list: List[int] = [
276
+ int(node.firstChild.nodeValue) # type: ignore[attr-defined]
277
+ for node in xmldoc.getElementsByTagName("Size")
278
+ if node.firstChild is not None
279
+ ]
280
+ total_size = sum(size_list)
287
281
  progress_callback.reset(total=total_size)
288
282
 
289
283
  # download each node key
290
284
  for node_xml in nodes_xml_list:
291
285
  node_key = unquote(
292
- node_xml.getElementsByTagName("Key")[0].firstChild.nodeValue
286
+ node_xml.getElementsByTagName("Key")[0].firstChild.nodeValue # type: ignore[union-attr]
293
287
  )
294
288
  # As "Key", "Size" and "ETag" (md5 hash) can also be retrieved from node_xml
295
289
  node_url = urljoin(bucket_url.strip("/") + "/", node_key.strip("/"))
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 MisconfiguredError, 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,10 +80,9 @@ 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)
75
86
 
76
87
  product_type_to_provider_config_map: Dict[str, List[ProviderConfig]]
77
88
 
@@ -111,7 +122,11 @@ class PluginManager:
111
122
  "Check that the plugin module (%s) is importable",
112
123
  entry_point.module_name,
113
124
  )
114
- 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
+ ):
115
130
  # use plugin providers if any
116
131
  plugin_providers_config_path = [
117
132
  str(x)
@@ -136,7 +151,7 @@ class PluginManager:
136
151
  self.providers_config = providers_config
137
152
 
138
153
  self.build_product_type_to_provider_config_map()
139
- self._built_plugins_cache: Dict[Tuple[str, str], Any] = {}
154
+ self._built_plugins_cache: Dict[Tuple[str, str, str], Any] = {}
140
155
 
141
156
  def build_product_type_to_provider_config_map(self) -> None:
142
157
  """Build mapping conf between product types and providers"""
@@ -172,13 +187,10 @@ class PluginManager:
172
187
 
173
188
  :param product_type: (optional) The product type that the constructed plugins
174
189
  must support
175
- :type product_type: str
176
190
  :param provider: (optional) The provider or the provider group on which to get
177
191
  the search plugins
178
- :type provider: str
179
192
  :returns: All the plugins supporting the product type, one by one (a generator
180
193
  object)
181
- :rtype: types.GeneratorType(:class:`~eodag.plugins.search.Search`
182
194
  or :class:`~eodag.plugins.download.Api`)
183
195
  :raises: :class:`~eodag.utils.exceptions.UnsupportedProvider`
184
196
  """
@@ -230,9 +242,7 @@ class PluginManager:
230
242
  product.
231
243
 
232
244
  :param product: The product to get a download plugin for
233
- :type product: :class:`~eodag.api.product._product.EOProduct`
234
245
  :returns: The download plugin capable of downloading the product
235
- :rtype: :class:`~eodag.plugins.download.Download` or :class:`~eodag.plugins.download.Api`
236
246
  """
237
247
  plugin_conf = self.providers_config[product.provider]
238
248
  if download := getattr(plugin_conf, "download", None):
@@ -251,27 +261,146 @@ class PluginManager:
251
261
  )
252
262
  return plugin
253
263
 
254
- def get_auth_plugin(self, provider: str) -> Optional[Authentication]:
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.
273
+
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]:
255
308
  """Build and return the authentication plugin for the given product_type and
256
309
  provider
257
310
 
258
311
  :param provider: The provider for which to get the authentication plugin
259
- :type provider: str
260
- :returns: The Authentication plugin for the provider
261
- :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
262
315
  """
263
- plugin_conf = self.providers_config[provider]
264
- auth: Optional[PluginConfig] = getattr(plugin_conf, "auth", None)
265
- if not auth:
266
- # We guess the plugin being built is of type Api, therefore no need
267
- # for an Auth plugin.
268
- return None
269
- auth.priority = plugin_conf.priority
270
- plugin = cast(
271
- Authentication,
272
- self._build_plugin(provider, auth, Authentication),
273
- )
274
- return plugin
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
+ # no match
339
+ return False
340
+
341
+ # providers configs with given provider at first
342
+ sorted_providers_config = deepcopy(self.providers_config)
343
+ sorted_providers_config = {
344
+ provider: sorted_providers_config.pop(provider),
345
+ **sorted_providers_config,
346
+ }
347
+
348
+ for plugin_provider, provider_conf in sorted_providers_config.items():
349
+ for key in AUTH_TOPIC_KEYS:
350
+ auth_conf = getattr(provider_conf, key, None)
351
+ if auth_conf is None:
352
+ continue
353
+ # plugin without configured match criteria: only works for given provider
354
+ unconfigured_match = (
355
+ True
356
+ if (
357
+ not getattr(auth_conf, "matching_conf", {})
358
+ and not getattr(auth_conf, "matching_url", None)
359
+ and provider == plugin_provider
360
+ )
361
+ else False
362
+ )
363
+
364
+ if unconfigured_match or _is_auth_plugin_matching(
365
+ auth_conf, matching_url, matching_conf
366
+ ):
367
+ auth_conf.priority = provider_conf.priority
368
+ plugin = cast(
369
+ Authentication,
370
+ self._build_plugin(plugin_provider, auth_conf, Authentication),
371
+ )
372
+ yield plugin
373
+ else:
374
+ continue
375
+
376
+ def get_auth(
377
+ self,
378
+ provider: str,
379
+ matching_url: Optional[str] = None,
380
+ matching_conf: Optional[PluginConfig] = None,
381
+ ) -> Optional[Union[AuthBase, Dict[str, str]]]:
382
+ """Authenticate and return the authenticated object for the first matching
383
+ authentication plugin
384
+
385
+ :param provider: The provider for which to get the authentication plugin
386
+ :param matching_url: url to compare with plugin matching_url pattern
387
+ :param matching_conf: configuration to compare with plugin matching_conf
388
+ :returns: All the Authentication plugins for the given criteria
389
+ """
390
+ for auth_plugin in self.get_auth_plugins(provider, matching_url, matching_conf):
391
+ if auth_plugin and callable(getattr(auth_plugin, "authenticate", None)):
392
+ try:
393
+ auth = auth_plugin.authenticate()
394
+ return auth
395
+ except (AuthenticationError, MisconfiguredError) as e:
396
+ logger.debug(f"Could not authenticate on {provider}: {str(e)}")
397
+ continue
398
+ else:
399
+ logger.debug(
400
+ f"Could not authenticate on {provider} using {auth_plugin} plugin"
401
+ )
402
+ continue
403
+ return None
275
404
 
276
405
  @staticmethod
277
406
  def get_crunch_plugin(name: str, **options: Any) -> Crunch:
@@ -279,11 +408,8 @@ class PluginManager:
279
408
  it with the `options`
280
409
 
281
410
  :param name: The name of the Crunch plugin to instantiate
282
- :type name: str
283
411
  :param options: The configuration parameters of the cruncher
284
- :type options: dict
285
412
  :returns: The cruncher named `name`
286
- :rtype: :class:`~eodag.plugins.crunch.Crunch`
287
413
  """
288
414
  klass = Crunch.get_plugin_by_class_name(name)
289
415
  return klass(options)
@@ -297,9 +423,7 @@ class PluginManager:
297
423
  """Set the priority of the given provider
298
424
 
299
425
  :param provider: The provider which is assigned the priority
300
- :type provider: str
301
426
  :param priority: The priority to assign to the provider
302
- :type priority: int
303
427
  """
304
428
  # Update the priority in the configurations so that it is taken into account
305
429
  # when a plugin of this provider is latterly built
@@ -313,9 +437,11 @@ class PluginManager:
313
437
  # Sort the provider configs, taking into account the new priority order
314
438
  provider_configs.sort(key=attrgetter("priority"), reverse=True)
315
439
  # Update the priority of already built plugins of the given provider
316
- for provider_name, topic_class in self._built_plugins_cache:
440
+ for provider_name, topic_class, auth_match_md5 in self._built_plugins_cache:
317
441
  if provider_name == provider:
318
- self._built_plugins_cache[(provider, topic_class)].priority = priority
442
+ self._built_plugins_cache[
443
+ (provider, topic_class, auth_match_md5)
444
+ ].priority = priority
319
445
 
320
446
  def _build_plugin(
321
447
  self,
@@ -327,19 +453,23 @@ class PluginManager:
327
453
  registered as the given provider
328
454
 
329
455
  :param provider: The provider for which to build the plugin
330
- :type provider: str
331
456
  :param plugin_conf: The configuration of the plugin to be built
332
- :type plugin_conf: :class:`~eodag.config.PluginConfig`
333
457
  :param topic_class: The type of plugin to build
334
- :type topic_class: :class:`~eodag.plugin.base.PluginTopic`
335
458
  :returns: The built plugin
336
- :rtype: :class:`~eodag.plugin.search.Search` or
337
459
  :class:`~eodag.plugin.download.Download` or
338
460
  :class:`~eodag.plugin.authentication.Authentication` or
339
461
  :class:`~eodag.plugin.crunch.Crunch`
340
462
  """
463
+ # md5 hash to helps identifying an auth plugin within several for a given provider
464
+ # (each has distinct matching settings)
465
+ auth_match_md5 = dict_md5sum(
466
+ {
467
+ "matching_url": getattr(plugin_conf, "matching_url", None),
468
+ "matching_conf": getattr(plugin_conf, "matching_conf", None),
469
+ }
470
+ )
341
471
  cached_instance = self._built_plugins_cache.setdefault(
342
- (provider, topic_class.__name__), None
472
+ (provider, topic_class.__name__, auth_match_md5), None
343
473
  )
344
474
  if cached_instance is not None:
345
475
  return cached_instance
@@ -349,5 +479,7 @@ class PluginManager:
349
479
  plugin: Union[Api, Search, Download, Authentication, Crunch] = plugin_class(
350
480
  provider, plugin_conf
351
481
  )
352
- self._built_plugins_cache[(provider, topic_class.__name__)] = plugin
482
+ self._built_plugins_cache[
483
+ (provider, topic_class.__name__, auth_match_md5)
484
+ ] = plugin
353
485
  return plugin
@@ -36,8 +36,8 @@ class PreparedSearch:
36
36
  """An object collecting needed information for search."""
37
37
 
38
38
  product_type: Optional[str] = None
39
- page: int = DEFAULT_PAGE
40
- items_per_page: int = DEFAULT_ITEMS_PER_PAGE
39
+ page: Optional[int] = DEFAULT_PAGE
40
+ items_per_page: Optional[int] = DEFAULT_ITEMS_PER_PAGE
41
41
  auth: Optional[Union[AuthBase, Dict[str, str]]] = None
42
42
  auth_plugin: Optional[Authentication] = None
43
43
  count: bool = True
@@ -45,10 +45,10 @@ class PreparedSearch:
45
45
  info_message: Optional[str] = None
46
46
  exception_message: Optional[str] = None
47
47
 
48
- need_count: bool = field(init=False)
49
- query_params: Dict[str, Any] = field(init=False)
50
- query_string: str = field(init=False)
51
- search_urls: List[str] = field(init=False)
52
- product_type_def_params: Dict[str, Any] = field(init=False)
53
- total_items_nb: int = field(init=False)
54
- sort_by_qs: str = field(init=False)
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)