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 :class:`~brownie.terminal.progress.Widget` Class ---------------------------------------------------- If you are going to implement your own widget, you will most likely inherit from :class:`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 :meth:`~brownie.terminal.progress.Widget.init` method is called, this is also why you want to start time measuring and similar things in this method instead of :meth:`__init__`. After this progress bar receives updated which each call to its :meth:`~brownie.terminal.progress.ProgressBar.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 :meth:`~brownie.terminal.progress.Widget.update` is called. At some point, whatever operation where are doing, ends this is the case when either :meth:`~brownie.terminal.progress.Widget.update` is called with a progress bar whose `step` attribute is equal to it's `maxsteps` attribute or :meth:`~bronwie.terminal.progress.Widget.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 :meth:`~brownie.terminal.progress.Widget.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 :attr:`~brownie.terminal.progress.Widget.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 :attr:`~brownie.terminal.progress.Widget.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 :class:`~brownie.terminal.progress.Widget` and implement a :meth:`__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 :meth:`init`. The method is called with the progress bar, the remaining width and any keyword arguments passed to the :meth:`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. :meth:`update` is called with the same arguments as :meth:`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 :meth:`update` to a simple assignment:: update = init We can ignore :meth:`finish` as it would do the same as :meth:`update` and the default implementation of :meth:`finish` calls :meth:`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 :meth:`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 :meth:`brownie.terminal.TerminalWriter.progress`:: yourwidgets = {'yourwidget': TextWidget} with writer.progress('$yourwidget', widgets=yourwidgets) as bar: # do something with progressbar ('bar') pass