eodag 3.0.0b3__py3-none-any.whl → 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eodag/api/core.py +347 -247
- eodag/api/product/_assets.py +44 -15
- eodag/api/product/_product.py +58 -47
- eodag/api/product/drivers/__init__.py +81 -4
- eodag/api/product/drivers/base.py +65 -4
- eodag/api/product/drivers/generic.py +65 -0
- eodag/api/product/drivers/sentinel1.py +97 -0
- eodag/api/product/drivers/sentinel2.py +95 -0
- eodag/api/product/metadata_mapping.py +129 -93
- eodag/api/search_result.py +28 -12
- eodag/cli.py +61 -24
- eodag/config.py +457 -167
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +53 -23
- eodag/plugins/apis/usgs.py +41 -17
- eodag/plugins/authentication/aws_auth.py +30 -18
- eodag/plugins/authentication/base.py +14 -3
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +14 -6
- eodag/plugins/authentication/keycloak.py +44 -25
- eodag/plugins/authentication/oauth.py +18 -4
- eodag/plugins/authentication/openid_connect.py +192 -171
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +22 -5
- eodag/plugins/authentication/token.py +95 -17
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +8 -5
- eodag/plugins/crunch/filter_date.py +9 -6
- eodag/plugins/crunch/filter_latest_intersect.py +9 -8
- eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
- eodag/plugins/crunch/filter_overlap.py +9 -11
- eodag/plugins/crunch/filter_property.py +10 -10
- eodag/plugins/download/aws.py +181 -105
- eodag/plugins/download/base.py +49 -67
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +247 -223
- eodag/plugins/download/s3rest.py +29 -28
- eodag/plugins/manager.py +176 -41
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +123 -60
- eodag/plugins/search/build_search_result.py +1046 -355
- eodag/plugins/search/cop_marine.py +132 -39
- eodag/plugins/search/creodias_s3.py +19 -68
- eodag/plugins/search/csw.py +48 -8
- eodag/plugins/search/data_request_search.py +124 -23
- eodag/plugins/search/qssearch.py +531 -310
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +23 -24
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1295 -355
- eodag/resources/providers.yml +1819 -3010
- eodag/resources/stac.yml +3 -163
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +115 -99
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -4
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +157 -117
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +57 -339
- eodag/rest/stac.py +133 -581
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +41 -30
- eodag/rest/types/queryables.py +42 -32
- eodag/rest/types/stac_search.py +15 -16
- eodag/rest/utils/__init__.py +14 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +153 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +4 -4
- eodag/types/queryables.py +183 -73
- eodag/types/search_args.py +6 -6
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +228 -106
- eodag/utils/exceptions.py +47 -26
- eodag/utils/import_system.py +2 -2
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -15
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +11 -11
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
-
from typing import TYPE_CHECKING, Any,
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
22
22
|
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
23
23
|
|
|
24
24
|
import requests
|
|
@@ -28,7 +28,13 @@ from requests.auth import AuthBase
|
|
|
28
28
|
from urllib3 import Retry
|
|
29
29
|
|
|
30
30
|
from eodag.plugins.authentication.base import Authentication
|
|
31
|
-
from eodag.utils import
|
|
31
|
+
from eodag.utils import (
|
|
32
|
+
HTTP_REQ_TIMEOUT,
|
|
33
|
+
REQ_RETRY_BACKOFF_FACTOR,
|
|
34
|
+
REQ_RETRY_STATUS_FORCELIST,
|
|
35
|
+
REQ_RETRY_TOTAL,
|
|
36
|
+
USER_AGENT,
|
|
37
|
+
)
|
|
32
38
|
from eodag.utils.exceptions import AuthenticationError, MisconfiguredError, TimeOutError
|
|
33
39
|
|
|
34
40
|
if TYPE_CHECKING:
|
|
@@ -41,7 +47,45 @@ logger = logging.getLogger("eodag.authentication.token")
|
|
|
41
47
|
|
|
42
48
|
|
|
43
49
|
class TokenAuth(Authentication):
|
|
44
|
-
"""TokenAuth authentication plugin
|
|
50
|
+
"""TokenAuth authentication plugin - fetches a token which is added to search/download requests.
|
|
51
|
+
|
|
52
|
+
When using headers, if only :attr:`~eodag.config.PluginConfig.headers` is given, it will be used for both token
|
|
53
|
+
retrieve and authentication. If :attr:`~eodag.config.PluginConfig.retrieve_headers` is given, it will be used for
|
|
54
|
+
token retrieve only. If both are given, :attr:`~eodag.config.PluginConfig.retrieve_headers` will be used for token
|
|
55
|
+
retrieve and :attr:`~eodag.config.PluginConfig.headers` for authentication.
|
|
56
|
+
|
|
57
|
+
:param provider: provider name
|
|
58
|
+
:param config: Authentication plugin configuration:
|
|
59
|
+
|
|
60
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): TokenAuth
|
|
61
|
+
* :attr:`~eodag.config.PluginConfig.auth_uri` (``str``) (**mandatory**): url used to fetch
|
|
62
|
+
the access token with user/password
|
|
63
|
+
* :attr:`~eodag.config.PluginConfig.headers` (``dict[str, str]``): Dictionary containing all
|
|
64
|
+
keys/value pairs that should be added to the headers
|
|
65
|
+
* :attr:`~eodag.config.PluginConfig.retrieve_headers` (``dict[str, str]``): Dictionary containing all
|
|
66
|
+
keys/value pairs that should be added to the headers for token retrieve only
|
|
67
|
+
* :attr:`~eodag.config.PluginConfig.refresh_uri` (``str``) : url used to fetch the
|
|
68
|
+
access token with a refresh token
|
|
69
|
+
* :attr:`~eodag.config.PluginConfig.token_type` (``str``): type of the token (``json``
|
|
70
|
+
or ``text``); default: ``text``
|
|
71
|
+
* :attr:`~eodag.config.PluginConfig.token_key` (``str``): (mandatory if token_type=json)
|
|
72
|
+
key to get the access token in the response to the token request
|
|
73
|
+
* :attr:`~eodag.config.PluginConfig.refresh_token_key` (``str``): key to get the refresh
|
|
74
|
+
token in the response to the token request
|
|
75
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
|
|
76
|
+
should be verified in the requests; default: ``True``
|
|
77
|
+
* :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
|
|
78
|
+
returned in case of an authentication error
|
|
79
|
+
* :attr:`~eodag.config.PluginConfig.req_data` (``dict[str, Any]``): if the credentials
|
|
80
|
+
should be sent as data in the post request, the json structure can be given in this parameter
|
|
81
|
+
* :attr:`~eodag.config.PluginConfig.retry_total` (``int``): :class:`urllib3.util.Retry` ``total`` parameter,
|
|
82
|
+
total number of retries to allow; default: ``3``
|
|
83
|
+
* :attr:`~eodag.config.PluginConfig.retry_backoff_factor` (``int``): :class:`urllib3.util.Retry`
|
|
84
|
+
``backoff_factor`` parameter, backoff factor to apply between attempts after the second try; default: ``2``
|
|
85
|
+
* :attr:`~eodag.config.PluginConfig.retry_status_forcelist` (``list[int]``): :class:`urllib3.util.Retry`
|
|
86
|
+
``status_forcelist`` parameter, list of integer HTTP status codes that we should force a retry on; default:
|
|
87
|
+
``[401, 429, 500, 502, 503, 504]``
|
|
88
|
+
"""
|
|
45
89
|
|
|
46
90
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
47
91
|
super(TokenAuth, self).__init__(provider, config)
|
|
@@ -56,11 +100,29 @@ class TokenAuth(Authentication):
|
|
|
56
100
|
self.config.auth_uri = self.config.auth_uri.format(
|
|
57
101
|
**self.config.credentials
|
|
58
102
|
)
|
|
59
|
-
|
|
103
|
+
|
|
104
|
+
# Format headers if needed with values from the credentials. Note:
|
|
105
|
+
# if only 'headers' is given, it will be used for both token retrieve and authentication.
|
|
106
|
+
# if 'retrieve_headers' is given, it will be used for token retrieve only.
|
|
107
|
+
# if both are given, 'retrieve_headers' will be used for token retrieve and 'headers' for authentication.
|
|
108
|
+
|
|
109
|
+
# If the authentication headers are undefined or None: use an empty dict.
|
|
110
|
+
# And don't format '{token}' now, it will be done later.
|
|
111
|
+
raw_headers = getattr(self.config, "headers", None) or {}
|
|
60
112
|
self.config.headers = {
|
|
61
113
|
header: value.format(**{"token": "{token}", **self.config.credentials})
|
|
62
|
-
for header, value in
|
|
114
|
+
for header, value in raw_headers.items()
|
|
63
115
|
}
|
|
116
|
+
|
|
117
|
+
# If the retrieve headers are undefined, their attribute must not be set in self.config.
|
|
118
|
+
# If they are defined but empty, use an empty dict instead of None.
|
|
119
|
+
if hasattr(self.config, "retrieve_headers"):
|
|
120
|
+
raw_retrieve_headers = self.config.retrieve_headers or {}
|
|
121
|
+
self.config.retrieve_headers = {
|
|
122
|
+
header: value.format(**self.config.credentials)
|
|
123
|
+
for header, value in raw_retrieve_headers.items()
|
|
124
|
+
}
|
|
125
|
+
|
|
64
126
|
except KeyError as e:
|
|
65
127
|
raise MisconfiguredError(
|
|
66
128
|
f"Missing credentials inputs for provider {self.provider}: {e}"
|
|
@@ -89,14 +151,15 @@ class TokenAuth(Authentication):
|
|
|
89
151
|
and e.response.status_code in auth_errors
|
|
90
152
|
):
|
|
91
153
|
raise AuthenticationError(
|
|
92
|
-
f"
|
|
93
|
-
f"
|
|
94
|
-
|
|
154
|
+
f"Please check your credentials for {self.provider}.",
|
|
155
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
156
|
+
response_text,
|
|
157
|
+
) from e
|
|
95
158
|
# other error
|
|
96
159
|
else:
|
|
97
160
|
raise AuthenticationError(
|
|
98
|
-
|
|
99
|
-
)
|
|
161
|
+
"Could no get authentication token", str(e), response_text
|
|
162
|
+
) from e
|
|
100
163
|
else:
|
|
101
164
|
if getattr(self.config, "token_type", "text") == "json":
|
|
102
165
|
token = response.json()[self.config.token_key]
|
|
@@ -116,16 +179,29 @@ class TokenAuth(Authentication):
|
|
|
116
179
|
self,
|
|
117
180
|
session: requests.Session,
|
|
118
181
|
) -> requests.Response:
|
|
182
|
+
retry_total = getattr(self.config, "retry_total", REQ_RETRY_TOTAL)
|
|
183
|
+
retry_backoff_factor = getattr(
|
|
184
|
+
self.config, "retry_backoff_factor", REQ_RETRY_BACKOFF_FACTOR
|
|
185
|
+
)
|
|
186
|
+
retry_status_forcelist = getattr(
|
|
187
|
+
self.config, "retry_status_forcelist", REQ_RETRY_STATUS_FORCELIST
|
|
188
|
+
)
|
|
189
|
+
|
|
119
190
|
retries = Retry(
|
|
120
|
-
total=
|
|
121
|
-
backoff_factor=
|
|
122
|
-
status_forcelist=
|
|
191
|
+
total=retry_total,
|
|
192
|
+
backoff_factor=retry_backoff_factor,
|
|
193
|
+
status_forcelist=retry_status_forcelist,
|
|
123
194
|
)
|
|
124
195
|
|
|
196
|
+
# Use the headers for retrieval if defined, else the headers for authentication
|
|
197
|
+
try:
|
|
198
|
+
headers = self.config.retrieve_headers
|
|
199
|
+
except AttributeError:
|
|
200
|
+
headers = self.config.headers
|
|
201
|
+
|
|
125
202
|
# append headers to req if some are specified in config
|
|
126
|
-
req_kwargs:
|
|
127
|
-
|
|
128
|
-
}
|
|
203
|
+
req_kwargs: dict[str, Any] = {"headers": dict(headers, **USER_AGENT)}
|
|
204
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
129
205
|
|
|
130
206
|
if self.refresh_token:
|
|
131
207
|
logger.debug("fetching access token with refresh token")
|
|
@@ -135,6 +211,7 @@ class TokenAuth(Authentication):
|
|
|
135
211
|
self.config.refresh_uri,
|
|
136
212
|
data={"refresh_token": self.refresh_token},
|
|
137
213
|
timeout=HTTP_REQ_TIMEOUT,
|
|
214
|
+
verify=ssl_verify,
|
|
138
215
|
**req_kwargs,
|
|
139
216
|
)
|
|
140
217
|
response.raise_for_status()
|
|
@@ -170,6 +247,7 @@ class TokenAuth(Authentication):
|
|
|
170
247
|
method=method,
|
|
171
248
|
url=self.config.auth_uri,
|
|
172
249
|
timeout=HTTP_REQ_TIMEOUT,
|
|
250
|
+
verify=ssl_verify,
|
|
173
251
|
**req_kwargs,
|
|
174
252
|
)
|
|
175
253
|
|
|
@@ -182,7 +260,7 @@ class RequestsTokenAuth(AuthBase):
|
|
|
182
260
|
token: str,
|
|
183
261
|
where: str,
|
|
184
262
|
qs_key: Optional[str] = None,
|
|
185
|
-
headers: Optional[
|
|
263
|
+
headers: Optional[dict[str, str]] = None,
|
|
186
264
|
) -> None:
|
|
187
265
|
self.token = token
|
|
188
266
|
self.where = where
|
|
@@ -38,25 +38,23 @@ class OIDCTokenExchangeAuth(Authentication):
|
|
|
38
38
|
"""Token exchange implementation using
|
|
39
39
|
:class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` token as subject.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
:param provider: provider name
|
|
42
|
+
:param config: Authentication plugin configuration:
|
|
43
|
+
|
|
44
|
+
* :attr:`~eodag.config.PluginConfig.subject` (``dict[str, Any]``) (**mandatory**):
|
|
45
|
+
The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin
|
|
46
|
+
configuration used to retrieve subject token
|
|
47
|
+
* :attr:`~eodag.config.PluginConfig.subject_issuer` (``str``) (**mandatory**): Identifies
|
|
48
|
+
the issuer of the subject_token
|
|
49
|
+
* :attr:`~eodag.config.PluginConfig.token_uri` (``str``) (**mandatory**): The url to
|
|
50
|
+
query to get the authorized token
|
|
51
|
+
* :attr:`~eodag.config.PluginConfig.client_id` (``str``) (**mandatory**): The OIDC
|
|
52
|
+
provider's client ID of the eodag provider
|
|
53
|
+
* :attr:`~eodag.config.PluginConfig.audience` (``str``) (**mandatory**): This parameter
|
|
54
|
+
specifies the target client you want the new token minted for.
|
|
55
|
+
* :attr:`~eodag.config.PluginConfig.token_key` (``str``) (**mandatory**): The key
|
|
56
|
+
pointing to the token in the json response to the POST request to the token server
|
|
42
57
|
|
|
43
|
-
# (mandatory) The full OIDCAuthorizationCodeFlowAuth plugin configuration used to retrieve subject token
|
|
44
|
-
subject:
|
|
45
|
-
|
|
46
|
-
# (mandatory) Identifies the issuer of the subject_token
|
|
47
|
-
subject_issuer:
|
|
48
|
-
|
|
49
|
-
# (mandatory) The url to query to get the authorized token
|
|
50
|
-
token_uri:
|
|
51
|
-
|
|
52
|
-
# (mandatory) The OIDC provider's client ID of the eodag provider
|
|
53
|
-
client_id:
|
|
54
|
-
|
|
55
|
-
# (mandatory) This parameter specifies the target client you want the new token minted for.
|
|
56
|
-
audience:
|
|
57
|
-
|
|
58
|
-
# (mandatory) The key pointing to the token in the json response to the POST request to the token server
|
|
59
|
-
token_key:
|
|
60
58
|
"""
|
|
61
59
|
|
|
62
60
|
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
|
|
@@ -71,7 +69,7 @@ class OIDCTokenExchangeAuth(Authentication):
|
|
|
71
69
|
]
|
|
72
70
|
|
|
73
71
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
74
|
-
super(
|
|
72
|
+
super().__init__(provider, config)
|
|
75
73
|
for required_key in self.REQUIRED_KEYS:
|
|
76
74
|
if getattr(self.config, required_key, None) is None:
|
|
77
75
|
raise MisconfiguredError(
|
|
@@ -100,12 +98,14 @@ class OIDCTokenExchangeAuth(Authentication):
|
|
|
100
98
|
"audience": self.config.audience,
|
|
101
99
|
}
|
|
102
100
|
logger.debug("Getting target auth token")
|
|
101
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
103
102
|
try:
|
|
104
103
|
auth_response = self.subject.session.post(
|
|
105
104
|
self.config.token_uri,
|
|
106
105
|
data=auth_data,
|
|
107
106
|
headers=USER_AGENT,
|
|
108
107
|
timeout=HTTP_REQ_TIMEOUT,
|
|
108
|
+
verify=ssl_verify,
|
|
109
109
|
)
|
|
110
110
|
auth_response.raise_for_status()
|
|
111
111
|
except requests.exceptions.Timeout as exc:
|
eodag/plugins/base.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License.
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import TYPE_CHECKING, Any
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
21
|
|
|
22
22
|
from eodag.utils.exceptions import PluginNotFoundError
|
|
23
23
|
|
|
@@ -29,21 +29,21 @@ class EODAGPluginMount(type):
|
|
|
29
29
|
"""Plugin mount"""
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
|
-
cls, name: str, bases:
|
|
32
|
+
cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]
|
|
33
33
|
) -> None:
|
|
34
34
|
if not hasattr(cls, "plugins"):
|
|
35
35
|
# This branch only executes when processing the mount point itself.
|
|
36
36
|
# So, since this is a new plugin type, not an implementation, this
|
|
37
37
|
# class shouldn't be registered as a plugin. Instead, it sets up a
|
|
38
38
|
# list where plugins can be registered later.
|
|
39
|
-
cls.plugins:
|
|
39
|
+
cls.plugins: list[EODAGPluginMount] = []
|
|
40
40
|
else:
|
|
41
41
|
# This must be a plugin implementation, which should be registered.
|
|
42
42
|
# Simply appending it to the list is all that's needed to keep
|
|
43
43
|
# track of it later.
|
|
44
44
|
cls.plugins.append(cls)
|
|
45
45
|
|
|
46
|
-
def get_plugins(cls, *args: Any, **kwargs: Any) ->
|
|
46
|
+
def get_plugins(cls, *args: Any, **kwargs: Any) -> list[EODAGPluginMount]:
|
|
47
47
|
"""Get plugins"""
|
|
48
48
|
return [plugin(*args, **kwargs) for plugin in cls.plugins]
|
|
49
49
|
|
eodag/plugins/crunch/base.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# limitations under the License
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from typing import TYPE_CHECKING, Any,
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
21
21
|
|
|
22
22
|
from eodag.config import PluginConfig
|
|
23
23
|
from eodag.plugins.base import PluginTopic
|
|
@@ -27,14 +27,17 @@ if TYPE_CHECKING:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Crunch(PluginTopic):
|
|
30
|
-
"""Base cruncher
|
|
30
|
+
"""Base cruncher
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
:param config: Crunch configuration
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config: Optional[dict[str, Any]]) -> None:
|
|
33
36
|
self.config = PluginConfig()
|
|
34
37
|
self.config.__dict__ = config if config is not None else {}
|
|
35
38
|
|
|
36
39
|
def proceed(
|
|
37
|
-
self, products:
|
|
38
|
-
) ->
|
|
40
|
+
self, products: list[EOProduct], **search_params: Any
|
|
41
|
+
) -> list[EOProduct]:
|
|
39
42
|
"""Implementation of how the results must be crunched"""
|
|
40
43
|
raise NotImplementedError
|
|
@@ -21,7 +21,7 @@ import datetime
|
|
|
21
21
|
import logging
|
|
22
22
|
import time
|
|
23
23
|
from datetime import datetime as dt
|
|
24
|
-
from typing import TYPE_CHECKING, Any
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
25
|
|
|
26
26
|
import dateutil.parser
|
|
27
27
|
from dateutil import tz
|
|
@@ -37,10 +37,13 @@ logger = logging.getLogger("eodag.crunch.date")
|
|
|
37
37
|
class FilterDate(Crunch):
|
|
38
38
|
"""FilterDate cruncher: filter products by date
|
|
39
39
|
|
|
40
|
+
Allows to filter out products that are older than a start date (optional) or more recent than an end date
|
|
41
|
+
(optional).
|
|
42
|
+
|
|
40
43
|
:param config: Crunch configuration, may contain :
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
* ``start`` (``str``): start sensing time in iso format
|
|
46
|
+
* ``end`` (``str``): end sensing time in iso format
|
|
44
47
|
"""
|
|
45
48
|
|
|
46
49
|
@staticmethod
|
|
@@ -54,8 +57,8 @@ class FilterDate(Crunch):
|
|
|
54
57
|
return dateutil.parser.parse(start_date)
|
|
55
58
|
|
|
56
59
|
def proceed(
|
|
57
|
-
self, products:
|
|
58
|
-
) ->
|
|
60
|
+
self, products: list[EOProduct], **search_params: Any
|
|
61
|
+
) -> list[EOProduct]:
|
|
59
62
|
"""Execute crunch: Filter products between start and end dates.
|
|
60
63
|
|
|
61
64
|
:param products: A list of products resulting from a search
|
|
@@ -86,7 +89,7 @@ class FilterDate(Crunch):
|
|
|
86
89
|
if not filter_start and not filter_end:
|
|
87
90
|
return products
|
|
88
91
|
|
|
89
|
-
filtered:
|
|
92
|
+
filtered: list[EOProduct] = []
|
|
90
93
|
for product in products:
|
|
91
94
|
|
|
92
95
|
# product start date
|
|
@@ -20,7 +20,7 @@ from __future__ import annotations
|
|
|
20
20
|
import datetime
|
|
21
21
|
import logging
|
|
22
22
|
import time
|
|
23
|
-
from typing import TYPE_CHECKING, Any,
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Union
|
|
24
24
|
|
|
25
25
|
import dateutil.parser
|
|
26
26
|
from shapely import geometry
|
|
@@ -39,7 +39,8 @@ logger = logging.getLogger("eodag.crunch.latest_intersect")
|
|
|
39
39
|
class FilterLatestIntersect(Crunch):
|
|
40
40
|
"""FilterLatestIntersect cruncher
|
|
41
41
|
|
|
42
|
-
Filter latest products (the ones with a the highest start date) that intersect search extent
|
|
42
|
+
Filter latest products (the ones with a the highest start date) that intersect search extent;
|
|
43
|
+
The configuration for this plugin is an empty dict
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
46
|
@staticmethod
|
|
@@ -53,14 +54,14 @@ class FilterLatestIntersect(Crunch):
|
|
|
53
54
|
return dateutil.parser.parse(start_date)
|
|
54
55
|
|
|
55
56
|
def proceed(
|
|
56
|
-
self, products:
|
|
57
|
-
) ->
|
|
57
|
+
self, products: list[EOProduct], **search_params: dict[str, Any]
|
|
58
|
+
) -> list[EOProduct]:
|
|
58
59
|
"""Execute crunch:
|
|
59
60
|
Filter latest products (the ones with a the highest start date) that intersect search extent.
|
|
60
61
|
|
|
61
62
|
:param products: A list of products resulting from a search
|
|
62
|
-
:param search_params: Search criteria that must contain
|
|
63
|
-
|
|
63
|
+
:param search_params: Search criteria that must contain ``geometry`` or ``geom`` parameters having value of
|
|
64
|
+
type :class:`shapely.geometry.base.BaseGeometry` or ``dict[str, Any]``
|
|
64
65
|
:returns: The filtered products
|
|
65
66
|
"""
|
|
66
67
|
logger.debug("Start filtering for latest products")
|
|
@@ -68,9 +69,9 @@ class FilterLatestIntersect(Crunch):
|
|
|
68
69
|
return []
|
|
69
70
|
# Warning: May crash if startTimeFromAscendingNode is not in the appropriate format
|
|
70
71
|
products.sort(key=self.sort_product_by_start_date, reverse=True)
|
|
71
|
-
filtered:
|
|
72
|
+
filtered: list[EOProduct] = []
|
|
72
73
|
add_to_filtered = filtered.append
|
|
73
|
-
footprint: Union[
|
|
74
|
+
footprint: Union[dict[str, Any], BaseGeometry, Any] = search_params.get(
|
|
74
75
|
"geometry"
|
|
75
76
|
) or search_params.get("geom")
|
|
76
77
|
if not footprint:
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import re
|
|
22
|
-
from typing import TYPE_CHECKING, Any,
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
23
23
|
|
|
24
24
|
from eodag.plugins.crunch.base import Crunch
|
|
25
25
|
from eodag.utils.exceptions import ValidationError
|
|
@@ -37,12 +37,12 @@ class FilterLatestByName(Crunch):
|
|
|
37
37
|
|
|
38
38
|
:param config: Crunch configuration, must contain :
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
* ``name_pattern`` (``str``) (**mandatory**): product name pattern
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
NAME_PATTERN_CONSTRAINT = re.compile(r"\(\?P<tileid>\\d\{6\}\)")
|
|
44
44
|
|
|
45
|
-
def __init__(self, config:
|
|
45
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
46
46
|
super(FilterLatestByName, self).__init__(config)
|
|
47
47
|
name_pattern = config.pop("name_pattern")
|
|
48
48
|
if not self.NAME_PATTERN_CONSTRAINT.search(name_pattern):
|
|
@@ -54,19 +54,19 @@ class FilterLatestByName(Crunch):
|
|
|
54
54
|
self.name_pattern = re.compile(name_pattern)
|
|
55
55
|
|
|
56
56
|
def proceed(
|
|
57
|
-
self, products:
|
|
58
|
-
) ->
|
|
57
|
+
self, products: list[EOProduct], **search_params: Any
|
|
58
|
+
) -> list[EOProduct]:
|
|
59
59
|
"""Execute crunch: Filter Search results to get only the latest product, based on the name of the product
|
|
60
60
|
|
|
61
61
|
:param products: A list of products resulting from a search
|
|
62
62
|
:returns: The filtered products
|
|
63
63
|
"""
|
|
64
64
|
logger.debug("Starting products filtering")
|
|
65
|
-
processed:
|
|
66
|
-
filtered:
|
|
65
|
+
processed: list[str] = []
|
|
66
|
+
filtered: list[EOProduct] = []
|
|
67
67
|
for product in products:
|
|
68
68
|
match = cast(
|
|
69
|
-
Optional[Match[Any]],
|
|
69
|
+
Optional[re.Match[Any]],
|
|
70
70
|
self.name_pattern.match(product.properties["title"]),
|
|
71
71
|
)
|
|
72
72
|
if match:
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
|
-
from typing import TYPE_CHECKING, Any
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
22
|
|
|
23
23
|
from eodag.plugins.crunch.base import Crunch
|
|
24
24
|
from eodag.utils import get_geometry_from_various
|
|
@@ -40,19 +40,17 @@ class FilterOverlap(Crunch):
|
|
|
40
40
|
|
|
41
41
|
Filter products, retaining only those that are overlapping with the search_extent
|
|
42
42
|
|
|
43
|
-
:param config: Crunch configuration
|
|
43
|
+
:param config: Crunch configuration may contain the following parameters which are mutually exclusive:
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
These configuration parameters are mutually exclusive.
|
|
45
|
+
* ``minimum_overlap`` (``Union[float, str]``): minimal overlap percentage; default: ``"0"``
|
|
46
|
+
* ``contains`` (``bool``): ``True`` if product geometry contains the search area; default: ``False``
|
|
47
|
+
* ``intersects`` (``bool``): ``True`` if product geometry intersects the search area; default: ``False``
|
|
48
|
+
* ``within`` (``bool``): ``True`` if product geometry is within the search area; default: ``False``
|
|
51
49
|
"""
|
|
52
50
|
|
|
53
51
|
def proceed(
|
|
54
|
-
self, products:
|
|
55
|
-
) ->
|
|
52
|
+
self, products: list[EOProduct], **search_params: Any
|
|
53
|
+
) -> list[EOProduct]:
|
|
56
54
|
"""Execute crunch: Filter products, retaining only those that are overlapping with the search_extent
|
|
57
55
|
|
|
58
56
|
:param products: A list of products resulting from a search
|
|
@@ -60,7 +58,7 @@ class FilterOverlap(Crunch):
|
|
|
60
58
|
:returns: The filtered products
|
|
61
59
|
"""
|
|
62
60
|
logger.debug("Start filtering for overlapping products")
|
|
63
|
-
filtered:
|
|
61
|
+
filtered: list[EOProduct] = []
|
|
64
62
|
add_to_filtered = filtered.append
|
|
65
63
|
|
|
66
64
|
search_geom = get_geometry_from_various(**search_params)
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import logging
|
|
21
21
|
import operator
|
|
22
|
-
from typing import TYPE_CHECKING, Any
|
|
22
|
+
from typing import TYPE_CHECKING, Any
|
|
23
23
|
|
|
24
24
|
from eodag.plugins.crunch.base import Crunch
|
|
25
25
|
|
|
@@ -34,15 +34,16 @@ class FilterProperty(Crunch):
|
|
|
34
34
|
|
|
35
35
|
Filter products, retaining only those whose property match criteria
|
|
36
36
|
|
|
37
|
-
:param config: Crunch configuration,
|
|
37
|
+
:param config: Crunch configuration, must contain :
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
* ``<property>`` ``(Any)`` (**mandatory**): property key from ``product.properties``, associated to its filter
|
|
40
|
+
value
|
|
41
|
+
* ``operator`` (``str``): Operator used for filtering (one of ``lt,le,eq,ne,ge,gt``). Default is ``eq``
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
44
|
def proceed(
|
|
44
|
-
self, products:
|
|
45
|
-
) ->
|
|
45
|
+
self, products: list[EOProduct], **search_params: Any
|
|
46
|
+
) -> list[EOProduct]:
|
|
46
47
|
"""Execute crunch: Filter products, retaining only those that match property filtering
|
|
47
48
|
|
|
48
49
|
:param products: A list of products resulting from a search
|
|
@@ -71,16 +72,15 @@ class FilterProperty(Crunch):
|
|
|
71
72
|
property_key,
|
|
72
73
|
property_value,
|
|
73
74
|
)
|
|
74
|
-
filtered:
|
|
75
|
+
filtered: list[EOProduct] = []
|
|
75
76
|
add_to_filtered = filtered.append
|
|
76
77
|
|
|
77
78
|
for product in products:
|
|
78
79
|
if property_key not in product.properties.keys():
|
|
79
80
|
logger.warning(
|
|
80
|
-
"
|
|
81
|
-
property_key,
|
|
81
|
+
f"{property_key} not found in {product}.properties, product skipped",
|
|
82
82
|
)
|
|
83
|
-
|
|
83
|
+
continue
|
|
84
84
|
if operator_method(product.properties[property_key], property_value):
|
|
85
85
|
add_to_filtered(product)
|
|
86
86
|
|