eodag 2.12.1__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.
- eodag/__init__.py +6 -8
- eodag/api/core.py +654 -538
- eodag/api/product/__init__.py +12 -2
- eodag/api/product/_assets.py +59 -16
- eodag/api/product/_product.py +100 -93
- eodag/api/product/drivers/__init__.py +7 -2
- eodag/api/product/drivers/base.py +0 -3
- eodag/api/product/metadata_mapping.py +192 -96
- eodag/api/search_result.py +69 -10
- eodag/cli.py +55 -25
- eodag/config.py +391 -116
- eodag/plugins/apis/base.py +11 -168
- eodag/plugins/apis/ecmwf.py +36 -25
- eodag/plugins/apis/usgs.py +80 -35
- eodag/plugins/authentication/aws_auth.py +13 -4
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +2 -2
- eodag/plugins/authentication/header.py +31 -6
- eodag/plugins/authentication/keycloak.py +17 -84
- eodag/plugins/authentication/oauth.py +3 -3
- eodag/plugins/authentication/openid_connect.py +268 -49
- eodag/plugins/authentication/qsauth.py +4 -1
- eodag/plugins/authentication/sas_auth.py +9 -2
- eodag/plugins/authentication/token.py +98 -47
- eodag/plugins/authentication/token_exchange.py +122 -0
- eodag/plugins/crunch/base.py +3 -1
- eodag/plugins/crunch/filter_date.py +3 -9
- eodag/plugins/crunch/filter_latest_intersect.py +0 -3
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -4
- eodag/plugins/crunch/filter_overlap.py +4 -8
- eodag/plugins/crunch/filter_property.py +5 -11
- eodag/plugins/download/aws.py +149 -185
- eodag/plugins/download/base.py +88 -97
- eodag/plugins/download/creodias_s3.py +1 -1
- eodag/plugins/download/http.py +638 -310
- eodag/plugins/download/s3rest.py +47 -45
- eodag/plugins/manager.py +228 -88
- eodag/plugins/search/__init__.py +36 -0
- eodag/plugins/search/base.py +239 -30
- eodag/plugins/search/build_search_result.py +382 -37
- eodag/plugins/search/cop_marine.py +441 -0
- eodag/plugins/search/creodias_s3.py +25 -20
- eodag/plugins/search/csw.py +5 -7
- eodag/plugins/search/data_request_search.py +61 -30
- eodag/plugins/search/qssearch.py +713 -255
- eodag/plugins/search/static_stac_search.py +106 -40
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1921 -34
- eodag/resources/providers.yml +4091 -3655
- eodag/resources/stac.yml +50 -216
- eodag/resources/stac_api.yml +71 -25
- eodag/resources/stac_provider.yml +5 -0
- eodag/resources/user_conf_template.yml +89 -32
- eodag/rest/__init__.py +6 -0
- eodag/rest/cache.py +70 -0
- eodag/rest/config.py +68 -0
- eodag/rest/constants.py +26 -0
- eodag/rest/core.py +735 -0
- eodag/rest/errors.py +178 -0
- eodag/rest/server.py +264 -431
- eodag/rest/stac.py +442 -836
- eodag/rest/types/collections_search.py +44 -0
- eodag/rest/types/eodag_search.py +238 -47
- eodag/rest/types/queryables.py +164 -0
- eodag/rest/types/stac_search.py +273 -0
- eodag/rest/utils/__init__.py +216 -0
- eodag/rest/utils/cql_evaluate.py +119 -0
- eodag/rest/utils/rfc3339.py +64 -0
- eodag/types/__init__.py +106 -10
- eodag/types/bbox.py +15 -14
- eodag/types/download_args.py +40 -0
- eodag/types/search_args.py +57 -7
- eodag/types/whoosh.py +79 -0
- eodag/utils/__init__.py +110 -91
- eodag/utils/constraints.py +37 -45
- eodag/utils/exceptions.py +39 -22
- eodag/utils/import_system.py +0 -4
- eodag/utils/logging.py +37 -80
- eodag/utils/notebook.py +4 -4
- eodag/utils/repr.py +113 -0
- eodag/utils/requests.py +128 -0
- eodag/utils/rest.py +100 -0
- eodag/utils/stac_reader.py +93 -21
- {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/METADATA +88 -53
- eodag-3.0.0.dist-info/RECORD +109 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/WHEEL +1 -1
- {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/entry_points.txt +7 -5
- eodag/plugins/apis/cds.py +0 -540
- eodag/rest/types/stac_queryables.py +0 -134
- eodag/rest/utils.py +0 -1133
- eodag-2.12.1.dist-info/RECORD +0 -94
- {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/LICENSE +0 -0
- {eodag-2.12.1.dist-info → eodag-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -46,6 +46,7 @@ class TokenAuth(Authentication):
|
|
|
46
46
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
47
47
|
super(TokenAuth, self).__init__(provider, config)
|
|
48
48
|
self.token = ""
|
|
49
|
+
self.refresh_token = ""
|
|
49
50
|
|
|
50
51
|
def validate_config_credentials(self) -> None:
|
|
51
52
|
"""Validate configured credentials"""
|
|
@@ -55,9 +56,9 @@ class TokenAuth(Authentication):
|
|
|
55
56
|
self.config.auth_uri = self.config.auth_uri.format(
|
|
56
57
|
**self.config.credentials
|
|
57
58
|
)
|
|
58
|
-
# format headers if needed
|
|
59
|
+
# format headers if needed (and accepts {token} to be formatted later)
|
|
59
60
|
self.config.headers = {
|
|
60
|
-
header: value.format(**self.config.credentials)
|
|
61
|
+
header: value.format(**{"token": "{token}", **self.config.credentials})
|
|
61
62
|
for header, value in getattr(self.config, "headers", {}).items()
|
|
62
63
|
}
|
|
63
64
|
except KeyError as e:
|
|
@@ -69,65 +70,112 @@ class TokenAuth(Authentication):
|
|
|
69
70
|
"""Authenticate"""
|
|
70
71
|
self.validate_config_credentials()
|
|
71
72
|
|
|
72
|
-
# append headers to req if some are specified in config
|
|
73
|
-
req_kwargs: Dict[str, Any] = (
|
|
74
|
-
{"headers": dict(self.config.headers, **USER_AGENT)}
|
|
75
|
-
if hasattr(self.config, "headers")
|
|
76
|
-
else {"headers": USER_AGENT}
|
|
77
|
-
)
|
|
78
73
|
s = requests.Session()
|
|
79
|
-
retries = Retry(
|
|
80
|
-
total=3, backoff_factor=2, status_forcelist=[401, 429, 500, 502, 503, 504]
|
|
81
|
-
)
|
|
82
|
-
s.mount(self.config.auth_uri, HTTPAdapter(max_retries=retries))
|
|
83
74
|
try:
|
|
84
75
|
# First get the token
|
|
85
|
-
|
|
86
|
-
response = s.post(
|
|
87
|
-
self.config.auth_uri,
|
|
88
|
-
data=self.config.credentials,
|
|
89
|
-
timeout=HTTP_REQ_TIMEOUT,
|
|
90
|
-
**req_kwargs,
|
|
91
|
-
)
|
|
92
|
-
else:
|
|
93
|
-
cred = self.config.credentials
|
|
94
|
-
response = s.get(
|
|
95
|
-
self.config.auth_uri,
|
|
96
|
-
auth=(cred["username"], cred["password"]),
|
|
97
|
-
timeout=HTTP_REQ_TIMEOUT,
|
|
98
|
-
**req_kwargs,
|
|
99
|
-
)
|
|
76
|
+
response = self._token_request(session=s)
|
|
100
77
|
response.raise_for_status()
|
|
101
78
|
except requests.exceptions.Timeout as exc:
|
|
102
79
|
raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
|
|
103
80
|
except RequestException as e:
|
|
104
81
|
response_text = getattr(e.response, "text", "").strip()
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
82
|
+
# check if error is identified as auth_error in provider conf
|
|
83
|
+
auth_errors = getattr(self.config, "auth_error_code", [None])
|
|
84
|
+
if not isinstance(auth_errors, list):
|
|
85
|
+
auth_errors = [auth_errors]
|
|
86
|
+
if (
|
|
87
|
+
e.response is not None
|
|
88
|
+
and getattr(e.response, "status_code", None)
|
|
89
|
+
and e.response.status_code in auth_errors
|
|
90
|
+
):
|
|
91
|
+
raise AuthenticationError(
|
|
92
|
+
f"Please check your credentials for {self.provider}.",
|
|
93
|
+
f"HTTP Error {e.response.status_code} returned.",
|
|
94
|
+
response_text,
|
|
95
|
+
) from e
|
|
96
|
+
# other error
|
|
97
|
+
else:
|
|
98
|
+
raise AuthenticationError(
|
|
99
|
+
"Could no get authentication token", str(e), response_text
|
|
100
|
+
) from e
|
|
108
101
|
else:
|
|
109
102
|
if getattr(self.config, "token_type", "text") == "json":
|
|
110
103
|
token = response.json()[self.config.token_key]
|
|
111
104
|
else:
|
|
112
105
|
token = response.text
|
|
113
|
-
headers = self._get_headers(token)
|
|
114
106
|
self.token = token
|
|
107
|
+
if getattr(self.config, "refresh_token_key", None):
|
|
108
|
+
self.refresh_token = response.json()[self.config.refresh_token_key]
|
|
109
|
+
if not hasattr(self.config, "headers"):
|
|
110
|
+
raise MisconfiguredError(f"Missing headers configuration for {self}")
|
|
115
111
|
# Return auth class set with obtained token
|
|
116
|
-
return RequestsTokenAuth(
|
|
117
|
-
|
|
118
|
-
def _get_headers(self, token: str) -> Dict[str, str]:
|
|
119
|
-
headers = self.config.headers
|
|
120
|
-
if "Authorization" in headers and "$" in headers["Authorization"]:
|
|
121
|
-
headers["Authorization"] = headers["Authorization"].replace("$token", token)
|
|
122
|
-
if (
|
|
123
|
-
self.token
|
|
124
|
-
and token != self.token
|
|
125
|
-
and self.token in headers["Authorization"]
|
|
126
|
-
):
|
|
127
|
-
headers["Authorization"] = headers["Authorization"].replace(
|
|
128
|
-
self.token, token
|
|
112
|
+
return RequestsTokenAuth(
|
|
113
|
+
token, "header", headers=getattr(self.config, "headers", {})
|
|
129
114
|
)
|
|
130
|
-
|
|
115
|
+
|
|
116
|
+
def _token_request(
|
|
117
|
+
self,
|
|
118
|
+
session: requests.Session,
|
|
119
|
+
) -> requests.Response:
|
|
120
|
+
retries = Retry(
|
|
121
|
+
total=3,
|
|
122
|
+
backoff_factor=2,
|
|
123
|
+
status_forcelist=[401, 429, 500, 502, 503, 504],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# append headers to req if some are specified in config
|
|
127
|
+
req_kwargs: Dict[str, Any] = {
|
|
128
|
+
"headers": dict(self.config.headers, **USER_AGENT)
|
|
129
|
+
}
|
|
130
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
131
|
+
|
|
132
|
+
if self.refresh_token:
|
|
133
|
+
logger.debug("fetching access token with refresh token")
|
|
134
|
+
session.mount(self.config.refresh_uri, HTTPAdapter(max_retries=retries))
|
|
135
|
+
try:
|
|
136
|
+
response = session.post(
|
|
137
|
+
self.config.refresh_uri,
|
|
138
|
+
data={"refresh_token": self.refresh_token},
|
|
139
|
+
timeout=HTTP_REQ_TIMEOUT,
|
|
140
|
+
verify=ssl_verify,
|
|
141
|
+
**req_kwargs,
|
|
142
|
+
)
|
|
143
|
+
response.raise_for_status()
|
|
144
|
+
return response
|
|
145
|
+
except requests.exceptions.HTTPError as e:
|
|
146
|
+
logger.debug(getattr(e.response, "text", "").strip())
|
|
147
|
+
|
|
148
|
+
logger.debug("fetching access token from %s", self.config.auth_uri)
|
|
149
|
+
# append headers to req if some are specified in config
|
|
150
|
+
session.mount(self.config.auth_uri, HTTPAdapter(max_retries=retries))
|
|
151
|
+
method = getattr(self.config, "request_method", "POST")
|
|
152
|
+
|
|
153
|
+
# send credentials also as data in POST requests
|
|
154
|
+
if method == "POST":
|
|
155
|
+
# append req_data to credentials if specified in config
|
|
156
|
+
req_kwargs["data"] = dict(
|
|
157
|
+
getattr(self.config, "req_data", {}), **self.config.credentials
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# credentials as auth tuple if possible
|
|
161
|
+
req_kwargs["auth"] = (
|
|
162
|
+
(
|
|
163
|
+
self.config.credentials["username"],
|
|
164
|
+
self.config.credentials["password"],
|
|
165
|
+
)
|
|
166
|
+
if all(
|
|
167
|
+
k in self.config.credentials.keys() for k in ["username", "password"]
|
|
168
|
+
)
|
|
169
|
+
else None
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return session.request(
|
|
173
|
+
method=method,
|
|
174
|
+
url=self.config.auth_uri,
|
|
175
|
+
timeout=HTTP_REQ_TIMEOUT,
|
|
176
|
+
verify=ssl_verify,
|
|
177
|
+
**req_kwargs,
|
|
178
|
+
)
|
|
131
179
|
|
|
132
180
|
|
|
133
181
|
class RequestsTokenAuth(AuthBase):
|
|
@@ -153,7 +201,8 @@ class RequestsTokenAuth(AuthBase):
|
|
|
153
201
|
if self.where == "qs":
|
|
154
202
|
parts = urlparse(str(request.url))
|
|
155
203
|
qs = parse_qs(parts.query)
|
|
156
|
-
|
|
204
|
+
if self.qs_key is not None:
|
|
205
|
+
qs[self.qs_key] = [self.token]
|
|
157
206
|
request.url = urlunparse(
|
|
158
207
|
(
|
|
159
208
|
parts.scheme,
|
|
@@ -165,5 +214,7 @@ class RequestsTokenAuth(AuthBase):
|
|
|
165
214
|
)
|
|
166
215
|
)
|
|
167
216
|
elif self.where == "header":
|
|
168
|
-
request.headers["Authorization"] =
|
|
217
|
+
request.headers["Authorization"] = request.headers.get(
|
|
218
|
+
"Authorization", "Bearer {token}"
|
|
219
|
+
).format(token=self.token)
|
|
169
220
|
return request
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2024, CS GROUP - France, https://www.csgroup.eu/
|
|
3
|
+
#
|
|
4
|
+
# This file is part of EODAG project
|
|
5
|
+
# https://www.github.com/CS-SI/EODAG
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
import requests
|
|
23
|
+
from requests import RequestException
|
|
24
|
+
|
|
25
|
+
from eodag.config import PluginConfig
|
|
26
|
+
from eodag.plugins.authentication import Authentication
|
|
27
|
+
from eodag.plugins.authentication.openid_connect import (
|
|
28
|
+
CodeAuthorizedAuth,
|
|
29
|
+
OIDCAuthorizationCodeFlowAuth,
|
|
30
|
+
)
|
|
31
|
+
from eodag.utils import HTTP_REQ_TIMEOUT, USER_AGENT
|
|
32
|
+
from eodag.utils.exceptions import AuthenticationError, MisconfiguredError, TimeOutError
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("eodag.auth.token_exchange")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class OIDCTokenExchangeAuth(Authentication):
|
|
38
|
+
"""Token exchange implementation using
|
|
39
|
+
:class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` token as subject.
|
|
40
|
+
|
|
41
|
+
The configuration keys of this plugin are as follows (they have no defaults)::
|
|
42
|
+
|
|
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
|
+
"""
|
|
61
|
+
|
|
62
|
+
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange"
|
|
63
|
+
SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token"
|
|
64
|
+
REQUIRED_KEYS = [
|
|
65
|
+
"subject",
|
|
66
|
+
"subject_issuer",
|
|
67
|
+
"token_uri",
|
|
68
|
+
"client_id",
|
|
69
|
+
"audience",
|
|
70
|
+
"token_key",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
74
|
+
super(OIDCTokenExchangeAuth, self).__init__(provider, config)
|
|
75
|
+
for required_key in self.REQUIRED_KEYS:
|
|
76
|
+
if getattr(self.config, required_key, None) is None:
|
|
77
|
+
raise MisconfiguredError(
|
|
78
|
+
f"Missing required entry for OIDCTokenExchangeAuth configuration: {required_key}"
|
|
79
|
+
)
|
|
80
|
+
self.subject = OIDCAuthorizationCodeFlowAuth(
|
|
81
|
+
provider,
|
|
82
|
+
PluginConfig.from_mapping(
|
|
83
|
+
{
|
|
84
|
+
"credentials": getattr(self.config, "credentials", {}),
|
|
85
|
+
**self.config.subject,
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def authenticate(self) -> CodeAuthorizedAuth:
|
|
91
|
+
"""Authenticate"""
|
|
92
|
+
logger.debug("Getting subject auth token")
|
|
93
|
+
subject_auth = self.subject.authenticate()
|
|
94
|
+
auth_data = {
|
|
95
|
+
"grant_type": self.GRANT_TYPE,
|
|
96
|
+
"subject_token": subject_auth.token,
|
|
97
|
+
"subject_issuer": self.config.subject_issuer,
|
|
98
|
+
"subject_token_type": self.SUBJECT_TOKEN_TYPE,
|
|
99
|
+
"client_id": self.config.client_id,
|
|
100
|
+
"audience": self.config.audience,
|
|
101
|
+
}
|
|
102
|
+
logger.debug("Getting target auth token")
|
|
103
|
+
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
104
|
+
try:
|
|
105
|
+
auth_response = self.subject.session.post(
|
|
106
|
+
self.config.token_uri,
|
|
107
|
+
data=auth_data,
|
|
108
|
+
headers=USER_AGENT,
|
|
109
|
+
timeout=HTTP_REQ_TIMEOUT,
|
|
110
|
+
verify=ssl_verify,
|
|
111
|
+
)
|
|
112
|
+
auth_response.raise_for_status()
|
|
113
|
+
except requests.exceptions.Timeout as exc:
|
|
114
|
+
raise TimeOutError(exc, timeout=HTTP_REQ_TIMEOUT) from exc
|
|
115
|
+
except RequestException as exc:
|
|
116
|
+
raise AuthenticationError("Could no get authentication token") from exc
|
|
117
|
+
finally:
|
|
118
|
+
self.subject.session.close()
|
|
119
|
+
|
|
120
|
+
token = auth_response.json()[self.config.token_key]
|
|
121
|
+
|
|
122
|
+
return CodeAuthorizedAuth(token, where="header")
|
eodag/plugins/crunch/base.py
CHANGED
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
21
21
|
|
|
22
|
+
from eodag.config import PluginConfig
|
|
22
23
|
from eodag.plugins.base import PluginTopic
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
@@ -29,7 +30,8 @@ class Crunch(PluginTopic):
|
|
|
29
30
|
"""Base cruncher"""
|
|
30
31
|
|
|
31
32
|
def __init__(self, config: Optional[Dict[str, Any]]) -> None:
|
|
32
|
-
self.config =
|
|
33
|
+
self.config = PluginConfig()
|
|
34
|
+
self.config.__dict__ = config if config is not None else {}
|
|
33
35
|
|
|
34
36
|
def proceed(
|
|
35
37
|
self, products: List[EOProduct], **search_params: Any
|
|
@@ -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, List
|
|
25
25
|
|
|
26
26
|
import dateutil.parser
|
|
27
27
|
from dateutil import tz
|
|
@@ -41,12 +41,8 @@ class FilterDate(Crunch):
|
|
|
41
41
|
|
|
42
42
|
- `start`: (optional) start sensing time in iso format
|
|
43
43
|
- `end`: (optional) end sensing time in iso format
|
|
44
|
-
|
|
45
|
-
:type config: dict
|
|
46
44
|
"""
|
|
47
45
|
|
|
48
|
-
config: Dict[str, str]
|
|
49
|
-
|
|
50
46
|
@staticmethod
|
|
51
47
|
def sort_product_by_start_date(product: EOProduct) -> dt:
|
|
52
48
|
"""Get product start date"""
|
|
@@ -63,16 +59,14 @@ class FilterDate(Crunch):
|
|
|
63
59
|
"""Execute crunch: Filter products between start and end dates.
|
|
64
60
|
|
|
65
61
|
:param products: A list of products resulting from a search
|
|
66
|
-
:type products: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
67
62
|
:returns: The filtered products
|
|
68
|
-
:rtype: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
69
63
|
"""
|
|
70
64
|
logger.debug("Start filtering by date")
|
|
71
65
|
if not products:
|
|
72
66
|
return []
|
|
73
67
|
|
|
74
68
|
# filter start date
|
|
75
|
-
filter_start_str = self.config.get("start", None)
|
|
69
|
+
filter_start_str = self.config.__dict__.get("start", None)
|
|
76
70
|
if filter_start_str:
|
|
77
71
|
filter_start = dateutil.parser.parse(filter_start_str)
|
|
78
72
|
if not filter_start.tzinfo:
|
|
@@ -81,7 +75,7 @@ class FilterDate(Crunch):
|
|
|
81
75
|
filter_start = None
|
|
82
76
|
|
|
83
77
|
# filter end date
|
|
84
|
-
filter_end_str = self.config.get("end", None)
|
|
78
|
+
filter_end_str = self.config.__dict__.get("end", None)
|
|
85
79
|
if filter_end_str:
|
|
86
80
|
filter_end = dateutil.parser.parse(filter_end_str)
|
|
87
81
|
if not filter_end.tzinfo:
|
|
@@ -59,12 +59,9 @@ class FilterLatestIntersect(Crunch):
|
|
|
59
59
|
Filter latest products (the ones with a the highest start date) that intersect search extent.
|
|
60
60
|
|
|
61
61
|
:param products: A list of products resulting from a search
|
|
62
|
-
:type products: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
63
62
|
:param search_params: Search criteria that must contain `geometry` (dict)
|
|
64
63
|
or search `geom` (:class:`shapely.geometry.base.BaseGeometry`) argument will be used
|
|
65
|
-
:type search_params: dict
|
|
66
64
|
:returns: The filtered products
|
|
67
|
-
:rtype: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
68
65
|
"""
|
|
69
66
|
logger.debug("Start filtering for latest products")
|
|
70
67
|
if not products:
|
|
@@ -26,6 +26,7 @@ from eodag.utils.exceptions import ValidationError
|
|
|
26
26
|
|
|
27
27
|
if TYPE_CHECKING:
|
|
28
28
|
from eodag.api.product import EOProduct
|
|
29
|
+
|
|
29
30
|
logger = logging.getLogger("eodag.crunch.latest_tpl_name")
|
|
30
31
|
|
|
31
32
|
|
|
@@ -37,8 +38,6 @@ class FilterLatestByName(Crunch):
|
|
|
37
38
|
:param config: Crunch configuration, must contain :
|
|
38
39
|
|
|
39
40
|
- `name_pattern` : product name pattern
|
|
40
|
-
|
|
41
|
-
:type config: dict
|
|
42
41
|
"""
|
|
43
42
|
|
|
44
43
|
NAME_PATTERN_CONSTRAINT = re.compile(r"\(\?P<tileid>\\d\{6\}\)")
|
|
@@ -60,9 +59,7 @@ class FilterLatestByName(Crunch):
|
|
|
60
59
|
"""Execute crunch: Filter Search results to get only the latest product, based on the name of the product
|
|
61
60
|
|
|
62
61
|
:param products: A list of products resulting from a search
|
|
63
|
-
:type products: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
64
62
|
:returns: The filtered products
|
|
65
|
-
:rtype: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
66
63
|
"""
|
|
67
64
|
logger.debug("Starting products filtering")
|
|
68
65
|
processed: List[str] = []
|
|
@@ -48,7 +48,6 @@ class FilterOverlap(Crunch):
|
|
|
48
48
|
- `within` : True if product geometry is within the search area
|
|
49
49
|
|
|
50
50
|
These configuration parameters are mutually exclusive.
|
|
51
|
-
:type config: dict
|
|
52
51
|
"""
|
|
53
52
|
|
|
54
53
|
def proceed(
|
|
@@ -57,11 +56,8 @@ class FilterOverlap(Crunch):
|
|
|
57
56
|
"""Execute crunch: Filter products, retaining only those that are overlapping with the search_extent
|
|
58
57
|
|
|
59
58
|
:param products: A list of products resulting from a search
|
|
60
|
-
:type products: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
61
59
|
:param search_params: Search criteria that must contain `geometry`
|
|
62
|
-
:type search_params: dict
|
|
63
60
|
:returns: The filtered products
|
|
64
|
-
:rtype: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
65
61
|
"""
|
|
66
62
|
logger.debug("Start filtering for overlapping products")
|
|
67
63
|
filtered: List[EOProduct] = []
|
|
@@ -73,10 +69,10 @@ class FilterOverlap(Crunch):
|
|
|
73
69
|
"geometry not found in cruncher arguments, filtering disabled."
|
|
74
70
|
)
|
|
75
71
|
return products
|
|
76
|
-
minimum_overlap = float(self.config.get("minimum_overlap", "0"))
|
|
77
|
-
contains = self.config.get("contains", False)
|
|
78
|
-
intersects = self.config.get("intersects", False)
|
|
79
|
-
within = self.config.get("within", False)
|
|
72
|
+
minimum_overlap = float(self.config.__dict__.get("minimum_overlap", "0"))
|
|
73
|
+
contains = self.config.__dict__.get("contains", False)
|
|
74
|
+
intersects = self.config.__dict__.get("intersects", False)
|
|
75
|
+
within = self.config.__dict__.get("within", False)
|
|
80
76
|
|
|
81
77
|
if contains and (within or intersects) or (within and intersects):
|
|
82
78
|
logger.warning(
|
|
@@ -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, List
|
|
23
23
|
|
|
24
24
|
from eodag.plugins.crunch.base import Crunch
|
|
25
25
|
|
|
@@ -38,23 +38,17 @@ class FilterProperty(Crunch):
|
|
|
38
38
|
|
|
39
39
|
- `property=value` : property key from product.properties, associated to its filter value
|
|
40
40
|
- `operator` : (optional) Operator used for filtering (one of `lt,le,eq,ne,ge,gt`). Default is `eq`
|
|
41
|
-
|
|
42
|
-
:type config: dict
|
|
43
41
|
"""
|
|
44
42
|
|
|
45
|
-
config: Dict[str, Union[str, Optional[str]]]
|
|
46
|
-
|
|
47
43
|
def proceed(
|
|
48
44
|
self, products: List[EOProduct], **search_params: Any
|
|
49
45
|
) -> List[EOProduct]:
|
|
50
46
|
"""Execute crunch: Filter products, retaining only those that match property filtering
|
|
51
47
|
|
|
52
48
|
:param products: A list of products resulting from a search
|
|
53
|
-
:type products: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
54
49
|
:returns: The filtered products
|
|
55
|
-
:rtype: list(:class:`~eodag.api.product._product.EOProduct`)
|
|
56
50
|
"""
|
|
57
|
-
operator_name = self.config.pop("operator", "eq") or "eq"
|
|
51
|
+
operator_name = self.config.__dict__.pop("operator", "eq") or "eq"
|
|
58
52
|
try:
|
|
59
53
|
operator_method = getattr(operator, operator_name)
|
|
60
54
|
except AttributeError:
|
|
@@ -64,12 +58,12 @@ class FilterProperty(Crunch):
|
|
|
64
58
|
)
|
|
65
59
|
return products
|
|
66
60
|
|
|
67
|
-
if len(self.config.keys()) != 1:
|
|
61
|
+
if len(self.config.__dict__.keys()) != 1:
|
|
68
62
|
logger.warning("One property is needed for filtering, filtering disabled.")
|
|
69
63
|
return products
|
|
70
64
|
|
|
71
|
-
property_key = next(iter(self.config))
|
|
72
|
-
property_value = self.config.get(property_key, None)
|
|
65
|
+
property_key = next(iter(self.config.__dict__))
|
|
66
|
+
property_value = self.config.__dict__.get(property_key, None)
|
|
73
67
|
|
|
74
68
|
logger.debug(
|
|
75
69
|
"Start filtering for products matching operator.%s(product.properties['%s'], %s)",
|