eodag 3.0.1__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 (87) hide show
  1. eodag/api/core.py +174 -138
  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 +117 -90
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +26 -5
  12. eodag/config.py +86 -92
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +42 -22
  15. eodag/plugins/apis/usgs.py +17 -16
  16. eodag/plugins/authentication/aws_auth.py +16 -13
  17. eodag/plugins/authentication/base.py +5 -3
  18. eodag/plugins/authentication/header.py +3 -3
  19. eodag/plugins/authentication/keycloak.py +4 -4
  20. eodag/plugins/authentication/oauth.py +7 -3
  21. eodag/plugins/authentication/openid_connect.py +22 -16
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +41 -10
  24. eodag/plugins/authentication/token_exchange.py +1 -1
  25. eodag/plugins/base.py +4 -4
  26. eodag/plugins/crunch/base.py +4 -4
  27. eodag/plugins/crunch/filter_date.py +4 -4
  28. eodag/plugins/crunch/filter_latest_intersect.py +6 -6
  29. eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
  30. eodag/plugins/crunch/filter_overlap.py +4 -4
  31. eodag/plugins/crunch/filter_property.py +6 -7
  32. eodag/plugins/download/aws.py +146 -87
  33. eodag/plugins/download/base.py +38 -56
  34. eodag/plugins/download/creodias_s3.py +29 -0
  35. eodag/plugins/download/http.py +173 -183
  36. eodag/plugins/download/s3rest.py +10 -11
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +90 -46
  40. eodag/plugins/search/build_search_result.py +1048 -361
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +9 -73
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +19 -18
  45. eodag/plugins/search/qssearch.py +99 -258
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +4 -4
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +1134 -325
  50. eodag/resources/providers.yml +906 -2006
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +10 -9
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +112 -82
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +33 -14
  58. eodag/rest/stac.py +41 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +29 -23
  61. eodag/rest/types/queryables.py +42 -31
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +14 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +141 -32
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +3 -3
  69. eodag/types/queryables.py +183 -72
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +127 -3
  72. eodag/utils/__init__.py +153 -51
  73. eodag/utils/exceptions.py +28 -21
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/repr.py +65 -6
  76. eodag/utils/requests.py +13 -13
  77. eodag/utils/rest.py +2 -2
  78. eodag/utils/s3.py +231 -0
  79. eodag/utils/stac_reader.py +10 -10
  80. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
  81. eodag-3.1.0.dist-info/RECORD +113 -0
  82. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
  83. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
  84. eodag/utils/constraints.py +0 -244
  85. eodag-3.0.1.dist-info/RECORD +0 -109
  86. {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
  87. {eodag-3.0.1.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
@@ -47,7 +47,12 @@ logger = logging.getLogger("eodag.authentication.token")
47
47
 
48
48
 
49
49
  class TokenAuth(Authentication):
50
- """TokenAuth authentication plugin - fetches a token which is added to search/download requests
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.
51
56
 
52
57
  :param provider: provider name
53
58
  :param config: Authentication plugin configuration:
@@ -55,6 +60,10 @@ class TokenAuth(Authentication):
55
60
  * :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): TokenAuth
56
61
  * :attr:`~eodag.config.PluginConfig.auth_uri` (``str``) (**mandatory**): url used to fetch
57
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
58
67
  * :attr:`~eodag.config.PluginConfig.refresh_uri` (``str``) : url used to fetch the
59
68
  access token with a refresh token
60
69
  * :attr:`~eodag.config.PluginConfig.token_type` (``str``): type of the token (``json``
@@ -67,13 +76,13 @@ class TokenAuth(Authentication):
67
76
  should be verified in the requests; default: ``True``
68
77
  * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
69
78
  returned in case of an authentication error
70
- * :attr:`~eodag.config.PluginConfig.req_data` (``Dict[str, Any]``): if the credentials
79
+ * :attr:`~eodag.config.PluginConfig.req_data` (``dict[str, Any]``): if the credentials
71
80
  should be sent as data in the post request, the json structure can be given in this parameter
72
81
  * :attr:`~eodag.config.PluginConfig.retry_total` (``int``): :class:`urllib3.util.Retry` ``total`` parameter,
73
82
  total number of retries to allow; default: ``3``
74
83
  * :attr:`~eodag.config.PluginConfig.retry_backoff_factor` (``int``): :class:`urllib3.util.Retry`
75
84
  ``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`
85
+ * :attr:`~eodag.config.PluginConfig.retry_status_forcelist` (``list[int]``): :class:`urllib3.util.Retry`
77
86
  ``status_forcelist`` parameter, list of integer HTTP status codes that we should force a retry on; default:
78
87
  ``[401, 429, 500, 502, 503, 504]``
79
88
  """
@@ -91,11 +100,29 @@ class TokenAuth(Authentication):
91
100
  self.config.auth_uri = self.config.auth_uri.format(
92
101
  **self.config.credentials
93
102
  )
94
- # 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 {}
95
112
  self.config.headers = {
96
113
  header: value.format(**{"token": "{token}", **self.config.credentials})
97
- for header, value in getattr(self.config, "headers", {}).items()
114
+ for header, value in raw_headers.items()
98
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
+
99
126
  except KeyError as e:
100
127
  raise MisconfiguredError(
101
128
  f"Missing credentials inputs for provider {self.provider}: {e}"
@@ -166,10 +193,14 @@ class TokenAuth(Authentication):
166
193
  status_forcelist=retry_status_forcelist,
167
194
  )
168
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
+
169
202
  # append headers to req if some are specified in config
170
- req_kwargs: Dict[str, Any] = {
171
- "headers": dict(self.config.headers, **USER_AGENT)
172
- }
203
+ req_kwargs: dict[str, Any] = {"headers": dict(headers, **USER_AGENT)}
173
204
  ssl_verify = getattr(self.config, "ssl_verify", True)
174
205
 
175
206
  if self.refresh_token:
@@ -229,7 +260,7 @@ class RequestsTokenAuth(AuthBase):
229
260
  token: str,
230
261
  where: str,
231
262
  qs_key: Optional[str] = None,
232
- headers: Optional[Dict[str, str]] = None,
263
+ headers: Optional[dict[str, str]] = None,
233
264
  ) -> None:
234
265
  self.token = token
235
266
  self.where = where
@@ -41,7 +41,7 @@ class OIDCTokenExchangeAuth(Authentication):
41
41
  :param provider: provider name
42
42
  :param config: Authentication plugin configuration:
43
43
 
44
- * :attr:`~eodag.config.PluginConfig.subject` (``Dict[str, Any]``) (**mandatory**):
44
+ * :attr:`~eodag.config.PluginConfig.subject` (``dict[str, Any]``) (**mandatory**):
45
45
  The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin
46
46
  configuration used to retrieve subject token
47
47
  * :attr:`~eodag.config.PluginConfig.subject_issuer` (``str``) (**mandatory**): Identifies
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
@@ -32,12 +32,12 @@ class Crunch(PluginTopic):
32
32
  :param config: Crunch configuration
33
33
  """
34
34
 
35
- def __init__(self, config: Optional[Dict[str, Any]]) -> None:
35
+ def __init__(self, config: Optional[dict[str, Any]]) -> None:
36
36
  self.config = PluginConfig()
37
37
  self.config.__dict__ = config if config is not None else {}
38
38
 
39
39
  def proceed(
40
- self, products: List[EOProduct], **search_params: Any
41
- ) -> List[EOProduct]:
40
+ self, products: list[EOProduct], **search_params: Any
41
+ ) -> list[EOProduct]:
42
42
  """Implementation of how the results must be crunched"""
43
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
@@ -57,8 +57,8 @@ class FilterDate(Crunch):
57
57
  return dateutil.parser.parse(start_date)
58
58
 
59
59
  def proceed(
60
- self, products: List[EOProduct], **search_params: Any
61
- ) -> List[EOProduct]:
60
+ self, products: list[EOProduct], **search_params: Any
61
+ ) -> list[EOProduct]:
62
62
  """Execute crunch: Filter products between start and end dates.
63
63
 
64
64
  :param products: A list of products resulting from a search
@@ -89,7 +89,7 @@ class FilterDate(Crunch):
89
89
  if not filter_start and not filter_end:
90
90
  return products
91
91
 
92
- filtered: List[EOProduct] = []
92
+ filtered: list[EOProduct] = []
93
93
  for product in products:
94
94
 
95
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
@@ -54,14 +54,14 @@ class FilterLatestIntersect(Crunch):
54
54
  return dateutil.parser.parse(start_date)
55
55
 
56
56
  def proceed(
57
- self, products: List[EOProduct], **search_params: Dict[str, Any]
58
- ) -> List[EOProduct]:
57
+ self, products: list[EOProduct], **search_params: dict[str, Any]
58
+ ) -> list[EOProduct]:
59
59
  """Execute crunch:
60
60
  Filter latest products (the ones with a the highest start date) that intersect search extent.
61
61
 
62
62
  :param products: A list of products resulting from a search
63
63
  :param search_params: Search criteria that must contain ``geometry`` or ``geom`` parameters having value of
64
- type :class:`shapely.geometry.base.BaseGeometry` or ``Dict[str, Any]``
64
+ type :class:`shapely.geometry.base.BaseGeometry` or ``dict[str, Any]``
65
65
  :returns: The filtered products
66
66
  """
67
67
  logger.debug("Start filtering for latest products")
@@ -69,9 +69,9 @@ class FilterLatestIntersect(Crunch):
69
69
  return []
70
70
  # Warning: May crash if startTimeFromAscendingNode is not in the appropriate format
71
71
  products.sort(key=self.sort_product_by_start_date, reverse=True)
72
- filtered: List[EOProduct] = []
72
+ filtered: list[EOProduct] = []
73
73
  add_to_filtered = filtered.append
74
- footprint: Union[Dict[str, Any], BaseGeometry, Any] = search_params.get(
74
+ footprint: Union[dict[str, Any], BaseGeometry, Any] = search_params.get(
75
75
  "geometry"
76
76
  ) or search_params.get("geom")
77
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
@@ -42,7 +42,7 @@ class FilterLatestByName(Crunch):
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
@@ -49,8 +49,8 @@ class FilterOverlap(Crunch):
49
49
  """
50
50
 
51
51
  def proceed(
52
- self, products: List[EOProduct], **search_params: Any
53
- ) -> List[EOProduct]:
52
+ self, products: list[EOProduct], **search_params: Any
53
+ ) -> list[EOProduct]:
54
54
  """Execute crunch: Filter products, retaining only those that are overlapping with the search_extent
55
55
 
56
56
  :param products: A list of products resulting from a search
@@ -58,7 +58,7 @@ class FilterOverlap(Crunch):
58
58
  :returns: The filtered products
59
59
  """
60
60
  logger.debug("Start filtering for overlapping products")
61
- filtered: List[EOProduct] = []
61
+ filtered: list[EOProduct] = []
62
62
  add_to_filtered = filtered.append
63
63
 
64
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
 
@@ -42,8 +42,8 @@ class FilterProperty(Crunch):
42
42
  """
43
43
 
44
44
  def proceed(
45
- self, products: List[EOProduct], **search_params: Any
46
- ) -> List[EOProduct]:
45
+ self, products: list[EOProduct], **search_params: Any
46
+ ) -> list[EOProduct]:
47
47
  """Execute crunch: Filter products, retaining only those that match property filtering
48
48
 
49
49
  :param products: A list of products resulting from a search
@@ -72,16 +72,15 @@ class FilterProperty(Crunch):
72
72
  property_key,
73
73
  property_value,
74
74
  )
75
- filtered: List[EOProduct] = []
75
+ filtered: list[EOProduct] = []
76
76
  add_to_filtered = filtered.append
77
77
 
78
78
  for product in products:
79
79
  if property_key not in product.properties.keys():
80
80
  logger.warning(
81
- "%s not found in product.properties, filtering disabled.",
82
- property_key,
81
+ f"{property_key} not found in {product}.properties, product skipped",
83
82
  )
84
- return products
83
+ continue
85
84
  if operator_method(product.properties[property_key], property_value):
86
85
  add_to_filtered(product)
87
86