eodag 3.0.1__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 +116 -86
- eodag/api/product/_assets.py +6 -6
- eodag/api/product/_product.py +18 -18
- eodag/api/product/metadata_mapping.py +39 -11
- eodag/cli.py +22 -1
- eodag/config.py +14 -14
- eodag/plugins/apis/ecmwf.py +37 -14
- eodag/plugins/apis/usgs.py +5 -5
- eodag/plugins/authentication/openid_connect.py +2 -2
- eodag/plugins/authentication/token.py +37 -6
- eodag/plugins/crunch/filter_property.py +2 -3
- eodag/plugins/download/aws.py +11 -12
- eodag/plugins/download/base.py +30 -39
- eodag/plugins/download/creodias_s3.py +29 -0
- eodag/plugins/download/http.py +144 -152
- eodag/plugins/download/s3rest.py +5 -7
- eodag/plugins/search/base.py +73 -25
- eodag/plugins/search/build_search_result.py +1047 -310
- eodag/plugins/search/creodias_s3.py +25 -19
- eodag/plugins/search/data_request_search.py +1 -1
- eodag/plugins/search/qssearch.py +51 -139
- eodag/resources/ext_product_types.json +1 -1
- eodag/resources/product_types.yml +391 -32
- eodag/resources/providers.yml +678 -1744
- eodag/rest/core.py +92 -62
- eodag/rest/server.py +31 -4
- eodag/rest/types/eodag_search.py +6 -0
- eodag/rest/types/queryables.py +5 -6
- eodag/rest/utils/__init__.py +3 -0
- eodag/types/__init__.py +56 -15
- eodag/types/download_args.py +2 -2
- eodag/types/queryables.py +180 -72
- eodag/types/whoosh.py +126 -0
- eodag/utils/__init__.py +71 -10
- eodag/utils/exceptions.py +27 -20
- eodag/utils/repr.py +65 -6
- eodag/utils/requests.py +11 -11
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/METADATA +76 -76
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/RECORD +43 -44
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/WHEEL +1 -1
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/entry_points.txt +3 -2
- eodag/utils/constraints.py +0 -244
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/LICENSE +0 -0
- {eodag-3.0.1.dist-info → eodag-3.1.0b1.dist-info}/top_level.txt +0 -0
eodag/plugins/download/http.py
CHANGED
|
@@ -27,6 +27,7 @@ from datetime import datetime
|
|
|
27
27
|
from email.message import Message
|
|
28
28
|
from itertools import chain
|
|
29
29
|
from json import JSONDecodeError
|
|
30
|
+
from pathlib import Path
|
|
30
31
|
from typing import (
|
|
31
32
|
TYPE_CHECKING,
|
|
32
33
|
Any,
|
|
@@ -80,8 +81,8 @@ from eodag.utils.exceptions import (
|
|
|
80
81
|
DownloadError,
|
|
81
82
|
MisconfiguredError,
|
|
82
83
|
NotAvailableError,
|
|
83
|
-
RequestError,
|
|
84
84
|
TimeOutError,
|
|
85
|
+
ValidationError,
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
if TYPE_CHECKING:
|
|
@@ -122,8 +123,6 @@ class HTTPDownload(Download):
|
|
|
122
123
|
default: ``5``
|
|
123
124
|
* :attr:`~eodag.config.PluginConfig.ssl_verify` (``bool``): if the ssl certificates should be verified in
|
|
124
125
|
requests; default: ``True``
|
|
125
|
-
* :attr:`~eodag.config.PluginConfig.output_extension` (``str``): which extension should be used for the
|
|
126
|
-
downloaded file
|
|
127
126
|
* :attr:`~eodag.config.PluginConfig.no_auth_download` (``bool``): if the download should be done without
|
|
128
127
|
authentication; default: ``True``
|
|
129
128
|
* :attr:`~eodag.config.PluginConfig.order_enabled` (``bool``): if the product has to be ordered to download it;
|
|
@@ -140,16 +139,15 @@ class HTTPDownload(Download):
|
|
|
140
139
|
configuration to handle the order status; contains information which method to use, how the response data is
|
|
141
140
|
interpreted, which status corresponds to success, ordered and error and what should be done on success.
|
|
142
141
|
* :attr:`~eodag.config.PluginConfig.products` (``Dict[str, Dict[str, Any]``): product type specific config; the
|
|
143
|
-
keys are the product types, the values are dictionaries which can contain the
|
|
144
|
-
:attr:`~eodag.config.PluginConfig.
|
|
145
|
-
overwrite the provider config for a specific product type
|
|
142
|
+
keys are the product types, the values are dictionaries which can contain the key
|
|
143
|
+
:attr:`~eodag.config.PluginConfig.extract` to overwrite the provider config for a specific product type
|
|
146
144
|
|
|
147
145
|
"""
|
|
148
146
|
|
|
149
147
|
def __init__(self, provider: str, config: PluginConfig) -> None:
|
|
150
148
|
super(HTTPDownload, self).__init__(provider, config)
|
|
151
149
|
|
|
152
|
-
def
|
|
150
|
+
def _order(
|
|
153
151
|
self,
|
|
154
152
|
product: EOProduct,
|
|
155
153
|
auth: Optional[AuthBase] = None,
|
|
@@ -226,9 +224,11 @@ class HTTPDownload(Download):
|
|
|
226
224
|
product.properties["storageStatus"] = STAGING_STATUS
|
|
227
225
|
except RequestException as e:
|
|
228
226
|
self._check_auth_exception(e)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
227
|
+
msg = f'{product.properties["title"]} could not be ordered'
|
|
228
|
+
if e.response is not None and e.response.status_code == 400:
|
|
229
|
+
raise ValidationError.from_error(e, msg) from e
|
|
230
|
+
else:
|
|
231
|
+
raise DownloadError.from_error(e, msg) from e
|
|
232
232
|
|
|
233
233
|
return self.order_response_process(response, product)
|
|
234
234
|
except requests.exceptions.Timeout as exc:
|
|
@@ -271,7 +271,7 @@ class HTTPDownload(Download):
|
|
|
271
271
|
|
|
272
272
|
return json_response
|
|
273
273
|
|
|
274
|
-
def
|
|
274
|
+
def _order_status(
|
|
275
275
|
self,
|
|
276
276
|
product: EOProduct,
|
|
277
277
|
auth: Optional[AuthBase] = None,
|
|
@@ -398,13 +398,11 @@ class HTTPDownload(Download):
|
|
|
398
398
|
# success and no need to get status response content
|
|
399
399
|
skip_parsing_status_response = True
|
|
400
400
|
except RequestException as e:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
) from e
|
|
401
|
+
msg = f'{product.properties["title"]} order status could not be checked'
|
|
402
|
+
if e.response is not None and e.response.status_code == 400:
|
|
403
|
+
raise ValidationError.from_error(e, msg) from e
|
|
404
|
+
else:
|
|
405
|
+
raise DownloadError.from_error(e, msg) from e
|
|
408
406
|
|
|
409
407
|
if not skip_parsing_status_response:
|
|
410
408
|
# status request
|
|
@@ -566,8 +564,8 @@ class HTTPDownload(Download):
|
|
|
566
564
|
product: EOProduct,
|
|
567
565
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
568
566
|
progress_callback: Optional[ProgressCallback] = None,
|
|
569
|
-
wait:
|
|
570
|
-
timeout:
|
|
567
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
568
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
571
569
|
**kwargs: Unpack[DownloadConf],
|
|
572
570
|
) -> Optional[str]:
|
|
573
571
|
"""Download a product using HTTP protocol.
|
|
@@ -585,13 +583,6 @@ class HTTPDownload(Download):
|
|
|
585
583
|
)
|
|
586
584
|
progress_callback = ProgressCallback(disable=True)
|
|
587
585
|
|
|
588
|
-
output_extension = getattr(self.config, "products", {}).get(
|
|
589
|
-
product.product_type, {}
|
|
590
|
-
).get("output_extension", None) or getattr(
|
|
591
|
-
self.config, "output_extension", ".zip"
|
|
592
|
-
)
|
|
593
|
-
kwargs["output_extension"] = kwargs.get("output_extension", output_extension)
|
|
594
|
-
|
|
595
586
|
fs_path, record_filename = self._prepare_download(
|
|
596
587
|
product,
|
|
597
588
|
progress_callback=progress_callback,
|
|
@@ -610,7 +601,7 @@ class HTTPDownload(Download):
|
|
|
610
601
|
try:
|
|
611
602
|
fs_path = self._download_assets(
|
|
612
603
|
product,
|
|
613
|
-
fs_path
|
|
604
|
+
fs_path,
|
|
614
605
|
record_filename,
|
|
615
606
|
auth,
|
|
616
607
|
progress_callback,
|
|
@@ -627,82 +618,62 @@ class HTTPDownload(Download):
|
|
|
627
618
|
|
|
628
619
|
url = product.remote_location
|
|
629
620
|
|
|
630
|
-
@self.
|
|
621
|
+
@self._order_download_retry(product, wait, timeout)
|
|
631
622
|
def download_request(
|
|
632
623
|
product: EOProduct,
|
|
633
624
|
auth: AuthBase,
|
|
634
625
|
progress_callback: ProgressCallback,
|
|
635
|
-
wait:
|
|
636
|
-
timeout:
|
|
626
|
+
wait: float,
|
|
627
|
+
timeout: float,
|
|
637
628
|
**kwargs: Unpack[DownloadConf],
|
|
638
|
-
) ->
|
|
639
|
-
chunks = self._stream_download(product, auth, progress_callback, **kwargs)
|
|
629
|
+
) -> os.PathLike:
|
|
640
630
|
is_empty = True
|
|
631
|
+
chunk_iterator = self._stream_download(
|
|
632
|
+
product, auth, progress_callback, **kwargs
|
|
633
|
+
)
|
|
634
|
+
if fs_path is not None:
|
|
635
|
+
ext = Path(product.filename).suffix
|
|
636
|
+
path = Path(fs_path).with_suffix(ext)
|
|
641
637
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
638
|
+
with open(path, "wb") as fhandle:
|
|
639
|
+
for chunk in chunk_iterator:
|
|
640
|
+
is_empty = False
|
|
641
|
+
progress_callback(len(chunk))
|
|
642
|
+
fhandle.write(chunk)
|
|
643
|
+
self.stream.close() # Closing response stream
|
|
646
644
|
|
|
647
|
-
|
|
648
|
-
|
|
645
|
+
if is_empty:
|
|
646
|
+
raise DownloadError(f"product {product.properties['id']} is empty")
|
|
649
647
|
|
|
650
|
-
|
|
648
|
+
return path
|
|
649
|
+
else:
|
|
650
|
+
raise DownloadError(
|
|
651
|
+
f"download of product {product.properties['id']} failed"
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
path = download_request(
|
|
655
|
+
product, auth, progress_callback, wait, timeout, **kwargs
|
|
656
|
+
)
|
|
651
657
|
|
|
652
658
|
with open(record_filename, "w") as fh:
|
|
653
659
|
fh.write(url)
|
|
654
660
|
logger.debug("Download recorded in %s", record_filename)
|
|
655
661
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
logger.warning(
|
|
659
|
-
"Downloaded product is not a Zip File. Please check its file type before using it"
|
|
660
|
-
)
|
|
661
|
-
new_fs_path = os.path.join(
|
|
662
|
-
os.path.dirname(fs_path),
|
|
663
|
-
sanitize(product.properties["title"]),
|
|
664
|
-
)
|
|
665
|
-
if os.path.isfile(fs_path) and not tarfile.is_tarfile(fs_path):
|
|
666
|
-
if not os.path.isdir(new_fs_path):
|
|
667
|
-
os.makedirs(new_fs_path)
|
|
668
|
-
shutil.move(fs_path, new_fs_path)
|
|
669
|
-
file_path = os.path.join(new_fs_path, os.path.basename(fs_path))
|
|
670
|
-
new_file_path = file_path[: file_path.index(".zip")]
|
|
671
|
-
shutil.move(file_path, new_file_path)
|
|
672
|
-
# in the case where the outputs extension has not been set
|
|
673
|
-
# to ".tar" in the product type nor provider configuration
|
|
674
|
-
elif tarfile.is_tarfile(fs_path):
|
|
675
|
-
if not new_fs_path.endswith(".tar"):
|
|
676
|
-
new_fs_path += ".tar"
|
|
677
|
-
shutil.move(fs_path, new_fs_path)
|
|
678
|
-
kwargs["output_extension"] = ".tar"
|
|
679
|
-
product_path = self._finalize(
|
|
680
|
-
new_fs_path,
|
|
681
|
-
progress_callback=progress_callback,
|
|
682
|
-
**kwargs,
|
|
683
|
-
)
|
|
684
|
-
product.location = path_to_uri(product_path)
|
|
685
|
-
return product_path
|
|
686
|
-
else:
|
|
687
|
-
# not a file (dir with zip extension)
|
|
688
|
-
shutil.move(fs_path, new_fs_path)
|
|
689
|
-
product.location = path_to_uri(new_fs_path)
|
|
690
|
-
return new_fs_path
|
|
691
|
-
|
|
692
|
-
if os.path.isfile(fs_path) and not (
|
|
693
|
-
zipfile.is_zipfile(fs_path) or tarfile.is_tarfile(fs_path)
|
|
662
|
+
if os.path.isfile(path) and not (
|
|
663
|
+
zipfile.is_zipfile(path) or tarfile.is_tarfile(path)
|
|
694
664
|
):
|
|
695
665
|
new_fs_path = os.path.join(
|
|
696
|
-
os.path.dirname(
|
|
666
|
+
os.path.dirname(path),
|
|
697
667
|
sanitize(product.properties["title"]),
|
|
698
668
|
)
|
|
699
669
|
if not os.path.isdir(new_fs_path):
|
|
700
670
|
os.makedirs(new_fs_path)
|
|
701
|
-
shutil.move(
|
|
671
|
+
shutil.move(path, new_fs_path)
|
|
702
672
|
product.location = path_to_uri(new_fs_path)
|
|
703
673
|
return new_fs_path
|
|
674
|
+
|
|
704
675
|
product_path = self._finalize(
|
|
705
|
-
|
|
676
|
+
str(path),
|
|
706
677
|
progress_callback=progress_callback,
|
|
707
678
|
**kwargs,
|
|
708
679
|
)
|
|
@@ -743,15 +714,6 @@ class HTTPDownload(Download):
|
|
|
743
714
|
ext = guess_extension(content_type)
|
|
744
715
|
if ext:
|
|
745
716
|
filename += ext
|
|
746
|
-
else:
|
|
747
|
-
output_extension: Optional[str] = (
|
|
748
|
-
getattr(self.config, "products", {})
|
|
749
|
-
.get(product.product_type, {})
|
|
750
|
-
.get("output_extension")
|
|
751
|
-
)
|
|
752
|
-
if output_extension:
|
|
753
|
-
filename += output_extension
|
|
754
|
-
|
|
755
717
|
return filename
|
|
756
718
|
|
|
757
719
|
def _stream_download_dict(
|
|
@@ -759,8 +721,8 @@ class HTTPDownload(Download):
|
|
|
759
721
|
product: EOProduct,
|
|
760
722
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
761
723
|
progress_callback: Optional[ProgressCallback] = None,
|
|
762
|
-
wait:
|
|
763
|
-
timeout:
|
|
724
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
725
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
764
726
|
**kwargs: Unpack[DownloadConf],
|
|
765
727
|
) -> StreamResponse:
|
|
766
728
|
r"""
|
|
@@ -834,17 +796,20 @@ class HTTPDownload(Download):
|
|
|
834
796
|
else:
|
|
835
797
|
pass
|
|
836
798
|
|
|
837
|
-
|
|
799
|
+
chunk_iterator = self._stream_download(
|
|
800
|
+
product, auth, progress_callback, **kwargs
|
|
801
|
+
)
|
|
802
|
+
|
|
838
803
|
# start reading chunks to set product.headers
|
|
839
804
|
try:
|
|
840
|
-
first_chunk = next(
|
|
805
|
+
first_chunk = next(chunk_iterator)
|
|
841
806
|
except StopIteration:
|
|
842
807
|
# product is empty file
|
|
843
808
|
logger.error("product %s is empty", product.properties["id"])
|
|
844
809
|
raise NotAvailableError(f"product {product.properties['id']} is empty")
|
|
845
810
|
|
|
846
811
|
return StreamResponse(
|
|
847
|
-
content=chain(iter([first_chunk]),
|
|
812
|
+
content=chain(iter([first_chunk]), chunk_iterator),
|
|
848
813
|
headers=product.headers,
|
|
849
814
|
)
|
|
850
815
|
|
|
@@ -902,6 +867,44 @@ class HTTPDownload(Download):
|
|
|
902
867
|
else:
|
|
903
868
|
logger.error("Error while getting resource :\n%s", tb.format_exc())
|
|
904
869
|
|
|
870
|
+
def _order_request(
|
|
871
|
+
self,
|
|
872
|
+
product: EOProduct,
|
|
873
|
+
auth: Optional[AuthBase],
|
|
874
|
+
) -> None:
|
|
875
|
+
if (
|
|
876
|
+
"orderLink" in product.properties
|
|
877
|
+
and product.properties.get("storageStatus") == OFFLINE_STATUS
|
|
878
|
+
and not product.properties.get("orderStatus")
|
|
879
|
+
):
|
|
880
|
+
self._order(product=product, auth=auth)
|
|
881
|
+
|
|
882
|
+
if (
|
|
883
|
+
product.properties.get("orderStatusLink", None)
|
|
884
|
+
and product.properties.get("storageStatus") != ONLINE_STATUS
|
|
885
|
+
):
|
|
886
|
+
self._order_status(product=product, auth=auth)
|
|
887
|
+
|
|
888
|
+
def order(
|
|
889
|
+
self,
|
|
890
|
+
product: EOProduct,
|
|
891
|
+
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
892
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
893
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
894
|
+
) -> None:
|
|
895
|
+
"""
|
|
896
|
+
Order product and poll to check its status
|
|
897
|
+
|
|
898
|
+
:param product: The EO product to download
|
|
899
|
+
:param auth: (optional) authenticated object
|
|
900
|
+
:param wait: (optional) Wait time in minutes between two order status check
|
|
901
|
+
:param timeout: (optional) Maximum time in minutes before stop checking
|
|
902
|
+
order status
|
|
903
|
+
"""
|
|
904
|
+
self._order_download_retry(product, wait, timeout)(self._order_request)(
|
|
905
|
+
product, auth
|
|
906
|
+
)
|
|
907
|
+
|
|
905
908
|
def _stream_download(
|
|
906
909
|
self,
|
|
907
910
|
product: EOProduct,
|
|
@@ -910,8 +913,9 @@ class HTTPDownload(Download):
|
|
|
910
913
|
**kwargs: Unpack[DownloadConf],
|
|
911
914
|
) -> Iterator[Any]:
|
|
912
915
|
"""
|
|
913
|
-
|
|
916
|
+
Fetches a zip file containing the assets of a given product as a stream
|
|
914
917
|
and returns a generator yielding the chunks of the file
|
|
918
|
+
|
|
915
919
|
:param product: product for which the assets should be downloaded
|
|
916
920
|
:param auth: The configuration of a plugin of type Authentication
|
|
917
921
|
:param progress_callback: A method or a callable object
|
|
@@ -928,18 +932,8 @@ class HTTPDownload(Download):
|
|
|
928
932
|
ssl_verify = getattr(self.config, "ssl_verify", True)
|
|
929
933
|
|
|
930
934
|
ordered_message = ""
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
and product.properties.get("storageStatus") == OFFLINE_STATUS
|
|
934
|
-
and not product.properties.get("orderStatus")
|
|
935
|
-
):
|
|
936
|
-
self.order_download(product=product, auth=auth)
|
|
937
|
-
|
|
938
|
-
if (
|
|
939
|
-
product.properties.get("orderStatusLink", None)
|
|
940
|
-
and product.properties.get("storageStatus") != ONLINE_STATUS
|
|
941
|
-
):
|
|
942
|
-
self.order_download_status(product=product, auth=auth)
|
|
935
|
+
# retry handled at download level
|
|
936
|
+
self._order_request(product, auth)
|
|
943
937
|
|
|
944
938
|
params = kwargs.pop("dl_url_params", None) or getattr(
|
|
945
939
|
self.config, "dl_url_params", {}
|
|
@@ -969,7 +963,7 @@ class HTTPDownload(Download):
|
|
|
969
963
|
auth = None
|
|
970
964
|
|
|
971
965
|
s = requests.Session()
|
|
972
|
-
|
|
966
|
+
self.stream = s.request(
|
|
973
967
|
req_method,
|
|
974
968
|
req_url,
|
|
975
969
|
stream=True,
|
|
@@ -979,48 +973,46 @@ class HTTPDownload(Download):
|
|
|
979
973
|
timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT,
|
|
980
974
|
verify=ssl_verify,
|
|
981
975
|
**req_kwargs,
|
|
982
|
-
)
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
product.headers["Content-Type"] = guessed_content_type
|
|
976
|
+
)
|
|
977
|
+
try:
|
|
978
|
+
self.stream.raise_for_status()
|
|
979
|
+
except requests.exceptions.Timeout as exc:
|
|
980
|
+
raise TimeOutError(exc, timeout=DEFAULT_STREAM_REQUESTS_TIMEOUT) from exc
|
|
981
|
+
except RequestException as e:
|
|
982
|
+
self._process_exception(e, product, ordered_message)
|
|
983
|
+
raise DownloadError(
|
|
984
|
+
f"download of {product.properties['id']} is empty"
|
|
985
|
+
) from e
|
|
986
|
+
else:
|
|
987
|
+
# check if product was ordered
|
|
988
|
+
|
|
989
|
+
if getattr(
|
|
990
|
+
self.stream, "status_code", None
|
|
991
|
+
) is not None and self.stream.status_code == getattr(
|
|
992
|
+
self.config, "order_status", {}
|
|
993
|
+
).get(
|
|
994
|
+
"ordered", {}
|
|
995
|
+
).get(
|
|
996
|
+
"http_code"
|
|
997
|
+
):
|
|
998
|
+
product.properties["storageStatus"] = "ORDERED"
|
|
999
|
+
self._process_exception(None, product, ordered_message)
|
|
1000
|
+
stream_size = self._check_stream_size(product) or None
|
|
1001
|
+
|
|
1002
|
+
product.headers = self.stream.headers
|
|
1003
|
+
filename = self._check_product_filename(product)
|
|
1004
|
+
product.headers["content-disposition"] = f"attachment; filename={filename}"
|
|
1005
|
+
content_type = product.headers.get("Content-Type")
|
|
1006
|
+
guessed_content_type = (
|
|
1007
|
+
guess_file_type(filename) if filename and not content_type else None
|
|
1008
|
+
)
|
|
1009
|
+
if guessed_content_type is not None:
|
|
1010
|
+
product.headers["Content-Type"] = guessed_content_type
|
|
1018
1011
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
yield chunk
|
|
1012
|
+
progress_callback.reset(total=stream_size)
|
|
1013
|
+
|
|
1014
|
+
product.filename = filename
|
|
1015
|
+
return self.stream.iter_content(chunk_size=64 * 1024)
|
|
1024
1016
|
|
|
1025
1017
|
def _stream_download_assets(
|
|
1026
1018
|
self,
|
|
@@ -1375,8 +1367,8 @@ class HTTPDownload(Download):
|
|
|
1375
1367
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
1376
1368
|
downloaded_callback: Optional[DownloadedCallback] = None,
|
|
1377
1369
|
progress_callback: Optional[ProgressCallback] = None,
|
|
1378
|
-
wait:
|
|
1379
|
-
timeout:
|
|
1370
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
1371
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
1380
1372
|
**kwargs: Unpack[DownloadConf],
|
|
1381
1373
|
):
|
|
1382
1374
|
"""
|
eodag/plugins/download/s3rest.py
CHANGED
|
@@ -95,8 +95,8 @@ class S3RestDownload(Download):
|
|
|
95
95
|
product: EOProduct,
|
|
96
96
|
auth: Optional[Union[AuthBase, Dict[str, str]]] = None,
|
|
97
97
|
progress_callback: Optional[ProgressCallback] = None,
|
|
98
|
-
wait:
|
|
99
|
-
timeout:
|
|
98
|
+
wait: float = DEFAULT_DOWNLOAD_WAIT,
|
|
99
|
+
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
|
|
100
100
|
**kwargs: Unpack[DownloadConf],
|
|
101
101
|
) -> Optional[str]:
|
|
102
102
|
"""Download method for S3 REST API.
|
|
@@ -130,9 +130,9 @@ class S3RestDownload(Download):
|
|
|
130
130
|
and "storageStatus" in product.properties
|
|
131
131
|
and product.properties["storageStatus"] != ONLINE_STATUS
|
|
132
132
|
):
|
|
133
|
-
self.http_download_plugin.
|
|
133
|
+
self.http_download_plugin._order(product=product, auth=auth)
|
|
134
134
|
|
|
135
|
-
@self.
|
|
135
|
+
@self._order_download_retry(product, wait, timeout)
|
|
136
136
|
def download_request(
|
|
137
137
|
product: EOProduct,
|
|
138
138
|
auth: AuthBase,
|
|
@@ -142,9 +142,7 @@ class S3RestDownload(Download):
|
|
|
142
142
|
):
|
|
143
143
|
# check order status
|
|
144
144
|
if product.properties.get("orderStatusLink", None):
|
|
145
|
-
self.http_download_plugin.
|
|
146
|
-
product=product, auth=auth
|
|
147
|
-
)
|
|
145
|
+
self.http_download_plugin._order_status(product=product, auth=auth)
|
|
148
146
|
|
|
149
147
|
# get bucket urls
|
|
150
148
|
bucket_name, prefix = get_bucket_name_and_prefix(
|
eodag/plugins/search/base.py
CHANGED
|
@@ -31,7 +31,7 @@ from eodag.api.product.metadata_mapping import (
|
|
|
31
31
|
from eodag.plugins.base import PluginTopic
|
|
32
32
|
from eodag.plugins.search import PreparedSearch
|
|
33
33
|
from eodag.types import model_fields_to_annotated
|
|
34
|
-
from eodag.types.queryables import Queryables
|
|
34
|
+
from eodag.types.queryables import Queryables, QueryablesDict
|
|
35
35
|
from eodag.types.search_args import SortByList
|
|
36
36
|
from eodag.utils import (
|
|
37
37
|
GENERIC_PRODUCT_TYPE,
|
|
@@ -325,35 +325,93 @@ class Search(PluginTopic):
|
|
|
325
325
|
sort_by_qs += parsed_sort_by_tpl
|
|
326
326
|
return (sort_by_qs, sort_by_qp)
|
|
327
327
|
|
|
328
|
+
def _get_product_type_queryables(
|
|
329
|
+
self, product_type: Optional[str], alias: Optional[str], filters: Dict[str, Any]
|
|
330
|
+
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
331
|
+
default_values: Dict[str, Any] = deepcopy(
|
|
332
|
+
getattr(self.config, "products", {}).get(product_type, {})
|
|
333
|
+
)
|
|
334
|
+
default_values.pop("metadata_mapping", None)
|
|
335
|
+
try:
|
|
336
|
+
filters["productType"] = product_type
|
|
337
|
+
return self.discover_queryables(**{**default_values, **filters}) or {}
|
|
338
|
+
except NotImplementedError:
|
|
339
|
+
return self.queryables_from_metadata_mapping(product_type, alias)
|
|
340
|
+
|
|
328
341
|
def list_queryables(
|
|
329
342
|
self,
|
|
330
343
|
filters: Dict[str, Any],
|
|
344
|
+
available_product_types: List[Any],
|
|
345
|
+
product_type_configs: Dict[str, Dict[str, Any]],
|
|
331
346
|
product_type: Optional[str] = None,
|
|
332
|
-
|
|
347
|
+
alias: Optional[str] = None,
|
|
348
|
+
) -> QueryablesDict:
|
|
333
349
|
"""
|
|
334
350
|
Get queryables
|
|
335
351
|
|
|
336
352
|
:param filters: Additional filters for queryables.
|
|
353
|
+
:param available_product_types: list of available product types
|
|
354
|
+
:param product_type_configs: dict containing the product type information for all used product types
|
|
337
355
|
:param product_type: (optional) The product type.
|
|
356
|
+
:param alias: (optional) alias of the product type
|
|
338
357
|
|
|
339
358
|
:return: A dictionary containing the queryable properties, associating parameters to their
|
|
340
359
|
annotated type.
|
|
341
360
|
"""
|
|
342
|
-
|
|
343
|
-
|
|
361
|
+
additional_info = (
|
|
362
|
+
"Please select a product type to get the possible values of the parameters!"
|
|
363
|
+
if not product_type
|
|
364
|
+
else ""
|
|
344
365
|
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
366
|
+
if product_type or getattr(self.config, "discover_queryables", {}).get(
|
|
367
|
+
"fetch_url", ""
|
|
368
|
+
):
|
|
369
|
+
if product_type:
|
|
370
|
+
self.config.product_type_config = product_type_configs[product_type]
|
|
371
|
+
queryables = self._get_product_type_queryables(product_type, alias, filters)
|
|
372
|
+
if getattr(self.config, "discover_queryables", {}).get(
|
|
373
|
+
"constraints_url", ""
|
|
374
|
+
):
|
|
375
|
+
additional_properties = False
|
|
376
|
+
else:
|
|
377
|
+
additional_properties = True
|
|
378
|
+
return QueryablesDict(
|
|
379
|
+
additional_properties=additional_properties,
|
|
380
|
+
additional_information=additional_info,
|
|
381
|
+
**queryables,
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
all_queryables: Dict[str, Any] = {}
|
|
385
|
+
for pt in available_product_types:
|
|
386
|
+
self.config.product_type_config = product_type_configs[pt]
|
|
387
|
+
pt_queryables = self._get_product_type_queryables(pt, None, filters)
|
|
388
|
+
# only use key and type because values and defaults will vary between product types
|
|
389
|
+
pt_queryables_neutral = {
|
|
390
|
+
k: Annotated[v.__args__[0], Field(default=None)]
|
|
391
|
+
for k, v in pt_queryables.items()
|
|
392
|
+
}
|
|
393
|
+
all_queryables.update(pt_queryables_neutral)
|
|
394
|
+
return QueryablesDict(
|
|
395
|
+
additional_properties=True,
|
|
396
|
+
additional_information=additional_info,
|
|
397
|
+
**all_queryables,
|
|
398
|
+
)
|
|
352
399
|
|
|
400
|
+
def queryables_from_metadata_mapping(
|
|
401
|
+
self, product_type: Optional[str] = None, alias: Optional[str] = None
|
|
402
|
+
) -> Dict[str, Annotated[Any, FieldInfo]]:
|
|
403
|
+
"""
|
|
404
|
+
Extract queryable parameters from product type metadata mapping.
|
|
405
|
+
:param product_type: product type id (optional)
|
|
406
|
+
:param alias: (optional) alias of the product type
|
|
407
|
+
:returns: dict of annotated queryables
|
|
408
|
+
"""
|
|
353
409
|
metadata_mapping: Dict[str, Any] = deepcopy(
|
|
354
410
|
self.get_metadata_mapping(product_type)
|
|
355
411
|
)
|
|
356
412
|
|
|
413
|
+
queryables: Dict[str, Annotated[Any, FieldInfo]] = {}
|
|
414
|
+
|
|
357
415
|
for param in list(metadata_mapping.keys()):
|
|
358
416
|
if NOT_MAPPED in metadata_mapping[param] or not isinstance(
|
|
359
417
|
metadata_mapping[param], list
|
|
@@ -363,28 +421,18 @@ class Search(PluginTopic):
|
|
|
363
421
|
eodag_queryables = copy_deepcopy(
|
|
364
422
|
model_fields_to_annotated(Queryables.model_fields)
|
|
365
423
|
)
|
|
424
|
+
# add default value for product type
|
|
425
|
+
if alias:
|
|
426
|
+
eodag_queryables.pop("productType")
|
|
427
|
+
eodag_queryables["productType"] = Annotated[str, Field(default=alias)]
|
|
366
428
|
for k, v in eodag_queryables.items():
|
|
367
429
|
eodag_queryable_field_info = (
|
|
368
430
|
get_args(v)[1] if len(get_args(v)) > 1 else None
|
|
369
431
|
)
|
|
370
432
|
if not isinstance(eodag_queryable_field_info, FieldInfo):
|
|
371
433
|
continue
|
|
372
|
-
# keep default field info of eodag queryables
|
|
373
|
-
if k in filters and k in queryables:
|
|
374
|
-
queryable_field_info = (
|
|
375
|
-
get_args(queryables[k])[1]
|
|
376
|
-
if len(get_args(queryables[k])) > 1
|
|
377
|
-
else None
|
|
378
|
-
)
|
|
379
|
-
if not isinstance(queryable_field_info, FieldInfo):
|
|
380
|
-
continue
|
|
381
|
-
queryable_field_info.default = filters[k]
|
|
382
|
-
continue
|
|
383
|
-
if k in queryables:
|
|
384
|
-
continue
|
|
385
434
|
if eodag_queryable_field_info.is_required() or (
|
|
386
435
|
(eodag_queryable_field_info.alias or k) in metadata_mapping
|
|
387
436
|
):
|
|
388
437
|
queryables[k] = v
|
|
389
|
-
|
|
390
438
|
return queryables
|