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.
Files changed (94) hide show
  1. eodag/api/core.py +347 -247
  2. eodag/api/product/_assets.py +44 -15
  3. eodag/api/product/_product.py +58 -47
  4. eodag/api/product/drivers/__init__.py +81 -4
  5. eodag/api/product/drivers/base.py +65 -4
  6. eodag/api/product/drivers/generic.py +65 -0
  7. eodag/api/product/drivers/sentinel1.py +97 -0
  8. eodag/api/product/drivers/sentinel2.py +95 -0
  9. eodag/api/product/metadata_mapping.py +129 -93
  10. eodag/api/search_result.py +28 -12
  11. eodag/cli.py +61 -24
  12. eodag/config.py +457 -167
  13. eodag/plugins/apis/base.py +10 -4
  14. eodag/plugins/apis/ecmwf.py +53 -23
  15. eodag/plugins/apis/usgs.py +41 -17
  16. eodag/plugins/authentication/aws_auth.py +30 -18
  17. eodag/plugins/authentication/base.py +14 -3
  18. eodag/plugins/authentication/generic.py +14 -3
  19. eodag/plugins/authentication/header.py +14 -6
  20. eodag/plugins/authentication/keycloak.py +44 -25
  21. eodag/plugins/authentication/oauth.py +18 -4
  22. eodag/plugins/authentication/openid_connect.py +192 -171
  23. eodag/plugins/authentication/qsauth.py +12 -4
  24. eodag/plugins/authentication/sas_auth.py +22 -5
  25. eodag/plugins/authentication/token.py +95 -17
  26. eodag/plugins/authentication/token_exchange.py +19 -19
  27. eodag/plugins/base.py +4 -4
  28. eodag/plugins/crunch/base.py +8 -5
  29. eodag/plugins/crunch/filter_date.py +9 -6
  30. eodag/plugins/crunch/filter_latest_intersect.py +9 -8
  31. eodag/plugins/crunch/filter_latest_tpl_name.py +8 -8
  32. eodag/plugins/crunch/filter_overlap.py +9 -11
  33. eodag/plugins/crunch/filter_property.py +10 -10
  34. eodag/plugins/download/aws.py +181 -105
  35. eodag/plugins/download/base.py +49 -67
  36. eodag/plugins/download/creodias_s3.py +40 -2
  37. eodag/plugins/download/http.py +247 -223
  38. eodag/plugins/download/s3rest.py +29 -28
  39. eodag/plugins/manager.py +176 -41
  40. eodag/plugins/search/__init__.py +6 -5
  41. eodag/plugins/search/base.py +123 -60
  42. eodag/plugins/search/build_search_result.py +1046 -355
  43. eodag/plugins/search/cop_marine.py +132 -39
  44. eodag/plugins/search/creodias_s3.py +19 -68
  45. eodag/plugins/search/csw.py +48 -8
  46. eodag/plugins/search/data_request_search.py +124 -23
  47. eodag/plugins/search/qssearch.py +531 -310
  48. eodag/plugins/search/stac_list_assets.py +85 -0
  49. eodag/plugins/search/static_stac_search.py +23 -24
  50. eodag/resources/ext_product_types.json +1 -1
  51. eodag/resources/product_types.yml +1295 -355
  52. eodag/resources/providers.yml +1819 -3010
  53. eodag/resources/stac.yml +3 -163
  54. eodag/resources/stac_api.yml +2 -2
  55. eodag/resources/user_conf_template.yml +115 -99
  56. eodag/rest/cache.py +2 -2
  57. eodag/rest/config.py +3 -4
  58. eodag/rest/constants.py +0 -1
  59. eodag/rest/core.py +157 -117
  60. eodag/rest/errors.py +181 -0
  61. eodag/rest/server.py +57 -339
  62. eodag/rest/stac.py +133 -581
  63. eodag/rest/types/collections_search.py +3 -3
  64. eodag/rest/types/eodag_search.py +41 -30
  65. eodag/rest/types/queryables.py +42 -32
  66. eodag/rest/types/stac_search.py +15 -16
  67. eodag/rest/utils/__init__.py +14 -21
  68. eodag/rest/utils/cql_evaluate.py +6 -6
  69. eodag/rest/utils/rfc3339.py +2 -2
  70. eodag/types/__init__.py +153 -32
  71. eodag/types/bbox.py +2 -2
  72. eodag/types/download_args.py +4 -4
  73. eodag/types/queryables.py +183 -73
  74. eodag/types/search_args.py +6 -6
  75. eodag/types/whoosh.py +127 -3
  76. eodag/utils/__init__.py +228 -106
  77. eodag/utils/exceptions.py +47 -26
  78. eodag/utils/import_system.py +2 -2
  79. eodag/utils/logging.py +37 -77
  80. eodag/utils/repr.py +65 -6
  81. eodag/utils/requests.py +13 -15
  82. eodag/utils/rest.py +2 -2
  83. eodag/utils/s3.py +231 -0
  84. eodag/utils/stac_reader.py +11 -11
  85. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/METADATA +81 -81
  86. eodag-3.1.0.dist-info/RECORD +113 -0
  87. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  88. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +5 -2
  89. eodag/resources/constraints/climate-dt.json +0 -13
  90. eodag/resources/constraints/extremes-dt.json +0 -8
  91. eodag/utils/constraints.py +0 -244
  92. eodag-3.0.0b3.dist-info/RECORD +0 -110
  93. {eodag-3.0.0b3.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  94. {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, Dict, Optional
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 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,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
- # format headers if needed (and accepts {token} to be formatted later)
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 getattr(self.config, "headers", {}).items()
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"HTTP Error {e.response.status_code} returned, {response_text}\n"
93
- f"Please check your credentials for {self.provider}"
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
- f"Could no get authentication token: {str(e)}, {response_text}"
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=3,
121
- backoff_factor=2,
122
- status_forcelist=[401, 429, 500, 502, 503, 504],
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: Dict[str, Any] = {
127
- "headers": dict(self.config.headers, **USER_AGENT)
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[Dict[str, str]] = None,
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
- 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:
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, Dict, List, Tuple
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: Tuple[type, ...], attrs: Dict[str, Any]
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: List[EODAGPluginMount] = []
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) -> List[EODAGPluginMount]:
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
 
@@ -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, Dict, List, Optional
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
- def __init__(self, config: Optional[Dict[str, Any]]) -> None:
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: List[EOProduct], **search_params: Any
38
- ) -> List[EOProduct]:
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, List
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
- - `start`: (optional) start sensing time in iso format
43
- - `end`: (optional) end sensing time in iso format
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: List[EOProduct], **search_params: Any
58
- ) -> List[EOProduct]:
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: List[EOProduct] = []
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, Dict, List, Union
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: List[EOProduct], **search_params: Any
57
- ) -> List[EOProduct]:
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 `geometry` (dict)
63
- or search `geom` (:class:`shapely.geometry.base.BaseGeometry`) argument will be used
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: List[EOProduct] = []
72
+ filtered: list[EOProduct] = []
72
73
  add_to_filtered = filtered.append
73
- footprint: Union[Dict[str, Any], BaseGeometry, Any] = search_params.get(
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, Dict, List, Match, Optional, cast
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
- - `name_pattern` : product name pattern
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: Dict[str, Any]) -> None:
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: List[EOProduct], **search_params: Any
58
- ) -> List[EOProduct]:
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: List[str] = []
66
- filtered: List[EOProduct] = []
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, List
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, 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.
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: List[EOProduct], **search_params: Any
55
- ) -> List[EOProduct]:
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: List[EOProduct] = []
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, List
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, 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`
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: List[EOProduct], **search_params: Any
45
- ) -> List[EOProduct]:
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: List[EOProduct] = []
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
- "%s not found in product.properties, filtering disabled.",
81
- property_key,
81
+ f"{property_key} not found in {product}.properties, product skipped",
82
82
  )
83
- return products
83
+ continue
84
84
  if operator_method(product.properties[property_key], property_value):
85
85
  add_to_filtered(product)
86
86