Search code examples
pythonamazon-s3pyqt5boto3qprogressbar

Showing a Progress Bar in a QMainWindow from a Callback, boto3.s3


I am creating a program that uploads a folder into a bucket. Right now I have the program and UI all set I would just like to add a progress bar showing which file is being uploaded.

I am wondering if there is a way to use the s3.upload_file(fileName, bucketName, objectName, Callback=ProgressPercentage(path.text())) to get a progress bar on my QMainWindow. Or if I need to go about this a different way.

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, 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, self._seen_so_far, self._size,
                    percentage))
            sys.stdout.flush()

Solution

  • The logic in this case is to create a QObject that has a signal that indicates the progress, in addition the upload task must be executed in a secondary thread so that the GUI does not freeze:

    import math
    import os
    import sys
    import threading
    
    import boto3
    
    from PyQt5 import QtCore, QtWidgets
    
    
    class S3Worker(QtCore.QObject):
        started = QtCore.pyqtSignal()
        finished = QtCore.pyqtSignal()
        percentageChanged = QtCore.pyqtSignal(int)
    
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self._s3 = boto3.client("s3")
    
        @property
        def s3(self):
            return self._s3
    
        def upload(self, filename, bucketname, objectname):
            self._size = float(os.path.getsize(filename))
            self._seen_so_far = 0
            threading.Thread(
                target=self._execute, args=(filename, bucketname, objectname), daemon=True
            ).start()
    
        def _execute(self, fileName, bucketName, objectName):
            self.started.emit()
            self.s3.upload_file(fileName, bucketName, objectName, Callback=self._callback)
            self.finished.emit()
    
        def _callback(self, bytes_amount):
            self._seen_so_far += bytes_amount
            percentage = (self._seen_so_far / self._size) * 100
            self.percentageChanged.emit(math.floor(percentage))
    
    
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.filename_le = QtWidgets.QLineEdit()
            self.upload_btn = QtWidgets.QPushButton("Upload")
            self.percentage_pb = QtWidgets.QProgressBar()
    
            lay = QtWidgets.QGridLayout(self)
            lay.addWidget(QtWidgets.QLabel("filename:"))
            lay.addWidget(self.filename_le, 0, 1)
            lay.addWidget(self.upload_btn, 0, 2)
            lay.addWidget(self.percentage_pb, 1, 0, 1, 3)
    
            self.qs3 = S3Worker()
    
            self.upload_btn.clicked.connect(self.start_upload)
            self.qs3.started.connect(lambda: self.upload_btn.setEnabled(False))
            self.qs3.finished.connect(lambda: self.upload_btn.setEnabled(True))
            self.qs3.percentageChanged.connect(self.percentage_pb.setValue)
    
        def start_upload(self):
            filename = self.filename_le.text()
            if os.path.exists(filename):
                self.qs3.upload(filename, "mybucket", "foobject")
    
    
    def main():
        app = QtWidgets.QApplication(sys.argv)
    
        w = Widget()
        w.show()
        sys.exit(app.exec_())
    
    if __name__ == "__main__":
        main()