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/config.py CHANGED
@@ -27,6 +27,7 @@ from typing import (
27
27
  ItemsView,
28
28
  Iterator,
29
29
  List,
30
+ Literal,
30
31
  Optional,
31
32
  Tuple,
32
33
  TypedDict,
@@ -40,13 +41,15 @@ import requests
40
41
  import yaml
41
42
  import yaml.constructor
42
43
  import yaml.parser
44
+ from annotated_types import Gt
43
45
  from jsonpath_ng import JSONPath
44
46
  from pkg_resources import resource_filename
45
- from requests.auth import AuthBase
46
47
 
48
+ from eodag.api.product.metadata_mapping import mtd_cfg_as_conversion_and_querypath
47
49
  from eodag.utils import (
48
50
  HTTP_REQ_TIMEOUT,
49
51
  USER_AGENT,
52
+ Annotated,
50
53
  cached_yaml_load,
51
54
  cached_yaml_load_all,
52
55
  cast_scalar_value,
@@ -54,6 +57,7 @@ from eodag.utils import (
54
57
  dict_items_recursive_apply,
55
58
  merge_mappings,
56
59
  slugify,
60
+ sort_dict,
57
61
  string_to_jsonpath,
58
62
  update_nested_dict,
59
63
  uri_to_path,
@@ -65,6 +69,8 @@ logger = logging.getLogger("eodag.config")
65
69
  EXT_PRODUCT_TYPES_CONF_URI = (
66
70
  "https://cs-si.github.io/eodag/eodag/resources/ext_product_types.json"
67
71
  )
72
+ AUTH_TOPIC_KEYS = ("auth", "search_auth", "download_auth")
73
+ PLUGINS_TOPICS_KEYS = ("api", "search", "download") + AUTH_TOPIC_KEYS
68
74
 
69
75
 
70
76
  class SimpleYamlProxyConfig:
@@ -106,31 +112,31 @@ class ProviderConfig(yaml.YAMLObject):
106
112
  """Representation of eodag configuration.
107
113
 
108
114
  :param name: The name of the provider
109
- :type name: str
110
115
  :param priority: (optional) The priority of the provider while searching a product.
111
116
  Lower value means lower priority. (Default: 0)
112
- :type priority: int
113
117
  :param api: (optional) The configuration of a plugin of type Api
114
- :type api: :class:`~eodag.config.PluginConfig`
115
118
  :param search: (optional) The configuration of a plugin of type Search
116
- :type search: :class:`~eodag.config.PluginConfig`
117
119
  :param products: (optional) The products types supported by the provider
118
- :type products: dict
119
120
  :param download: (optional) The configuration of a plugin of type Download
120
- :type download: :class:`~eodag.config.PluginConfig`
121
121
  :param auth: (optional) The configuration of a plugin of type Authentication
122
- :type auth: :class:`~eodag.config.PluginConfig`
122
+ :param search_auth: (optional) The configuration of a plugin of type Authentication for search
123
+ :param download_auth: (optional) The configuration of a plugin of type Authentication for download
123
124
  :param kwargs: Additional configuration variables for this provider
124
- :type kwargs: Any
125
125
  """
126
126
 
127
127
  name: str
128
+ group: str
128
129
  priority: int = 0 # Set default priority to 0
130
+ roles: List[str]
131
+ description: str
132
+ url: str
129
133
  api: PluginConfig
130
134
  search: PluginConfig
131
135
  products: Dict[str, Any]
132
136
  download: PluginConfig
133
137
  auth: PluginConfig
138
+ search_auth: PluginConfig
139
+ download_auth: PluginConfig
134
140
  product_types_fetched: bool # set in core.update_product_types_list
135
141
 
136
142
  yaml_loader = yaml.Loader
@@ -138,7 +144,7 @@ class ProviderConfig(yaml.YAMLObject):
138
144
  yaml_tag = "!provider"
139
145
 
140
146
  @classmethod
141
- def from_yaml(cls, loader: yaml.Loader, node: Any) -> ProviderConfig:
147
+ def from_yaml(cls, loader: yaml.Loader, node: Any) -> Iterator[ProviderConfig]:
142
148
  """Build a :class:`~eodag.config.ProviderConfig` from Yaml"""
143
149
  cls.validate(tuple(node_key.value for node_key, _ in node.value))
144
150
  for node_key, node_value in node.value:
@@ -151,7 +157,7 @@ class ProviderConfig(yaml.YAMLObject):
151
157
  def from_mapping(cls, mapping: Dict[str, Any]) -> ProviderConfig:
152
158
  """Build a :class:`~eodag.config.ProviderConfig` from a mapping"""
153
159
  cls.validate(mapping)
154
- for key in ("api", "search", "download", "auth"):
160
+ for key in PLUGINS_TOPICS_KEYS:
155
161
  if key in mapping:
156
162
  mapping[key] = PluginConfig.from_mapping(mapping[key])
157
163
  c = cls()
@@ -163,15 +169,13 @@ class ProviderConfig(yaml.YAMLObject):
163
169
  """Validate a :class:`~eodag.config.ProviderConfig`
164
170
 
165
171
  :param config_keys: The configurations keys to validate
166
- :type config_keys: dict
167
172
  """
168
173
  if "name" not in config_keys:
169
174
  raise ValidationError("Provider config must have name key")
170
- if not any(k in config_keys for k in ("api", "search", "download", "auth")):
175
+ if not any(k in config_keys for k in PLUGINS_TOPICS_KEYS):
171
176
  raise ValidationError("A provider must implement at least one plugin")
172
- if "api" in config_keys and any(
173
- k in config_keys for k in ("search", "download", "auth")
174
- ):
177
+ non_api_keys = [k for k in PLUGINS_TOPICS_KEYS if k != "api"]
178
+ if "api" in config_keys and any(k in config_keys for k in non_api_keys):
175
179
  raise ValidationError(
176
180
  "A provider implementing an Api plugin must not implement any other "
177
181
  "type of plugin"
@@ -181,7 +185,6 @@ class ProviderConfig(yaml.YAMLObject):
181
185
  """Update the configuration parameters with values from `mapping`
182
186
 
183
187
  :param mapping: The mapping from which to override configuration parameters
184
- :type mapping: dict
185
188
  """
186
189
  if mapping is None:
187
190
  mapping = {}
@@ -190,11 +193,10 @@ class ProviderConfig(yaml.YAMLObject):
190
193
  {
191
194
  key: value
192
195
  for key, value in mapping.items()
193
- if key not in ("name", "api", "search", "download", "auth")
194
- and value is not None
196
+ if key not in PLUGINS_TOPICS_KEYS and value is not None
195
197
  },
196
198
  )
197
- for key in ("api", "search", "download", "auth"):
199
+ for key in PLUGINS_TOPICS_KEYS:
198
200
  current_value: Optional[Dict[str, Any]] = getattr(self, key, None)
199
201
  mapping_value = mapping.get(key, {})
200
202
  if current_value is not None:
@@ -204,102 +206,314 @@ class ProviderConfig(yaml.YAMLObject):
204
206
 
205
207
 
206
208
  class PluginConfig(yaml.YAMLObject):
207
- """Representation of a plugin config
208
-
209
- :param name: The name of the plugin class to use to instantiate the plugin object
210
- :type name: str
211
- :param metadata_mapping: (optional) The mapping between eodag metadata and
212
- the plugin specific metadata
213
- :type metadata_mapping: dict
214
- :param free_params: (optional) Additional configuration parameters
215
- :type free_params: dict
209
+ """Representation of a plugin config.
210
+
211
+ This class variables describe available plugins configuration parameters.
216
212
  """
217
213
 
218
214
  class Pagination(TypedDict):
219
215
  """Search pagination configuration"""
220
216
 
217
+ #: The maximum number of items per page that the provider can handle
221
218
  max_items_per_page: int
219
+ #: Key path for the number of total items in the provider result
222
220
  total_items_nb_key_path: Union[str, JSONPath]
221
+ #: Key path for the next page URL
223
222
  next_page_url_key_path: Union[str, JSONPath]
223
+ #: Key path for the next page POST request query-object (body)
224
224
  next_page_query_obj_key_path: Union[str, JSONPath]
225
+ # TODO: change this typing to bool and adapt code to it
225
226
  next_page_merge_key_path: Union[str, JSONPath]
227
+ #: Template to add to :attr:`~eodag.config.PluginConfig.Pagination.next_page_url_tpl` to enable count in
228
+ #: search request
229
+ count_tpl: str
230
+ #: The f-string template for pagination requests.
226
231
  next_page_url_tpl: str
232
+ #: The query-object for POST pagination requests.
227
233
  next_page_query_obj: str
234
+ #: The endpoint for counting the number of items satisfying a request
228
235
  count_endpoint: str
236
+ #: Index of the starting page
229
237
  start_page: int
230
238
 
239
+ class Sort(TypedDict):
240
+ """Configuration for sort during search"""
241
+
242
+ #: Default sort settings
243
+ sort_by_default: List[Tuple[str, str]]
244
+ #: F-string template to add to :attr:`~eodag.config.PluginConfig.Pagination.next_page_url_tpl` to sort search
245
+ #: results
246
+ sort_by_tpl: str
247
+ #: Mapping between eodag and provider query parameters used for sort
248
+ sort_param_mapping: Dict[str, str]
249
+ #: Mapping between eodag and provider sort-order parameters
250
+ sort_order_mapping: Dict[Literal["ascending", "descending"], str]
251
+ #: Maximum number of allowed sort parameters per request
252
+ max_sort_params: Annotated[int, Gt(0)]
253
+
254
+ class DiscoverMetadata(TypedDict):
255
+ """Configuration for metadata discovery (search result properties)"""
256
+
257
+ #: Whether metadata discovery is enabled or not
258
+ auto_discovery: bool
259
+ #: Metadata regex pattern used for discovery in search result properties
260
+ metadata_pattern: str
261
+ #: Configuration/template that will be used to query for a discovered parameter
262
+ search_param: str
263
+ #: Path to the metadata in search result
264
+ metadata_path: str
265
+
266
+ class OrderOnResponse(TypedDict):
267
+ """Configuration for order on-response during download"""
268
+
269
+ #: Parameters metadata-mapping to apply to the order response
270
+ metadata_mapping: Dict[str, Union[str, List[str]]]
271
+
272
+ class OrderStatusSuccess(TypedDict):
273
+ """
274
+ Configuration to identify order status success during download
275
+
276
+ Order status response matching the following parameters are considered success
277
+ At least one is required
278
+ """
279
+
280
+ #: Success value for ``status``
281
+ status: str
282
+ #: Success value for ``message``
283
+ message: str
284
+ #: Success value for status response HTTP code
285
+ http_code: int
286
+
287
+ class OrderStatusOrdered(TypedDict):
288
+ """
289
+ Configuration to identify order status ordered during download
290
+ """
291
+
292
+ #: HTTP code of the order status response
293
+ http_code: int
294
+
295
+ class OrderStatusRequest(TypedDict):
296
+ """
297
+ Order status request configuration
298
+ """
299
+
300
+ #: Request HTTP method
301
+ method: str
302
+ #: Request hearders
303
+ headers: Dict[str, Any]
304
+
231
305
  class OrderStatusOnSuccess(TypedDict):
232
- """Configuration for order on-success during download"""
306
+ """Configuration for order status on-success during download"""
233
307
 
308
+ #: Whether a new search is needed on success or not
234
309
  need_search: bool
310
+ #: Return type of the success result
235
311
  result_type: str
312
+ #: Key in the success response that gives access to the result
236
313
  results_entry: str
314
+ #: Metadata-mapping to apply to the success status result
237
315
  metadata_mapping: Dict[str, Union[str, List[str]]]
238
316
 
317
+ class OrderStatus(TypedDict):
318
+ """Configuration for order status during download"""
319
+
320
+ #: Order status request configuration
321
+ request: PluginConfig.OrderStatusRequest
322
+ #: Metadata-mapping used to parse order status response
323
+ metadata_mapping: Dict[str, Union[str, List[str]]]
324
+ #: Configuration to identify order status success during download
325
+ success: PluginConfig.OrderStatusSuccess
326
+ #: Part of the order status response that tells there is an error
327
+ error: Dict[str, Any]
328
+ #: Configuration to identify order status ordered during download
329
+ ordered: PluginConfig.OrderStatusOrdered
330
+ #: Configuration for order status on-success during download
331
+ on_success: PluginConfig.OrderStatusOnSuccess
332
+
333
+ #: :class:`~eodag.plugins.base.PluginTopic` The name of the plugin class to use to instantiate the plugin object
239
334
  name: str
335
+ #: :class:`~eodag.plugins.base.PluginTopic` Plugin type
240
336
  type: str
337
+ #: :class:`~eodag.plugins.base.PluginTopic` Whether the ssl certificates should be verified in the request or not
338
+ ssl_verify: bool
339
+ #: :class:`~eodag.plugins.base.PluginTopic` Default s3 bucket
340
+ s3_bucket: str
341
+ #: :class:`~eodag.plugins.base.PluginTopic` Authentication error codes
342
+ auth_error_code: Union[int, List[int]]
343
+
344
+ # search & api -----------------------------------------------------------------------------------------------------
345
+ # copied from ProviderConfig in PluginManager.get_search_plugins()
346
+ priority: int
347
+ # copied from ProviderConfig in PluginManager.get_search_plugins()
348
+ products: Dict[str, Any]
349
+ # per product type metadata-mapping, set in core._prepare_search
350
+ product_type_config: Dict[str, Any]
241
351
 
242
- # search & api ---------------------------------------------------------------------
243
- priority: int # copied from ProviderConfig in PluginManager.get_search_plugins()
244
- products: Dict[
245
- str, Any
246
- ] # copied from ProviderConfig in PluginManager.get_search_plugins()
247
- product_type_config: Dict[str, Any] # set in core._prepare_search
248
- auth: Union[AuthBase, Dict[str, str]] # set in core._do_search
352
+ #: :class:`~eodag.plugins.search.base.Search` Plugin API endpoint
249
353
  api_endpoint: str
354
+ #: :class:`~eodag.plugins.search.base.Search` Whether Search plugin needs authentification or not
250
355
  need_auth: bool
356
+ #: :class:`~eodag.plugins.search.base.Search` Return type of the provider result
251
357
  result_type: str
358
+ #: :class:`~eodag.plugins.search.base.Search`
359
+ #: Key in the provider search result that gives access to the result entries
252
360
  results_entry: str
361
+ #: :class:`~eodag.plugins.search.base.Search` Dict containing parameters for pagination
253
362
  pagination: PluginConfig.Pagination
254
- query_params_key: str
255
- discover_metadata: Dict[str, str]
363
+ #: :class:`~eodag.plugins.search.base.Search` Configuration for sorting the results
364
+ sort: PluginConfig.Sort
365
+ #: :class:`~eodag.plugins.search.base.Search` Configuration for the metadata auto-discovery
366
+ discover_metadata: PluginConfig.DiscoverMetadata
367
+ #: :class:`~eodag.plugins.search.base.Search` Configuration for the product types auto-discovery
256
368
  discover_product_types: Dict[str, Any]
369
+ #: :class:`~eodag.plugins.search.base.Search` Configuration for the queryables auto-discovery
257
370
  discover_queryables: Dict[str, Any]
371
+ #: :class:`~eodag.plugins.search.base.Search` The mapping between eodag metadata and the plugin specific metadata
258
372
  metadata_mapping: Dict[str, Union[str, List[str]]]
259
- free_params: Dict[Any, Any]
260
- free_text_search_operations: Dict[str, Any] # ODataV4Search
261
- metadata_pre_mapping: Dict[str, Any] # ODataV4Search
262
- data_request_url: str # DataRequestSearch
263
- status_url: str # DataRequestSearch
264
- result_url: str # DataRequestSearch
265
- search_definition: Dict[str, Any] # CSWSearch
266
- merge_responses: bool # PostJsonSearch for aws_eos
267
- collection: bool # PostJsonSearch for aws_eos
268
- max_connections: int # StaticStacSearch
269
- timeout: float # StaticStacSearch
270
- s3_bucket: str # CreodiasS3Search
271
-
272
- # download -------------------------------------------------------------------------
373
+ #: :class:`~eodag.plugins.search.base.Search` URL of the constraint file used to build queryables
374
+ constraints_file_url: str
375
+ #: :class:`~eodag.plugins.search.base.Search` Parameters to remove from queryables
376
+ remove_from_queryables: List[str]
377
+ #: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Dict describing free text search request build
378
+ free_text_search_operations: Dict[str, Any]
379
+ #: :class:`~eodag.plugins.search.qssearch.ODataV4Search` Dict used to simplify further metadata extraction
380
+ metadata_pre_mapping: Dict[str, Any]
381
+ #: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch` URL to which the data request shall be sent
382
+ data_request_url: str
383
+ #: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch` URL to fetch the status of the data request
384
+ status_url: str
385
+ #: :class:`~eodag.plugins.search.data_request_search.DataRequestSearch`
386
+ #: URL to fetch the search result when the data request is done
387
+ result_url: str
388
+ #: :class:`~eodag.plugins.search.csw.CSWSearch` Search definition dictionary
389
+ search_definition: Dict[str, Any]
390
+ #: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Whether to merge responses or not (`aws_eos` specific)
391
+ merge_responses: bool
392
+ #: :class:`~eodag.plugins.search.qssearch.PostJsonSearch` Collections names (`aws_eos` specific)
393
+ collection: List[str]
394
+ #: :class:`~eodag.plugins.search.static_stac_search.StaticStacSearch`
395
+ #: Maximum number of connections for HTTP requests
396
+ max_connections: int
397
+ #: :class:`~eodag.plugins.search.base.Search` Time to wait until request timeout in seconds
398
+ timeout: float
399
+ #: :class:`~eodag.plugins.search.build_search_result.BuildSearchResult`
400
+ #: Whether end date should be excluded from search request or not
401
+ end_date_excluded: bool
402
+ #: :class:`~eodag.plugins.search.build_search_result.BuildSearchResult`
403
+ #: List of parameters used to parse metadata but that must not be included to the query
404
+ remove_from_query: List[str]
405
+
406
+ # download ---------------------------------------------------------------------------------------------------------
407
+ #: :class:`~eodag.plugins.download.base.Download` Default endpoint url
273
408
  base_uri: str
274
- outputs_prefix: str
409
+ #: :class:`~eodag.plugins.download.base.Download` Where to store downloaded products, as an absolute file path
410
+ output_dir: str
411
+ #: :class:`~eodag.plugins.download.base.Download`
412
+ #: Whether the content of the downloaded file should be extracted or not
275
413
  extract: bool
276
- order_enabled: bool # HTTPDownload
277
- order_method: str # HTTPDownload
278
- order_headers: Dict[str, str] # HTTPDownload
279
- order_status_on_success: PluginConfig.OrderStatusOnSuccess
280
- bucket_path_level: int # S3RestDownload
281
-
282
- # auth -----------------------------------------------------------------------------
414
+ #: :class:`~eodag.plugins.download.base.Download` Which extension should be used for the downloaded file
415
+ output_extension: str
416
+ #: :class:`~eodag.plugins.download.base.Download` Whether the directory structure should be flattened or not
417
+ flatten_top_dirs: bool
418
+ #: :class:`~eodag.plugins.download.http.HTTPDownload` Whether the product has to be ordered to download it or not
419
+ order_enabled: bool
420
+ #: :class:`~eodag.plugins.download.http.HTTPDownload` HTTP request method for the order request
421
+ order_method: str
422
+ #: :class:`~eodag.plugins.download.http.HTTPDownload` Headers to be added to the order request
423
+ order_headers: Dict[str, str]
424
+ #: :class:`~eodag.plugins.download.http.HTTPDownload`
425
+ #: Dictionary containing the key :attr:`~eodag.config.PluginConfig.metadata_mapping` which can be used to add new
426
+ #: product properties based on the data in response to the order request
427
+ order_on_response: PluginConfig.OrderOnResponse
428
+ #: :class:`~eodag.plugins.download.http.HTTPDownload` Order status handling
429
+ order_status: PluginConfig.OrderStatus
430
+ #: :class:`~eodag.plugins.download.http.HTTPDownload`
431
+ #: Do not authenticate the download request but only the order and order status ones
432
+ no_auth_download: bool
433
+ #: :class:`~eodag.plugins.download.s3rest.S3RestDownload`
434
+ #: At which level of the path part of the url the bucket can be found
435
+ bucket_path_level: int
436
+ #: :class:`~eodag.plugins.download.aws.AwsDownload` Whether download is done from a requester-pays bucket or not
437
+ requester_pays: bool
438
+ #: :class:`~eodag.plugins.download.aws.AwsDownload` S3 endpoint
439
+ s3_endpoint: str
440
+
441
+ # auth -------------------------------------------------------------------------------------------------------------
442
+ #: :class:`~eodag.plugins.authentication.base.Authentication` Authentication credentials dictionary
283
443
  credentials: Dict[str, str]
444
+ #: :class:`~eodag.plugins.authentication.base.Authentication` Authentication URL
284
445
  auth_uri: str
285
- auth_base_uri: str
286
- auth_error_code: int
446
+ #: :class:`~eodag.plugins.authentication.base.Authentication`
447
+ #: Dictionary containing all keys/value pairs that should be added to the headers
287
448
  headers: Dict[str, str]
288
- token_provision: str # KeycloakOIDCPasswordAuth
289
- client_id: str # KeycloakOIDCPasswordAuth
290
- client_secret: str # KeycloakOIDCPasswordAuth
291
- realm: str # KeycloakOIDCPasswordAuth
292
- user_consent_needed: str # OIDCAuthorizationCodeFlowAuth
293
- authentication_uri_source: str # OIDCAuthorizationCodeFlowAuth
294
- redirect_uri: str # OIDCAuthorizationCodeFlowAuth
295
- authorization_uri: str # OIDCAuthorizationCodeFlowAuth
296
- login_form_xpath: str # OIDCAuthorizationCodeFlowAuth
297
- user_consent_form_xpath: str # OIDCAuthorizationCodeFlowAuth
298
- user_consent_form_data: Dict[str, str] # OIDCAuthorizationCodeFlowAuth
299
- token_exchange_post_data_method: str # OIDCAuthorizationCodeFlowAuth
300
- token_uri: str # OIDCAuthorizationCodeFlowAuth
301
- token_key: str # OIDCAuthorizationCodeFlowAuth
302
- signed_url_key: str # SASAuth
449
+ #: :class:`~eodag.plugins.authentication.base.Authentication`
450
+ #: The key pointing to the token in the response from the token server
451
+ token_key: str
452
+ #: :class:`~eodag.plugins.authentication.base.Authentication`
453
+ #: Key to get the refresh token in the response from the token server
454
+ refresh_token_key: str
455
+ #: :class:`~eodag.plugins.authentication.base.Authentication` URL pattern to match with search plugin endpoint or
456
+ #: download link
457
+ matching_url: str
458
+ #: :class:`~eodag.plugins.authentication.base.Authentication` Part of the search or download plugin configuration
459
+ #: that needs authentication
460
+ matching_conf: Dict[str, Any]
461
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase`
462
+ #: How the token should be used in the request
463
+ token_provision: str
464
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase` The OIDC provider's client ID
465
+ client_id: str
466
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCRefreshTokenBase` The OIDC provider's client secret
467
+ client_secret: str
468
+ #: :class:`~eodag.plugins.authentication.keycloak.KeycloakOIDCPasswordAuth`
469
+ #: Base url used in the request to fetch the token
470
+ auth_base_uri: str
471
+ #: :class:`~eodag.plugins.authentication.keycloak.KeycloakOIDCPasswordAuth` Keycloak realm
472
+ realm: str
473
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
474
+ #: Whether a user consent is needed during the authentication or not
475
+ user_consent_needed: str
476
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
477
+ #: Where to look for the :attr:`~eodag.config.PluginConfig.authorization_uri`
478
+ authentication_uri_source: str
479
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
480
+ #: The callback url that will handle the code given by the OIDC provider
481
+ redirect_uri: str
482
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
483
+ #: The authorization url of the server (where to query for grants)
484
+ authorization_uri: str
485
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
486
+ #: The xpath to the HTML form element representing the user login form
487
+ login_form_xpath: str
488
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
489
+ #: The xpath to the user consent form
490
+ user_consent_form_xpath: str
491
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
492
+ #: The data that will be passed with the POST request on the form 'action' URL
493
+ user_consent_form_data: Dict[str, str]
494
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
495
+ #: Way to pass the data to the POST request that is made to the token server
496
+ token_exchange_post_data_method: str
497
+ #: :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth`
498
+ #: The url to query to get the authorized token
499
+ token_uri: str
500
+ #: :class:`~eodag.plugins.authentication.sas_auth.SASAuth` Key to get the signed url
501
+ signed_url_key: str
502
+ #: :class:`~eodag.plugins.authentication.token.TokenAuth`
503
+ #: Credentials json structure if they should be sent as POST data
504
+ req_data: Dict[str, Any]
505
+ #: :class:`~eodag.plugins.authentication.token.TokenAuth` URL used to fetch the access token with a refresh token
506
+ refresh_uri: str
507
+ #: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
508
+ #: The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin configuration
509
+ #: used to retrieve subject token
510
+ subject: Dict[str, Any]
511
+ #: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
512
+ #: Identifies the issuer of the `subject_token`
513
+ subject_issuer: str
514
+ #: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
515
+ #: Audience that the token ID is intended for. :attr:`~eodag.config.PluginConfig.client_id` of the Relying Party
516
+ audience: str
303
517
 
304
518
  yaml_loader = yaml.Loader
305
519
  yaml_dumper = yaml.SafeDumper
@@ -330,7 +544,6 @@ class PluginConfig(yaml.YAMLObject):
330
544
  """Update the configuration parameters with values from `mapping`
331
545
 
332
546
  :param mapping: The mapping from which to override configuration parameters
333
- :type mapping: dict
334
547
  """
335
548
  if mapping is None:
336
549
  mapping = {}
@@ -340,13 +553,12 @@ class PluginConfig(yaml.YAMLObject):
340
553
 
341
554
 
342
555
  def load_default_config() -> Dict[str, ProviderConfig]:
343
- """Load the providers configuration into a dictionnary.
556
+ """Load the providers configuration into a dictionary.
344
557
 
345
558
  Load from eodag `resources/providers.yml` or `EODAG_PROVIDERS_CFG_FILE` environment
346
559
  variable if exists.
347
560
 
348
561
  :returns: The default provider's configuration
349
- :rtype: dict
350
562
  """
351
563
  eodag_providers_cfg_file = os.getenv(
352
564
  "EODAG_PROVIDERS_CFG_FILE"
@@ -355,14 +567,12 @@ def load_default_config() -> Dict[str, ProviderConfig]:
355
567
 
356
568
 
357
569
  def load_config(config_path: str) -> Dict[str, ProviderConfig]:
358
- """Load the providers configuration into a dictionnary from a given file
570
+ """Load the providers configuration into a dictionary from a given file
359
571
 
360
572
  :param config_path: The path to the provider config file
361
- :type config_path: str
362
573
  :returns: The default provider's configuration
363
- :rtype: dict
364
574
  """
365
- logger.debug(f"Loading configuration from {config_path}")
575
+ logger.debug("Loading configuration from %s", config_path)
366
576
  config: Dict[str, ProviderConfig] = {}
367
577
  try:
368
578
  # Providers configs are stored in this file as separated yaml documents
@@ -373,12 +583,65 @@ def load_config(config_path: str) -> Dict[str, ProviderConfig]:
373
583
  raise e
374
584
  stac_provider_config = load_stac_provider_config()
375
585
  for provider_config in providers_configs:
376
- # for provider_config in copy.deepcopy(providers_configs):
377
586
  provider_config_init(provider_config, stac_provider_config)
378
587
  config[provider_config.name] = provider_config
379
588
  return config
380
589
 
381
590
 
591
+ def credentials_in_auth(auth_conf: PluginConfig) -> bool:
592
+ """Checks if credentials are set for this Authentication plugin configuration
593
+
594
+ :param auth_conf: Authentication plugin configuration
595
+ :returns: True if credentials are set, else False
596
+ """
597
+ return any(
598
+ c is not None for c in (getattr(auth_conf, "credentials", {}) or {}).values()
599
+ )
600
+
601
+
602
+ def share_credentials(
603
+ providers_config: Dict[str, ProviderConfig],
604
+ ) -> None:
605
+ """Share credentials between plugins having the same matching criteria
606
+
607
+ :param providers_configs: eodag providers configurations
608
+ """
609
+ auth_confs_with_creds = [
610
+ getattr(p, k)
611
+ for p in providers_config.values()
612
+ for k in AUTH_TOPIC_KEYS
613
+ if hasattr(p, k) and credentials_in_auth(getattr(p, k))
614
+ ]
615
+ for provider, provider_config in providers_config.items():
616
+ if auth_confs_with_creds:
617
+ for auth_topic_key in AUTH_TOPIC_KEYS:
618
+ provider_config_auth = getattr(provider_config, auth_topic_key, None)
619
+ if provider_config_auth and not credentials_in_auth(
620
+ provider_config_auth
621
+ ):
622
+ # no credentials set for this provider
623
+ provider_matching_conf = getattr(
624
+ provider_config_auth, "matching_conf", {}
625
+ )
626
+ provider_matching_url = getattr(
627
+ provider_config_auth, "matching_url", None
628
+ )
629
+ for conf_with_creds in auth_confs_with_creds:
630
+ # copy credentials between plugins if `matching_conf` or `matching_url` are matching
631
+ if (
632
+ provider_matching_conf
633
+ and sort_dict(provider_matching_conf)
634
+ == sort_dict(getattr(conf_with_creds, "matching_conf", {}))
635
+ ) or (
636
+ provider_matching_url
637
+ and provider_matching_url
638
+ == getattr(conf_with_creds, "matching_url", None)
639
+ ):
640
+ getattr(
641
+ providers_config[provider], auth_topic_key
642
+ ).credentials = conf_with_creds.credentials
643
+
644
+
382
645
  def provider_config_init(
383
646
  provider_config: ProviderConfig,
384
647
  stac_search_default_conf: Optional[Dict[str, Any]] = None,
@@ -386,19 +649,17 @@ def provider_config_init(
386
649
  """Applies some default values to provider config
387
650
 
388
651
  :param provider_config: An eodag provider configuration
389
- :type provider_config: :class:`~eodag.config.ProviderConfig`
390
652
  :param stac_search_default_conf: default conf to overwrite with provider_config if STAC
391
- :type stac_search_default_conf: dict
392
653
  """
393
- # For the provider, set the default outputs_prefix of its download plugin
654
+ # For the provider, set the default output_dir of its download plugin
394
655
  # as tempdir in a portable way
395
- for param_name in ("download", "api"):
396
- if param_name in vars(provider_config):
397
- param_value = getattr(provider_config, param_name)
398
- if not getattr(param_value, "outputs_prefix", None):
399
- param_value.outputs_prefix = tempfile.gettempdir()
400
- if not getattr(param_value, "delete_archive", None):
401
- param_value.delete_archive = True
656
+ for download_topic_key in ("download", "api"):
657
+ if download_topic_key in vars(provider_config):
658
+ download_conf = getattr(provider_config, download_topic_key)
659
+ if not getattr(download_conf, "output_dir", None):
660
+ download_conf.output_dir = tempfile.gettempdir()
661
+ if not getattr(download_conf, "delete_archive", None):
662
+ download_conf.delete_archive = True
402
663
 
403
664
  try:
404
665
  if (
@@ -425,9 +686,7 @@ def override_config_from_file(config: Dict[str, Any], file_path: str) -> None:
425
686
  """Override a configuration with the values in a file
426
687
 
427
688
  :param config: An eodag providers configuration dictionary
428
- :type config: dict
429
689
  :param file_path: The path to the file from where the new values will be read
430
- :type file_path: str
431
690
  """
432
691
  logger.info("Loading user configuration from: %s", os.path.abspath(file_path))
433
692
  with open(os.path.abspath(os.path.realpath(file_path)), "r") as fh:
@@ -445,7 +704,6 @@ def override_config_from_env(config: Dict[str, Any]) -> None:
445
704
  """Override a configuration with environment variables values
446
705
 
447
706
  :param config: An eodag providers configuration dictionary
448
- :type config: dict
449
707
  """
450
708
 
451
709
  def build_mapping_from_env(
@@ -465,11 +723,8 @@ def override_config_from_env(config: Dict[str, Any]) -> None:
465
723
  }
466
724
 
467
725
  :param env_var: The environment variable to be transformed into a dictionary
468
- :type env_var: str
469
726
  :param env_value: The value from environment variable
470
- :type env_value: str
471
727
  :param mapping: The mapping in which the value will be created
472
- :type mapping: dict
473
728
  """
474
729
  parts = env_var.split("__")
475
730
  iter_parts = iter(parts)
@@ -521,11 +776,39 @@ def override_config_from_mapping(
521
776
  """Override a configuration with the values in a mapping
522
777
 
523
778
  :param config: An eodag providers configuration dictionary
524
- :type config: dict
525
779
  :param mapping: The mapping containing the values to be overriden
526
- :type mapping: dict
527
780
  """
528
781
  for provider, new_conf in mapping.items():
782
+ # check if metada-mapping as already been built as jsonpath in providers_config
783
+ if not isinstance(new_conf, dict):
784
+ continue
785
+ new_conf_search = new_conf.get("search", {}) or {}
786
+ new_conf_api = new_conf.get("api", {}) or {}
787
+ if provider in config and "metadata_mapping" in {
788
+ **new_conf_search,
789
+ **new_conf_api,
790
+ }:
791
+ search_plugin_key = (
792
+ "search" if "metadata_mapping" in new_conf_search else "api"
793
+ )
794
+ # get some already configured value
795
+ configured_metadata_mapping = getattr(
796
+ config[provider], search_plugin_key
797
+ ).metadata_mapping
798
+ some_configured_value = next(iter(configured_metadata_mapping.values()))
799
+ # check if the configured value has already been built as jsonpath
800
+ if (
801
+ isinstance(some_configured_value, list)
802
+ and isinstance(some_configured_value[1], tuple)
803
+ or isinstance(some_configured_value, tuple)
804
+ ):
805
+ # also build as jsonpath the incoming conf
806
+ mtd_cfg_as_conversion_and_querypath(
807
+ deepcopy(mapping[provider][search_plugin_key]["metadata_mapping"]),
808
+ mapping[provider][search_plugin_key]["metadata_mapping"],
809
+ )
810
+
811
+ # try overriding conf
529
812
  old_conf: Optional[Dict[str, Any]] = config.get(provider)
530
813
  if old_conf is not None:
531
814
  old_conf.update(new_conf)
@@ -551,9 +834,7 @@ def merge_configs(config: Dict[str, Any], other_config: Dict[str, Any]) -> None:
551
834
  """Override a configuration with the values of another configuration
552
835
 
553
836
  :param config: An eodag providers configuration dictionary
554
- :type config: dict
555
837
  :param other_config: An eodag providers configuration dictionary
556
- :type other_config: dict
557
838
  """
558
839
  # configs union with other_config values as default
559
840
  other_config = dict(config, **other_config)
@@ -582,20 +863,18 @@ def merge_configs(config: Dict[str, Any], other_config: Dict[str, Any]) -> None:
582
863
 
583
864
 
584
865
  def load_yml_config(yml_path: str) -> Dict[Any, Any]:
585
- """Load a conf dictionnary from given yml absolute path
866
+ """Load a conf dictionary from given yml absolute path
586
867
 
587
868
  :returns: The yml configuration file
588
- :rtype: dict
589
869
  """
590
870
  config = SimpleYamlProxyConfig(yml_path)
591
871
  return dict_items_recursive_apply(config.source, string_to_jsonpath)
592
872
 
593
873
 
594
874
  def load_stac_config() -> Dict[str, Any]:
595
- """Load the stac configuration into a dictionnary
875
+ """Load the stac configuration into a dictionary
596
876
 
597
877
  :returns: The stac configuration
598
- :rtype: dict
599
878
  """
600
879
  return load_yml_config(
601
880
  resource_filename("eodag", os.path.join("resources/", "stac.yml"))
@@ -603,10 +882,9 @@ def load_stac_config() -> Dict[str, Any]:
603
882
 
604
883
 
605
884
  def load_stac_api_config() -> Dict[str, Any]:
606
- """Load the stac API configuration into a dictionnary
885
+ """Load the stac API configuration into a dictionary
607
886
 
608
887
  :returns: The stac API configuration
609
- :rtype: dict
610
888
  """
611
889
  return load_yml_config(
612
890
  resource_filename("eodag", os.path.join("resources/", "stac_api.yml"))
@@ -614,10 +892,9 @@ def load_stac_api_config() -> Dict[str, Any]:
614
892
 
615
893
 
616
894
  def load_stac_provider_config() -> Dict[str, Any]:
617
- """Load the stac provider configuration into a dictionnary
895
+ """Load the stac provider configuration into a dictionary
618
896
 
619
897
  :returns: The stac provider configuration
620
- :rtype: dict
621
898
  """
622
899
  return SimpleYamlProxyConfig(
623
900
  resource_filename("eodag", os.path.join("resources/", "stac_provider.yml"))
@@ -630,9 +907,7 @@ def get_ext_product_types_conf(
630
907
  """Read external product types conf
631
908
 
632
909
  :param conf_uri: URI to local or remote configuration file
633
- :type conf_uri: str
634
910
  :returns: The external product types configuration
635
- :rtype: dict
636
911
  """
637
912
  logger.info("Fetching external product types from %s", conf_uri)
638
913
  if conf_uri.lower().startswith("http"):