Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api_reference/eoproduct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Download
--------

.. automethod:: EOProduct.download
.. automethod:: EOProduct.stream_download
.. automethod:: EOProduct.get_quicklook

Conversion
Expand Down
576 changes: 576 additions & 0 deletions docs/notebooks/tutos/tuto_stream_download_to_s3.ipynb

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions docs/tutos.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ or run locally after being downloaded (see how to :ref:`install_notebooks`).
notebooks/tutos/tuto_burnt_areas_snappy.ipynb
notebooks/tutos/tuto_dedt_lumi_roi.ipynb
notebooks/tutos/tuto_fedeo_ceda.ipynb
notebooks/tutos/tuto_stream_download_to_s3.ipynb

.. grid:: 1 2 2 3
:gutter: 4
Expand Down Expand Up @@ -124,3 +125,11 @@ or run locally after being downloaded (see how to :ref:`install_notebooks`).
:shadow: md

Access Fedeo data through the CEDA API using the dedicated EODAG plugin.

.. grid-item-card:: Download product as stream to S3
:link: notebooks/tutos/tuto_stream_download_to_s3
:link-type: doc
:text-align: center
:shadow: md

Download a product data as a stream and upload it on a S3 bucket without storing it locally.
10 changes: 9 additions & 1 deletion eodag/api/product/_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
if TYPE_CHECKING:
from eodag.api.product import EOProduct
from eodag.types.download_args import DownloadConf
from eodag.utils import Unpack
from eodag.utils import StreamResponse, Unpack


class AssetsDict(UserDict):
Expand Down Expand Up @@ -190,6 +190,14 @@ def download(self, **kwargs: Unpack[DownloadConf]) -> str:
"""
return self.product.download(asset=self.key, **kwargs)

def stream_download(self, **kwargs: Unpack[DownloadConf]) -> StreamResponse:
"""Downloads a single asset as StreamResponse

:param kwargs: (optional) Additional named-arguments passed to `plugin.download()`
:returns: StreamResponse stream representation of the asset file
"""
return self.product.stream_download(asset=self.key, **kwargs)

def _repr_html_(self):
thead = f"""<thead><tr><td style='text-align: left; color: grey;'>
{type(self).__name__}&ensp;-&ensp;{self.key}
Expand Down
50 changes: 47 additions & 3 deletions eodag/api/product/_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import re
import tempfile
from datetime import datetime
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast
from typing import TYPE_CHECKING, Any, Iterable, Literal, Optional, Union, cast

import orjson
import requests
Expand Down Expand Up @@ -59,6 +59,7 @@
STAC_VERSION,
USER_AGENT,
ProgressCallback,
StreamResponse,
format_string,
get_geometry_from_various,
)
Expand All @@ -77,7 +78,6 @@
from eodag.types.download_args import DownloadConf
from eodag.utils import Unpack


logger = logging.getLogger("eodag.product")


Expand Down Expand Up @@ -481,6 +481,50 @@ def download(

return fs_path

def stream_download(
self,
byte_range: tuple[Optional[int], Optional[int]] = (None, None),
compress: Literal["zip", "raw", "auto"] = "auto",
wait: float = DEFAULT_DOWNLOAD_WAIT,
timeout: float = DEFAULT_DOWNLOAD_TIMEOUT,
**kwargs: Unpack[DownloadConf],
) -> StreamResponse:
"""Download as StreamResponse the EO product using the provided download plugin and the
authenticator if necessary.

:param byte_range: (optional) Tuple of first index / last index byte to read
:param compress: (optional) "zip", "raw", "auto"
:param wait: (optional) If download fails, wait time in minutes between
two download tries
:param timeout: (optional) If download fails, maximum time in minutes
before stop retrying to download
:param kwargs: additional kwargs like `dl_url_params` (dict) can be provided
and will override any other values defined in a configuration
file or with environment variables.
:returns: StreamResponse Stream representation of a file
:raises: :class:`~eodag.utils.exceptions.PluginImplementationError`
:raises: :class:`RuntimeError`
"""
if self.downloader is None:
raise RuntimeError(
"EO product is unable to stream_download itself due to lacking of a "
"download plugin"
)
auth = (
self.downloader_auth.authenticate()
if self.downloader_auth is not None
else self.downloader_auth
)
return self.downloader.stream_download(
self,
auth,
byte_range,
compress,
wait=wait,
timeout=timeout,
**kwargs,
)

def _init_progress_bar(
self,
progress_callback: Optional[ProgressCallback],
Expand Down Expand Up @@ -536,7 +580,7 @@ def _download_quicklook(
verify=ssl_verify,
) as stream:
stream.raise_for_status()
stream_size = int(stream.headers.get("content-length", 0))
stream_size = int(stream.headers.get("Content-Length", 0))
progress_callback.reset(stream_size)
with open(quicklook_file, "wb") as fhandle:
for chunk in stream.iter_content(chunk_size=64 * 1024):
Expand Down
2 changes: 1 addition & 1 deletion eodag/plugins/apis/usgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def download_request(
raise NotAvailableError(error_message)
else:
stream_size = (
int(stream.headers.get("content-length", 0)) or None
int(stream.headers.get("Content-Length", 0)) or None
)
progress_callback.reset(total=stream_size)
with open(fs_path, "wb") as fhandle:
Expand Down
5 changes: 5 additions & 0 deletions eodag/plugins/download/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""EODAG download package"""
from .aws import AwsDownload
from .base import Download
from .http import HTTPDownload

__all__ = ["Download", "AwsDownload", "HTTPDownload"]
8 changes: 6 additions & 2 deletions eodag/plugins/download/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
properties_from_xml,
)
from eodag.plugins.authentication.aws_auth import AwsAuth, raise_if_auth_error
from eodag.plugins.download.base import Download
from eodag.utils import (
DEFAULT_DOWNLOAD_TIMEOUT,
DEFAULT_DOWNLOAD_WAIT,
Expand All @@ -62,6 +61,8 @@
)
from eodag.utils.s3 import S3FileInfo, open_s3_zipped_object, stream_download_from_s3

from .base import Download

if TYPE_CHECKING:
from mypy_boto3_s3 import S3ServiceResource
from mypy_boto3_s3.client import S3Client
Expand Down Expand Up @@ -680,7 +681,7 @@ def _get_unique_products(

return unique_product_chunks

def _stream_download_dict(
def stream_download(
self,
product: EOProduct,
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
Expand Down Expand Up @@ -1176,3 +1177,6 @@ def get_chunk_dest_path(

logger.debug(f"Downloading {chunk.key} to {product_path}")
return product_path


__all__ = ["AwsDownload"]
12 changes: 9 additions & 3 deletions eodag/plugins/download/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import tarfile
import tempfile
import zipfile
from abc import abstractmethod
from datetime import datetime, timedelta
from pathlib import Path
from time import sleep
Expand Down Expand Up @@ -102,6 +103,7 @@ def __init__(self, provider: str, config: PluginConfig) -> None:
super(Download, self).__init__(provider, config)
self._authenticate = bool(getattr(self.config, "authenticate", False))

@abstractmethod
def download(
self,
product: EOProduct,
Expand Down Expand Up @@ -134,7 +136,8 @@ def download(
"A Download plugin must implement a method named download"
)

def _stream_download_dict(
@abstractmethod
def stream_download(
self,
product: EOProduct,
auth: Optional[Union[AuthBase, S3ServiceResource]] = None,
Expand All @@ -145,7 +148,7 @@ def _stream_download_dict(
**kwargs: Unpack[DownloadConf],
) -> StreamResponse:
r"""
Base _stream_download_dict method. Not available, it must be defined for each plugin.
Base stream_download method. Not available, it must be defined for each plugin.

:param product: The EO product to download
:param auth: (optional) authenticated object
Expand All @@ -160,7 +163,7 @@ def _stream_download_dict(
:returns: Dictionary of :class:`~fastapi.responses.StreamingResponse` keyword-arguments
"""
raise NotImplementedError(
"Download streaming must be implemented using a method named _stream_download_dict"
"Download streaming must be implemented using a method named stream_download"
)

def _prepare_download(
Expand Down Expand Up @@ -773,3 +776,6 @@ def _config_executor(

if thread_name_prefix:
executor._thread_name_prefix = "eodag-download-all"


__all__ = ["Download"]
Loading
Loading