eodag 3.0.0b3__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.
- eodag/api/core.py +189 -125
- eodag/api/product/metadata_mapping.py +12 -3
- eodag/api/search_result.py +29 -3
- eodag/cli.py +35 -19
- eodag/config.py +412 -116
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +14 -4
- eodag/plugins/apis/usgs.py +25 -2
- eodag/plugins/authentication/aws_auth.py +14 -5
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +57 -10
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +4 -3
- eodag/plugins/download/aws.py +39 -22
- eodag/plugins/download/base.py +11 -11
- eodag/plugins/download/creodias_s3.py +11 -2
- eodag/plugins/download/http.py +86 -52
- eodag/plugins/download/s3rest.py +20 -18
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +33 -14
- eodag/plugins/search/build_search_result.py +55 -51
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +20 -5
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +532 -152
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +187 -56
- eodag/resources/providers.yml +1610 -1701
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +61 -51
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +24 -325
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +13 -8
- eodag/rest/types/queryables.py +1 -2
- eodag/rest/types/stac_search.py +11 -2
- eodag/types/__init__.py +15 -3
- eodag/types/download_args.py +1 -1
- eodag/types/queryables.py +1 -2
- eodag/types/search_args.py +3 -3
- eodag/utils/__init__.py +77 -57
- eodag/utils/exceptions.py +23 -9
- eodag/utils/logging.py +37 -77
- eodag/utils/requests.py +1 -3
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/METADATA +11 -12
- eodag-3.0.1.dist-info/RECORD +109 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
eodag/plugins/download/http.py
CHANGED
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import os
|
|
22
|
+
import re
|
|
22
23
|
import shutil
|
|
23
24
|
import tarfile
|
|
24
25
|
import zipfile
|
|
@@ -79,6 +80,7 @@ from eodag.utils.exceptions import (
|
|
|
79
80
|
DownloadError,
|
|
80
81
|
MisconfiguredError,
|
|
81
82
|
NotAvailableError,
|
|
83
|
+
RequestError,
|
|
82
84
|
TimeOutError,
|
|
83
85
|
)
|
|
84
86
|
|
|
@@ -100,18 +102,47 @@ class HTTPDownload(Download):
|
|
|
100
102
|
:param provider: provider name
|
|
101
103
|
:param config: Download plugin configuration:
|
|
102
104
|
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
|
|
105
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): ``HTTPDownload``
|
|
106
|
+
* :attr:`~eodag.config.PluginConfig.base_uri` (``str``): default endpoint url
|
|
107
|
+
* :attr:`~eodag.config.PluginConfig.method` (``str``): HTTP request method for the download request (``GET`` or
|
|
108
|
+
``POST``); default: ``GET``
|
|
109
|
+
* :attr:`~eodag.config.PluginConfig.extract` (``bool``): if the content of the downloaded file should be
|
|
110
|
+
extracted; default: ``True``
|
|
111
|
+
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is returned in case of an
|
|
112
|
+
authentication error
|
|
113
|
+
* :attr:`~eodag.config.PluginConfig.dl_url_params` (``Dict[str, Any]``): parameters to be
|
|
114
|
+
added to the query params of the request
|
|
115
|
+
* :attr:`~eodag.config.PluginConfig.archive_depth` (``int``): level in extracted path tree where to find data;
|
|
116
|
+
default: ``1``
|
|
117
|
+
* :attr:`~eodag.config.PluginConfig.flatten_top_dirs` (``bool``): if the directory structure should be
|
|
118
|
+
flattened; default: ``True``
|
|
119
|
+
* :attr:`~eodag.config.PluginConfig.ignore_assets` (``bool``): ignore assets and download using downloadLink;
|
|
120
|
+
default: ``False``
|
|
121
|
+
* :attr:`~eodag.config.PluginConfig.timeout` (``int``): time to wait until request timeout in seconds;
|
|
122
|
+
default: ``5``
|
|
123
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be verified in
|
|
124
|
+
requests; default: ``True``
|
|
125
|
+
* :attr:`~eodag.config.PluginConfig.output_extension` (``str``): which extension should be used for the
|
|
126
|
+
downloaded file
|
|
127
|
+
* :attr:`~eodag.config.PluginConfig.no_auth_download` (``bool``): if the download should be done without
|
|
128
|
+
authentication; default: ``True``
|
|
129
|
+
* :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): if the product has to be ordered to download it;
|
|
130
|
+
if this parameter is set to true, a mapping for the orderLink has to be added to the metadata mapping of
|
|
131
|
+
the search plugin used for the provider; default: ``False``
|
|
132
|
+
* :attr:`~eodag.config.PluginConfig.order_method` (``str``): HTTP request method for the order request (``GET``
|
|
133
|
+
or ``POST``); default: ``GET``
|
|
134
|
+
* :attr:`~eodag.config.PluginConfig.order_headers` (``[Dict[str, str]]``): headers to be added to the order
|
|
135
|
+
request
|
|
136
|
+
* :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
|
|
137
|
+
a typed dictionary containing the key ``metadata_mapping`` which can be used to add new product properties
|
|
138
|
+
based on the data in response to the order request
|
|
139
|
+
* :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
|
|
140
|
+
configuration to handle the order status; contains information which method to use, how the response data is
|
|
141
|
+
interpreted, which status corresponds to success, ordered and error and what should be done on success.
|
|
142
|
+
* :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type specific config; the
|
|
143
|
+
keys are the product types, the values are dictionaries which can contain the keys
|
|
144
|
+
:attr:`~eodag.config.PluginConfig.output_extension` and :attr:`~eodag.config.PluginConfig.extract` to
|
|
145
|
+
overwrite the provider config for a specific product type
|
|
115
146
|
|
|
116
147
|
"""
|
|
117
148
|
|
|
@@ -130,12 +161,13 @@ class HTTPDownload(Download):
|
|
|
130
161
|
and has `orderLink` in its properties.
|
|
131
162
|
Product ordering can be configured using the following download plugin parameters:
|
|
132
163
|
|
|
133
|
-
-
|
|
164
|
+
- :attr:`~eodag.config.PluginConfig.order_enabled`: Wether order is enabled or not (may not use this method
|
|
134
165
|
if no `orderLink` exists)
|
|
135
166
|
|
|
136
|
-
-
|
|
167
|
+
- :attr:`~eodag.config.PluginConfig.order_method`: (optional) HTTP request method, GET (default) or POST
|
|
137
168
|
|
|
138
|
-
-
|
|
169
|
+
- :attr:`~eodag.config.PluginConfig.order_on_response`: (optional) things to do with obtained order
|
|
170
|
+
response:
|
|
139
171
|
|
|
140
172
|
- *metadata_mapping*: edit or add new product propoerties properties
|
|
141
173
|
|
|
@@ -193,18 +225,11 @@ class HTTPDownload(Download):
|
|
|
193
225
|
logger.debug(ordered_message)
|
|
194
226
|
product.properties["storageStatus"] = STAGING_STATUS
|
|
195
227
|
except RequestException as e:
|
|
196
|
-
if hasattr(e, "response") and (
|
|
197
|
-
content := getattr(e.response, "content", None)
|
|
198
|
-
):
|
|
199
|
-
error_message = f"{content.decode('utf-8')} - {e}"
|
|
200
|
-
else:
|
|
201
|
-
error_message = str(e)
|
|
202
|
-
logger.warning(
|
|
203
|
-
"%s could not be ordered, request returned %s",
|
|
204
|
-
product.properties["title"],
|
|
205
|
-
error_message,
|
|
206
|
-
)
|
|
207
228
|
self._check_auth_exception(e)
|
|
229
|
+
title = product.properties["title"]
|
|
230
|
+
message = f"{title} could not be ordered"
|
|
231
|
+
raise RequestError.from_error(e, message) from e
|
|
232
|
+
|
|
208
233
|
return self.order_response_process(response, product)
|
|
209
234
|
except requests.exceptions.Timeout as exc:
|
|
210
235
|
raise TimeOutError(exc, timeout=timeout) from exc
|
|
@@ -256,7 +281,7 @@ class HTTPDownload(Download):
|
|
|
256
281
|
It will be executed before each download retry.
|
|
257
282
|
Product order status request can be configured using the following download plugin parameters:
|
|
258
283
|
|
|
259
|
-
-
|
|
284
|
+
- :attr:`~eodag.config.PluginConfig.order_status`: :class:`~eodag.config.PluginConfig.OrderStatus`
|
|
260
285
|
|
|
261
286
|
Product properties used for order status:
|
|
262
287
|
|
|
@@ -739,7 +764,7 @@ class HTTPDownload(Download):
|
|
|
739
764
|
**kwargs: Unpack[DownloadConf],
|
|
740
765
|
) -> StreamResponse:
|
|
741
766
|
r"""
|
|
742
|
-
Returns
|
|
767
|
+
Returns dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments.
|
|
743
768
|
It contains a generator to streamed download chunks and the response headers.
|
|
744
769
|
|
|
745
770
|
:param product: The EO product to download
|
|
@@ -752,7 +777,7 @@ class HTTPDownload(Download):
|
|
|
752
777
|
and `dl_url_params` (dict) can be provided as additional kwargs
|
|
753
778
|
and will override any other values defined in a configuration
|
|
754
779
|
file or with environment variables.
|
|
755
|
-
:returns:
|
|
780
|
+
:returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
|
|
756
781
|
"""
|
|
757
782
|
if auth is not None and not isinstance(auth, AuthBase):
|
|
758
783
|
raise MisconfiguredError(f"Incompatible auth plugin: {type(auth)}")
|
|
@@ -837,12 +862,9 @@ class HTTPDownload(Download):
|
|
|
837
862
|
and e.response.status_code in auth_errors
|
|
838
863
|
):
|
|
839
864
|
raise AuthenticationError(
|
|
840
|
-
"
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
response_text,
|
|
844
|
-
self.provider,
|
|
845
|
-
)
|
|
865
|
+
f"Please check your credentials for {self.provider}.",
|
|
866
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
867
|
+
response_text,
|
|
846
868
|
)
|
|
847
869
|
|
|
848
870
|
def _process_exception(
|
|
@@ -903,6 +925,8 @@ class HTTPDownload(Download):
|
|
|
903
925
|
logger.info("Progress bar unavailable, please call product.download()")
|
|
904
926
|
progress_callback = ProgressCallback(disable=True)
|
|
905
927
|
|
|
928
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
929
|
+
|
|
906
930
|
ordered_message = ""
|
|
907
931
|
if (
|
|
908
932
|
"orderLink" in product.properties
|
|
@@ -953,6 +977,7 @@ class HTTPDownload(Download):
|
|
|
953
977
|
params=params,
|
|
954
978
|
headers=USER_AGENT,
|
|
955
979
|
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
980
|
+
verify=ssl_verify,
|
|
956
981
|
**req_kwargs,
|
|
957
982
|
) as self.stream:
|
|
958
983
|
try:
|
|
@@ -1055,6 +1080,16 @@ class HTTPDownload(Download):
|
|
|
1055
1080
|
"flatten_top_dirs", getattr(self.config, "flatten_top_dirs", True)
|
|
1056
1081
|
)
|
|
1057
1082
|
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
1083
|
+
matching_url = (
|
|
1084
|
+
getattr(product.downloader_auth.config, "matching_url", "")
|
|
1085
|
+
if product.downloader_auth
|
|
1086
|
+
else ""
|
|
1087
|
+
)
|
|
1088
|
+
matching_conf = (
|
|
1089
|
+
getattr(product.downloader_auth.config, "matching_conf", None)
|
|
1090
|
+
if product.downloader_auth
|
|
1091
|
+
else None
|
|
1092
|
+
)
|
|
1058
1093
|
|
|
1059
1094
|
# loop for assets download
|
|
1060
1095
|
for asset in assets_values:
|
|
@@ -1063,11 +1098,16 @@ class HTTPDownload(Download):
|
|
|
1063
1098
|
f"Local asset detected. Download skipped for {asset['href']}"
|
|
1064
1099
|
)
|
|
1065
1100
|
continue
|
|
1066
|
-
|
|
1101
|
+
if matching_conf or (
|
|
1102
|
+
matching_url and re.match(matching_url, asset["href"])
|
|
1103
|
+
):
|
|
1104
|
+
auth_object = auth
|
|
1105
|
+
else:
|
|
1106
|
+
auth_object = None
|
|
1067
1107
|
with requests.get(
|
|
1068
1108
|
asset["href"],
|
|
1069
1109
|
stream=True,
|
|
1070
|
-
auth=
|
|
1110
|
+
auth=auth_object,
|
|
1071
1111
|
params=params,
|
|
1072
1112
|
headers=USER_AGENT,
|
|
1073
1113
|
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
@@ -1080,8 +1120,7 @@ class HTTPDownload(Download):
|
|
|
1080
1120
|
exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT
|
|
1081
1121
|
) from exc
|
|
1082
1122
|
except RequestException as e:
|
|
1083
|
-
|
|
1084
|
-
self._handle_asset_exception(e, asset, raise_errors=raise_errors)
|
|
1123
|
+
self._handle_asset_exception(e, asset)
|
|
1085
1124
|
else:
|
|
1086
1125
|
asset_rel_path = (
|
|
1087
1126
|
asset.rel_path.replace(assets_common_subdir, "").strip(os.sep)
|
|
@@ -1239,27 +1278,22 @@ class HTTPDownload(Download):
|
|
|
1239
1278
|
|
|
1240
1279
|
return fs_dir_path
|
|
1241
1280
|
|
|
1242
|
-
def _handle_asset_exception(
|
|
1243
|
-
self, e: RequestException, asset: Asset, raise_errors: bool = False
|
|
1244
|
-
) -> None:
|
|
1281
|
+
def _handle_asset_exception(self, e: RequestException, asset: Asset) -> None:
|
|
1245
1282
|
# check if error is identified as auth_error in provider conf
|
|
1246
1283
|
auth_errors = getattr(self.config, "auth_error_code", [None])
|
|
1247
1284
|
if not isinstance(auth_errors, list):
|
|
1248
1285
|
auth_errors = [auth_errors]
|
|
1249
1286
|
if e.response is not None and e.response.status_code in auth_errors:
|
|
1250
1287
|
raise AuthenticationError(
|
|
1251
|
-
"
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
e.response.text.strip(),
|
|
1255
|
-
self.provider,
|
|
1256
|
-
)
|
|
1288
|
+
f"Please check your credentials for {self.provider}.",
|
|
1289
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
1290
|
+
e.response.text.strip(),
|
|
1257
1291
|
)
|
|
1258
|
-
elif raise_errors:
|
|
1259
|
-
raise DownloadError(e)
|
|
1260
1292
|
else:
|
|
1261
|
-
logger.
|
|
1262
|
-
|
|
1293
|
+
logger.error(
|
|
1294
|
+
"Unexpected error at download of asset %s: %s", asset["href"], e
|
|
1295
|
+
)
|
|
1296
|
+
raise DownloadError(e)
|
|
1263
1297
|
|
|
1264
1298
|
def _get_asset_sizes(
|
|
1265
1299
|
self,
|
eodag/plugins/download/s3rest.py
CHANGED
|
@@ -62,23 +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
|
-
|
|
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
72
|
:param config: Download plugin configuration:
|
|
72
73
|
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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
|
|
82
87
|
"""
|
|
83
88
|
|
|
84
89
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
@@ -189,12 +194,9 @@ class S3RestDownload(Download):
|
|
|
189
194
|
auth_errors = [auth_errors]
|
|
190
195
|
if err.response and err.response.status_code in auth_errors:
|
|
191
196
|
raise AuthenticationError(
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
err.response.text.strip(),
|
|
196
|
-
self.provider,
|
|
197
|
-
)
|
|
197
|
+
f"Please check your credentials for {self.provider}.",
|
|
198
|
+
f"HTTP Error {err.response.status_code} returned.",
|
|
199
|
+
err.response.text.strip(),
|
|
198
200
|
)
|
|
199
201
|
# product not available
|
|
200
202
|
elif (
|
|
@@ -225,7 +227,7 @@ class S3RestDownload(Download):
|
|
|
225
227
|
self.__class__.__name__,
|
|
226
228
|
bucket_contents.text,
|
|
227
229
|
)
|
|
228
|
-
raise RequestError(
|
|
230
|
+
raise RequestError.from_error(err) from err
|
|
229
231
|
try:
|
|
230
232
|
xmldoc = minidom.parseString(bucket_contents.text)
|
|
231
233
|
except ExpatError as err:
|
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
|
|
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
|
|
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
|
|
@@ -70,7 +82,7 @@ class PluginManager:
|
|
|
70
82
|
supported by ``eodag``
|
|
71
83
|
"""
|
|
72
84
|
|
|
73
|
-
supported_topics =
|
|
85
|
+
supported_topics = set(PLUGINS_TOPICS_KEYS)
|
|
74
86
|
|
|
75
87
|
product_type_to_provider_config_map: Dict[str, List[ProviderConfig]]
|
|
76
88
|
|
|
@@ -139,7 +151,7 @@ class PluginManager:
|
|
|
139
151
|
self.providers_config = providers_config
|
|
140
152
|
|
|
141
153
|
self.build_product_type_to_provider_config_map()
|
|
142
|
-
self._built_plugins_cache: Dict[Tuple[str, str], Any] = {}
|
|
154
|
+
self._built_plugins_cache: Dict[Tuple[str, str, str], Any] = {}
|
|
143
155
|
|
|
144
156
|
def build_product_type_to_provider_config_map(self) -> None:
|
|
145
157
|
"""Build mapping conf between product types and providers"""
|
|
@@ -249,25 +261,146 @@ class PluginManager:
|
|
|
249
261
|
)
|
|
250
262
|
return plugin
|
|
251
263
|
|
|
252
|
-
def get_auth_plugin(
|
|
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]:
|
|
253
308
|
"""Build and return the authentication plugin for the given product_type and
|
|
254
309
|
provider
|
|
255
310
|
|
|
256
311
|
:param provider: The provider for which to get the authentication plugin
|
|
257
|
-
:
|
|
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
|
|
258
315
|
"""
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
271
404
|
|
|
272
405
|
@staticmethod
|
|
273
406
|
def get_crunch_plugin(name: str, **options: Any) -> Crunch:
|
|
@@ -304,9 +437,11 @@ class PluginManager:
|
|
|
304
437
|
# Sort the provider configs, taking into account the new priority order
|
|
305
438
|
provider_configs.sort(key=attrgetter("priority"), reverse=True)
|
|
306
439
|
# Update the priority of already built plugins of the given provider
|
|
307
|
-
for provider_name, topic_class in self._built_plugins_cache:
|
|
440
|
+
for provider_name, topic_class, auth_match_md5 in self._built_plugins_cache:
|
|
308
441
|
if provider_name == provider:
|
|
309
|
-
self._built_plugins_cache[
|
|
442
|
+
self._built_plugins_cache[
|
|
443
|
+
(provider, topic_class, auth_match_md5)
|
|
444
|
+
].priority = priority
|
|
310
445
|
|
|
311
446
|
def _build_plugin(
|
|
312
447
|
self,
|
|
@@ -325,8 +460,16 @@ class PluginManager:
|
|
|
325
460
|
:class:`~eodag.plugin.authentication.Authentication` or
|
|
326
461
|
:class:`~eodag.plugin.crunch.Crunch`
|
|
327
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
|
+
)
|
|
328
471
|
cached_instance = self._built_plugins_cache.setdefault(
|
|
329
|
-
(provider, topic_class.__name__), None
|
|
472
|
+
(provider, topic_class.__name__, auth_match_md5), None
|
|
330
473
|
)
|
|
331
474
|
if cached_instance is not None:
|
|
332
475
|
return cached_instance
|
|
@@ -336,5 +479,7 @@ class PluginManager:
|
|
|
336
479
|
plugin: Union[Api, Search, Download, Authentication, Crunch] = plugin_class(
|
|
337
480
|
provider, plugin_conf
|
|
338
481
|
)
|
|
339
|
-
self._built_plugins_cache[
|
|
482
|
+
self._built_plugins_cache[
|
|
483
|
+
(provider, topic_class.__name__, auth_match_md5)
|
|
484
|
+
] = plugin
|
|
340
485
|
return plugin
|