Bagaimana cara menyimpan objek S3 ke file menggunakan boto3

132

Saya mencoba melakukan "hello world" dengan klien boto3 baru untuk AWS.

Use-case yang saya miliki cukup sederhana: dapatkan objek dari S3 dan simpan ke file.

Dalam boto 2.XI akan melakukannya seperti ini:

import boto
key = boto.connect_s3().get_bucket('foo').get_key('foo')
key.get_contents_to_filename('/tmp/foo')

Dalam boto 3. Saya tidak dapat menemukan cara bersih untuk melakukan hal yang sama, jadi saya mengulangi secara manual objek "Streaming":

import boto3
key = boto3.resource('s3').Object('fooo', 'docker/my-image.tar.gz').get()
with open('/tmp/my-image.tar.gz', 'w') as f:
    chunk = key['Body'].read(1024*8)
    while chunk:
        f.write(chunk)
        chunk = key['Body'].read(1024*8)

atau

import boto3
key = boto3.resource('s3').Object('fooo', 'docker/my-image.tar.gz').get()
with open('/tmp/my-image.tar.gz', 'w') as f:
    for chunk in iter(lambda: key['Body'].read(4096), b''):
        f.write(chunk)

Dan itu bekerja dengan baik. Saya bertanya-tanya apakah ada fungsi "asli" boto3 yang akan melakukan tugas yang sama?

Vor
sumber

Jawaban:

216

Ada kustomisasi yang masuk ke Boto3 baru-baru ini yang membantu dengan ini (antara lain). Saat ini terpapar pada klien S3 tingkat rendah, dan dapat digunakan seperti ini:

s3_client = boto3.client('s3')
open('hello.txt').write('Hello, world!')

# Upload the file to S3
s3_client.upload_file('hello.txt', 'MyBucket', 'hello-remote.txt')

# Download the file from S3
s3_client.download_file('MyBucket', 'hello-remote.txt', 'hello2.txt')
print(open('hello2.txt').read())

Fungsi-fungsi ini akan secara otomatis menangani membaca / menulis file serta melakukan unggahan banyak bagian secara paralel untuk file besar.

Catatan yang s3_client.download_filetidak akan membuat direktori. Itu dapat dibuat sebagai pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).

Daniel
sumber
1
@Aniel: Terima kasih atas balasan Anda. Bisakah Anda menjawab jawabannya jika saya ingin mengunggah file menggunakan unggahan multipart di boto3.
Rahul KP
1
@RahulKumarPatle upload_filemetode ini akan secara otomatis menggunakan unggahan multi bagian untuk file besar.
Daniel
4
Bagaimana Anda memberikan kredensial kepada Anda menggunakan pendekatan ini?
JHowIX
1
@JHowIX Anda dapat mengkonfigurasi kredensial secara global (mis. Lihat boto3.readthedocs.org/en/latest/guide/… ) atau Anda dapat meneruskannya saat membuat klien. Lihat boto3.readthedocs.org/en/latest/reference/core/… untuk info lebih lanjut tentang opsi yang tersedia!
Daniel
2
@VladNikiporoff "Unggah dari sumber ke tujuan" "Unduh dari sumber ke tujuan"
jkdev
59

boto3 sekarang memiliki antarmuka yang lebih bagus daripada klien:

resource = boto3.resource('s3')
my_bucket = resource.Bucket('MyBucket')
my_bucket.download_file(key, local_filename)

Ini dengan sendirinya tidak jauh lebih baik daripada clientdalam jawaban yang diterima (meskipun dokumen mengatakan bahwa itu melakukan pekerjaan yang lebih baik mencoba mengunggah unggahan dan unduhan pada kegagalan) tetapi mengingat bahwa sumber daya umumnya lebih ergonomis (misalnya, ember s3 dan sumber daya objek lebih bagus daripada metode klien) ini memungkinkan Anda untuk tetap berada di lapisan sumber daya tanpa harus drop down.

Resources umumnya dapat dibuat dengan cara yang sama seperti klien, dan mereka mengambil semua atau sebagian besar argumen yang sama dan hanya meneruskannya ke klien internal mereka.

quodlibetor
sumber
1
Contoh yang bagus, dan untuk menambahkan karena pertanyaan awal bertanya tentang menyimpan objek, metode yang relevan di sini adalah my_bucket.upload_file()(atau my_bucket.upload_fileobj()jika Anda memiliki objek BytesIO).
SMX
Di mana tepatnya dokumen mengatakan bahwa resourcemelakukan pekerjaan yang lebih baik dalam mencoba ulang? Saya tidak dapat menemukan indikasi seperti itu.
Acumenus
42

Bagi Anda yang ingin mensimulasikan set_contents_from_stringmetode boto2 seperti, Anda dapat mencoba

import boto3
from cStringIO import StringIO

s3c = boto3.client('s3')
contents = 'My string to save to S3 object'
target_bucket = 'hello-world.by.vor'
target_file = 'data/hello.txt'
fake_handle = StringIO(contents)

# notice if you do fake_handle.read() it reads like a file handle
s3c.put_object(Bucket=target_bucket, Key=target_file, Body=fake_handle.read())

Untuk Python3:

Dalam python3 baik StringIO dan cStringIO hilang . Gunakan StringIOimpor seperti:

from io import StringIO

Untuk mendukung kedua versi:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO
cgseller
sumber
15
Itulah jawabannya. Inilah pertanyaannya: "Bagaimana Anda menyimpan string ke objek S3 menggunakan boto3?"
jkdev
untuk python3 saya harus menggunakan import io; fake_handl e = io.StringIO (isi)
Felix
16
# Preface: File is json with contents: {'name': 'Android', 'status': 'ERROR'}

import boto3
import io

s3 = boto3.resource('s3')

obj = s3.Object('my-bucket', 'key-to-file.json')
data = io.BytesIO()
obj.download_fileobj(data)

# object is now a bytes string, Converting it to a dict:
new_dict = json.loads(data.getvalue().decode("utf-8"))

print(new_dict['status']) 
# Should print "Error"
Lord Sumner
sumber
14
Jangan pernah masukkan AWS_ACCESS_KEY_ID Anda atau AWS_SECRET_ACCESS_KEY Anda dalam kode Anda. Ini harus didefinisikan dengan aws configureperintah awscli dan mereka akan ditemukan secara otomatis oleh botocore.
Miles Erickson
3

Saat Anda ingin membaca file dengan konfigurasi yang berbeda dari yang standar, jangan ragu untuk menggunakan mpu.aws.s3_download(s3path, destination)kode yang disalin secara langsung atau yang disalin:

def s3_download(source, destination,
                exists_strategy='raise',
                profile_name=None):
    """
    Copy a file from an S3 source to a local destination.

    Parameters
    ----------
    source : str
        Path starting with s3://, e.g. 's3://bucket-name/key/foo.bar'
    destination : str
    exists_strategy : {'raise', 'replace', 'abort'}
        What is done when the destination already exists?
    profile_name : str, optional
        AWS profile

    Raises
    ------
    botocore.exceptions.NoCredentialsError
        Botocore is not able to find your credentials. Either specify
        profile_name or add the environment variables AWS_ACCESS_KEY_ID,
        AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.
        See https://boto3.readthedocs.io/en/latest/guide/configuration.html
    """
    exists_strategies = ['raise', 'replace', 'abort']
    if exists_strategy not in exists_strategies:
        raise ValueError('exists_strategy \'{}\' is not in {}'
                         .format(exists_strategy, exists_strategies))
    session = boto3.Session(profile_name=profile_name)
    s3 = session.resource('s3')
    bucket_name, key = _s3_path_split(source)
    if os.path.isfile(destination):
        if exists_strategy is 'raise':
            raise RuntimeError('File \'{}\' already exists.'
                               .format(destination))
        elif exists_strategy is 'abort':
            return
    s3.Bucket(bucket_name).download_file(key, destination)

from collections import namedtuple

S3Path = namedtuple("S3Path", ["bucket_name", "key"])


def _s3_path_split(s3_path):
    """
    Split an S3 path into bucket and key.

    Parameters
    ----------
    s3_path : str

    Returns
    -------
    splitted : (str, str)
        (bucket, key)

    Examples
    --------
    >>> _s3_path_split('s3://my-bucket/foo/bar.jpg')
    S3Path(bucket_name='my-bucket', key='foo/bar.jpg')
    """
    if not s3_path.startswith("s3://"):
        raise ValueError(
            "s3_path is expected to start with 's3://', " "but was {}"
            .format(s3_path)
        )
    bucket_key = s3_path[len("s3://"):]
    bucket_name, key = bucket_key.split("/", 1)
    return S3Path(bucket_name, key)
Martin Thoma
sumber
Tidak bekerja NameError: name '_s3_path_split' is not defined
Dave Liu
@DaveLiu Terima kasih atas petunjuknya; Saya telah menyesuaikan kodenya. Paket itu seharusnya sudah berfungsi sebelumnya.
Martin Thoma
1

Catatan: Saya berasumsi Anda telah mengonfigurasi otentikasi secara terpisah. Kode di bawah ini untuk mengunduh objek tunggal dari bucket S3.

import boto3

#initiate s3 client 
s3 = boto3.resource('s3')

#Download object to the file    
s3.Bucket('mybucket').download_file('hello.txt', '/tmp/hello.txt')
Tushar Niras
sumber