Як зберегти об'єкт S3 у файлі за допомогою boto3


132

Я намагаюся створити "привіт світ" з новим клієнтом boto3 для AWS.

Випадок використання у мене досить простий: дістаньте об’єкт від S3 і збережіть його у файл.

У boto 2.XI це зробить так:

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

У бото 3. Я не можу знайти чистий спосіб зробити те ж саме, тому я вручну ітераюю над об'єктом "Потокове передавання":

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)

або

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)

І це чудово працює. Мені було цікаво, чи є якась "рідна" функція boto3, яка зробить те саме завдання?

Відповіді:


216

Існує налаштування, яка нещодавно увійшла до Boto3, що допомагає в цьому (серед іншого). Наразі він відкритий на низькому рівні клієнта S3, і його можна використовувати так:

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

Ці функції автоматично оброблятимуть читання / запис файлів, а також робити багаточастинні завантаження паралельно для великих файлів.

Зверніть увагу, що s3_client.download_fileкаталог не створить. Його можна створити як pathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True).


1
@Daniel: Дякую за вашу відповідь. Чи можете ви відповісти, якщо я хочу завантажити файл, використовуючи багатозапчастину завантаження в boto3.
Рахул КП

1
@RahulKumarPatle upload_fileметод автоматично використовувати багаточастинні завантаження для великих файлів.
Даніель

4
Як ви передаєте вам повноваження, використовуючи такий підхід?
JHowIX

1
@JHowIX Ви можете або налаштувати облікові дані в усьому світі (наприклад, див. Boto3.readthedocs.org/en/latest/guide/… ), або можете передати їх під час створення клієнта. Дивіться boto3.readthedocs.org/en/latest/reference/core/… для отримання додаткової інформації про доступні варіанти!
Даніель

2
@VladNikiporoff "Завантажити з джерела до місця призначення" "Завантажити з джерела до місця призначення"
jkdev

59

boto3 тепер має приємніший інтерфейс, ніж клієнт:

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

Це саме по собі не є надзвичайно кращим, ніж clientу прийнятій відповіді (хоча документи говорять, що це виконує кращу роботу, повторюючи завантаження та завантаження при невдачі), але враховуючи, що ресурси, як правило, більш ергономічні (наприклад, відро s3 та об'єктні ресурси є кращими, ніж клієнтські методи), це дозволяє вам залишатися на рівні ресурсу без необхідності падіння.

Resources як правило, вони можуть бути створені так само, як клієнти, і вони беруть всі або більшість однакових аргументів і просто передають їх своїм внутрішнім клієнтам.


1
Чудовий приклад, і щоб додати, оскільки оригінальне запитання задає питання про збереження об'єкта, відповідним методом тут є my_bucket.upload_file()(або my_bucket.upload_fileobj()якщо у вас є об'єкт BytesIO).
SMX

Точно там, де говорять документи, що resourceробить кращу роботу при повторному спробі? Я не міг знайти жодної такої ознаки.
Акумен

42

Для тих із вас, хто хотів би імітувати set_contents_from_stringподібні методи boto2, ви можете спробувати

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

Для Python3:

У python3 немає і StringIO, і cStringIO . Використовуйте StringIOімпорт на зразок:

from io import StringIO

Для підтримки обох версій:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO

15
Ось відповідь. Ось питання: "Як зберегти рядок до об'єкта S3 за допомогою boto3?"
jkdev

для python3 мені довелося використовувати імпорт io; fake_handl e = io.StringIO (зміст)
Фелікс

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"

14
Ніколи не вводьте у свій код AWS_ACCESS_KEY_ID або AWS_SECRET_ACCESS_KEY. Їх слід визначити за допомогою aws configureкоманди awscli, і вони будуть автоматично знайдені botocore.
Майлз Еріксон

3

Коли ви хочете прочитати файл з іншою конфігурацією, ніж типова, не соромтесь використовувати mpu.aws.s3_download(s3path, destination)безпосередньо або скопійований код:

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)

Не працює. NameError: name '_s3_path_split' is not defined
Дейв Лю

@DaveLiu Дякую за підказку; Я відкоригував код. Хоча пакет повинен був працювати і раніше.
Мартін Тома

1

Примітка. Я припускаю, що ви налаштували автентифікацію окремо. Нижче наведено код для завантаження одного об'єкта з відра 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')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.