So speichern Sie ein S3-Objekt mit boto3 in einer Datei

131

Ich versuche eine "Hallo Welt" mit dem neuen boto3 Client für AWS zu machen.

Der Anwendungsfall, den ich habe, ist ziemlich einfach: Holen Sie sich ein Objekt aus S3 und speichern Sie es in der Datei.

In Boto 2.XI würde es so machen:

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

In Boto 3. Ich kann keinen sauberen Weg finden, um dasselbe zu tun, daher iteriere ich manuell über das "Streaming" -Objekt:

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)

oder

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)

Und es funktioniert gut. Ich habe mich gefragt, ob es eine "native" boto3-Funktion gibt, die dieselbe Aufgabe übernimmt.

Vor
quelle

Antworten:

215

In Boto3 wurde kürzlich eine Anpassung vorgenommen, die (unter anderem) dabei hilft. Es ist derzeit auf dem Low-Level-S3-Client verfügbar und kann folgendermaßen verwendet werden:

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

Diese Funktionen übernehmen automatisch das Lesen / Schreiben von Dateien sowie das parallele Hochladen mehrerer Teile für große Dateien.

Beachten Sie, dass s3_client.download_filekein Verzeichnis erstellt wird. Es kann erstellt werden als pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).

Daniel
quelle
1
@ Daniel: Danke für deine Antwort. Können Sie die Antwort beantworten, wenn ich eine Datei mit einem mehrteiligen Upload in boto3 hochladen möchte?
Rahul KP
1
@RahulKumarPatle Die upload_fileMethode verwendet automatisch mehrteilige Uploads für große Dateien.
Daniel
4
Wie geben Sie Ihre Anmeldeinformationen mit diesem Ansatz weiter?
JHowIX
1
@JHowIX Sie können die Anmeldeinformationen entweder global konfigurieren (siehe z. B. boto3.readthedocs.org/en/latest/guide/… ) oder sie beim Erstellen des Clients übergeben. Siehe boto3.readthedocs.org/en/latest/reference/core/... für weitere Informationen zu den verfügbaren Optionen
Daniel
2
@VladNikiporoff "Von Quelle zu Ziel hochladen" "Von Quelle zu Ziel herunterladen"
jkdev
59

boto3 hat jetzt eine schönere Oberfläche als der Client:

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

Dies allein ist nicht enorm besser als die clientin der akzeptierten Antwort (obwohl die docs sagen , dass es eine bessere Arbeit Up- und Downloads bei einem Fehler Neuen Versuch) aber wenn man bedenkt , dass die Ressourcen im Allgemeinen ergonomischer sind (zum Beispiel der s3 Eimer und Objekt - Ressourcen sind netter als die Client-Methoden) Dies ermöglicht es Ihnen, auf der Ressourcenebene zu bleiben, ohne herunterfallen zu müssen.

Resources Im Allgemeinen können sie auf die gleiche Weise wie Clients erstellt werden. Sie verwenden alle oder die meisten der gleichen Argumente und leiten sie einfach an ihre internen Clients weiter.

Quodlibetor
quelle
1
Tolles Beispiel, und um hinzuzufügen, da die ursprüngliche Frage nach dem Speichern eines Objekts fragt, ist die relevante Methode hier my_bucket.upload_file()(oder my_bucket.upload_fileobj()wenn Sie ein BytesIO-Objekt haben).
SMX
Wo genau sagen die Dokumente, dass resourcees besser ist, es erneut zu versuchen? Ich konnte keinen solchen Hinweis finden.
Acumenus
42

Für diejenigen unter Ihnen, die set_contents_from_stringähnliche boto2-Methoden simulieren möchten, können Sie es versuchen

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

Für Python3:

In Python3 sind sowohl StringIO als auch cStringIO weg . Verwenden Sie den StringIOImport wie folgt:

from io import StringIO

So unterstützen Sie beide Versionen:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO
cgseller
quelle
15
Das ist die Antwort. Hier ist die Frage: "Wie speichert man mit boto3 eine Zeichenfolge in einem S3-Objekt?"
jkdev
für python3 musste ich import io verwenden; fake_handl e = io.StringIO (Inhalt)
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
quelle
14
Fügen Sie niemals Ihre AWS_ACCESS_KEY_ID oder Ihre AWS_SECRET_ACCESS_KEY in Ihren Code ein. Diese sollten mit dem aws configureBefehl awscli definiert werden und werden automatisch von gefunden botocore.
Miles Erickson
3

Wenn Sie eine Datei mit einer anderen Konfiguration als der Standardkonfiguration lesen möchten, können Sie entweder mpu.aws.s3_download(s3path, destination)direkt oder den kopierten Code verwenden:

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
quelle
Funktioniert nicht NameError: name '_s3_path_split' is not defined
Dave Liu
@ DaveLiu Danke für den Hinweis; Ich habe den Code angepasst. Das Paket sollte jedoch vorher funktioniert haben.
Martin Thoma
1

Hinweis: Ich gehe davon aus, dass Sie die Authentifizierung separat konfiguriert haben. Der folgende Code dient zum Herunterladen des einzelnen Objekts aus dem S3-Bucket.

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
quelle