eodag 3.0.0b2__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.
Files changed (84) hide show
  1. eodag/__init__.py +6 -8
  2. eodag/api/core.py +295 -287
  3. eodag/api/product/__init__.py +10 -4
  4. eodag/api/product/_assets.py +2 -14
  5. eodag/api/product/_product.py +16 -30
  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 +12 -31
  9. eodag/api/search_result.py +33 -12
  10. eodag/cli.py +35 -19
  11. eodag/config.py +455 -155
  12. eodag/plugins/apis/base.py +13 -7
  13. eodag/plugins/apis/ecmwf.py +16 -7
  14. eodag/plugins/apis/usgs.py +68 -16
  15. eodag/plugins/authentication/aws_auth.py +25 -7
  16. eodag/plugins/authentication/base.py +10 -1
  17. eodag/plugins/authentication/generic.py +14 -3
  18. eodag/plugins/authentication/header.py +12 -4
  19. eodag/plugins/authentication/keycloak.py +41 -22
  20. eodag/plugins/authentication/oauth.py +11 -1
  21. eodag/plugins/authentication/openid_connect.py +183 -167
  22. eodag/plugins/authentication/qsauth.py +12 -4
  23. eodag/plugins/authentication/sas_auth.py +19 -2
  24. eodag/plugins/authentication/token.py +59 -11
  25. eodag/plugins/authentication/token_exchange.py +19 -19
  26. eodag/plugins/crunch/base.py +7 -2
  27. eodag/plugins/crunch/filter_date.py +8 -11
  28. eodag/plugins/crunch/filter_latest_intersect.py +5 -7
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +2 -5
  30. eodag/plugins/crunch/filter_overlap.py +9 -15
  31. eodag/plugins/crunch/filter_property.py +9 -14
  32. eodag/plugins/download/aws.py +84 -99
  33. eodag/plugins/download/base.py +36 -77
  34. eodag/plugins/download/creodias_s3.py +11 -2
  35. eodag/plugins/download/http.py +134 -109
  36. eodag/plugins/download/s3rest.py +37 -43
  37. eodag/plugins/manager.py +173 -41
  38. eodag/plugins/search/__init__.py +9 -9
  39. eodag/plugins/search/base.py +35 -35
  40. eodag/plugins/search/build_search_result.py +55 -64
  41. eodag/plugins/search/cop_marine.py +113 -32
  42. eodag/plugins/search/creodias_s3.py +20 -8
  43. eodag/plugins/search/csw.py +41 -1
  44. eodag/plugins/search/data_request_search.py +119 -14
  45. eodag/plugins/search/qssearch.py +619 -197
  46. eodag/plugins/search/static_stac_search.py +25 -23
  47. eodag/resources/ext_product_types.json +1 -1
  48. eodag/resources/product_types.yml +211 -56
  49. eodag/resources/providers.yml +1762 -1809
  50. eodag/resources/stac.yml +3 -163
  51. eodag/resources/user_conf_template.yml +134 -119
  52. eodag/rest/config.py +1 -2
  53. eodag/rest/constants.py +0 -1
  54. eodag/rest/core.py +70 -92
  55. eodag/rest/errors.py +181 -0
  56. eodag/rest/server.py +24 -330
  57. eodag/rest/stac.py +105 -630
  58. eodag/rest/types/eodag_search.py +17 -15
  59. eodag/rest/types/queryables.py +5 -14
  60. eodag/rest/types/stac_search.py +18 -13
  61. eodag/rest/utils/rfc3339.py +0 -1
  62. eodag/types/__init__.py +24 -6
  63. eodag/types/download_args.py +14 -5
  64. eodag/types/queryables.py +1 -2
  65. eodag/types/search_args.py +10 -11
  66. eodag/types/whoosh.py +0 -2
  67. eodag/utils/__init__.py +97 -136
  68. eodag/utils/constraints.py +0 -8
  69. eodag/utils/exceptions.py +23 -9
  70. eodag/utils/import_system.py +0 -4
  71. eodag/utils/logging.py +37 -80
  72. eodag/utils/notebook.py +4 -4
  73. eodag/utils/requests.py +13 -23
  74. eodag/utils/rest.py +0 -4
  75. eodag/utils/stac_reader.py +3 -15
  76. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/METADATA +41 -24
  77. eodag-3.0.1.dist-info/RECORD +109 -0
  78. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/WHEEL +1 -1
  79. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/entry_points.txt +1 -0
  80. eodag/resources/constraints/climate-dt.json +0 -13
  81. eodag/resources/constraints/extremes-dt.json +0 -8
  82. eodag-3.0.0b2.dist-info/RECORD +0 -110
  83. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/LICENSE +0 -0
  84. {eodag-3.0.0b2.dist-info → eodag-3.0.1.dist-info}/top_level.txt +0 -0
@@ -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 HTTP_REQ_TIMEOUT, USER_AGENT
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,36 @@ 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
+ :param provider: provider name
53
+ :param config: Authentication plugin configuration:
54
+
55
+ * :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): TokenAuth
56
+ * :attr:`~eodag.config.PluginConfig.auth_uri` (``str``) (**mandatory**): url used to fetch
57
+ the access token with user/password
58
+ * :attr:`~eodag.config.PluginConfig.refresh_uri` (``str``) : url used to fetch the
59
+ access token with a refresh token
60
+ * :attr:`~eodag.config.PluginConfig.token_type` (``str``): type of the token (``json``
61
+ or ``text``); default: ``text``
62
+ * :attr:`~eodag.config.PluginConfig.token_key` (``str``): (mandatory if token_type=json)
63
+ key to get the access token in the response to the token request
64
+ * :attr:`~eodag.config.PluginConfig.refresh_token_key` (``str``): key to get the refresh
65
+ token in the response to the token request
66
+ * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
67
+ should be verified in the requests; default: ``True``
68
+ * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
69
+ returned in case of an authentication error
70
+ * :attr:`~eodag.config.PluginConfig.req_data` (``Dict[str, Any]``): if the credentials
71
+ should be sent as data in the post request, the json structure can be given in this parameter
72
+ * :attr:`~eodag.config.PluginConfig.retry_total` (``int``): :class:`urllib3.util.Retry` ``total`` parameter,
73
+ total number of retries to allow; default: ``3``
74
+ * :attr:`~eodag.config.PluginConfig.retry_backoff_factor` (``int``): :class:`urllib3.util.Retry`
75
+ ``backoff_factor`` parameter, backoff factor to apply between attempts after the second try; default: ``2``
76
+ * :attr:`~eodag.config.PluginConfig.retry_status_forcelist` (``List[int]``): :class:`urllib3.util.Retry`
77
+ ``status_forcelist`` parameter, list of integer HTTP status codes that we should force a retry on; default:
78
+ ``[401, 429, 500, 502, 503, 504]``
79
+ """
45
80
 
46
81
  def __init__(self, provider: str, config: PluginConfig) -> None:
47
82
  super(TokenAuth, self).__init__(provider, config)
@@ -89,14 +124,15 @@ class TokenAuth(Authentication):
89
124
  and e.response.status_code in auth_errors
90
125
  ):
91
126
  raise AuthenticationError(
92
- f"HTTP Error {e.response.status_code} returned, {response_text}\n"
93
- f"Please check your credentials for {self.provider}"
94
- )
127
+ f"Please check your credentials for {self.provider}.",
128
+ f"HTTP Error {e.response.status_code} returned.",
129
+ response_text,
130
+ ) from e
95
131
  # other error
96
132
  else:
97
133
  raise AuthenticationError(
98
- f"Could no get authentication token: {str(e)}, {response_text}"
99
- )
134
+ "Could no get authentication token", str(e), response_text
135
+ ) from e
100
136
  else:
101
137
  if getattr(self.config, "token_type", "text") == "json":
102
138
  token = response.json()[self.config.token_key]
@@ -116,16 +152,25 @@ class TokenAuth(Authentication):
116
152
  self,
117
153
  session: requests.Session,
118
154
  ) -> requests.Response:
155
+ retry_total = getattr(self.config, "retry_total", REQ_RETRY_TOTAL)
156
+ retry_backoff_factor = getattr(
157
+ self.config, "retry_backoff_factor", REQ_RETRY_BACKOFF_FACTOR
158
+ )
159
+ retry_status_forcelist = getattr(
160
+ self.config, "retry_status_forcelist", REQ_RETRY_STATUS_FORCELIST
161
+ )
162
+
119
163
  retries = Retry(
120
- total=3,
121
- backoff_factor=2,
122
- status_forcelist=[401, 429, 500, 502, 503, 504],
164
+ total=retry_total,
165
+ backoff_factor=retry_backoff_factor,
166
+ status_forcelist=retry_status_forcelist,
123
167
  )
124
168
 
125
169
  # append headers to req if some are specified in config
126
170
  req_kwargs: Dict[str, Any] = {
127
171
  "headers": dict(self.config.headers, **USER_AGENT)
128
172
  }
173
+ ssl_verify = getattr(self.config, "ssl_verify", True)
129
174
 
130
175
  if self.refresh_token:
131
176
  logger.debug("fetching access token with refresh token")
@@ -135,6 +180,7 @@ class TokenAuth(Authentication):
135
180
  self.config.refresh_uri,
136
181
  data={"refresh_token": self.refresh_token},
137
182
  timeout=HTTP_REQ_TIMEOUT,
183
+ verify=ssl_verify,
138
184
  **req_kwargs,
139
185
  )
140
186
  response.raise_for_status()
@@ -170,6 +216,7 @@ class TokenAuth(Authentication):
170
216
  method=method,
171
217
  url=self.config.auth_uri,
172
218
  timeout=HTTP_REQ_TIMEOUT,
219
+ verify=ssl_verify,
173
220
  **req_kwargs,
174
221
  )
175
222
 
@@ -197,7 +244,8 @@ class RequestsTokenAuth(AuthBase):
197
244
  if self.where == "qs":
198
245
  parts = urlparse(str(request.url))
199
246
  qs = parse_qs(parts.query)
200
- qs[self.qs_key] = self.token # type: ignore
247
+ if self.qs_key is not None:
248
+ qs[self.qs_key] = [self.token]
201
249
  request.url = urlunparse(
202
250
  (
203
251
  parts.scheme,
@@ -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
- The configuration keys of this plugin are as follows (they have no defaults)::
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(OIDCTokenExchangeAuth, self).__init__(provider, config)
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:
@@ -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:
@@ -26,10 +27,14 @@ if TYPE_CHECKING:
26
27
 
27
28
 
28
29
  class Crunch(PluginTopic):
29
- """Base cruncher"""
30
+ """Base cruncher
31
+
32
+ :param config: Crunch configuration
33
+ """
30
34
 
31
35
  def __init__(self, config: Optional[Dict[str, Any]]) -> None:
32
- self.config = config if config is not None else {}
36
+ self.config = PluginConfig()
37
+ self.config.__dict__ = config if config is not None else {}
33
38
 
34
39
  def proceed(
35
40
  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
@@ -37,16 +37,15 @@ logger = logging.getLogger("eodag.crunch.date")
37
37
  class FilterDate(Crunch):
38
38
  """FilterDate cruncher: filter products by date
39
39
 
40
- :param config: Crunch configuration, may contain :
40
+ Allows to filter out products that are older than a start date (optional) or more recent than an end date
41
+ (optional).
41
42
 
42
- - `start`: (optional) start sensing time in iso format
43
- - `end`: (optional) end sensing time in iso format
43
+ :param config: Crunch configuration, may contain :
44
44
 
45
- :type config: dict
45
+ * ``start`` (``str``): start sensing time in iso format
46
+ * ``end`` (``str``): end sensing time in iso format
46
47
  """
47
48
 
48
- config: Dict[str, str]
49
-
50
49
  @staticmethod
51
50
  def sort_product_by_start_date(product: EOProduct) -> dt:
52
51
  """Get product start date"""
@@ -63,16 +62,14 @@ class FilterDate(Crunch):
63
62
  """Execute crunch: Filter products between start and end dates.
64
63
 
65
64
  :param products: A list of products resulting from a search
66
- :type products: list(:class:`~eodag.api.product._product.EOProduct`)
67
65
  :returns: The filtered products
68
- :rtype: list(:class:`~eodag.api.product._product.EOProduct`)
69
66
  """
70
67
  logger.debug("Start filtering by date")
71
68
  if not products:
72
69
  return []
73
70
 
74
71
  # filter start date
75
- filter_start_str = self.config.get("start", None)
72
+ filter_start_str = self.config.__dict__.get("start", None)
76
73
  if filter_start_str:
77
74
  filter_start = dateutil.parser.parse(filter_start_str)
78
75
  if not filter_start.tzinfo:
@@ -81,7 +78,7 @@ class FilterDate(Crunch):
81
78
  filter_start = None
82
79
 
83
80
  # filter end date
84
- filter_end_str = self.config.get("end", None)
81
+ filter_end_str = self.config.__dict__.get("end", None)
85
82
  if filter_end_str:
86
83
  filter_end = dateutil.parser.parse(filter_end_str)
87
84
  if not filter_end.tzinfo:
@@ -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,18 +54,15 @@ class FilterLatestIntersect(Crunch):
53
54
  return dateutil.parser.parse(start_date)
54
55
 
55
56
  def proceed(
56
- self, products: List[EOProduct], **search_params: Any
57
+ self, products: List[EOProduct], **search_params: Dict[str, Any]
57
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
- :type products: list(:class:`~eodag.api.product._product.EOProduct`)
63
- :param search_params: Search criteria that must contain `geometry` (dict)
64
- or search `geom` (:class:`shapely.geometry.base.BaseGeometry`) argument will be used
65
- :type search_params: dict
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]``
66
65
  :returns: The filtered products
67
- :rtype: list(:class:`~eodag.api.product._product.EOProduct`)
68
66
  """
69
67
  logger.debug("Start filtering for latest products")
70
68
  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
 
@@ -36,9 +37,7 @@ class FilterLatestByName(Crunch):
36
37
 
37
38
  :param config: Crunch configuration, must contain :
38
39
 
39
- - `name_pattern` : product name pattern
40
-
41
- :type config: dict
40
+ * ``name_pattern`` (``str``) (**mandatory**): product name pattern
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] = []
@@ -40,15 +40,12 @@ 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, may contain :
43
+ :param config: Crunch configuration may contain the following parameters which are mutually exclusive:
44
44
 
45
- - `minimum_overlap` : minimal overlap percentage
46
- - `contains` : True if product geometry contains the search area
47
- - `intersects` : True if product geometry intersects the search area
48
- - `within` : True if product geometry is within the search area
49
-
50
- These configuration parameters are mutually exclusive.
51
- :type config: dict
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``
52
49
  """
53
50
 
54
51
  def proceed(
@@ -57,11 +54,8 @@ class FilterOverlap(Crunch):
57
54
  """Execute crunch: Filter products, retaining only those that are overlapping with the search_extent
58
55
 
59
56
  :param products: A list of products resulting from a search
60
- :type products: list(:class:`~eodag.api.product._product.EOProduct`)
61
57
  :param search_params: Search criteria that must contain `geometry`
62
- :type search_params: dict
63
58
  :returns: The filtered products
64
- :rtype: list(:class:`~eodag.api.product._product.EOProduct`)
65
59
  """
66
60
  logger.debug("Start filtering for overlapping products")
67
61
  filtered: List[EOProduct] = []
@@ -73,10 +67,10 @@ class FilterOverlap(Crunch):
73
67
  "geometry not found in cruncher arguments, filtering disabled."
74
68
  )
75
69
  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)
70
+ minimum_overlap = float(self.config.__dict__.get("minimum_overlap", "0"))
71
+ contains = self.config.__dict__.get("contains", False)
72
+ intersects = self.config.__dict__.get("intersects", False)
73
+ within = self.config.__dict__.get("within", False)
80
74
 
81
75
  if contains and (within or intersects) or (within and intersects):
82
76
  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
 
@@ -34,27 +34,22 @@ class FilterProperty(Crunch):
34
34
 
35
35
  Filter products, retaining only those whose property match criteria
36
36
 
37
- :param config: Crunch configuration, should contain :
37
+ :param config: Crunch configuration, must contain :
38
38
 
39
- - `property=value` : property key from product.properties, associated to its filter value
40
- - `operator` : (optional) Operator used for filtering (one of `lt,le,eq,ne,ge,gt`). Default is `eq`
41
-
42
- :type config: dict
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``
43
42
  """
44
43
 
45
- config: Dict[str, Union[str, Optional[str]]]
46
-
47
44
  def proceed(
48
45
  self, products: List[EOProduct], **search_params: Any
49
46
  ) -> List[EOProduct]:
50
47
  """Execute crunch: Filter products, retaining only those that match property filtering
51
48
 
52
49
  :param products: A list of products resulting from a search
53
- :type products: list(:class:`~eodag.api.product._product.EOProduct`)
54
50
  :returns: The filtered products
55
- :rtype: list(:class:`~eodag.api.product._product.EOProduct`)
56
51
  """
57
- operator_name = self.config.pop("operator", "eq") or "eq"
52
+ operator_name = self.config.__dict__.pop("operator", "eq") or "eq"
58
53
  try:
59
54
  operator_method = getattr(operator, operator_name)
60
55
  except AttributeError:
@@ -64,12 +59,12 @@ class FilterProperty(Crunch):
64
59
  )
65
60
  return products
66
61
 
67
- if len(self.config.keys()) != 1:
62
+ if len(self.config.__dict__.keys()) != 1:
68
63
  logger.warning("One property is needed for filtering, filtering disabled.")
69
64
  return products
70
65
 
71
- property_key = next(iter(self.config))
72
- property_value = self.config.get(property_key, None)
66
+ property_key = next(iter(self.config.__dict__))
67
+ property_value = self.config.__dict__.get(property_key, None)
73
68
 
74
69
  logger.debug(
75
70
  "Start filtering for products matching operator.%s(product.properties['%s'], %s)",