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
@@ -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
- if getattr(self.config, "request_method", "POST") == "POST":
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
- raise AuthenticationError(
106
- f"Could no get authentication token: {str(e)}, {response_text}"
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(token, "header", headers=headers)
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
- return headers
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
- qs[self.qs_key] = self.token # type: ignore
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"] = "Bearer {}".format(self.token)
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")
@@ -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 = config if config is not None else {}
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, Dict, List
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, Dict, List, Optional, Union
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)",