Creating Progress Bar Widgets

If you want to add a dynamic element to a progress bar you have to implement it as a so-called widget. This guide will help you implement one.

The Widget Class

If you are going to implement your own widget, you will most likely inherit from brownie.terminal.progress.Widget, this class sets some sane defaults and implements the interface a widget is expected to have. If you are not going to inherit from it, you will have to implement its interface so either way you might want to take a look at the API documentation now in either case you will need it later.

Background Information

The Basics

A progress bar is instantiated with a list of widgets, each widget provides an interface which provides information on the widget itself, its size and obviously methods to “render” it.

The progress bar goes through 3 stages: Before anything serious happens an initial version is written to stdout, for this the init() method is called, this is also why you want to start time measuring and similar things in this method instead of __init__().

After this progress bar receives updated which each call to its next() method, an update represents one or more steps of progress. Such a step could correspond to a file being processed or to a single byte of data transferred. In any case for each update update() is called.

At some point, whatever operation where are doing, ends this is the case when either update() is called with a progress bar whose step attribute is equal to it’s maxsteps attribute or finish() is called.

Now each of these methods is expected to return a string representing the widget.

The Size

A progress bar may take up only a single line of space which we want to use wisely. Therefore each widget is called with the remaining width and it should not use more than that, however there is no way for it to know how much the other widgets need.

So apart from not returning a string longer than the given remaining width there are other things you can do to make sure that the progress bar is able to handle the widgets intelligently.

A lot of widgets know before update is called (given the number of steps) how much space they require, if this is the case for your widget you can implement size_hint() so that it returns the required size. This allows the progress bar to allocate the size for your widget before any the other widgets are rendered.

If it is not possible for your widget to determine the required space, for example because it relies on time, it can set the priority attribute, this determines in which order (highest first) the widgets are rendered and the first widget can use most of the space.

Expectations

Your widget might have certain expectations for the progress bar, at the moment like requiring the maximum number of steps to be known (which would otherwise be None). If your widget does set the requires_fixed_size attribute to True, this will result in a nice error message, for users of your widgets, if an attempt is made to use it with a progress bar without providing a maximum number of steps.

Tutorial

After reading so much about how everything works lets make a simple widget starting with someone really simple.

In order to represent static text a TextWidget is used internally, we are going to create one just like it.

First the basics, we text is given on initialization of the widget so we simply inherit from Widget and implement a __init__() method:

from brownie.terminal.progress import Widget

class TextWidget(Widget):
    def __init__(self, text):
        self.text = text

In order to get the text displayed at the first stage we need to implement init(). The method is called with the progress bar, the remaining width and any keyword arguments passed to the next() method of the progress bar:

def init(self, progressbar, remaining_width, **kwargs):
    return self.text

The method is supposed to return a string, text is a string so we can simply return it.

Now comes the next stage: updating. update() is called with the same arguments as init() and again we simply want to display the text so we return it:

def update(self, progressbar, remaining_width, **kwargs):
    return self.text

As both methods have the same signature and do the same we can reduce update() to a simple assignment:

update = init

We can ignore finish() as it would do the same as update() and the default implementation of finish() calls update() and returns the result of that call.

We want to make sure that the text is displayed and has priority over something like a bar showing the percentage by being filled and as we know the size of our output we can implement size_hint() for that:

def size_hint(self, progressbar):
    return len(self.text)

So all in all our result looks like this:

from brownie.terminal.progress import Widget

class TextWidget(Widget):
    def __init__(self, text):
        self.text = text

    def size_hint(self, progressbar):
        return len(self.text)

    def init(self, progressbar, remaining_width, **kwargs):
        return self.text

    update = init

In order to use the widget you have to pass it to brownie.terminal.TerminalWriter.progress():

yourwidgets = {'yourwidget': TextWidget}

with writer.progress('$yourwidget', widgets=yourwidgets) as bar:
    # do something with progressbar ('bar')
    pass

Table Of Contents

Navigation

Documentation overview

This Page

Fork me on GitHub