Comment enregistrer un objet S3 dans un fichier à l'aide de boto3

132

J'essaye de faire un "bonjour le monde" avec le nouveau client boto3 pour AWS.

Le cas d'utilisation que j'ai est assez simple: obtenir un objet de S3 et l'enregistrer dans le fichier.

Dans boto 2.X, je le ferais comme ceci:

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

Dans boto 3. Je ne parviens pas à trouver un moyen propre de faire la même chose, donc j'effectue manuellement une itération sur l'objet "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)

ou

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)

Et cela fonctionne très bien. Je me demandais s'il existe une fonction boto3 "native" qui fera la même tâche?

Vor
la source

Réponses:

216

Il y a une personnalisation qui est entrée dans Boto3 récemment qui aide avec ceci (entre autres). Il est actuellement exposé sur le client S3 de bas niveau et peut être utilisé comme ceci:

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

Ces fonctions géreront automatiquement la lecture / l'écriture de fichiers ainsi que les téléchargements en plusieurs parties en parallèle pour les fichiers volumineux.

Notez que s3_client.download_filecela ne créera pas de répertoire. Il peut être créé comme pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).

Daniel
la source
1
@Daniel: Merci pour votre réponse. Pouvez-vous répondre à la réponse si je veux télécharger un fichier en utilisant le téléchargement en plusieurs parties dans boto3.
Rahul KP
1
@RahulKumarPatle la upload_fileméthode utilisera automatiquement les téléchargements en plusieurs parties pour les fichiers volumineux.
Daniel
4
Comment passez-vous vos informations d'identification en utilisant cette approche?
JHowIX
1
@JHowIX vous pouvez soit configurer les informations d'identification globalement (par exemple voir boto3.readthedocs.org/en/latest/guide/… ), soit les transmettre lors de la création du client. Voir boto3.readthedocs.org/en/latest/reference/core/... pour plus d'informations sur les options disponibles!
Daniel
2
@VladNikiporoff "Télécharger de la source vers la destination" "Télécharger de la source vers la destination"
jkdev
59

boto3 a maintenant une interface plus agréable que le client:

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

Ce n'est pas en soi extrêmement meilleur que la clientréponse acceptée (bien que la documentation indique que cela fait un meilleur travail en réessayant les téléchargements et les téléchargements en cas d'échec), mais étant donné que les ressources sont généralement plus ergonomiques (par exemple, le seau s3 et les ressources d' objets sont plus agréables que les méthodes clientes) cela vous permet de rester au niveau de la couche de ressources sans avoir à descendre.

Resources peuvent généralement être créés de la même manière que les clients, et ils prennent tous ou la plupart des mêmes arguments et les transmettent simplement à leurs clients internes.

quodlibetor
la source
1
Excellent exemple, et à ajouter puisque la question d'origine concerne l'enregistrement d'un objet, la méthode pertinente ici est my_bucket.upload_file()(ou my_bucket.upload_fileobj()si vous avez un objet BytesIO).
SMX
Où les documents disent-ils que cela resourcefait un meilleur travail pour réessayer? Je n'ai pas pu trouver une telle indication.
Acumenus
42

Pour ceux d'entre vous qui souhaitent simuler les set_contents_from_stringméthodes boto2 similaires, vous pouvez essayer

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

Pour Python3:

Dans python3, StringIO et cStringIO ont disparu . Utilisez l' StringIOimportation comme:

from io import StringIO

Pour prendre en charge les deux versions:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO
cgseller
la source
15
Voilà la réponse. Voici la question: "Comment enregistrer une chaîne dans un objet S3 en utilisant boto3?"
jkdev
pour python3, j'ai dû utiliser import io; fake_handl e = io.StringIO (contenu)
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
la source
14
Ne mettez jamais votre AWS_ACCESS_KEY_ID ou votre AWS_SECRET_ACCESS_KEY dans votre code. Ceux-ci doivent être définis avec la aws configurecommande awscli et ils seront trouvés automatiquement par botocore.
Miles Erickson
3

Lorsque vous souhaitez lire un fichier avec une configuration différente de celle par défaut, n'hésitez pas à utiliser mpu.aws.s3_download(s3path, destination)directement ou le code copié:

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
la source
Ça ne marche pas. NameError: name '_s3_path_split' is not defined
Dave Liu le
@DaveLiu Merci pour l'indice; J'ai ajusté le code. Le package aurait dû fonctionner avant, cependant.
Martin Thoma le
1

Remarque: je suppose que vous avez configuré l'authentification séparément. Le code ci-dessous consiste à télécharger l'objet unique à partir du compartiment 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
la source
Ce code ne sera pas téléchargé à partir de l'intérieur et du dossier s3, existe-t-il un moyen de le faire de cette manière?
Marilu