eodag 3.1.0b1__py3-none-any.whl → 3.1.0b2__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 (85) hide show
  1. eodag/api/core.py +59 -52
  2. eodag/api/product/_assets.py +5 -5
  3. eodag/api/product/_product.py +27 -12
  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 +62 -74
  10. eodag/api/search_result.py +13 -23
  11. eodag/cli.py +4 -4
  12. eodag/config.py +66 -69
  13. eodag/plugins/apis/base.py +1 -1
  14. eodag/plugins/apis/ecmwf.py +10 -9
  15. eodag/plugins/apis/usgs.py +11 -10
  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 +14 -14
  22. eodag/plugins/authentication/sas_auth.py +4 -4
  23. eodag/plugins/authentication/token.py +7 -7
  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 +4 -4
  32. eodag/plugins/download/aws.py +47 -66
  33. eodag/plugins/download/base.py +8 -17
  34. eodag/plugins/download/creodias_s3.py +2 -2
  35. eodag/plugins/download/http.py +30 -32
  36. eodag/plugins/download/s3rest.py +5 -4
  37. eodag/plugins/manager.py +10 -20
  38. eodag/plugins/search/__init__.py +6 -5
  39. eodag/plugins/search/base.py +35 -40
  40. eodag/plugins/search/build_search_result.py +69 -68
  41. eodag/plugins/search/cop_marine.py +22 -12
  42. eodag/plugins/search/creodias_s3.py +8 -78
  43. eodag/plugins/search/csw.py +11 -11
  44. eodag/plugins/search/data_request_search.py +16 -15
  45. eodag/plugins/search/qssearch.py +56 -52
  46. eodag/plugins/search/stac_list_assets.py +85 -0
  47. eodag/plugins/search/static_stac_search.py +3 -3
  48. eodag/resources/ext_product_types.json +1 -1
  49. eodag/resources/product_types.yml +288 -288
  50. eodag/resources/providers.yml +146 -6
  51. eodag/resources/stac_api.yml +2 -2
  52. eodag/resources/user_conf_template.yml +11 -0
  53. eodag/rest/cache.py +2 -2
  54. eodag/rest/config.py +3 -3
  55. eodag/rest/core.py +24 -24
  56. eodag/rest/errors.py +5 -5
  57. eodag/rest/server.py +3 -11
  58. eodag/rest/stac.py +40 -38
  59. eodag/rest/types/collections_search.py +3 -3
  60. eodag/rest/types/eodag_search.py +23 -23
  61. eodag/rest/types/queryables.py +13 -13
  62. eodag/rest/types/stac_search.py +15 -25
  63. eodag/rest/utils/__init__.py +11 -21
  64. eodag/rest/utils/cql_evaluate.py +6 -6
  65. eodag/rest/utils/rfc3339.py +2 -2
  66. eodag/types/__init__.py +24 -18
  67. eodag/types/bbox.py +2 -2
  68. eodag/types/download_args.py +2 -2
  69. eodag/types/queryables.py +5 -2
  70. eodag/types/search_args.py +4 -4
  71. eodag/types/whoosh.py +1 -3
  72. eodag/utils/__init__.py +81 -40
  73. eodag/utils/exceptions.py +2 -2
  74. eodag/utils/import_system.py +2 -2
  75. eodag/utils/requests.py +2 -2
  76. eodag/utils/rest.py +2 -2
  77. eodag/utils/s3.py +208 -0
  78. eodag/utils/stac_reader.py +10 -10
  79. {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/METADATA +5 -4
  80. eodag-3.1.0b2.dist-info/RECORD +113 -0
  81. {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/entry_points.txt +1 -0
  82. eodag-3.1.0b1.dist-info/RECORD +0 -108
  83. {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/LICENSE +0 -0
  84. {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.dist-info}/WHEEL +0 -0
  85. {eodag-3.1.0b1.dist-info → eodag-3.1.0b2.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, 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,7 +72,7 @@ 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:
@@ -23,21 +23,7 @@ import re
23
23
  from datetime import datetime
24
24
  from itertools import chain
25
25
  from pathlib import Path
26
- from typing import (
27
- TYPE_CHECKING,
28
- Any,
29
- Callable,
30
- Dict,
31
- Iterator,
32
- List,
33
- Match,
34
- Optional,
35
- Set,
36
- Tuple,
37
- TypedDict,
38
- Union,
39
- cast,
40
- )
26
+ from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Union, cast
41
27
 
42
28
  import boto3
43
29
  import requests
@@ -81,6 +67,7 @@ if TYPE_CHECKING:
81
67
  from eodag.api.product import EOProduct
82
68
  from eodag.api.search_result import SearchResult
83
69
  from eodag.config import PluginConfig
70
+ from eodag.types import S3SessionKwargs
84
71
  from eodag.types.download_args import DownloadConf
85
72
  from eodag.utils import DownloadedCallback, Unpack
86
73
 
@@ -230,14 +217,14 @@ class AwsDownload(Download):
230
217
  * :attr:`~eodag.config.PluginConfig.bucket_path_level` (``int``): at which level of the
231
218
  path part of the url the bucket can be found; If no bucket_path_level is given, the bucket
232
219
  is taken from the first element of the netloc part.
233
- * :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type
220
+ * :attr:`~eodag.config.PluginConfig.products` (``dict[str, dict[str, Any]``): product type
234
221
  specific config; the keys are the product types, the values are dictionaries which can contain the keys:
235
222
 
236
223
  * **default_bucket** (``str``): bucket where the product type can be found
237
224
  * **complementary_url_key** (``str``): keys to add additional urls
238
225
  * **build_safe** (``bool``): if a SAFE (Standard Archive Format for Europe) product should
239
226
  be created; used for Sentinel products; default: False
240
- * **fetch_metadata** (``Dict[str, Any]``): config for metadata to be fetched for the SAFE product
227
+ * **fetch_metadata** (``dict[str, Any]``): config for metadata to be fetched for the SAFE product
241
228
 
242
229
  """
243
230
 
@@ -249,7 +236,7 @@ class AwsDownload(Download):
249
236
  def download(
250
237
  self,
251
238
  product: EOProduct,
252
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
239
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
253
240
  progress_callback: Optional[ProgressCallback] = None,
254
241
  wait: float = DEFAULT_DOWNLOAD_WAIT,
255
242
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -407,7 +394,7 @@ class AwsDownload(Download):
407
394
  product: EOProduct,
408
395
  progress_callback: ProgressCallback,
409
396
  **kwargs: Unpack[DownloadConf],
410
- ) -> Tuple[Optional[str], Optional[str]]:
397
+ ) -> tuple[Optional[str], Optional[str]]:
411
398
  """
412
399
  preparation for the download:
413
400
  - check if file was already downloaded
@@ -480,7 +467,7 @@ class AwsDownload(Download):
480
467
  product: EOProduct,
481
468
  asset_filter: Optional[str] = None,
482
469
  ignore_assets: Optional[bool] = False,
483
- ) -> List[Tuple[str, Optional[str]]]:
470
+ ) -> list[tuple[str, Optional[str]]]:
484
471
  """
485
472
  Retrieves the bucket names and path prefixes for the assets
486
473
 
@@ -522,9 +509,9 @@ class AwsDownload(Download):
522
509
 
523
510
  def _do_authentication(
524
511
  self,
525
- bucket_names_and_prefixes: List[Tuple[str, Optional[str]]],
526
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
527
- ) -> Tuple[Dict[str, Any], ResourceCollection]:
512
+ bucket_names_and_prefixes: list[tuple[str, Optional[str]]],
513
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
514
+ ) -> tuple[dict[str, Any], ResourceCollection]:
528
515
  """
529
516
  authenticates with s3 and retrieves the available objects
530
517
  raises an error when authentication is not possible
@@ -538,8 +525,8 @@ class AwsDownload(Download):
538
525
  )
539
526
  if auth is None:
540
527
  auth = {}
541
- authenticated_objects: Dict[str, Any] = {}
542
- auth_error_messages: Set[str] = set()
528
+ authenticated_objects: dict[str, Any] = {}
529
+ auth_error_messages: set[str] = set()
543
530
  for _, pack in enumerate(bucket_names_and_prefixes):
544
531
  try:
545
532
  bucket_name, prefix = pack
@@ -591,12 +578,12 @@ class AwsDownload(Download):
591
578
 
592
579
  def _get_unique_products(
593
580
  self,
594
- bucket_names_and_prefixes: List[Tuple[str, Optional[str]]],
595
- authenticated_objects: Dict[str, Any],
581
+ bucket_names_and_prefixes: list[tuple[str, Optional[str]]],
582
+ authenticated_objects: dict[str, Any],
596
583
  asset_filter: Optional[str],
597
584
  ignore_assets: bool,
598
585
  product: EOProduct,
599
- ) -> Set[Any]:
586
+ ) -> set[Any]:
600
587
  """
601
588
  retrieve unique product chunks based on authenticated objects and asset filters
602
589
  :param bucket_names_and_prefixes: list of bucket names and corresponding path prefixes
@@ -606,7 +593,7 @@ class AwsDownload(Download):
606
593
  :param product: product that shall be downloaded
607
594
  :return: set of product chunks that can be downloaded
608
595
  """
609
- product_chunks: List[Any] = []
596
+ product_chunks: list[Any] = []
610
597
  for bucket_name, prefix in bucket_names_and_prefixes:
611
598
  # unauthenticated items filtered out
612
599
  if bucket_name in authenticated_objects.keys():
@@ -637,7 +624,7 @@ class AwsDownload(Download):
637
624
 
638
625
  def _raise_if_auth_error(self, exception: ClientError) -> None:
639
626
  """Raises an error if given exception is an authentication error"""
640
- err = cast(Dict[str, str], exception.response["Error"])
627
+ err = cast(dict[str, str], exception.response["Error"])
641
628
  if err["Code"] in AWS_AUTH_ERROR_MESSAGES and "key" in err["Message"].lower():
642
629
  raise AuthenticationError(
643
630
  f"Please check your credentials for {self.provider}.",
@@ -648,7 +635,7 @@ class AwsDownload(Download):
648
635
  def _stream_download_dict(
649
636
  self,
650
637
  product: EOProduct,
651
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
638
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
652
639
  progress_callback: Optional[ProgressCallback] = None,
653
640
  wait: float = DEFAULT_DOWNLOAD_WAIT,
654
641
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -754,11 +741,11 @@ class AwsDownload(Download):
754
741
 
755
742
  def _stream_download(
756
743
  self,
757
- unique_product_chunks: Set[Any],
744
+ unique_product_chunks: set[Any],
758
745
  product: EOProduct,
759
746
  build_safe: bool,
760
747
  progress_callback: ProgressCallback,
761
- assets_values: List[Dict[str, Any]],
748
+ assets_values: list[dict[str, Any]],
762
749
  ) -> Iterator[Any]:
763
750
  """Yield product data chunks"""
764
751
 
@@ -829,7 +816,7 @@ class AwsDownload(Download):
829
816
  )
830
817
 
831
818
  def _get_commonpath(
832
- self, product: EOProduct, product_chunks: Set[Any], build_safe: bool
819
+ self, product: EOProduct, product_chunks: set[Any], build_safe: bool
833
820
  ) -> str:
834
821
  chunk_paths = []
835
822
  for product_chunk in product_chunks:
@@ -839,8 +826,8 @@ class AwsDownload(Download):
839
826
  return os.path.commonpath(chunk_paths)
840
827
 
841
828
  def get_rio_env(
842
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
843
- ) -> Dict[str, Any]:
829
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
830
+ ) -> dict[str, Any]:
844
831
  """Get rasterio environment variables needed for data access authentication.
845
832
 
846
833
  :param bucket_name: Bucket containg objects
@@ -848,23 +835,26 @@ class AwsDownload(Download):
848
835
  :param auth_dict: Dictionary containing authentication keys
849
836
  :returns: The rasterio environement variables
850
837
  """
851
- if self.s3_session is not None:
852
- if self.requester_pays:
853
- return {"session": self.s3_session, "requester_pays": True}
854
- else:
855
- return {"session": self.s3_session}
838
+ rio_env_kwargs = {}
839
+ if endpoint_url := getattr(self.config, "s3_endpoint", None):
840
+ rio_env_kwargs["endpoint_url"] = endpoint_url.split("://")[-1]
841
+ rio_env_kwargs |= auth_dict
842
+
843
+ if self.s3_session is None:
844
+ _ = self.get_authenticated_objects(bucket_name, prefix, auth_dict)
856
845
 
857
- _ = self.get_authenticated_objects(bucket_name, prefix, auth_dict)
858
846
  if self.s3_session is not None:
859
847
  if self.requester_pays:
860
- return {"session": self.s3_session, "requester_pays": True}
861
- else:
862
- return {"session": self.s3_session}
848
+ rio_env_kwargs["requester_pays"] = True
849
+ return {
850
+ "session": self.s3_session,
851
+ **rio_env_kwargs,
852
+ }
863
853
  else:
864
- return {"aws_unsigned": True}
854
+ return {"aws_unsigned": True, **rio_env_kwargs}
865
855
 
866
856
  def get_authenticated_objects(
867
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
857
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
868
858
  ) -> ResourceCollection:
869
859
  """Get boto3 authenticated objects for the given bucket using
870
860
  the most adapted auth strategy.
@@ -876,8 +866,8 @@ class AwsDownload(Download):
876
866
  :param auth_dict: Dictionary containing authentication keys
877
867
  :returns: The boto3 authenticated objects
878
868
  """
879
- auth_methods: List[
880
- Callable[[str, str, Dict[str, str]], Optional[ResourceCollection]]
869
+ auth_methods: list[
870
+ Callable[[str, str, S3SessionKwargs], Optional[ResourceCollection]]
881
871
  ] = [
882
872
  self._get_authenticated_objects_unsigned,
883
873
  self._get_authenticated_objects_from_auth_profile,
@@ -912,7 +902,7 @@ class AwsDownload(Download):
912
902
  )
913
903
 
914
904
  def _get_authenticated_objects_unsigned(
915
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
905
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
916
906
  ) -> Optional[ResourceCollection]:
917
907
  """Auth strategy using no-sign-request"""
918
908
 
@@ -927,7 +917,7 @@ class AwsDownload(Download):
927
917
  return objects
928
918
 
929
919
  def _get_authenticated_objects_from_auth_profile(
930
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
920
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
931
921
  ) -> Optional[ResourceCollection]:
932
922
  """Auth strategy using RequestPayer=requester and ``aws_profile`` from provided credentials"""
933
923
 
@@ -950,21 +940,12 @@ class AwsDownload(Download):
950
940
  return None
951
941
 
952
942
  def _get_authenticated_objects_from_auth_keys(
953
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
943
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
954
944
  ) -> Optional[ResourceCollection]:
955
945
  """Auth strategy using RequestPayer=requester and ``aws_access_key_id``/``aws_secret_access_key``
956
946
  from provided credentials"""
957
947
 
958
948
  if all(k in auth_dict for k in ("aws_access_key_id", "aws_secret_access_key")):
959
- S3SessionKwargs = TypedDict(
960
- "S3SessionKwargs",
961
- {
962
- "aws_access_key_id": str,
963
- "aws_secret_access_key": str,
964
- "aws_session_token": str,
965
- },
966
- total=False,
967
- )
968
949
  s3_session_kwargs: S3SessionKwargs = {
969
950
  "aws_access_key_id": auth_dict["aws_access_key_id"],
970
951
  "aws_secret_access_key": auth_dict["aws_secret_access_key"],
@@ -989,7 +970,7 @@ class AwsDownload(Download):
989
970
  return None
990
971
 
991
972
  def _get_authenticated_objects_from_env(
992
- self, bucket_name: str, prefix: str, auth_dict: Dict[str, str]
973
+ self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
993
974
  ) -> Optional[ResourceCollection]:
994
975
  """Auth strategy using RequestPayer=requester and current environment"""
995
976
 
@@ -1009,7 +990,7 @@ class AwsDownload(Download):
1009
990
 
1010
991
  def get_product_bucket_name_and_prefix(
1011
992
  self, product: EOProduct, url: Optional[str] = None
1012
- ) -> Tuple[str, Optional[str]]:
993
+ ) -> tuple[str, Optional[str]]:
1013
994
  """Extract bucket name and prefix from product URL
1014
995
 
1015
996
  :param product: The EO product to download
@@ -1140,7 +1121,7 @@ class AwsDownload(Download):
1140
1121
  s1_title_suffix: Optional[str] = None
1141
1122
  # S2 common
1142
1123
  if product.product_type and "S2_MSI" in product.product_type:
1143
- title_search: Optional[Match[str]] = re.search(
1124
+ title_search: Optional[re.Match[str]] = re.search(
1144
1125
  r"^\w+_\w+_(\w+)_(\w+)_(\w+)_(\w+)_(\w+)$",
1145
1126
  product.properties["title"],
1146
1127
  )
@@ -1326,13 +1307,13 @@ class AwsDownload(Download):
1326
1307
  def download_all(
1327
1308
  self,
1328
1309
  products: SearchResult,
1329
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
1310
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
1330
1311
  downloaded_callback: Optional[DownloadedCallback] = None,
1331
1312
  progress_callback: Optional[ProgressCallback] = None,
1332
1313
  wait: float = DEFAULT_DOWNLOAD_WAIT,
1333
1314
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
1334
1315
  **kwargs: Unpack[DownloadConf],
1335
- ) -> List[str]:
1316
+ ) -> list[str]:
1336
1317
  """
1337
1318
  download_all using parent (base plugin) method
1338
1319
  """
@@ -26,17 +26,7 @@ import tempfile
26
26
  import zipfile
27
27
  from datetime import datetime, timedelta
28
28
  from time import sleep
29
- from typing import (
30
- TYPE_CHECKING,
31
- Any,
32
- Callable,
33
- Dict,
34
- List,
35
- Optional,
36
- Tuple,
37
- TypeVar,
38
- Union,
39
- )
29
+ from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
40
30
 
41
31
  from eodag.plugins.base import PluginTopic
42
32
  from eodag.utils import (
@@ -60,6 +50,7 @@ if TYPE_CHECKING:
60
50
  from eodag.api.product import EOProduct
61
51
  from eodag.api.search_result import SearchResult
62
52
  from eodag.config import PluginConfig
53
+ from eodag.types import S3SessionKwargs
63
54
  from eodag.types.download_args import DownloadConf
64
55
  from eodag.utils import DownloadedCallback, Unpack
65
56
 
@@ -110,7 +101,7 @@ class Download(PluginTopic):
110
101
  def download(
111
102
  self,
112
103
  product: EOProduct,
113
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
104
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
114
105
  progress_callback: Optional[ProgressCallback] = None,
115
106
  wait: float = DEFAULT_DOWNLOAD_WAIT,
116
107
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -140,7 +131,7 @@ class Download(PluginTopic):
140
131
  def _stream_download_dict(
141
132
  self,
142
133
  product: EOProduct,
143
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
134
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
144
135
  progress_callback: Optional[ProgressCallback] = None,
145
136
  wait: float = DEFAULT_DOWNLOAD_WAIT,
146
137
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -170,7 +161,7 @@ class Download(PluginTopic):
170
161
  product: EOProduct,
171
162
  progress_callback: Optional[ProgressCallback] = None,
172
163
  **kwargs: Unpack[DownloadConf],
173
- ) -> Tuple[Optional[str], Optional[str]]:
164
+ ) -> tuple[Optional[str], Optional[str]]:
174
165
  """Check if file has already been downloaded, and prepare product download
175
166
 
176
167
  :param product: The EO product to download
@@ -439,13 +430,13 @@ class Download(PluginTopic):
439
430
  def download_all(
440
431
  self,
441
432
  products: SearchResult,
442
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
433
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
443
434
  downloaded_callback: Optional[DownloadedCallback] = None,
444
435
  progress_callback: Optional[ProgressCallback] = None,
445
436
  wait: float = DEFAULT_DOWNLOAD_WAIT,
446
437
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
447
438
  **kwargs: Unpack[DownloadConf],
448
- ) -> List[str]:
439
+ ) -> list[str]:
449
440
  """
450
441
  Base download_all method.
451
442
 
@@ -474,7 +465,7 @@ class Download(PluginTopic):
474
465
  # Products are going to be removed one by one from this sequence once
475
466
  # downloaded.
476
467
  products = products[:]
477
- paths: List[str] = []
468
+ paths: list[str] = []
478
469
  # initiate retry loop
479
470
  start_time = datetime.now()
480
471
  stop_time = start_time + timedelta(minutes=timeout)
@@ -15,7 +15,7 @@
15
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
- from typing import List, Optional, Tuple
18
+ from typing import Optional
19
19
 
20
20
  import boto3
21
21
  from botocore.exceptions import ClientError
@@ -73,7 +73,7 @@ class CreodiasS3Download(AwsDownload):
73
73
  product: EOProduct,
74
74
  asset_filter: Optional[str] = None,
75
75
  ignore_assets: Optional[bool] = False,
76
- ) -> List[Tuple[str, Optional[str]]]:
76
+ ) -> list[tuple[str, Optional[str]]]:
77
77
  """
78
78
  Retrieves the bucket names and path prefixes for the assets
79
79
 
@@ -28,17 +28,7 @@ from email.message import Message
28
28
  from itertools import chain
29
29
  from json import JSONDecodeError
30
30
  from pathlib import Path
31
- from typing import (
32
- TYPE_CHECKING,
33
- Any,
34
- Dict,
35
- Iterator,
36
- List,
37
- Optional,
38
- TypedDict,
39
- Union,
40
- cast,
41
- )
31
+ from typing import TYPE_CHECKING, Any, Iterator, Optional, TypedDict, Union, cast
42
32
  from urllib.parse import parse_qs, urlparse
43
33
 
44
34
  import geojson
@@ -72,6 +62,7 @@ from eodag.utils import (
72
62
  guess_file_type,
73
63
  parse_header,
74
64
  path_to_uri,
65
+ rename_with_version,
75
66
  sanitize,
76
67
  string_to_jsonpath,
77
68
  uri_to_path,
@@ -91,6 +82,7 @@ if TYPE_CHECKING:
91
82
  from eodag.api.product import Asset, EOProduct # type: ignore
92
83
  from eodag.api.search_result import SearchResult
93
84
  from eodag.config import PluginConfig
85
+ from eodag.types import S3SessionKwargs
94
86
  from eodag.types.download_args import DownloadConf
95
87
  from eodag.utils import DownloadedCallback, Unpack
96
88
 
@@ -111,7 +103,7 @@ class HTTPDownload(Download):
111
103
  extracted; default: ``True``
112
104
  * :attr:`~eodag.config.PluginConfig.auth_error_code` (``int``): which error code is returned in case of an
113
105
  authentication error
114
- * :attr:`~eodag.config.PluginConfig.dl_url_params` (``Dict[str, Any]``): parameters to be
106
+ * :attr:`~eodag.config.PluginConfig.dl_url_params` (``dict[str, Any]``): parameters to be
115
107
  added to the query params of the request
116
108
  * :attr:`~eodag.config.PluginConfig.archive_depth` (``int``): level in extracted path tree where to find data;
117
109
  default: ``1``
@@ -130,7 +122,7 @@ class HTTPDownload(Download):
130
122
  the search plugin used for the provider; default: ``False``
131
123
  * :attr:`~eodag.config.PluginConfig.order_method` (``str``): HTTP request method for the order request (``GET``
132
124
  or ``POST``); default: ``GET``
133
- * :attr:`~eodag.config.PluginConfig.order_headers` (``[Dict[str, str]]``): headers to be added to the order
125
+ * :attr:`~eodag.config.PluginConfig.order_headers` (``[dict[str, str]]``): headers to be added to the order
134
126
  request
135
127
  * :attr:`~eodag.config.PluginConfig.order_on_response` (:class:`~eodag.config.PluginConfig.OrderOnResponse`):
136
128
  a typed dictionary containing the key ``metadata_mapping`` which can be used to add new product properties
@@ -138,7 +130,7 @@ class HTTPDownload(Download):
138
130
  * :attr:`~eodag.config.PluginConfig.order_status` (:class:`~eodag.config.PluginConfig.OrderStatus`):
139
131
  configuration to handle the order status; contains information which method to use, how the response data is
140
132
  interpreted, which status corresponds to success, ordered and error and what should be done on success.
141
- * :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type specific config; the
133
+ * :attr:`~eodag.config.PluginConfig.products` (``dict[str, dict[str, Any]``): product type specific config; the
142
134
  keys are the product types, the values are dictionaries which can contain the key
143
135
  :attr:`~eodag.config.PluginConfig.extract` to overwrite the provider config for a specific product type
144
136
 
@@ -152,7 +144,7 @@ class HTTPDownload(Download):
152
144
  product: EOProduct,
153
145
  auth: Optional[AuthBase] = None,
154
146
  **kwargs: Unpack[DownloadConf],
155
- ) -> Optional[Dict[str, Any]]:
147
+ ) -> Optional[dict[str, Any]]:
156
148
  """Send product order request.
157
149
 
158
150
  It will be executed once before the download retry loop, if the product is OFFLINE
@@ -184,7 +176,7 @@ class HTTPDownload(Download):
184
176
  ssl_verify = getattr(self.config, "ssl_verify", True)
185
177
  timeout = getattr(self.config, "timeout", HTTP_REQ_TIMEOUT)
186
178
  OrderKwargs = TypedDict(
187
- "OrderKwargs", {"json": Dict[str, Union[Any, List[str]]]}, total=False
179
+ "OrderKwargs", {"json": dict[str, Union[Any, list[str]]]}, total=False
188
180
  )
189
181
  order_kwargs: OrderKwargs = {}
190
182
  if order_method == "POST":
@@ -236,7 +228,7 @@ class HTTPDownload(Download):
236
228
 
237
229
  def order_response_process(
238
230
  self, response: Response, product: EOProduct
239
- ) -> Optional[Dict[str, Any]]:
231
+ ) -> Optional[dict[str, Any]]:
240
232
  """Process order response
241
233
 
242
234
  :param response: The order response
@@ -300,7 +292,7 @@ class HTTPDownload(Download):
300
292
  def _request(
301
293
  url: str,
302
294
  method: str = "GET",
303
- headers: Optional[Dict[str, Any]] = None,
295
+ headers: Optional[dict[str, Any]] = None,
304
296
  json: Optional[Any] = None,
305
297
  timeout: int = HTTP_REQ_TIMEOUT,
306
298
  ) -> Response:
@@ -336,7 +328,7 @@ class HTTPDownload(Download):
336
328
  except requests.exceptions.Timeout as exc:
337
329
  raise TimeOutError(exc, timeout=timeout) from exc
338
330
 
339
- status_request: Dict[str, Any] = status_config.get("request", {})
331
+ status_request: dict[str, Any] = status_config.get("request", {})
340
332
  status_request_method = str(status_request.get("method", "GET")).upper()
341
333
 
342
334
  if status_request_method == "POST":
@@ -353,8 +345,8 @@ class HTTPDownload(Download):
353
345
 
354
346
  # check header for success before full status request
355
347
  skip_parsing_status_response = False
356
- status_dict: Dict[str, Any] = {}
357
- config_on_success: Dict[str, Any] = status_config.get("on_success", {})
348
+ status_dict: dict[str, Any] = {}
349
+ config_on_success: dict[str, Any] = status_config.get("on_success", {})
358
350
  on_success_mm = config_on_success.get("metadata_mapping", {})
359
351
 
360
352
  status_response_content_needed = (
@@ -438,13 +430,13 @@ class HTTPDownload(Download):
438
430
  product.properties["orderStatus"] = status_dict.get("status")
439
431
 
440
432
  # handle status error
441
- errors: Dict[str, Any] = status_config.get("error", {})
433
+ errors: dict[str, Any] = status_config.get("error", {})
442
434
  if errors and errors.items() <= status_dict.items():
443
435
  raise DownloadError(
444
436
  f"Provider {product.provider} returned: {status_dict.get('error_message', status_message)}"
445
437
  )
446
438
 
447
- success_status: Dict[str, Any] = status_config.get("success", {}).get("status")
439
+ success_status: dict[str, Any] = status_config.get("success", {}).get("status")
448
440
  # if not success
449
441
  if (success_status and success_status != status_dict.get("status")) or (
450
442
  success_code and success_code != response.status_code
@@ -562,7 +554,7 @@ class HTTPDownload(Download):
562
554
  def download(
563
555
  self,
564
556
  product: EOProduct,
565
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
557
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
566
558
  progress_callback: Optional[ProgressCallback] = None,
567
559
  wait: float = DEFAULT_DOWNLOAD_WAIT,
568
560
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -666,6 +658,8 @@ class HTTPDownload(Download):
666
658
  os.path.dirname(path),
667
659
  sanitize(product.properties["title"]),
668
660
  )
661
+ if os.path.isfile(new_fs_path):
662
+ rename_with_version(new_fs_path)
669
663
  if not os.path.isdir(new_fs_path):
670
664
  os.makedirs(new_fs_path)
671
665
  shutil.move(path, new_fs_path)
@@ -719,7 +713,7 @@ class HTTPDownload(Download):
719
713
  def _stream_download_dict(
720
714
  self,
721
715
  product: EOProduct,
722
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
716
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
723
717
  progress_callback: Optional[ProgressCallback] = None,
724
718
  wait: float = DEFAULT_DOWNLOAD_WAIT,
725
719
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
@@ -778,13 +772,17 @@ class HTTPDownload(Download):
778
772
  )
779
773
 
780
774
  else:
775
+ # get first chunk to check if it does not contain an error (if it does, that error will be raised)
776
+ first_chunks_tuple = next(chunks_tuples)
781
777
  outputs_filename = (
782
778
  sanitize(product.properties["title"])
783
779
  if "title" in product.properties
784
780
  else sanitize(product.properties.get("id", "download"))
785
781
  )
786
782
  return StreamResponse(
787
- content=stream_zip(chunks_tuples),
783
+ content=stream_zip(
784
+ chain(iter([first_chunks_tuple]), chunks_tuples)
785
+ ),
788
786
  media_type="application/zip",
789
787
  headers={
790
788
  "content-disposition": f"attachment; filename={outputs_filename}.zip",
@@ -888,7 +886,7 @@ class HTTPDownload(Download):
888
886
  def order(
889
887
  self,
890
888
  product: EOProduct,
891
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
889
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
892
890
  wait: float = DEFAULT_DOWNLOAD_WAIT,
893
891
  timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
894
892
  ) -> None:
@@ -951,7 +949,7 @@ class HTTPDownload(Download):
951
949
  if not query_dict and parts.query:
952
950
  query_dict = geojson.loads(parts.query)
953
951
  req_url = parts._replace(query="").geturl()
954
- req_kwargs: Dict[str, Any] = {"json": query_dict} if query_dict else {}
952
+ req_kwargs: dict[str, Any] = {"json": query_dict} if query_dict else {}
955
953
  else:
956
954
  req_url = url
957
955
  req_kwargs = {}
@@ -1019,7 +1017,7 @@ class HTTPDownload(Download):
1019
1017
  product: EOProduct,
1020
1018
  auth: Optional[AuthBase] = None,
1021
1019
  progress_callback: Optional[ProgressCallback] = None,
1022
- assets_values: List[Asset] = [],
1020
+ assets_values: list[Asset] = [],
1023
1021
  **kwargs: Unpack[DownloadConf],
1024
1022
  ) -> Iterator[Any]:
1025
1023
  if progress_callback is None:
@@ -1289,9 +1287,9 @@ class HTTPDownload(Download):
1289
1287
 
1290
1288
  def _get_asset_sizes(
1291
1289
  self,
1292
- assets_values: List[Asset],
1290
+ assets_values: list[Asset],
1293
1291
  auth: Optional[AuthBase],
1294
- params: Optional[Dict[str, str]],
1292
+ params: Optional[dict[str, str]],
1295
1293
  zipped: bool = False,
1296
1294
  ) -> int:
1297
1295
  total_size = 0
@@ -1364,7 +1362,7 @@ class HTTPDownload(Download):
1364
1362
  def download_all(
1365
1363
  self,
1366
1364
  products: SearchResult,
1367
- auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
1365
+ auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
1368
1366
  downloaded_callback: Optional[DownloadedCallback] = None,
1369
1367
  progress_callback: Optional[ProgressCallback] = None,
1370
1368
  wait: float = DEFAULT_DOWNLOAD_WAIT,