Cómo guardar un objeto S3 en un archivo usando boto3

132

Estoy tratando de hacer un "hola mundo" con el nuevo cliente boto3 para AWS.

El caso de uso que tengo es bastante simple: obtener un objeto de S3 y guardarlo en el archivo.

En boto 2.XI lo haría así:

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

En boto 3. No puedo encontrar una manera limpia de hacer lo mismo, así que estoy iterando manualmente sobre el objeto "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)

o

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)

Y funciona bien. Me preguntaba si hay alguna función boto3 "nativa" que haga la misma tarea.

Vor
fuente

Respuestas:

216

Hay una personalización que entró en Boto3 recientemente que ayuda con esto (entre otras cosas). Actualmente está expuesto en el cliente S3 de bajo nivel, y puede usarse así:

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

Estas funciones manejarán automáticamente la lectura / escritura de archivos, así como la carga de varias partes en paralelo para archivos grandes.

Tenga en cuenta que s3_client.download_fileno creará un directorio. Se puede crear como pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).

Daniel
fuente
1
@Daniel: Gracias por tu respuesta. ¿Puede responder la respuesta si quiero cargar el archivo usando la carga multiparte en boto3?
Rahul KP
1
@RahulKumarPatle, el upload_filemétodo usará automáticamente cargas de varias partes para archivos grandes.
Daniel
44
¿Cómo pasa sus credenciales usando este enfoque?
JHowIX
1
@JHowIX puede configurar las credenciales globalmente (por ejemplo, ver boto3.readthedocs.org/en/latest/guide/… ) o puede pasarlas al crear el cliente. ¡Vea boto3.readthedocs.org/en/latest/reference/core/… para obtener más información sobre las opciones disponibles!
Daniel
2
@VladNikiporoff "Cargar de origen a destino" "Descargar de origen a destino"
jkdev
59

boto3 ahora tiene una mejor interfaz que el cliente:

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

Esto por sí solo no es tremendamente mejor que clienten la respuesta aceptada (aunque los documentos dicen que hace un mejor trabajo al volver a intentar cargar y descargar en caso de error), pero teniendo en cuenta que los recursos son generalmente más ergonómicos (por ejemplo, el cubo s3 y los recursos de objeto son mejores que los métodos del cliente), esto le permite permanecer en la capa de recursos sin tener que desplegarse.

Resources generalmente se pueden crear de la misma manera que los clientes, y toman todos o la mayoría de los mismos argumentos y los envían a sus clientes internos.

quodlibetor
fuente
1
Gran ejemplo, y para agregarlo ya que la pregunta original pregunta sobre cómo guardar un objeto, el método relevante aquí es my_bucket.upload_file()(o my_bucket.upload_fileobj()si tiene un objeto BytesIO).
SMX
¿Exactamente dónde dicen los documentos que resourcehace un mejor trabajo al reintentar? No pude encontrar ninguna indicación.
Acumenus
42

Para aquellos de ustedes que deseen simular los set_contents_from_stringmétodos similares de boto2, pueden intentar

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

Para Python3:

En python3, StringIO y cStringIO se han ido . Use la StringIOimportación como:

from io import StringIO

Para admitir ambas versiones:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO
cgseller
fuente
15
Esa es la respuesta. Aquí está la pregunta: "¿Cómo guardar una cadena en un objeto S3 usando boto3?"
jkdev
para python3 tuve que usar import io; fake_handl e = io.StringIO (contenido)
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
fuente
14
Nunca ponga su AWS_ACCESS_KEY_ID o su AWS_SECRET_ACCESS_KEY en su código. Deben definirse con el aws configurecomando awscli y serán encontrados automáticamente por botocore.
Miles Erickson
3

Cuando desee leer un archivo con una configuración diferente a la predeterminada, siéntase libre de usar mpu.aws.s3_download(s3path, destination)directamente o el código copiado:

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
fuente
No funciona NameError: name '_s3_path_split' is not defined
Dave Liu
@DaveLiu Gracias por la pista; He ajustado el código. Sin embargo, el paquete debería haber funcionado antes.
Martin Thoma
1

Nota: Supongo que ha configurado la autenticación por separado. El siguiente código es descargar el único objeto del depósito 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
fuente