Отслеживайте ход загрузки файла S3 с помощью boto3 и обратных вызовов

Я пытаюсь загрузить текстовый файл с S3, используя boto3.

Вот что я написал.

class ProgressPercentage(object):
    def __init__(self, filename):
        self._filename = filename
        self._size = float(os.path.getsize(filename))
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        # To simplify we'll assume this is hooked up
        # to a single filename.
        with self._lock:
            self._seen_so_far += bytes_amount
            percentage = round((self._seen_so_far / self._size) * 100,2)
            LoggingFile('{} is the file name. {} out of {} done. The percentage completed is {} %'.format(str(self._filename), str(self._seen_so_far), str(self._size),str(percentage)))
            sys.stdout.flush()

и я вызываю это, используя

transfer.download_file(BUCKET_NAME,FILE_NAME,'{}{}'.format(LOCAL_PATH_TEMP , FILE_NAME),callback = ProgressPercentage(LOCAL_PATH_TEMP + FILE_NAME))

это дает мне ошибку, что файл отсутствует в папке. Очевидно, когда у меня уже есть файл с таким именем в той же папке, он работает, но когда я загружаю новый файл, возникает ошибка.

Какую коррекцию мне нужно сделать?


person Kshitij Marwah    schedule 24.01.2017    source источник
comment
здесь нет указаний на то, какая здесь переменная transfer.   -  person Naz    schedule 19.08.2019
comment
вам также не хватает sys, threading и что такое LoggingFile?   -  person Naz    schedule 19.08.2019


Ответы (8)


callback = ProgressPercentage(LOCAL_PATH_TEMP + FILE_NAME)) создает объект ProgressPercentage, запускает его метод __init__ и передает объект как callback методу download_file. Это означает, что метод __init__ запускается до начала download_file.

В методе __init__ вы пытаетесь прочитать размер загружаемого локального файла, что вызывает исключение, поскольку файл не существует, поскольку загрузка еще не началась. Если вы уже загрузили файл, то проблем нет, так как локальная копия существует и ее размер можно прочитать.

Конечно, это просто причина исключения, которое вы видите. Вы используете свойство _size в качестве максимального значения прогресса загрузки. Однако вы пытаетесь использовать размер локального файла. Пока файл не загружен полностью, локальная файловая система не знает, насколько велик файл, она знает только, сколько места он занимает прямо сейчас. Это означает, что по мере загрузки файл будет постепенно увеличиваться, пока не достигнет своего полного размера. Таким образом, не имеет смысла рассматривать размер локального файла как максимальный размер загрузки. Это может сработать в том случае, если вы уже скачали файл, но это не очень полезно.

Решением вашей проблемы будет проверка размера файла, который вы собираетесь загрузить, а не размера локальной копии. Это гарантирует, что вы получите фактический размер того, что вы загружаете, и что файл существует (поскольку вы не могли бы загрузить его, если бы это было не так). Вы можете сделать это, получив размер удаленного файла с помощью head_object следующим образом

class ProgressPercentage(object):
    def __init__(self, client, bucket, filename):
        # ... everything else the same
        self._size = client.head_object(Bucket=bucket, Key=filename).ContentLength

    # ...

# If you still have the client object you could pass that directly 
# instead of transfer._manager._client
progress = ProgressPercentage(transfer._manager._client, BUCKET_NAME, FILE_NAME)
transfer.download_file(..., callback=progress)

И последнее замечание: хотя вы получили код из документации Boto3, это не сработало, потому что предназначалось для загрузки файлов. В этом случае локальный файл является источником и его существование гарантировано.

person yummies    schedule 25.01.2017
comment
Итак... не знаю, если это только я, но в документах 1.9.96 именованный аргумент равен callback с минусом c. Но в коде той же версии (скачанной через pip) я получил заглавную C для этого точно такого же аргумента /: me = запутал. Я опубликую свой код в качестве примера ниже. - person Boop; 18.02.2019
comment
отлично, это работает для меня! нужно было только внести одно незначительное изменение. head_object возвращает словарь. client.head_object(Bucket=bucket, Key=filename).get('ContentLength') - person Gustavo_fringe; 22.03.2019
comment
Как бы вы отобразили ход указанной загрузки или загрузки по мере того, как она происходит, это только показывает, как получить процент загрузки для конкретного вызова? - person ViaTech; 17.07.2019
comment
Это не МВП. Я не понимаю, как использовать этот код. transfer также не определен. - person Naz; 19.08.2019
comment
Я немного изменил код, и теперь он работает! client.Object(bucket, filename).get()['ContentLength'] где мой клиент - boto3 resource object. Мне нужно внести это изменение, так как я создаю объект сеанса boto3 с ключом доступа/аксесссексеткей. - person appletabo; 12.05.2021

Установите progressbar с помощью pip3 install progressbar

import boto3, os
import progressbar

bucket_name = "<your-s3-bucket-name>"
folder_name = "<your-directory-name-locally>"
file_name = "<your-filename-locally>"
path = folder_name + "/" + file_name
s3 = boto3.client('s3', aws_access_key_id="<your_aws_access_key_id>", aws_secret_access_key="<your_aws_secret_access_key>")

statinfo = os.stat(file_name)

up_progress = progressbar.progressbar.ProgressBar(maxval=statinfo.st_size)

up_progress.start()

def upload_progress(chunk):
    up_progress.update(up_progress.currval + chunk)

s3.upload_file(file_name, bucket_name, path, Callback=upload_progress)

up_progress.finish()
person Adam Kurkiewicz    schedule 18.12.2018
comment
Просто получаю ответ @EmmanuelNK для работы с последним pip3 - person Adam Kurkiewicz; 18.12.2018
comment
from hurry.filesize import size не используется. - person Evan; 22.01.2019

Это моя реализация. Никаких других зависимостей, взломайте функцию обратного вызова прогресса, чтобы отображать все, что вы хотите.

import sys
import boto3

s3_client = boto3.client('s3')

def download(local_file_name, s3_bucket, s3_object_key):

    meta_data = s3_client.head_object(Bucket=s3_bucket, Key=s3_object_key)
    total_length = int(meta_data.get('ContentLength', 0))
    downloaded = 0

    def progress(chunk):
        nonlocal downloaded
        downloaded += chunk
        done = int(50 * downloaded / total_length)
        sys.stdout.write("\r[%s%s]" % ('=' * done, ' ' * (50-done)) )
        sys.stdout.flush()

    print(f'Downloading {s3_object_key}')
    with open(local_file_name, 'wb') as f:
        s3_client.download_fileobj(s3_bucket, s3_object_key, f, Callback=progress)

e.g.

local_file_name = 'test.csv'
s3_bucket = 'my-bucket'
s3_object_key = 'industry/test.csv'

download(local_file_name, s3_bucket, s3_object_key)

Демо:

введите здесь описание изображения

Проверено с boto3>=1.14.19, python>=3.7

person Glen Thompson    schedule 08.07.2020

Объект client.head_object(Bucket=bucket, Key=filename) является словарем. Доступ к размеру файла можно получить с помощью ['ContentLength'].

Следовательно, код:
self._size = client.head_object(Bucket=bucket, Key=filename).ContentLength
должен стать следующим:
self._size = float(client.head_object(Bucket=bucket, Key=filename)['ContentLength'])

Тогда это работает. Спасибо!

person nicolas.f.g    schedule 20.06.2017

Следуя официальному документу, не так сложно применить отслеживание прогресса (функции download_file и upload_file аналогичны). Вот полный код с некоторыми изменениями, чтобы увидеть размер данных предпочтительным образом.

import logging
import boto3
from botocore.exceptions import ClientError
import os
import sys
import threading
import math 

ACCESS_KEY = 'xxx'
SECRET_KEY = 'xxx'
REGION_NAME= 'ap-southeast-1'

class ProgressPercentage(object):
    def __init__(self, filename, filesize):
        self._filename = filename
        self._size = filesize
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        def convertSize(size):
            if (size == 0):
                return '0B'
            size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
            i = int(math.floor(math.log(size,1024)))
            p = math.pow(1024,i)
            s = round(size/p,2)
            return '%.2f %s' % (s,size_name[i])

        # To simplify, assume this is hooked up to a single filename
        with self._lock:
            self._seen_so_far += bytes_amount
            percentage = (self._seen_so_far / self._size) * 100
            sys.stdout.write(
                "\r%s  %s / %s  (%.2f%%)        " % (
                    self._filename, convertSize(self._seen_so_far), convertSize(self._size),
                    percentage))
            sys.stdout.flush()


def download_file(file_name, object_name, bucket_name):
    # If S3 object_name was not specified, use file_name
    if object_name is None:
        object_name = file_name

    # Initialize s3 client
    s3_client = boto3.client(service_name="s3",
                aws_access_key_id=ACCESS_KEY,
                aws_secret_access_key=SECRET_KEY,
                region_name=REGION_NAME)
    try:
        response = s3_client.download_file(
            Bucket=bucket_name, 
            Key=object_name, 
            Filename=file_name,
            Callback=ProgressPercentage(file_name, (s3_client.head_object(Bucket=bucket_name, Key=object_name))["ContentLength"])
            )
    except ClientError as e:
        logging.error(e)
        return False
    return True

file_name = "./output.csv.gz"
bucket_name = "mybucket"
object_name = "result/output.csv.gz" 
download_file(file_name, object_name, bucket_name )
person Nguyen Van Duc    schedule 21.01.2020

Кто-то может наткнуться на этот ответ при попытке сделать это (согласно названию вопроса). Самый простой способ показать прогресс загрузки s3:

импортировать библиотеку индикаторов выполнения в свой проект. Вот что я использовал: https://github.com/anler/progressbar

Затем:

import progressbar
from hurry.filesize import size
import boto3

bucket = "my-bucket-name"
s3_client = boto3.resource('s3')
...
...

# you get the filesize from wherever you have the file on. your system maybe?
filesize = size(file) 

up_progress = progressbar.AnimatedProgressBar(end=filesize, width=50)
def upload_progress(chunk):
    up_progress + chunk # Notice! No len()
    up_progress.show_progress()
s3_client.meta.client.upload_file(file, bucket, s3_file_name, Callback=upload_progress)

Здесь важно отметить использование параметра Обратный вызов (заглавная C). В основном он возвращает количество байтов, загруженных в s3. Итак, если вы знаете исходный размер файла, простая математика даст вам индикатор выполнения. Затем вы можете использовать любую библиотеку индикаторов выполнения.

person Emmanuel N K    schedule 16.08.2018
comment
Не работает с версией индикатора выполнения, которую я установил с помощью pip3. - person Adam Kurkiewicz; 18.12.2018
comment
Я должен был упомянуть, что просто вставил библиотеку в свой проект напрямую с помощью pip3. Для тех, кому интересно, как: создайте папку с именем progressbar и поместите ее с остальными вашими библиотеками Python, внутри нее добавьте пустой файл __init__.py. Затем добавьте файл progressbar.py из репозитория github. Затем вы обычно импортируете его в свой проект. - person Emmanuel N K; 19.12.2018

Информация

  • Авторы сообщений @Kshitij Marwah, @yummies и nicolas.f.g
  • Использование boto3 1.9.96 (дл через pip)
  • Удалено threading
  • Изменен формат отображения (переписать строку выше, пока не завершится dl)
  • Публикация, потому что разница ч / б онлайн-документа и загруженного пакета

код

class ProgressPercentage(object):
    def __init__(self, o_s3bucket, key_name):
        self._key_name = key_name
        boto_client = o_s3bucket.meta.client
        # ContentLength is an int
        self._size = boto_client.head_object(Bucket=o_s3bucket.name, Key=key_name)['ContentLength']
        self._seen_so_far = 0
        sys.stdout.write('\n')

    def __call__(self, bytes_amount):
        self._seen_so_far += bytes_amount
        percentage = (float(self._seen_so_far) / float(self._size)) * 100
        TERM_UP_ONE_LINE = '\033[A'
        TERM_CLEAR_LINE = '\033[2K'
        sys.stdout.write('\r' + TERM_UP_ONE_LINE + TERM_CLEAR_LINE)
        sys.stdout.write('{} {}/{} ({}%)\n'.format(self._key_name, str(self._seen_so_far), str(self._size), str(percentage)))
        sys.stdout.flush()

Потом назвал так

Обратите внимание на заглавную C на Callback (это отличается от онлайн-документа)

progress = ProgressPercentage(o_s3bucket, key_name)
o_s3bucket.download_file(key_name, full_local_path, Callback=progress)

где o_s3bucket:

bucket_name = 'my_bucket_name'
aws_profile = 'default' # this is used to catch creds from .aws/credentials ini file
boto_session = boto3.session.Session(profile_name=aws_profile)
o_s3bucket = boto_session.resource('s3').Bucket(bucket_name)

чт

person Boop    schedule 18.02.2019
comment
Как можно передать Callback logging, чтобы предоставить %, отправленный в AWS? Что-то вроде INFO: 10% of xyz.file uploaded, INFO: 20% of xyz.file uploaded и т. д. до INFO: xyz.file successfully uploaded включительно. - person SeaDude; 19.09.2020
comment
Думаю, обратный вызов вызывается каждый раз, когда приходят новые пакеты (или отправляются на загрузку). Но определенно много времени. Я бы не советовал регистрировать прогресс, потому что это не добавляет ценности и занимает место впустую. Но вы, конечно, можете это сделать: здесь, в моем методе __call__, вы можете вызвать ведение журнала: он будет логировать каждый шаг. Надеюсь, я закрою ваши вопросы - person Boop; 19.09.2020
comment
Спасибо @Boop. Дополнительная ценность (по крайней мере, для меня) возникает, когда код Python размещается как функция Azure и объем передаваемых данных велик. Я хочу регистрировать только каждые 10% или тому подобное. Я отрежу и посмотрю, что я могу придумать. - person SeaDude; 19.09.2020

Вот вариант, который мне показался полезным с использованием click (просто запустите pip install click перед применением код ниже) библиотека:

import click
import boto3
import os


file_path = os.path.join('tmp', 'file_path')
s3_client = boto3.resource('s3')
with click.progressbar(length=os.path.getsize(file_path)) as progress_bar:
    with open(file_path, mode='rb') as upload_file:
        s3_client.upload_fileobj(
            upload_file,
            'bucket_name',
            'foo_bar',
            Callback=progress_bar.update
)

person Andriy Ivaneyko    schedule 28.01.2021