Jak zapisać obiekt S3 do pliku za pomocą boto3

132

Próbuję stworzyć "hello world" z nowym klientem boto3 dla AWS.

Mój przypadek użycia jest dość prosty: pobierz obiekt z S3 i zapisz go do pliku.

W boto 2.X zrobiłbym to tak:

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

Na boto 3. Nie mogę znaleźć prostego sposobu na zrobienie tego samego, więc ręcznie iteruję po obiekcie „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)

lub

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)

I działa dobrze. Zastanawiałem się, czy jest jakaś "natywna" funkcja boto3, która będzie wykonywać to samo zadanie?

Vor
źródło

Odpowiedzi:

220

Niedawno w Boto3 pojawiło się dostosowanie, które pomaga w tym (między innymi). Jest obecnie ujawniany na kliencie S3 niskiego poziomu i może być używany w następujący sposób:

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())

Te funkcje automatycznie obsługują odczytywanie / zapisywanie plików, a także równoległe przesyłanie wielu części w przypadku dużych plików.

Zauważ, że s3_client.download_filenie utworzy katalogu. Można go utworzyć jako plik pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).

Daniel
źródło
1
@Daniel: Dziękuję za twoją odpowiedź. Czy możesz odpowiedzieć, jeśli chcę przesłać plik za pomocą przesyłania wieloczęściowego w boto3.
Rahul KP
1
@RahulKumarPatle upload_filemetoda automatycznie użyje przesyłania wieloczęściowego w przypadku dużych plików.
Daniel
4
W jaki sposób przekazujesz swoje dane uwierzytelniające za pomocą tego podejścia?
JHowIX
1
@JHowIX możesz albo skonfigurować poświadczenia globalnie (np. Zobacz boto3.readthedocs.org/en/latest/guide/ ... ) lub możesz je przekazać podczas tworzenia klienta. Więcej informacji na temat dostępnych opcji znajdziesz na boto3.readthedocs.org/en/latest/reference/core/… !
Daniel
2
@VladNikiporoff "Prześlij ze źródła do miejsca docelowego" "Pobierz ze źródła do miejsca docelowego"
jkdev
60

boto3 ma teraz ładniejszy interfejs niż klient:

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

To samo w sobie nie jest ogromnie lepszy niż clientodpowiedź dozwolone (chociaż docs powiedzieć, że ma lepszą pracę Ponowna próba przesyłanie i pobieranie na niepowodzenie), ale biorąc pod uwagę, że zasoby są na ogół bardziej ergonomiczne (np S3 wiadra i obiektów zasoby są ładniejsze niż metody klienckie) pozwala to pozostać w warstwie zasobów bez konieczności opuszczania.

Resources generalnie mogą być tworzone w taki sam sposób jak klienci, przyjmują wszystkie lub większość tych samych argumentów i po prostu przekazują je swoim klientom wewnętrznym.

quodlibetor
źródło
1
Świetny przykład, a do dodania, ponieważ pierwotne pytanie dotyczy zapisania obiektu, odpowiednią metodą jest tutaj my_bucket.upload_file()(lub my_bucket.upload_fileobj()jeśli masz obiekt BytesIO).
SMX
1
Dokładnie gdzie doktorzy mówią, że resourcelepiej radzi sobie z ponowną próbą? Nie mogłem znaleźć takiego wskazania.
Acumenus
43

Dla tych z Was, którzy chcieliby symulować metody set_contents_from_stringpodobne do boto2, możesz spróbować

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())

W przypadku Python3:

W pythonie3 zniknęły zarówno StringIO, jak i cStringIO . Użyj StringIOimportu jak:

from io import StringIO

Aby obsługiwać obie wersje:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO
cgseller
źródło
16
To jest odpowiedź. Oto pytanie: „Jak zapisać ciąg znaków do obiektu S3 za pomocą boto3?”
jkdev
dla python3 musiałem użyć importu io; fake_handl e = io.StringIO (zawartość)
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
źródło
14
Nigdy nie umieszczaj swojego AWS_ACCESS_KEY_ID ani AWS_SECRET_ACCESS_KEY w kodzie. Powinny być zdefiniowane za pomocą aws configurepolecenia awscli, a zostaną znalezione automatycznie przez botocore.
Miles Erickson
3

Uwaga: zakładam, że skonfigurowałeś uwierzytelnianie oddzielnie. Poniższy kod służy do pobrania pojedynczego obiektu z wiadra 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
źródło
Ten kod nie zostanie pobrany z folderu wewnętrznego i s3, czy jest sposób, aby to zrobić w ten sposób?
Marilu
3

Jeśli chcesz odczytać plik z inną konfiguracją niż domyślna, możesz użyć mpu.aws.s3_download(s3path, destination)bezpośrednio lub kodu wklejonego:

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
źródło
Nie działa. NameError: name '_s3_path_split' is not defined
Dave Liu
@DaveLiu Dziękuję za podpowiedź; Poprawiłem kod. Jednak pakiet powinien był działać wcześniej.
Martin Thoma
0

Jeśli chcesz pobrać wersję pliku, musisz użyć get_object.

import boto3

bucket = 'bucketName'
prefix = 'path/to/file/'
filename = 'fileName.ext'

s3c = boto3.client('s3')
s3r = boto3.resource('s3')

if __name__ == '__main__':
    for version in s3r.Bucket(bucket).object_versions.filter(Prefix=prefix + filename):
        file = version.get()
        version_id = file.get('VersionId')
        obj = s3c.get_object(
            Bucket=bucket,
            Key=prefix + filename,
            VersionId=version_id,
        )
        with open(f"{filename}.{version_id}", 'wb') as f:
            for chunk in obj['Body'].iter_chunks(chunk_size=4096):
                f.write(chunk)

Odniesienie: https://botocore.amazonaws.com/v1/documentation/api/latest/reference/response.html

chrześcijanin
źródło