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/base.py
CHANGED
|
@@ -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,10 +101,10 @@ class Download(PluginTopic):
|
|
|
110
101
|
def download(
|
|
111
102
|
self,
|
|
112
103
|
product: EOProduct,
|
|
113
|
-
auth: Optional[Union[AuthBase,
|
|
104
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
114
105
|
progress_callback: Optional[ProgressCallback] = None,
|
|
115
|
-
wait:
|
|
116
|
-
timeout:
|
|
106
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
107
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
117
108
|
**kwargs: Unpack[DownloadConf],
|
|
118
109
|
) -> Optional[str]:
|
|
119
110
|
r"""
|
|
@@ -140,10 +131,10 @@ class Download(PluginTopic):
|
|
|
140
131
|
def _stream_download_dict(
|
|
141
132
|
self,
|
|
142
133
|
product: EOProduct,
|
|
143
|
-
auth: Optional[Union[AuthBase,
|
|
134
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
144
135
|
progress_callback: Optional[ProgressCallback] = None,
|
|
145
|
-
wait:
|
|
146
|
-
timeout:
|
|
136
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
137
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
147
138
|
**kwargs: Unpack[DownloadConf],
|
|
148
139
|
) -> StreamResponse:
|
|
149
140
|
r"""
|
|
@@ -170,7 +161,7 @@ class Download(PluginTopic):
|
|
|
170
161
|
product: EOProduct,
|
|
171
162
|
progress_callback: Optional[ProgressCallback] = None,
|
|
172
163
|
**kwargs: Unpack[DownloadConf],
|
|
173
|
-
) ->
|
|
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
|
|
@@ -202,8 +193,8 @@ class Download(PluginTopic):
|
|
|
202
193
|
or getattr(self.config, "output_dir", tempfile.gettempdir())
|
|
203
194
|
or tempfile.gettempdir()
|
|
204
195
|
)
|
|
205
|
-
output_extension = kwargs.get("output_extension"
|
|
206
|
-
self.config, "output_extension", "
|
|
196
|
+
output_extension = kwargs.get("output_extension") or getattr(
|
|
197
|
+
self.config, "output_extension", ""
|
|
207
198
|
)
|
|
208
199
|
|
|
209
200
|
# Strong asumption made here: all products downloaded will be zip files
|
|
@@ -233,9 +224,13 @@ class Download(PluginTopic):
|
|
|
233
224
|
logger.warning(
|
|
234
225
|
f"Unable to create records directory. Got:\n{tb.format_exc()}",
|
|
235
226
|
)
|
|
227
|
+
url_hash = hashlib.md5(url.encode("utf-8")).hexdigest()
|
|
228
|
+
old_record_filename = os.path.join(download_records_dir, url_hash)
|
|
236
229
|
record_filename = os.path.join(
|
|
237
230
|
download_records_dir, self.generate_record_hash(product)
|
|
238
231
|
)
|
|
232
|
+
if os.path.isfile(old_record_filename):
|
|
233
|
+
os.rename(old_record_filename, record_filename)
|
|
239
234
|
if os.path.isfile(record_filename) and os.path.isfile(fs_path):
|
|
240
235
|
logger.info(
|
|
241
236
|
f"Product already downloaded: {fs_path}",
|
|
@@ -339,13 +334,7 @@ class Download(PluginTopic):
|
|
|
339
334
|
if delete_archive is not None
|
|
340
335
|
else getattr(self.config, "delete_archive", True)
|
|
341
336
|
)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
product_path = (
|
|
345
|
-
fs_path[: fs_path.index(output_extension)]
|
|
346
|
-
if output_extension in fs_path
|
|
347
|
-
else fs_path
|
|
348
|
-
)
|
|
337
|
+
product_path, _ = os.path.splitext(fs_path)
|
|
349
338
|
product_path_exists = os.path.exists(product_path)
|
|
350
339
|
if product_path_exists and os.path.isfile(product_path):
|
|
351
340
|
logger.info(
|
|
@@ -422,10 +411,10 @@ class Download(PluginTopic):
|
|
|
422
411
|
|
|
423
412
|
tmp_dir.cleanup()
|
|
424
413
|
|
|
425
|
-
if delete_archive:
|
|
414
|
+
if delete_archive and os.path.isfile(fs_path):
|
|
426
415
|
logger.info(f"Deleting archive {os.path.basename(fs_path)}")
|
|
427
416
|
os.unlink(fs_path)
|
|
428
|
-
|
|
417
|
+
elif os.path.isfile(fs_path):
|
|
429
418
|
logger.info(
|
|
430
419
|
f"Archive deletion is deactivated, keeping {os.path.basename(fs_path)}"
|
|
431
420
|
)
|
|
@@ -441,13 +430,13 @@ class Download(PluginTopic):
|
|
|
441
430
|
def download_all(
|
|
442
431
|
self,
|
|
443
432
|
products: SearchResult,
|
|
444
|
-
auth: Optional[Union[AuthBase,
|
|
433
|
+
auth: Optional[Union[AuthBase, S3SessionKwargs]] = None,
|
|
445
434
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
446
435
|
progress_callback: Optional[ProgressCallback] = None,
|
|
447
|
-
wait:
|
|
448
|
-
timeout:
|
|
436
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
437
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
449
438
|
**kwargs: Unpack[DownloadConf],
|
|
450
|
-
) ->
|
|
439
|
+
) -> list[str]:
|
|
451
440
|
"""
|
|
452
441
|
Base download_all method.
|
|
453
442
|
|
|
@@ -476,7 +465,7 @@ class Download(PluginTopic):
|
|
|
476
465
|
# Products are going to be removed one by one from this sequence once
|
|
477
466
|
# downloaded.
|
|
478
467
|
products = products[:]
|
|
479
|
-
paths:
|
|
468
|
+
paths: list[str] = []
|
|
480
469
|
# initiate retry loop
|
|
481
470
|
start_time = datetime.now()
|
|
482
471
|
stop_time = start_time + timedelta(minutes=timeout)
|
|
@@ -541,7 +530,7 @@ class Download(PluginTopic):
|
|
|
541
530
|
)
|
|
542
531
|
raise
|
|
543
532
|
|
|
544
|
-
except RuntimeError:
|
|
533
|
+
except (RuntimeError, Exception):
|
|
545
534
|
import traceback as tb
|
|
546
535
|
|
|
547
536
|
logger.error(
|
|
@@ -549,16 +538,9 @@ class Download(PluginTopic):
|
|
|
549
538
|
"Skipping it"
|
|
550
539
|
)
|
|
551
540
|
logger.debug(f"\n{tb.format_exc()}")
|
|
552
|
-
stop_time = datetime.now()
|
|
553
|
-
|
|
554
|
-
except Exception:
|
|
555
|
-
import traceback as tb
|
|
556
541
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
"Skipping it",
|
|
560
|
-
)
|
|
561
|
-
logger.debug(f"\n{tb.format_exc()}")
|
|
542
|
+
# product skipped, to not retry it
|
|
543
|
+
products.remove(product)
|
|
562
544
|
|
|
563
545
|
if (
|
|
564
546
|
len(products) > 0
|
|
@@ -585,14 +567,14 @@ class Download(PluginTopic):
|
|
|
585
567
|
|
|
586
568
|
return paths
|
|
587
569
|
|
|
588
|
-
def
|
|
589
|
-
self, product: EOProduct, wait:
|
|
570
|
+
def _order_download_retry(
|
|
571
|
+
self, product: EOProduct, wait: float, timeout: float
|
|
590
572
|
) -> Callable[[Callable[..., T]], Callable[..., T]]:
|
|
591
573
|
"""
|
|
592
|
-
|
|
574
|
+
Order download retry decorator.
|
|
593
575
|
|
|
594
|
-
Retries the wrapped
|
|
595
|
-
exception is thrown until
|
|
576
|
+
Retries the wrapped order_download method after ``wait`` minutes if a
|
|
577
|
+
``NotAvailableError`` exception is thrown until ``timeout`` minutes.
|
|
596
578
|
|
|
597
579
|
:param product: The EO product to download
|
|
598
580
|
:param wait: If download fails, wait time in minutes between two download tries
|
|
@@ -601,7 +583,7 @@ class Download(PluginTopic):
|
|
|
601
583
|
:returns: decorator
|
|
602
584
|
"""
|
|
603
585
|
|
|
604
|
-
def decorator(
|
|
586
|
+
def decorator(order_download: Callable[..., T]) -> Callable[..., T]:
|
|
605
587
|
def download_and_retry(*args: Any, **kwargs: Unpack[DownloadConf]) -> T:
|
|
606
588
|
# initiate retry loop
|
|
607
589
|
start_time = datetime.now()
|
|
@@ -618,7 +600,7 @@ class Download(PluginTopic):
|
|
|
618
600
|
if datetime_now >= product.next_try:
|
|
619
601
|
product.next_try += timedelta(minutes=wait)
|
|
620
602
|
try:
|
|
621
|
-
return
|
|
603
|
+
return order_download(*args, **kwargs)
|
|
622
604
|
|
|
623
605
|
except NotAvailableError as e:
|
|
624
606
|
if not getattr(self.config, "order_enabled", False):
|
|
@@ -634,7 +616,7 @@ class Download(PluginTopic):
|
|
|
634
616
|
).seconds
|
|
635
617
|
retry_count += 1
|
|
636
618
|
retry_info = (
|
|
637
|
-
f"[Retry #{retry_count}] Waited {wait_seconds}s,
|
|
619
|
+
f"[Retry #{retry_count}] Waited {wait_seconds}s, checking order status again"
|
|
638
620
|
f" (retry every {wait}' for {timeout}')"
|
|
639
621
|
)
|
|
640
622
|
logger.info(not_available_info)
|
|
@@ -656,8 +638,8 @@ class Download(PluginTopic):
|
|
|
656
638
|
).microseconds / 1e6
|
|
657
639
|
retry_count += 1
|
|
658
640
|
retry_info = (
|
|
659
|
-
f"[Retry #{retry_count}] Waiting {wait_seconds}s until next
|
|
660
|
-
f"
|
|
641
|
+
f"[Retry #{retry_count}] Waiting {wait_seconds}s until next order status check"
|
|
642
|
+
f" (retry every {wait}' for {timeout}')"
|
|
661
643
|
)
|
|
662
644
|
logger.info(not_available_info)
|
|
663
645
|
# Retry-After info from Response header
|
|
@@ -678,12 +660,12 @@ class Download(PluginTopic):
|
|
|
678
660
|
logger.info(not_available_info)
|
|
679
661
|
raise NotAvailableError(
|
|
680
662
|
f"{product.properties['title']} is not available ({product.properties['storageStatus']})"
|
|
681
|
-
f" and
|
|
663
|
+
f" and order was not successfull, timeout reached"
|
|
682
664
|
)
|
|
683
665
|
elif datetime_now >= stop_time:
|
|
684
666
|
raise NotAvailableError(not_available_info)
|
|
685
667
|
|
|
686
|
-
return
|
|
668
|
+
return order_download(*args, **kwargs)
|
|
687
669
|
|
|
688
670
|
return download_and_retry
|
|
689
671
|
|
|
@@ -15,10 +15,12 @@
|
|
|
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 Optional
|
|
18
19
|
|
|
19
20
|
import boto3
|
|
20
21
|
from botocore.exceptions import ClientError
|
|
21
22
|
|
|
23
|
+
from eodag import EOProduct
|
|
22
24
|
from eodag.plugins.download.aws import AwsDownload
|
|
23
25
|
from eodag.utils.exceptions import MisconfiguredError
|
|
24
26
|
|
|
@@ -65,3 +67,30 @@ class CreodiasS3Download(AwsDownload):
|
|
|
65
67
|
list(objects.filter(Prefix=prefix).limit(1))
|
|
66
68
|
self.s3_session = s3_session
|
|
67
69
|
return objects
|
|
70
|
+
|
|
71
|
+
def _get_bucket_names_and_prefixes(
|
|
72
|
+
self,
|
|
73
|
+
product: EOProduct,
|
|
74
|
+
asset_filter: Optional[str] = None,
|
|
75
|
+
ignore_assets: Optional[bool] = False,
|
|
76
|
+
) -> list[tuple[str, Optional[str]]]:
|
|
77
|
+
"""
|
|
78
|
+
Retrieves the bucket names and path prefixes for the assets
|
|
79
|
+
|
|
80
|
+
:param product: product for which the assets shall be downloaded
|
|
81
|
+
:param asset_filter: text for which the assets should be filtered
|
|
82
|
+
:param ignore_assets: if product instead of individual assets should be used
|
|
83
|
+
:return: tuples of bucket names and prefixes
|
|
84
|
+
"""
|
|
85
|
+
# if assets are defined, use them instead of scanning product.location
|
|
86
|
+
if len(product.assets) > 0 and not ignore_assets:
|
|
87
|
+
bucket_names_and_prefixes = super()._get_bucket_names_and_prefixes(
|
|
88
|
+
product, asset_filter, ignore_assets
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
# if no assets are given, use productIdentifier to get S3 path for download
|
|
92
|
+
s3_url = "s3:/" + product.properties["productIdentifier"]
|
|
93
|
+
bucket_names_and_prefixes = [
|
|
94
|
+
self.get_product_bucket_name_and_prefix(product, s3_url)
|
|
95
|
+
]
|
|
96
|
+
return bucket_names_and_prefixes
|