Download product as stream to S3

Hint

You can run this notebook in a live session with Binder.

Download product as stream to S3#

In this tutorial, we’ll use EOProduct.stream_download() method to download its data as a stream and upload it on a S3 bucket without storing it locally.

Configure and prepare S3#

In this tutorial, we’ll run a local S3 using rustfs and Docker, but feel free to use another one.

In a terminal, execute the following command to run a minimal local S3 using default settings:

docker run --rm -p 9000:9000 rustfs/rustfs:latest

Connect to running S3:

[1]:
import boto3

# default rustfs settings
S3_HOST = "http://127.0.0.1:9000"
S3_USERNAME = "rustfsadmin"
S3_PASSWORD = "rustfsadmin"

# desired upload settings
S3_BUCKET = "test"
CHUNK_SIZE = 8388608  # 8Mo

# Connect to local S3
s3 = boto3.client(
    "s3",
    aws_access_key_id=S3_USERNAME,
    aws_secret_access_key=S3_PASSWORD,
    endpoint_url=S3_HOST
)

List existing buckets:

[2]:
buckets = s3.list_buckets()
buckets_names = [b.get('Name') for b in buckets.get('Buckets', [])]
buckets_names
[2]:
[]

Create desired bucket if it does not exist, then list avaible buckets again:

[3]:
if S3_BUCKET not in buckets_names:
    s3.create_bucket(ACL='private', Bucket=S3_BUCKET)

buckets = s3.list_buckets()
buckets_names = [b.get('Name') for b in buckets.get('Buckets', [])]
buckets_names
[3]:
['test']

Check bucket content:

[4]:
s3.list_objects_v2(Bucket=S3_BUCKET).get("Contents", [])
[4]:
[]

Search using EODAG#

Make sure you configured cop_dataspace credentials, or use another data provider.

[5]:
from eodag import EODataAccessGateway

dag = EODataAccessGateway()
results = dag.search(
    provider="cop_dataspace",
    collection="S2_MSI_L1C",
    start="2026-01-01",
    end="2026-01-31",
)
product = results[0]
product
[5]:
EOProduct
provider: 'cop_dataspace',
collection: 'S2_MSI_L1C',
properties["id"]: 'S2C_MSIL1C_20260101T000441_N0511_R073_T57NUF_20260101T013646',
properties["start_datetime"]: '2026-01-01T00:04:41.025000Z',
properties["end_datetime"]: '2026-01-01T00:04:41.025000Z',
properties: (30){
constellation: 'SENTINEL-2',
datetime: '2026-01-01T00:04:41.025000Z',
end_datetime: '2026-01-01T00:04:41.025000Z',
id: 'S2C_MSIL1C_20260101T000441_N0511_R073_T57NUF_20260101T013646',
instruments: ['MSI' ],
platform: 'S2C',
providers: [{ 'name': 'ESA' , 'roles': ['producer' ] } ],
published: '2026-01-01T02:14:33.246522Z',
start_datetime: '2026-01-01T00:04:41.025000Z',
title: 'S2C_MSIL1C_20260101T000441_N0511_R073_T57NUF_20260101T013646',
uid: '26f31e51-3ce6-4ffe-a293-f98a680ca95c',
updated: '2026-01-01T02:14:33.246522Z',
cop_dataspace:sourceProduct: 'S2C_OPER_MSI_L1C_TL_2CPS_20260101T013646_A006907_T57NUF_N05.11,S2C_OPER_MSI_L1C_DS_2CPS_20260101T013646_S20260101T000443_N05.11,S2C_OPER_MSI_L1C_TC_2CPS_20260101T013646_A006907_T57NUF_N05.11.jp2',
cop_dataspace:sourceProductOriginDate: '2026-01-01T02:11:30Z,2026-01-01T02:11:26Z,2026-01-01T02:11:30Z',
eo:cloud_cover: 99.80956352516,
eodag:download_link: 'https://catalogue.dataspace.copernicus.eu/odata/v1/Products(26f31e51-3ce6-4ffe-a293-f98a680ca95c)/$value',
eodag:quicklook: 'https://catalogue.dataspace.copernicus.eu/odata/v1/Assets(2911b42e-c26e-42f5-beb3-d9e8519964bc)/$value',
eodag:thumbnail: 'https://catalogue.dataspace.copernicus.eu/odata/v1/Assets(2911b42e-c26e-42f5-beb3-d9e8519964bc)/$value',
grid:code: 'MGRS-57NUF',
order:status: 'succeeded',
processing:datetime: '2026-01-01T01:36:46.000000Z',
processing:level: 'S2MSI1C',
processing:version: '05.11',
product:type: 'S2MSI1C',
s2:datastrip_id: 'S2C_OPER_MSI_L1C_DS_2CPS_20260101T013646_S20260101T000443_N05.11',
s2:datatake_id: 'GS2C_20260101T000441_006907_N05.11',
s2:datatake_type: 'INS-NOBS',
s2:tile_id: 'S2C_OPER_MSI_L1C_TL_2CPS_20260101T013646_A006907_T57NUF_N05.11',
sat:absolute_orbit: 6907,
sat:relative_orbit: 73,
}
assets: (0)
geometry
thumbnail

Upload to S3#

[6]:
from tqdm.auto import tqdm

stream = product.stream_download()

s3_key = "{}/{}".format(
    product.properties["title"],
    stream.filename
)

with tqdm(unit="B", unit_scale=True) as pbar:
    s3.upload_fileobj(
        stream.content,
        S3_BUCKET,
        s3_key,
        Config=boto3.s3.transfer.TransferConfig(multipart_threshold=CHUNK_SIZE),
        Callback=pbar.update
    )

Uploaded file should now be listed in bucket content:

[7]:
s3.list_objects_v2(Bucket=S3_BUCKET).get("Contents", [])
[7]:
[{'Key': 'S2C_MSIL1C_20260101T000441_N0511_R073_T57NUF_20260101T013646/S2C_MSIL1C_20260101T000441_N0511_R073_T57NUF_20260101T013646.zip',
  'LastModified': datetime.datetime(2026, 2, 20, 17, 9, 42, 997000, tzinfo=tzutc()),
  'ETag': '"183e1c7afa997137f28c7c29093f85a7-3"',
  'Size': 20619376,
  'StorageClass': 'STANDARD'}]

Note

Available assets can also be downloaded as stream using Asset.stream_download():

product.assets["foo"].stream_download()
[ ]: