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.
- eodag/api/core.py +174 -138
- eodag/api/product/_assets.py +44 -15
- eodag/api/product/_product.py +58 -47
- eodag/api/product/drivers/__init__.py +81 -4
- eodag/api/product/drivers/base.py +65 -4
- eodag/api/product/drivers/generic.py +65 -0
- eodag/api/product/drivers/sentinel1.py +97 -0
- eodag/api/product/drivers/sentinel2.py +95 -0
- eodag/api/product/metadata_mapping.py +117 -90
- eodag/api/search_result.py +13 -23
- eodag/cli.py +26 -5
- eodag/config.py +86 -92
- eodag/plugins/apis/base.py +1 -1
- eodag/plugins/apis/ecmwf.py +42 -22
- eodag/plugins/apis/usgs.py +17 -16
- eodag/plugins/authentication/aws_auth.py +16 -13
- eodag/plugins/authentication/base.py +5 -3
- eodag/plugins/authentication/header.py +3 -3
- eodag/plugins/authentication/keycloak.py +4 -4
- eodag/plugins/authentication/oauth.py +7 -3
- eodag/plugins/authentication/openid_connect.py +22 -16
- eodag/plugins/authentication/sas_auth.py +4 -4
- eodag/plugins/authentication/token.py +41 -10
- eodag/plugins/authentication/token_exchange.py +1 -1
- eodag/plugins/base.py +4 -4
- eodag/plugins/crunch/base.py +4 -4
- eodag/plugins/crunch/filter_date.py +4 -4
- eodag/plugins/crunch/filter_latest_intersect.py +6 -6
- eodag/plugins/crunch/filter_latest_tpl_name.py +7 -7
- eodag/plugins/crunch/filter_overlap.py +4 -4
- eodag/plugins/crunch/filter_property.py +6 -7
- eodag/plugins/download/aws.py +146 -87
- eodag/plugins/download/base.py +38 -56
- eodag/plugins/download/creodias_s3.py +29 -0
- eodag/plugins/download/http.py +173 -183
- eodag/plugins/download/s3rest.py +10 -11
- eodag/plugins/manager.py +10 -20
- eodag/plugins/search/__init__.py +6 -5
- eodag/plugins/search/base.py +90 -46
- eodag/plugins/search/build_search_result.py +1048 -361
- eodag/plugins/search/cop_marine.py +22 -12
- eodag/plugins/search/creodias_s3.py +9 -73
- eodag/plugins/search/csw.py +11 -11
- eodag/plugins/search/data_request_search.py +19 -18
- eodag/plugins/search/qssearch.py +99 -258
- eodag/plugins/search/stac_list_assets.py +85 -0
- eodag/plugins/search/static_stac_search.py +4 -4
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +1134 -325
- eodag/resources/providers.yml +906 -2006
- eodag/resources/stac_api.yml +2 -2
- eodag/resources/user_conf_template.yml +10 -9
- eodag/rest/cache.py +2 -2
- eodag/rest/config.py +3 -3
- eodag/rest/core.py +112 -82
- eodag/rest/errors.py +5 -5
- eodag/rest/server.py +33 -14
- eodag/rest/stac.py +41 -38
- eodag/rest/types/collections_search.py +3 -3
- eodag/rest/types/eodag_search.py +29 -23
- eodag/rest/types/queryables.py +42 -31
- eodag/rest/types/stac_search.py +15 -25
- eodag/rest/utils/__init__.py +14 -21
- eodag/rest/utils/cql_evaluate.py +6 -6
- eodag/rest/utils/rfc3339.py +2 -2
- eodag/types/__init__.py +141 -32
- eodag/types/bbox.py +2 -2
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +183 -72
- eodag/types/search_args.py +4 -4
- eodag/types/whoosh.py +127 -3
- eodag/utils/__init__.py +153 -51
- eodag/utils/exceptions.py +28 -21
- eodag/utils/import_system.py +2 -2
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +13 -13
- eodag/utils/rest.py +2 -2
- eodag/utils/s3.py +231 -0
- eodag/utils/stac_reader.py +10 -10
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/METADATA +77 -76
- eodag-3.1.0.dist-info/RECORD +113 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/WHEEL +1 -1
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/entry_points.txt +4 -2
- eodag/utils/constraints.py +0 -244
- eodag-3.0.1.dist-info/RECORD +0 -109
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/LICENSE +0 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0.dist-info}/top_level.txt +0 -0
eodag/plugins/download/aws.py
CHANGED
|
@@ -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
|
|
@@ -74,6 +60,7 @@ from eodag.utils.exceptions import (
|
|
|
74
60
|
NotAvailableError,
|
|
75
61
|
TimeOutError,
|
|
76
62
|
)
|
|
63
|
+
from eodag.utils.s3 import open_s3_zipped_object
|
|
77
64
|
|
|
78
65
|
if TYPE_CHECKING:
|
|
79
66
|
from boto3.resources.collection import ResourceCollection
|
|
@@ -81,6 +68,7 @@ if TYPE_CHECKING:
|
|
|
81
68
|
from eodag.api.product import EOProduct
|
|
82
69
|
from eodag.api.search_result import SearchResult
|
|
83
70
|
from eodag.config import PluginConfig
|
|
71
|
+
from eodag.types import S3SessionKwargs
|
|
84
72
|
from eodag.types.download_args import DownloadConf
|
|
85
73
|
from eodag.utils import DownloadedCallback, Unpack
|
|
86
74
|
|
|
@@ -208,6 +196,7 @@ AWS_AUTH_ERROR_MESSAGES = [
|
|
|
208
196
|
"AccessDenied",
|
|
209
197
|
"InvalidAccessKeyId",
|
|
210
198
|
"SignatureDoesNotMatch",
|
|
199
|
+
"InvalidRequest",
|
|
211
200
|
]
|
|
212
201
|
|
|
213
202
|
|
|
@@ -230,14 +219,14 @@ class AwsDownload(Download):
|
|
|
230
219
|
* :attr:`~eodag.config.PluginConfig.bucket_path_level` (``int``): at which level of the
|
|
231
220
|
path part of the url the bucket can be found; If no bucket_path_level is given, the bucket
|
|
232
221
|
is taken from the first element of the netloc part.
|
|
233
|
-
* :attr:`~eodag.config.PluginConfig.products` (``
|
|
222
|
+
* :attr:`~eodag.config.PluginConfig.products` (``dict[str, dict[str, Any]``): product type
|
|
234
223
|
specific config; the keys are the product types, the values are dictionaries which can contain the keys:
|
|
235
224
|
|
|
236
225
|
* **default_bucket** (``str``): bucket where the product type can be found
|
|
237
226
|
* **complementary_url_key** (``str``): keys to add additional urls
|
|
238
227
|
* **build_safe** (``bool``): if a SAFE (Standard Archive Format for Europe) product should
|
|
239
228
|
be created; used for Sentinel products; default: False
|
|
240
|
-
* **fetch_metadata** (``
|
|
229
|
+
* **fetch_metadata** (``dict[str, Any]``): config for metadata to be fetched for the SAFE product
|
|
241
230
|
|
|
242
231
|
"""
|
|
243
232
|
|
|
@@ -245,14 +234,15 @@ class AwsDownload(Download):
|
|
|
245
234
|
super(AwsDownload, self).__init__(provider, config)
|
|
246
235
|
self.requester_pays = getattr(self.config, "requester_pays", False)
|
|
247
236
|
self.s3_session: Optional[boto3.session.Session] = None
|
|
237
|
+
self.s3_resource: Optional[boto3.resources.base.ServiceResource] = None
|
|
248
238
|
|
|
249
239
|
def download(
|
|
250
240
|
self,
|
|
251
241
|
product: EOProduct,
|
|
252
|
-
auth: Optional[Union[AuthBase,
|
|
242
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
253
243
|
progress_callback: Optional[ProgressCallback] = None,
|
|
254
|
-
wait:
|
|
255
|
-
timeout:
|
|
244
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
245
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
256
246
|
**kwargs: Unpack[DownloadConf],
|
|
257
247
|
) -> Optional[str]:
|
|
258
248
|
"""Download method for AWS S3 API.
|
|
@@ -338,19 +328,32 @@ class AwsDownload(Download):
|
|
|
338
328
|
bucket_names_and_prefixes, auth
|
|
339
329
|
)
|
|
340
330
|
|
|
331
|
+
# files in zip
|
|
332
|
+
updated_bucket_names_and_prefixes = self._download_file_in_zip(
|
|
333
|
+
product, bucket_names_and_prefixes, product_local_path, progress_callback
|
|
334
|
+
)
|
|
335
|
+
# prevent nothing-to-download errors if download was performed in zip
|
|
336
|
+
raise_error = (
|
|
337
|
+
False
|
|
338
|
+
if len(updated_bucket_names_and_prefixes) != len(bucket_names_and_prefixes)
|
|
339
|
+
else True
|
|
340
|
+
)
|
|
341
|
+
|
|
341
342
|
# downloadable files
|
|
342
343
|
unique_product_chunks = self._get_unique_products(
|
|
343
|
-
|
|
344
|
+
updated_bucket_names_and_prefixes,
|
|
344
345
|
authenticated_objects,
|
|
345
346
|
asset_filter,
|
|
346
347
|
ignore_assets,
|
|
347
348
|
product,
|
|
349
|
+
raise_error=raise_error,
|
|
348
350
|
)
|
|
349
351
|
|
|
350
352
|
total_size = sum([p.size for p in unique_product_chunks]) or None
|
|
351
353
|
|
|
352
354
|
# download
|
|
353
|
-
|
|
355
|
+
if len(unique_product_chunks) > 0:
|
|
356
|
+
progress_callback.reset(total=total_size)
|
|
354
357
|
try:
|
|
355
358
|
for product_chunk in unique_product_chunks:
|
|
356
359
|
try:
|
|
@@ -402,17 +405,65 @@ class AwsDownload(Download):
|
|
|
402
405
|
|
|
403
406
|
return product_local_path
|
|
404
407
|
|
|
408
|
+
def _download_file_in_zip(
|
|
409
|
+
self, product, bucket_names_and_prefixes, product_local_path, progress_callback
|
|
410
|
+
):
|
|
411
|
+
"""
|
|
412
|
+
Download file in zip from a prefix like `foo/bar.zip!file.txt`
|
|
413
|
+
"""
|
|
414
|
+
if self.s3_resource is None:
|
|
415
|
+
logger.debug("Cannot check files in s3 zip without s3 resource")
|
|
416
|
+
return bucket_names_and_prefixes
|
|
417
|
+
|
|
418
|
+
s3_client = self.s3_resource.meta.client
|
|
419
|
+
|
|
420
|
+
downloaded = []
|
|
421
|
+
for i, pack in enumerate(bucket_names_and_prefixes):
|
|
422
|
+
bucket_name, prefix = pack
|
|
423
|
+
if ".zip!" in prefix:
|
|
424
|
+
splitted_path = prefix.split(".zip!")
|
|
425
|
+
zip_prefix = f"{splitted_path[0]}.zip"
|
|
426
|
+
rel_path = splitted_path[-1]
|
|
427
|
+
dest_file = os.path.join(product_local_path, rel_path)
|
|
428
|
+
dest_abs_path_dir = os.path.dirname(dest_file)
|
|
429
|
+
if not os.path.isdir(dest_abs_path_dir):
|
|
430
|
+
os.makedirs(dest_abs_path_dir)
|
|
431
|
+
|
|
432
|
+
with open_s3_zipped_object(
|
|
433
|
+
bucket_name, zip_prefix, s3_client, partial=False
|
|
434
|
+
) as zip_file:
|
|
435
|
+
# file size
|
|
436
|
+
file_info = zip_file.getinfo(rel_path)
|
|
437
|
+
progress_callback.reset(total=file_info.file_size)
|
|
438
|
+
with zip_file.open(rel_path) as extracted, open(
|
|
439
|
+
dest_file, "wb"
|
|
440
|
+
) as output_file:
|
|
441
|
+
# Read in 1MB chunks
|
|
442
|
+
for zchunk in iter(lambda: extracted.read(1024 * 1024), b""):
|
|
443
|
+
output_file.write(zchunk)
|
|
444
|
+
progress_callback(len(zchunk))
|
|
445
|
+
|
|
446
|
+
downloaded.append(i)
|
|
447
|
+
|
|
448
|
+
return [
|
|
449
|
+
pack
|
|
450
|
+
for i, pack in enumerate(bucket_names_and_prefixes)
|
|
451
|
+
if i not in downloaded
|
|
452
|
+
]
|
|
453
|
+
|
|
405
454
|
def _download_preparation(
|
|
406
455
|
self,
|
|
407
456
|
product: EOProduct,
|
|
408
457
|
progress_callback: ProgressCallback,
|
|
409
458
|
**kwargs: Unpack[DownloadConf],
|
|
410
|
-
) ->
|
|
459
|
+
) -> tuple[Optional[str], Optional[str]]:
|
|
411
460
|
"""
|
|
412
|
-
|
|
461
|
+
Preparation for the download:
|
|
462
|
+
|
|
413
463
|
- check if file was already downloaded
|
|
414
464
|
- get file path
|
|
415
465
|
- create directories
|
|
466
|
+
|
|
416
467
|
:param product: product to be downloaded
|
|
417
468
|
:param progress_callback: progress callback to be used
|
|
418
469
|
:param kwargs: additional arguments
|
|
@@ -436,7 +487,8 @@ class AwsDownload(Download):
|
|
|
436
487
|
|
|
437
488
|
def _configure_safe_build(self, build_safe: bool, product: EOProduct):
|
|
438
489
|
"""
|
|
439
|
-
|
|
490
|
+
Updates the product properties with fetch metadata if safe build is enabled
|
|
491
|
+
|
|
440
492
|
:param build_safe: if safe build is enabled
|
|
441
493
|
:param product: product to be updated
|
|
442
494
|
"""
|
|
@@ -480,9 +532,10 @@ class AwsDownload(Download):
|
|
|
480
532
|
product: EOProduct,
|
|
481
533
|
asset_filter: Optional[str] = None,
|
|
482
534
|
ignore_assets: Optional[bool] = False,
|
|
483
|
-
) ->
|
|
535
|
+
) -> list[tuple[str, Optional[str]]]:
|
|
484
536
|
"""
|
|
485
|
-
|
|
537
|
+
Retrieves the bucket names and path prefixes for the assets
|
|
538
|
+
|
|
486
539
|
:param product: product for which the assets shall be downloaded
|
|
487
540
|
:param asset_filter: text for which the assets should be filtered
|
|
488
541
|
:param ignore_assets: if product instead of individual assets should be used
|
|
@@ -521,14 +574,15 @@ class AwsDownload(Download):
|
|
|
521
574
|
|
|
522
575
|
def _do_authentication(
|
|
523
576
|
self,
|
|
524
|
-
bucket_names_and_prefixes:
|
|
525
|
-
auth: Optional[Union[AuthBase,
|
|
526
|
-
) ->
|
|
577
|
+
bucket_names_and_prefixes: list[tuple[str, Optional[str]]],
|
|
578
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
579
|
+
) -> tuple[dict[str, Any], ResourceCollection]:
|
|
527
580
|
"""
|
|
528
|
-
|
|
529
|
-
|
|
581
|
+
Authenticates with s3 and retrieves the available objects
|
|
582
|
+
|
|
530
583
|
:param bucket_names_and_prefixes: list of bucket names and corresponding path prefixes
|
|
531
584
|
:param auth: authentication information
|
|
585
|
+
:raises AuthenticationError: authentication is not possible
|
|
532
586
|
:return: authenticated objects per bucket, list of available objects
|
|
533
587
|
"""
|
|
534
588
|
if not isinstance(auth, (dict, type(None))):
|
|
@@ -537,8 +591,8 @@ class AwsDownload(Download):
|
|
|
537
591
|
)
|
|
538
592
|
if auth is None:
|
|
539
593
|
auth = {}
|
|
540
|
-
authenticated_objects:
|
|
541
|
-
auth_error_messages:
|
|
594
|
+
authenticated_objects: dict[str, Any] = {}
|
|
595
|
+
auth_error_messages: set[str] = set()
|
|
542
596
|
for _, pack in enumerate(bucket_names_and_prefixes):
|
|
543
597
|
try:
|
|
544
598
|
bucket_name, prefix = pack
|
|
@@ -590,22 +644,25 @@ class AwsDownload(Download):
|
|
|
590
644
|
|
|
591
645
|
def _get_unique_products(
|
|
592
646
|
self,
|
|
593
|
-
bucket_names_and_prefixes:
|
|
594
|
-
authenticated_objects:
|
|
647
|
+
bucket_names_and_prefixes: list[tuple[str, Optional[str]]],
|
|
648
|
+
authenticated_objects: dict[str, Any],
|
|
595
649
|
asset_filter: Optional[str],
|
|
596
650
|
ignore_assets: bool,
|
|
597
651
|
product: EOProduct,
|
|
598
|
-
|
|
652
|
+
raise_error: bool = True,
|
|
653
|
+
) -> set[Any]:
|
|
599
654
|
"""
|
|
600
|
-
|
|
655
|
+
Retrieve unique product chunks based on authenticated objects and asset filters
|
|
656
|
+
|
|
601
657
|
:param bucket_names_and_prefixes: list of bucket names and corresponding path prefixes
|
|
602
658
|
:param authenticated_objects: available objects per bucket
|
|
603
659
|
:param asset_filter: text for which assets should be filtered
|
|
604
660
|
:param ignore_assets: if product instead of individual assets should be used
|
|
605
661
|
:param product: product that shall be downloaded
|
|
662
|
+
:param raise_error: raise error if there is nothing to download
|
|
606
663
|
:return: set of product chunks that can be downloaded
|
|
607
664
|
"""
|
|
608
|
-
product_chunks:
|
|
665
|
+
product_chunks: list[Any] = []
|
|
609
666
|
for bucket_name, prefix in bucket_names_and_prefixes:
|
|
610
667
|
# unauthenticated items filtered out
|
|
611
668
|
if bucket_name in authenticated_objects.keys():
|
|
@@ -624,19 +681,19 @@ class AwsDownload(Download):
|
|
|
624
681
|
unique_product_chunks,
|
|
625
682
|
)
|
|
626
683
|
)
|
|
627
|
-
if not unique_product_chunks:
|
|
684
|
+
if not unique_product_chunks and raise_error:
|
|
628
685
|
raise NotAvailableError(
|
|
629
686
|
rf"No file basename matching re.fullmatch(r'{asset_filter}') was found in {product.remote_location}"
|
|
630
687
|
)
|
|
631
688
|
|
|
632
|
-
if not unique_product_chunks:
|
|
689
|
+
if not unique_product_chunks and raise_error:
|
|
633
690
|
raise NoMatchingProductType("No product found to download.")
|
|
634
691
|
|
|
635
692
|
return unique_product_chunks
|
|
636
693
|
|
|
637
694
|
def _raise_if_auth_error(self, exception: ClientError) -> None:
|
|
638
695
|
"""Raises an error if given exception is an authentication error"""
|
|
639
|
-
err = cast(
|
|
696
|
+
err = cast(dict[str, str], exception.response["Error"])
|
|
640
697
|
if err["Code"] in AWS_AUTH_ERROR_MESSAGES and "key" in err["Message"].lower():
|
|
641
698
|
raise AuthenticationError(
|
|
642
699
|
f"Please check your credentials for {self.provider}.",
|
|
@@ -647,10 +704,10 @@ class AwsDownload(Download):
|
|
|
647
704
|
def _stream_download_dict(
|
|
648
705
|
self,
|
|
649
706
|
product: EOProduct,
|
|
650
|
-
auth: Optional[Union[AuthBase,
|
|
707
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
651
708
|
progress_callback: Optional[ProgressCallback] = None,
|
|
652
|
-
wait:
|
|
653
|
-
timeout:
|
|
709
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
710
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
654
711
|
**kwargs: Unpack[DownloadConf],
|
|
655
712
|
) -> StreamResponse:
|
|
656
713
|
r"""
|
|
@@ -713,6 +770,13 @@ class AwsDownload(Download):
|
|
|
713
770
|
bucket_names_and_prefixes, auth
|
|
714
771
|
)
|
|
715
772
|
|
|
773
|
+
# stream not implemented for prefixes like `foo/bar.zip!file.txt`
|
|
774
|
+
for _, prefix in bucket_names_and_prefixes:
|
|
775
|
+
if prefix and ".zip!" in prefix:
|
|
776
|
+
raise NotImplementedError(
|
|
777
|
+
"Download streaming is not implemented for files in zip on S3"
|
|
778
|
+
)
|
|
779
|
+
|
|
716
780
|
# downloadable files
|
|
717
781
|
unique_product_chunks = self._get_unique_products(
|
|
718
782
|
bucket_names_and_prefixes,
|
|
@@ -731,12 +795,12 @@ class AwsDownload(Download):
|
|
|
731
795
|
else sanitize(product.properties.get("id", "download"))
|
|
732
796
|
)
|
|
733
797
|
|
|
734
|
-
if len(assets_values)
|
|
798
|
+
if len(assets_values) <= 1:
|
|
735
799
|
first_chunks_tuple = next(chunks_tuples)
|
|
736
800
|
# update headers
|
|
737
801
|
filename = os.path.basename(list(unique_product_chunks)[0].key)
|
|
738
802
|
headers = {"content-disposition": f"attachment; filename={filename}"}
|
|
739
|
-
if assets_values[0].get("type", None):
|
|
803
|
+
if assets_values and assets_values[0].get("type", None):
|
|
740
804
|
headers["content-type"] = assets_values[0]["type"]
|
|
741
805
|
|
|
742
806
|
return StreamResponse(
|
|
@@ -753,11 +817,11 @@ class AwsDownload(Download):
|
|
|
753
817
|
|
|
754
818
|
def _stream_download(
|
|
755
819
|
self,
|
|
756
|
-
unique_product_chunks:
|
|
820
|
+
unique_product_chunks: set[Any],
|
|
757
821
|
product: EOProduct,
|
|
758
822
|
build_safe: bool,
|
|
759
823
|
progress_callback: ProgressCallback,
|
|
760
|
-
assets_values:
|
|
824
|
+
assets_values: list[dict[str, Any]],
|
|
761
825
|
) -> Iterator[Any]:
|
|
762
826
|
"""Yield product data chunks"""
|
|
763
827
|
|
|
@@ -799,7 +863,6 @@ class AwsDownload(Download):
|
|
|
799
863
|
common_path = self._get_commonpath(
|
|
800
864
|
product, unique_product_chunks, build_safe
|
|
801
865
|
)
|
|
802
|
-
|
|
803
866
|
for product_chunk in unique_product_chunks:
|
|
804
867
|
try:
|
|
805
868
|
chunk_rel_path = self.get_chunk_dest_path(
|
|
@@ -817,8 +880,7 @@ class AwsDownload(Download):
|
|
|
817
880
|
# out of SAFE format chunk
|
|
818
881
|
logger.warning(e)
|
|
819
882
|
continue
|
|
820
|
-
|
|
821
|
-
if len(assets_values) == 1:
|
|
883
|
+
if len(assets_values) <= 1:
|
|
822
884
|
yield from get_chunk_parts(product_chunk, progress_callback)
|
|
823
885
|
else:
|
|
824
886
|
yield (
|
|
@@ -830,7 +892,7 @@ class AwsDownload(Download):
|
|
|
830
892
|
)
|
|
831
893
|
|
|
832
894
|
def _get_commonpath(
|
|
833
|
-
self, product: EOProduct, product_chunks:
|
|
895
|
+
self, product: EOProduct, product_chunks: set[Any], build_safe: bool
|
|
834
896
|
) -> str:
|
|
835
897
|
chunk_paths = []
|
|
836
898
|
for product_chunk in product_chunks:
|
|
@@ -840,8 +902,8 @@ class AwsDownload(Download):
|
|
|
840
902
|
return os.path.commonpath(chunk_paths)
|
|
841
903
|
|
|
842
904
|
def get_rio_env(
|
|
843
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
844
|
-
) ->
|
|
905
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
906
|
+
) -> dict[str, Any]:
|
|
845
907
|
"""Get rasterio environment variables needed for data access authentication.
|
|
846
908
|
|
|
847
909
|
:param bucket_name: Bucket containg objects
|
|
@@ -849,23 +911,26 @@ class AwsDownload(Download):
|
|
|
849
911
|
:param auth_dict: Dictionary containing authentication keys
|
|
850
912
|
:returns: The rasterio environement variables
|
|
851
913
|
"""
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
914
|
+
rio_env_kwargs = {}
|
|
915
|
+
if endpoint_url := getattr(self.config, "s3_endpoint", None):
|
|
916
|
+
rio_env_kwargs["endpoint_url"] = endpoint_url.split("://")[-1]
|
|
917
|
+
rio_env_kwargs |= auth_dict
|
|
918
|
+
|
|
919
|
+
if self.s3_session is None:
|
|
920
|
+
_ = self.get_authenticated_objects(bucket_name, prefix, auth_dict)
|
|
857
921
|
|
|
858
|
-
_ = self.get_authenticated_objects(bucket_name, prefix, auth_dict)
|
|
859
922
|
if self.s3_session is not None:
|
|
860
923
|
if self.requester_pays:
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
924
|
+
rio_env_kwargs["requester_pays"] = True
|
|
925
|
+
return {
|
|
926
|
+
"session": self.s3_session,
|
|
927
|
+
**rio_env_kwargs,
|
|
928
|
+
}
|
|
864
929
|
else:
|
|
865
|
-
return {"aws_unsigned": True}
|
|
930
|
+
return {"aws_unsigned": True, **rio_env_kwargs}
|
|
866
931
|
|
|
867
932
|
def get_authenticated_objects(
|
|
868
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
933
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
869
934
|
) -> ResourceCollection:
|
|
870
935
|
"""Get boto3 authenticated objects for the given bucket using
|
|
871
936
|
the most adapted auth strategy.
|
|
@@ -877,8 +942,8 @@ class AwsDownload(Download):
|
|
|
877
942
|
:param auth_dict: Dictionary containing authentication keys
|
|
878
943
|
:returns: The boto3 authenticated objects
|
|
879
944
|
"""
|
|
880
|
-
auth_methods:
|
|
881
|
-
Callable[[str, str,
|
|
945
|
+
auth_methods: list[
|
|
946
|
+
Callable[[str, str, S3SessionKwargs], Optional[ResourceCollection]]
|
|
882
947
|
] = [
|
|
883
948
|
self._get_authenticated_objects_unsigned,
|
|
884
949
|
self._get_authenticated_objects_from_auth_profile,
|
|
@@ -913,7 +978,7 @@ class AwsDownload(Download):
|
|
|
913
978
|
)
|
|
914
979
|
|
|
915
980
|
def _get_authenticated_objects_unsigned(
|
|
916
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
981
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
917
982
|
) -> Optional[ResourceCollection]:
|
|
918
983
|
"""Auth strategy using no-sign-request"""
|
|
919
984
|
|
|
@@ -928,7 +993,7 @@ class AwsDownload(Download):
|
|
|
928
993
|
return objects
|
|
929
994
|
|
|
930
995
|
def _get_authenticated_objects_from_auth_profile(
|
|
931
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
996
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
932
997
|
) -> Optional[ResourceCollection]:
|
|
933
998
|
"""Auth strategy using RequestPayer=requester and ``aws_profile`` from provided credentials"""
|
|
934
999
|
|
|
@@ -946,26 +1011,18 @@ class AwsDownload(Download):
|
|
|
946
1011
|
objects = s3_resource.Bucket(bucket_name).objects
|
|
947
1012
|
list(objects.filter(Prefix=prefix).limit(1))
|
|
948
1013
|
self.s3_session = s3_session
|
|
1014
|
+
self.s3_resource = s3_resource
|
|
949
1015
|
return objects
|
|
950
1016
|
else:
|
|
951
1017
|
return None
|
|
952
1018
|
|
|
953
1019
|
def _get_authenticated_objects_from_auth_keys(
|
|
954
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
1020
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
955
1021
|
) -> Optional[ResourceCollection]:
|
|
956
1022
|
"""Auth strategy using RequestPayer=requester and ``aws_access_key_id``/``aws_secret_access_key``
|
|
957
1023
|
from provided credentials"""
|
|
958
1024
|
|
|
959
1025
|
if all(k in auth_dict for k in ("aws_access_key_id", "aws_secret_access_key")):
|
|
960
|
-
S3SessionKwargs = TypedDict(
|
|
961
|
-
"S3SessionKwargs",
|
|
962
|
-
{
|
|
963
|
-
"aws_access_key_id": str,
|
|
964
|
-
"aws_secret_access_key": str,
|
|
965
|
-
"aws_session_token": str,
|
|
966
|
-
},
|
|
967
|
-
total=False,
|
|
968
|
-
)
|
|
969
1026
|
s3_session_kwargs: S3SessionKwargs = {
|
|
970
1027
|
"aws_access_key_id": auth_dict["aws_access_key_id"],
|
|
971
1028
|
"aws_secret_access_key": auth_dict["aws_secret_access_key"],
|
|
@@ -985,12 +1042,13 @@ class AwsDownload(Download):
|
|
|
985
1042
|
objects = s3_resource.Bucket(bucket_name).objects
|
|
986
1043
|
list(objects.filter(Prefix=prefix).limit(1))
|
|
987
1044
|
self.s3_session = s3_session
|
|
1045
|
+
self.s3_resource = s3_resource
|
|
988
1046
|
return objects
|
|
989
1047
|
else:
|
|
990
1048
|
return None
|
|
991
1049
|
|
|
992
1050
|
def _get_authenticated_objects_from_env(
|
|
993
|
-
self, bucket_name: str, prefix: str, auth_dict:
|
|
1051
|
+
self, bucket_name: str, prefix: str, auth_dict: S3SessionKwargs
|
|
994
1052
|
) -> Optional[ResourceCollection]:
|
|
995
1053
|
"""Auth strategy using RequestPayer=requester and current environment"""
|
|
996
1054
|
|
|
@@ -1006,11 +1064,12 @@ class AwsDownload(Download):
|
|
|
1006
1064
|
objects = s3_resource.Bucket(bucket_name).objects
|
|
1007
1065
|
list(objects.filter(Prefix=prefix).limit(1))
|
|
1008
1066
|
self.s3_session = s3_session
|
|
1067
|
+
self.s3_resource = s3_resource
|
|
1009
1068
|
return objects
|
|
1010
1069
|
|
|
1011
1070
|
def get_product_bucket_name_and_prefix(
|
|
1012
1071
|
self, product: EOProduct, url: Optional[str] = None
|
|
1013
|
-
) ->
|
|
1072
|
+
) -> tuple[str, Optional[str]]:
|
|
1014
1073
|
"""Extract bucket name and prefix from product URL
|
|
1015
1074
|
|
|
1016
1075
|
:param product: The EO product to download
|
|
@@ -1141,7 +1200,7 @@ class AwsDownload(Download):
|
|
|
1141
1200
|
s1_title_suffix: Optional[str] = None
|
|
1142
1201
|
# S2 common
|
|
1143
1202
|
if product.product_type and "S2_MSI" in product.product_type:
|
|
1144
|
-
title_search: Optional[Match[str]] = re.search(
|
|
1203
|
+
title_search: Optional[re.Match[str]] = re.search(
|
|
1145
1204
|
r"^\w+_\w+_(\w+)_(\w+)_(\w+)_(\w+)_(\w+)$",
|
|
1146
1205
|
product.properties["title"],
|
|
1147
1206
|
)
|
|
@@ -1327,13 +1386,13 @@ class AwsDownload(Download):
|
|
|
1327
1386
|
def download_all(
|
|
1328
1387
|
self,
|
|
1329
1388
|
products: SearchResult,
|
|
1330
|
-
auth: Optional[Union[AuthBase,
|
|
1389
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
1331
1390
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
1332
1391
|
progress_callback: Optional[ProgressCallback] = None,
|
|
1333
|
-
wait:
|
|
1334
|
-
timeout:
|
|
1392
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
1393
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1335
1394
|
**kwargs: Unpack[DownloadConf],
|
|
1336
|
-
) ->
|
|
1395
|
+
) -> list[str]:
|
|
1337
1396
|
"""
|
|
1338
1397
|
download_all using parent (base plugin) method
|
|
1339
1398
|
"""
|