eodag 3.0.0b3__py3-none-any.whl → 3.1.0b1__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 +292 -198
- eodag/api/product/_assets.py +6 -6
- eodag/api/product/_product.py +18 -18
- eodag/api/product/metadata_mapping.py +51 -14
- eodag/api/search_result.py +29 -3
- eodag/cli.py +57 -20
- eodag/config.py +413 -117
- eodag/plugins/apis/base.py +10 -4
- eodag/plugins/apis/ecmwf.py +49 -16
- eodag/plugins/apis/usgs.py +30 -7
- eodag/plugins/authentication/aws_auth.py +14 -5
- eodag/plugins/authentication/base.py +10 -1
- eodag/plugins/authentication/generic.py +14 -3
- eodag/plugins/authentication/header.py +12 -4
- eodag/plugins/authentication/keycloak.py +41 -22
- eodag/plugins/authentication/oauth.py +11 -1
- eodag/plugins/authentication/openid_connect.py +178 -163
- eodag/plugins/authentication/qsauth.py +12 -4
- eodag/plugins/authentication/sas_auth.py +19 -2
- eodag/plugins/authentication/token.py +93 -15
- eodag/plugins/authentication/token_exchange.py +19 -19
- eodag/plugins/crunch/base.py +4 -1
- eodag/plugins/crunch/filter_date.py +5 -2
- eodag/plugins/crunch/filter_latest_intersect.py +5 -4
- eodag/plugins/crunch/filter_latest_tpl_name.py +1 -1
- eodag/plugins/crunch/filter_overlap.py +5 -7
- eodag/plugins/crunch/filter_property.py +6 -6
- eodag/plugins/download/aws.py +50 -34
- eodag/plugins/download/base.py +41 -50
- eodag/plugins/download/creodias_s3.py +40 -2
- eodag/plugins/download/http.py +221 -195
- eodag/plugins/download/s3rest.py +25 -25
- eodag/plugins/manager.py +168 -23
- eodag/plugins/search/base.py +106 -39
- eodag/plugins/search/build_search_result.py +1065 -324
- eodag/plugins/search/cop_marine.py +112 -29
- eodag/plugins/search/creodias_s3.py +45 -24
- eodag/plugins/search/csw.py +41 -1
- eodag/plugins/search/data_request_search.py +109 -9
- eodag/plugins/search/qssearch.py +549 -257
- eodag/plugins/search/static_stac_search.py +20 -21
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +577 -87
- eodag/resources/providers.yml +1619 -2776
- eodag/resources/stac.yml +3 -163
- eodag/resources/user_conf_template.yml +112 -97
- eodag/rest/config.py +1 -2
- eodag/rest/constants.py +0 -1
- eodag/rest/core.py +138 -98
- eodag/rest/errors.py +181 -0
- eodag/rest/server.py +55 -329
- eodag/rest/stac.py +93 -544
- eodag/rest/types/eodag_search.py +19 -8
- eodag/rest/types/queryables.py +6 -8
- eodag/rest/types/stac_search.py +11 -2
- eodag/rest/utils/__init__.py +3 -0
- eodag/types/__init__.py +71 -18
- eodag/types/download_args.py +3 -3
- eodag/types/queryables.py +180 -73
- eodag/types/search_args.py +3 -3
- eodag/types/whoosh.py +126 -0
- eodag/utils/__init__.py +147 -66
- eodag/utils/exceptions.py +47 -26
- eodag/utils/logging.py +37 -77
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +11 -13
- eodag/utils/stac_reader.py +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/METADATA +80 -81
- eodag-3.1.0b1.dist-info/RECORD +108 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +4 -2
- eodag/resources/constraints/climate-dt.json +0 -13
- eodag/resources/constraints/extremes-dt.json +0 -8
- eodag/utils/constraints.py +0 -244
- eodag-3.0.0b3.dist-info/RECORD +0 -110
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
- {eodag-3.0.0b3.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
eodag/plugins/download/base.py
CHANGED
|
@@ -81,15 +81,15 @@ class Download(PluginTopic):
|
|
|
81
81
|
|
|
82
82
|
- download data in the ``output_dir`` folder defined in the plugin's
|
|
83
83
|
configuration or passed through kwargs
|
|
84
|
-
- extract products from their archive (if relevant) if ``extract`` is set to True
|
|
85
|
-
(True by default)
|
|
84
|
+
- extract products from their archive (if relevant) if ``extract`` is set to ``True``
|
|
85
|
+
(``True`` by default)
|
|
86
86
|
- save a product in an archive/directory (in ``output_dir``) whose name must be
|
|
87
87
|
the product's ``title`` property
|
|
88
88
|
- update the product's ``location`` attribute once its data is downloaded (and
|
|
89
89
|
eventually after it's extracted) to the product's location given as a file URI
|
|
90
|
-
(e.g.
|
|
91
|
-
|
|
92
|
-
- save a *record* file in the directory ``output_dir/.downloaded`` whose name
|
|
90
|
+
(e.g. ``file:///tmp/product_folder`` on Linux or
|
|
91
|
+
``file:///C:/Users/username/AppData/Local/Temp`` on Windows)
|
|
92
|
+
- save a *record* file in the directory ``{output_dir}/.downloaded`` whose name
|
|
93
93
|
is built on the MD5 hash of the product's ``product_type`` and ``properties['id']``
|
|
94
94
|
attributes (``hashlib.md5((product.product_type+"-"+product.properties['id']).encode("utf-8")).hexdigest()``)
|
|
95
95
|
and whose content is the product's ``remote_location`` attribute itself.
|
|
@@ -112,8 +112,8 @@ class Download(PluginTopic):
|
|
|
112
112
|
product: EOProduct,
|
|
113
113
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
114
114
|
progress_callback: Optional[ProgressCallback] = None,
|
|
115
|
-
wait:
|
|
116
|
-
timeout:
|
|
115
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
116
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
117
117
|
**kwargs: Unpack[DownloadConf],
|
|
118
118
|
) -> Optional[str]:
|
|
119
119
|
r"""
|
|
@@ -130,8 +130,8 @@ class Download(PluginTopic):
|
|
|
130
130
|
and will override any other values defined in a configuration
|
|
131
131
|
file or with environment variables.
|
|
132
132
|
:returns: The absolute path to the downloaded product in the local filesystem
|
|
133
|
-
(e.g.
|
|
134
|
-
|
|
133
|
+
(e.g. ``/tmp/product.zip`` on Linux or
|
|
134
|
+
``C:\\Users\\username\\AppData\\Local\\Temp\\product.zip`` on Windows)
|
|
135
135
|
"""
|
|
136
136
|
raise NotImplementedError(
|
|
137
137
|
"A Download plugin must implement a method named download"
|
|
@@ -142,8 +142,8 @@ class Download(PluginTopic):
|
|
|
142
142
|
product: EOProduct,
|
|
143
143
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
144
144
|
progress_callback: Optional[ProgressCallback] = None,
|
|
145
|
-
wait:
|
|
146
|
-
timeout:
|
|
145
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
146
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
147
147
|
**kwargs: Unpack[DownloadConf],
|
|
148
148
|
) -> StreamResponse:
|
|
149
149
|
r"""
|
|
@@ -155,11 +155,11 @@ class Download(PluginTopic):
|
|
|
155
155
|
:param wait: (optional) If download fails, wait time in minutes between two download tries
|
|
156
156
|
:param timeout: (optional) If download fails, maximum time in minutes before stop retrying
|
|
157
157
|
to download
|
|
158
|
-
:param kwargs:
|
|
159
|
-
and
|
|
158
|
+
:param kwargs: ``output_dir`` (str), ``extract`` (bool), ``delete_archive`` (bool)
|
|
159
|
+
and ``dl_url_params`` (dict) can be provided as additional kwargs
|
|
160
160
|
and will override any other values defined in a configuration
|
|
161
161
|
file or with environment variables.
|
|
162
|
-
:returns:
|
|
162
|
+
:returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
|
|
163
163
|
"""
|
|
164
164
|
raise NotImplementedError(
|
|
165
165
|
"Download streaming must be implemented using a method named _stream_download_dict"
|
|
@@ -202,8 +202,8 @@ class Download(PluginTopic):
|
|
|
202
202
|
or getattr(self.config, "output_dir", tempfile.gettempdir())
|
|
203
203
|
or tempfile.gettempdir()
|
|
204
204
|
)
|
|
205
|
-
output_extension = kwargs.get("output_extension"
|
|
206
|
-
self.config, "output_extension", "
|
|
205
|
+
output_extension = kwargs.get("output_extension") or getattr(
|
|
206
|
+
self.config, "output_extension", ""
|
|
207
207
|
)
|
|
208
208
|
|
|
209
209
|
# Strong asumption made here: all products downloaded will be zip files
|
|
@@ -233,9 +233,13 @@ class Download(PluginTopic):
|
|
|
233
233
|
logger.warning(
|
|
234
234
|
f"Unable to create records directory. Got:\n{tb.format_exc()}",
|
|
235
235
|
)
|
|
236
|
+
url_hash = hashlib.md5(url.encode("utf-8")).hexdigest()
|
|
237
|
+
old_record_filename = os.path.join(download_records_dir, url_hash)
|
|
236
238
|
record_filename = os.path.join(
|
|
237
239
|
download_records_dir, self.generate_record_hash(product)
|
|
238
240
|
)
|
|
241
|
+
if os.path.isfile(old_record_filename):
|
|
242
|
+
os.rename(old_record_filename, record_filename)
|
|
239
243
|
if os.path.isfile(record_filename) and os.path.isfile(fs_path):
|
|
240
244
|
logger.info(
|
|
241
245
|
f"Product already downloaded: {fs_path}",
|
|
@@ -339,13 +343,7 @@ class Download(PluginTopic):
|
|
|
339
343
|
if delete_archive is not None
|
|
340
344
|
else getattr(self.config, "delete_archive", True)
|
|
341
345
|
)
|
|
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
|
-
)
|
|
346
|
+
product_path, _ = os.path.splitext(fs_path)
|
|
349
347
|
product_path_exists = os.path.exists(product_path)
|
|
350
348
|
if product_path_exists and os.path.isfile(product_path):
|
|
351
349
|
logger.info(
|
|
@@ -422,10 +420,10 @@ class Download(PluginTopic):
|
|
|
422
420
|
|
|
423
421
|
tmp_dir.cleanup()
|
|
424
422
|
|
|
425
|
-
if delete_archive:
|
|
423
|
+
if delete_archive and os.path.isfile(fs_path):
|
|
426
424
|
logger.info(f"Deleting archive {os.path.basename(fs_path)}")
|
|
427
425
|
os.unlink(fs_path)
|
|
428
|
-
|
|
426
|
+
elif os.path.isfile(fs_path):
|
|
429
427
|
logger.info(
|
|
430
428
|
f"Archive deletion is deactivated, keeping {os.path.basename(fs_path)}"
|
|
431
429
|
)
|
|
@@ -444,8 +442,8 @@ class Download(PluginTopic):
|
|
|
444
442
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
445
443
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
446
444
|
progress_callback: Optional[ProgressCallback] = None,
|
|
447
|
-
wait:
|
|
448
|
-
timeout:
|
|
445
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
446
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
449
447
|
**kwargs: Unpack[DownloadConf],
|
|
450
448
|
) -> List[str]:
|
|
451
449
|
"""
|
|
@@ -458,7 +456,7 @@ class Download(PluginTopic):
|
|
|
458
456
|
:param auth: (optional) authenticated object
|
|
459
457
|
:param downloaded_callback: (optional) A method or a callable object which takes
|
|
460
458
|
as parameter the ``product``. You can use the base class
|
|
461
|
-
:class:`~eodag.
|
|
459
|
+
:class:`~eodag.utils.DownloadedCallback` and override
|
|
462
460
|
its ``__call__`` method. Will be called each time a product
|
|
463
461
|
finishes downloading
|
|
464
462
|
:param progress_callback: (optional) A progress callback
|
|
@@ -541,7 +539,7 @@ class Download(PluginTopic):
|
|
|
541
539
|
)
|
|
542
540
|
raise
|
|
543
541
|
|
|
544
|
-
except RuntimeError:
|
|
542
|
+
except (RuntimeError, Exception):
|
|
545
543
|
import traceback as tb
|
|
546
544
|
|
|
547
545
|
logger.error(
|
|
@@ -549,16 +547,9 @@ class Download(PluginTopic):
|
|
|
549
547
|
"Skipping it"
|
|
550
548
|
)
|
|
551
549
|
logger.debug(f"\n{tb.format_exc()}")
|
|
552
|
-
stop_time = datetime.now()
|
|
553
|
-
|
|
554
|
-
except Exception:
|
|
555
|
-
import traceback as tb
|
|
556
550
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
"Skipping it",
|
|
560
|
-
)
|
|
561
|
-
logger.debug(f"\n{tb.format_exc()}")
|
|
551
|
+
# product skipped, to not retry it
|
|
552
|
+
products.remove(product)
|
|
562
553
|
|
|
563
554
|
if (
|
|
564
555
|
len(products) > 0
|
|
@@ -585,14 +576,14 @@ class Download(PluginTopic):
|
|
|
585
576
|
|
|
586
577
|
return paths
|
|
587
578
|
|
|
588
|
-
def
|
|
589
|
-
self, product: EOProduct, wait:
|
|
579
|
+
def _order_download_retry(
|
|
580
|
+
self, product: EOProduct, wait: float, timeout: float
|
|
590
581
|
) -> Callable[[Callable[..., T]], Callable[..., T]]:
|
|
591
582
|
"""
|
|
592
|
-
|
|
583
|
+
Order download retry decorator.
|
|
593
584
|
|
|
594
|
-
Retries the wrapped
|
|
595
|
-
exception is thrown until
|
|
585
|
+
Retries the wrapped order_download method after ``wait`` minutes if a
|
|
586
|
+
``NotAvailableError`` exception is thrown until ``timeout`` minutes.
|
|
596
587
|
|
|
597
588
|
:param product: The EO product to download
|
|
598
589
|
:param wait: If download fails, wait time in minutes between two download tries
|
|
@@ -601,7 +592,7 @@ class Download(PluginTopic):
|
|
|
601
592
|
:returns: decorator
|
|
602
593
|
"""
|
|
603
594
|
|
|
604
|
-
def decorator(
|
|
595
|
+
def decorator(order_download: Callable[..., T]) -> Callable[..., T]:
|
|
605
596
|
def download_and_retry(*args: Any, **kwargs: Unpack[DownloadConf]) -> T:
|
|
606
597
|
# initiate retry loop
|
|
607
598
|
start_time = datetime.now()
|
|
@@ -618,7 +609,7 @@ class Download(PluginTopic):
|
|
|
618
609
|
if datetime_now >= product.next_try:
|
|
619
610
|
product.next_try += timedelta(minutes=wait)
|
|
620
611
|
try:
|
|
621
|
-
return
|
|
612
|
+
return order_download(*args, **kwargs)
|
|
622
613
|
|
|
623
614
|
except NotAvailableError as e:
|
|
624
615
|
if not getattr(self.config, "order_enabled", False):
|
|
@@ -634,7 +625,7 @@ class Download(PluginTopic):
|
|
|
634
625
|
).seconds
|
|
635
626
|
retry_count += 1
|
|
636
627
|
retry_info = (
|
|
637
|
-
f"[Retry #{retry_count}] Waited {wait_seconds}s,
|
|
628
|
+
f"[Retry #{retry_count}] Waited {wait_seconds}s, checking order status again"
|
|
638
629
|
f" (retry every {wait}' for {timeout}')"
|
|
639
630
|
)
|
|
640
631
|
logger.info(not_available_info)
|
|
@@ -656,8 +647,8 @@ class Download(PluginTopic):
|
|
|
656
647
|
).microseconds / 1e6
|
|
657
648
|
retry_count += 1
|
|
658
649
|
retry_info = (
|
|
659
|
-
f"[Retry #{retry_count}] Waiting {wait_seconds}s until next
|
|
660
|
-
f"
|
|
650
|
+
f"[Retry #{retry_count}] Waiting {wait_seconds}s until next order status check"
|
|
651
|
+
f" (retry every {wait}' for {timeout}')"
|
|
661
652
|
)
|
|
662
653
|
logger.info(not_available_info)
|
|
663
654
|
# Retry-After info from Response header
|
|
@@ -678,12 +669,12 @@ class Download(PluginTopic):
|
|
|
678
669
|
logger.info(not_available_info)
|
|
679
670
|
raise NotAvailableError(
|
|
680
671
|
f"{product.properties['title']} is not available ({product.properties['storageStatus']})"
|
|
681
|
-
f" and
|
|
672
|
+
f" and order was not successfull, timeout reached"
|
|
682
673
|
)
|
|
683
674
|
elif datetime_now >= stop_time:
|
|
684
675
|
raise NotAvailableError(not_available_info)
|
|
685
676
|
|
|
686
|
-
return
|
|
677
|
+
return order_download(*args, **kwargs)
|
|
687
678
|
|
|
688
679
|
return download_and_retry
|
|
689
680
|
|
|
@@ -15,17 +15,28 @@
|
|
|
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
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
|
|
|
25
27
|
|
|
26
28
|
class CreodiasS3Download(AwsDownload):
|
|
27
29
|
"""
|
|
28
|
-
Download on creodias s3 from their VMs
|
|
30
|
+
Download on creodias s3 from their VMs (extension of :class:`~eodag.plugins.download.aws.AwsDownload`)
|
|
31
|
+
|
|
32
|
+
:param provider: provider name
|
|
33
|
+
:param config: Download plugin configuration:
|
|
34
|
+
|
|
35
|
+
* :attr:`~eodag.config.PluginConfig.type` (``str``) (**mandatory**): CreodiasS3Download
|
|
36
|
+
* :attr:`~eodag.config.PluginConfig.base_uri` (``str``) (**mandatory**): s3 endpoint url
|
|
37
|
+
* :attr:`~eodag.config.PluginConfig.s3_bucket` (``str``) (**mandatory**): bucket where the products can be found
|
|
38
|
+
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be
|
|
39
|
+
verified in requests; default: ``True``
|
|
29
40
|
"""
|
|
30
41
|
|
|
31
42
|
def _get_authenticated_objects_unsigned(self, bucket_name, prefix, auth_dict):
|
|
@@ -50,9 +61,36 @@ class CreodiasS3Download(AwsDownload):
|
|
|
50
61
|
|
|
51
62
|
s3_session = boto3.session.Session(**auth_dict)
|
|
52
63
|
s3_resource = s3_session.resource(
|
|
53
|
-
"s3", endpoint_url=getattr(self.config, "
|
|
64
|
+
"s3", endpoint_url=getattr(self.config, "s3_endpoint", None)
|
|
54
65
|
)
|
|
55
66
|
objects = s3_resource.Bucket(bucket_name).objects.filter()
|
|
56
67
|
list(objects.filter(Prefix=prefix).limit(1))
|
|
57
68
|
self.s3_session = s3_session
|
|
58
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
|