eodag 3.2.0__py3-none-any.whl → 3.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
eodag/api/core.py CHANGED
@@ -1599,6 +1599,7 @@ class EODataAccessGateway:
1599
1599
  if kwargs.get("raise_errors"):
1600
1600
  raise
1601
1601
  logger.warning(e)
1602
+ results.errors.append((plugin.provider, e))
1602
1603
  continue
1603
1604
 
1604
1605
  # try using crunch to get unique result
@@ -1622,7 +1623,7 @@ class EODataAccessGateway:
1622
1623
  "Several products found for this id (%s). You may try searching using more selective criteria.",
1623
1624
  results,
1624
1625
  )
1625
- return SearchResult([], 0)
1626
+ return SearchResult([], 0, results.errors)
1626
1627
 
1627
1628
  def _fetch_external_product_type(self, provider: str, product_type: str):
1628
1629
  plugins = self._plugins_manager.get_search_plugins(provider=provider)
@@ -193,8 +193,8 @@ class SearchResult(UserList):
193
193
  <details><summary style='color: grey; font-family: monospace;'>
194
194
  {i}&ensp;
195
195
  {type(p).__name__}(id=<span style='color: black;'>{
196
- p.properties['id']
197
- }</span>, provider={p.provider})
196
+ p.properties["id"]
197
+ }</span>, provider={p.provider})
198
198
  </summary>
199
199
  {p._repr_html_()}
200
200
  </details>
@@ -214,13 +214,12 @@ class SearchResult(UserList):
214
214
  return super().extend(other)
215
215
 
216
216
 
217
- class RawSearchResult(UserList):
217
+ class RawSearchResult(UserList[dict[str, Any]]):
218
218
  """An object representing a collection of raw/unparsed search results obtained from a provider.
219
219
 
220
220
  :param results: A list of raw/unparsed search results
221
221
  """
222
222
 
223
- data: list[Any]
224
223
  query_params: dict[str, Any]
225
224
  product_type_def_params: dict[str, Any]
226
225
 
eodag/config.py CHANGED
@@ -615,6 +615,9 @@ class PluginConfig(yaml.YAMLObject):
615
615
  #: :class:`~eodag.plugins.authentication.token.TokenAuth`
616
616
  #: type of the token
617
617
  token_type: str
618
+ #: :class:`~eodag.plugins.authentication.token.TokenAuth`
619
+ #: key to get the expiration time of the token
620
+ token_expiration_key: str
618
621
  #: :class:`~eodag.plugins.authentication.token_exchange.OIDCTokenExchangeAuth`
619
622
  #: The full :class:`~eodag.plugins.authentication.openid_connect.OIDCAuthorizationCodeFlowAuth` plugin configuration
620
623
  #: used to retrieve subject token
@@ -18,6 +18,7 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import logging
21
+ from datetime import datetime, timedelta
21
22
  from typing import TYPE_CHECKING, Any, Optional
22
23
  from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
23
24
 
@@ -72,6 +73,8 @@ class TokenAuth(Authentication):
72
73
  key to get the access token in the response to the token request
73
74
  * :attr:`~eodag.config.PluginConfig.refresh_token_key` (``str``): key to get the refresh
74
75
  token in the response to the token request
76
+ * :attr:`~eodag.config.PluginConfig.token_expiration_key` (``str``): key to get expiration time of
77
+ the token (given in s)
75
78
  * :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates
76
79
  should be verified in the requests; default: ``True``
77
80
  * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is
@@ -91,6 +94,7 @@ class TokenAuth(Authentication):
91
94
  super(TokenAuth, self).__init__(provider, config)
92
95
  self.token = ""
93
96
  self.refresh_token = ""
97
+ self.token_expiration = datetime.now()
94
98
 
95
99
  def validate_config_credentials(self) -> None:
96
100
  """Validate configured credentials"""
@@ -131,7 +135,11 @@ class TokenAuth(Authentication):
131
135
  def authenticate(self) -> AuthBase:
132
136
  """Authenticate"""
133
137
  self.validate_config_credentials()
134
-
138
+ if self.token and self.token_expiration > datetime.now():
139
+ logger.debug("using existing access token")
140
+ return RequestsTokenAuth(
141
+ self.token, "header", headers=getattr(self.config, "headers", {})
142
+ )
135
143
  s = requests.Session()
136
144
  try:
137
145
  # First get the token
@@ -168,6 +176,12 @@ class TokenAuth(Authentication):
168
176
  self.token = token
169
177
  if getattr(self.config, "refresh_token_key", None):
170
178
  self.refresh_token = response.json()[self.config.refresh_token_key]
179
+ if getattr(self.config, "token_expiration_key", None):
180
+ expiration_time = response.json()[self.config.token_expiration_key]
181
+ self.token_expiration = datetime.now() + timedelta(
182
+ seconds=expiration_time
183
+ )
184
+
171
185
  if not hasattr(self.config, "headers"):
172
186
  raise MisconfiguredError(f"Missing headers configuration for {self}")
173
187
  # Return auth class set with obtained token
@@ -179,6 +193,7 @@ class TokenAuth(Authentication):
179
193
  self,
180
194
  session: requests.Session,
181
195
  ) -> requests.Response:
196
+
182
197
  retry_total = getattr(self.config, "retry_total", REQ_RETRY_TOTAL)
183
198
  retry_backoff_factor = getattr(
184
199
  self.config, "retry_backoff_factor", REQ_RETRY_BACKOFF_FACTOR
@@ -216,7 +216,7 @@ class HTTPDownload(Download):
216
216
  product.properties["storageStatus"] = STAGING_STATUS
217
217
  except RequestException as e:
218
218
  self._check_auth_exception(e)
219
- msg = f'{product.properties["title"]} could not be ordered'
219
+ msg = f"{product.properties['title']} could not be ordered"
220
220
  if e.response is not None and e.response.status_code == 400:
221
221
  raise ValidationError.from_error(e, msg) from e
222
222
  else:
@@ -255,6 +255,16 @@ class HTTPDownload(Download):
255
255
  product.properties.update(
256
256
  {k: v for k, v in properties_update.items() if v != NOT_AVAILABLE}
257
257
  )
258
+ # the job id becomes the product id for EcmwfSearch products
259
+ if "ORDERABLE" in product.properties.get("id", ""):
260
+ product.properties["id"] = product.properties.get(
261
+ "orderId", product.properties["id"]
262
+ )
263
+ product.properties["title"] = (
264
+ (product.product_type or product.provider).upper()
265
+ + "_"
266
+ + product.properties["id"]
267
+ )
258
268
  if "downloadLink" in product.properties:
259
269
  product.remote_location = product.location = product.properties[
260
270
  "downloadLink"
@@ -390,7 +400,10 @@ class HTTPDownload(Download):
390
400
  # success and no need to get status response content
391
401
  skip_parsing_status_response = True
392
402
  except RequestException as e:
393
- msg = f'{product.properties["title"]} order status could not be checked'
403
+ msg = (
404
+ f"{product.properties.get('title') or product.properties.get('id') or product} "
405
+ "order status could not be checked"
406
+ )
394
407
  if e.response is not None and e.response.status_code == 400:
395
408
  raise ValidationError.from_error(e, msg) from e
396
409
  else:
@@ -426,9 +439,14 @@ class HTTPDownload(Download):
426
439
  f"{product.properties['title']} order status: {status_percent}"
427
440
  )
428
441
 
429
- status_message = status_dict.get("message")
442
+ product.properties.update(
443
+ {k: v for k, v in status_dict.items() if v != NOT_AVAILABLE}
444
+ )
445
+
430
446
  product.properties["orderStatus"] = status_dict.get("status")
431
447
 
448
+ status_message = status_dict.get("message")
449
+
432
450
  # handle status error
433
451
  errors: dict[str, Any] = status_config.get("error", {})
434
452
  if errors and errors.items() <= status_dict.items():
@@ -436,13 +454,14 @@ class HTTPDownload(Download):
436
454
  f"Provider {product.provider} returned: {status_dict.get('error_message', status_message)}"
437
455
  )
438
456
 
457
+ product.properties["storageStatus"] = STAGING_STATUS
458
+
439
459
  success_status: dict[str, Any] = status_config.get("success", {}).get("status")
440
460
  # if not success
441
461
  if (success_status and success_status != status_dict.get("status")) or (
442
462
  success_code and success_code != response.status_code
443
463
  ):
444
- error = NotAvailableError(status_message)
445
- raise error
464
+ return None
446
465
 
447
466
  product.properties["storageStatus"] = ONLINE_STATUS
448
467
 
@@ -461,7 +480,11 @@ class HTTPDownload(Download):
461
480
  product.properties["title"],
462
481
  e,
463
482
  )
464
- return None
483
+ msg = f"{product.properties['title']} order status could not be checked"
484
+ if e.response is not None and e.response.status_code == 400:
485
+ raise ValidationError.from_error(e, msg) from e
486
+ else:
487
+ raise DownloadError.from_error(e, msg) from e
465
488
 
466
489
  result_type = config_on_success.get("result_type", "json")
467
490
  result_entry = config_on_success.get("results_entry")
@@ -626,6 +649,8 @@ class HTTPDownload(Download):
626
649
  if fs_path is not None:
627
650
  ext = Path(product.filename).suffix
628
651
  path = Path(fs_path).with_suffix(ext)
652
+ if "ORDERABLE" in path.stem and product.properties.get("title"):
653
+ path = path.with_stem(sanitize(product.properties["title"]))
629
654
 
630
655
  with open(path, "wb") as fhandle:
631
656
  for chunk in chunk_iterator:
@@ -961,17 +986,21 @@ class HTTPDownload(Download):
961
986
  auth = None
962
987
 
963
988
  s = requests.Session()
964
- self.stream = s.request(
965
- req_method,
966
- req_url,
967
- stream=True,
968
- auth=auth,
969
- params=params,
970
- headers=USER_AGENT,
971
- timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
972
- verify=ssl_verify,
973
- **req_kwargs,
974
- )
989
+ try:
990
+ self.stream = s.request(
991
+ req_method,
992
+ req_url,
993
+ stream=True,
994
+ auth=auth,
995
+ params=params,
996
+ headers=USER_AGENT,
997
+ timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
998
+ verify=ssl_verify,
999
+ **req_kwargs,
1000
+ )
1001
+ except requests.exceptions.MissingSchema:
1002
+ # location is not a valid url -> product is not available yet
1003
+ raise NotAvailableError("Product is not available yet")
975
1004
  try:
976
1005
  self.stream.raise_for_status()
977
1006
  except requests.exceptions.Timeout as exc: