Module mfutil.cli

Utility functions to build CLI (Python >= 3.6 only).

Functions

def echo_bold(message)

Write a message in bold (if supported).

Args

message : string
message to write in bold.
def echo_clean()

Clean waiting status.

def echo_nok(message='')

Write [ERROR] with colors if supported a little optional message.

Args

message : string
little optional message.
def echo_ok(message='')

Write [OK] with colors if supported a little optional message.

Args

message : string
little optional message.
def echo_running(message=None)

Write [RUNNING] with colors if supported.

You can pass an optional message which will be rendered before [RUNNING] on the same line.

Args

message : string
little optional message.
def echo_warning(message='')

Write [WARNING] with colors if supported a little optional message.

Args

message : string
little optional message.
def is_interactive(target=None)

Return True if we are in an interactive terminal.

Args

target : string
can be None (for stdout AND stderr checking), "stdout" (for stdout checking only or "stderr" (for stderr checking only).

Returns

boolean (True (interactive) or False (non-interactive).

Raises

MFUtilException
if target is invalid

Classes

class MFProgress (*args, **kwargs)

[Rich Progress] (https://rich.readthedocs.io/en/latest/progress.html) child class.

This class add three features to the original one:

  • support (basic) rendering in non-terminal
  • task status management
  • different default columns setup (but you can override this)
  • rendering on stdout by default (but you can change this by providing a custom Console object)

You can use it exactly like the original [Progress class] (https://rich.readthedocs.io/en/latest/reference/progress.html):

import time
from mfutil.cli import MFProgress

with MFProgress() as progress:
    t1 = p.add_task("Foo task")
    t2 = p.add_task("Foo task")
    while not progress.finished:
        progress.update(t1, advance=10)
        progress.update(t2, advance=10)
        time.sleep(1)

For status management:

  • if you leave MFProgress context manager, not finished tasks are automatically set to ERROR state, finished tasks are automatically set to OK state
  • you have 3 new methods to manually override this behaviour: (complete_task(), complete_task_nok(), complete_task_warning())

Example:

import time
from mfutil.cli import MFProgress

with MFProgress() as progress:
    t1 = p.add_task("Foo task")
    t2 = p.add_task("Foo task")
    i = 0
    while not progress.finished:
        progress.update(t1, advance=10)
        if i < 5:
            progress.update(t2, advance=10)
        elif i == 5:
            progress.complete_task_failed(t2, "unknown error")
        time.sleep(1)
        i = i + 1
Expand source code
class MFProgress(Progress):
    """[Rich Progress]
    (https://rich.readthedocs.io/en/latest/progress.html) child class.

    This class add three features to the original one:

    - support (basic) rendering in non-terminal
    - task status management
    - different default columns setup (but you can override this)
    - rendering on stdout by default (but you can change this by providing
        a custom Console object)

    You can use it exactly like the original [Progress class]
    (https://rich.readthedocs.io/en/latest/reference/progress.html):

    ```python
    import time
    from mfutil.cli import MFProgress

    with MFProgress() as progress:
        t1 = p.add_task("Foo task")
        t2 = p.add_task("Foo task")
        while not progress.finished:
            progress.update(t1, advance=10)
            progress.update(t2, advance=10)
            time.sleep(1)
    ```

    For status management:

    - if you leave MFProgress context manager, not finished tasks are
        automatically set to `ERROR` state, finished tasks are automatically
        set to `OK` state
    - you have 3 new methods to manually override this behaviour:
        (complete_task(), complete_task_nok(), complete_task_warning())

    Example:

    ```python
    import time
    from mfutil.cli import MFProgress

    with MFProgress() as progress:
        t1 = p.add_task("Foo task")
        t2 = p.add_task("Foo task")
        i = 0
        while not progress.finished:
            progress.update(t1, advance=10)
            if i < 5:
                progress.update(t2, advance=10)
            elif i == 5:
                progress.complete_task_failed(t2, "unknown error")
            time.sleep(1)
            i = i + 1
    ```

    """
    def __init__(self, *args, **kwargs):
        console = kwargs.get("console", None)
        if not console:
            self._interactive = _is_interactive(sys.stdout)
            kwargs["console"] = Console()
        else:
            self._interactive = _is_interactive(console.file)
        if len(args) == 0:
            columns = ["[progress.description]{task.description}",
                       BarColumn(12),
                       StateColumn(),
                       MFTimeRemainingColumn()]
            self.mfprogress_columns = True
        else:
            columns = args
            self.mfprogress_columns = False
        Progress.__init__(self, *columns, **kwargs)

    def make_tasks_table(self, tasks):
        if self.mfprogress_columns:
            return self._mfprogress_make_tasks_table(tasks)
        else:
            return MFProgress.make_tasks_table(self, tasks)

    def _mfprogress_make_tasks_table(self, tasks):
        """Get a table to render the Progress display."""
        table = Table.grid()
        table.pad_edge = True
        table.padding = (0, 1, 0, 0)
        try:
            finished = self.finished
        except Exception:
            finished = False
        if finished:
            table.add_column(width=57, no_wrap=True)
            table.add_column(width=0)
            table.add_column()
            table.add_column()
        else:
            table.add_column(width=45, no_wrap=True)
            table.add_column()
            table.add_column()
            table.add_column()
        for task in tasks:
            if task.visible:
                row = []
                append = row.append
                for index, column in enumerate(self.columns):
                    if isinstance(column, str):
                        txt = column.format(task=task)
                        if finished:
                            if len(txt) > 79:
                                txt = txt[0:74] + "[[...]]"
                        else:
                            if len(txt) > 67:
                                txt = txt[0:62] + "[[...]]"
                        append(txt)
                        table.columns[index].no_wrap = True
                    else:
                        widget = column(task)
                        append(widget)
                        if isinstance(widget, (str, Text)):
                            table.columns[index].no_wrap = True
                table.add_row(*row)
        return table

    def __exit__(self, *args, **kwargs):
        with self._lock:
            for tid, task in self._tasks.items():
                if not task.finished:
                    self.complete_task_nok(tid)
        Progress.__exit__(self, *args, **kwargs)

    def start(self, *args, **kwargs):
        if self._interactive:
            return Progress.start(self, *args, **kwargs)

    def __complete_task_nok_nolock(self, task_id, status_extra=""):
        task = self._tasks[task_id]
        self.update(task_id, completed=task.total, status="ERROR",
                    refresh=False, status_extra=status_extra)

    def complete_task_nok(self, task_id, status_extra=""):
        """Complete a task with ERROR status.

        Args:
            task_id (TaskID): a task ID.
            status_extra (str): a little extra message to add.

        """
        with self._lock:
            self.__complete_task_nok_nolock(task_id, status_extra=status_extra)

    def complete_task_warning(self, task_id, status_extra=""):
        """Complete a task with WARNING status.

        Args:
            task_id (TaskID): a task ID.
            status_extra (str): a little extra message to add.

        """
        with self._lock:
            task = self._tasks[task_id]
            self.update(task_id, completed=task.total, status="WARNING",
                        refresh=False, status_extra=status_extra)

    def complete_task(self, task_id, status_extra=""):
        """Complete a task with OK status.

        Args:
            task_id (TaskID): a task ID.
            status_extra (str): a little extra message to add.

        """
        with self._lock:
            task = self._tasks[task_id]
            self.update(task_id, completed=task.total, status="OK",
                        refresh=False, status_extra=status_extra)

    def stop(self):
        if self._interactive:
            return Progress.stop(self)
        else:
            with self._lock:
                for tid, task in self._tasks.items():
                    status = ""
                    extra = task.fields.get('status_extra', '')
                    if len(extra) > 0:
                        extra = " (%s)" % extra
                    if task.finished:
                        if task.fields.get('status', 'OK') == 'OK':
                            status = "OK"
                        elif task.fields.get('status', 'OK') == 'WARNING':
                            status = "WARNING"
                        else:
                            status = "ERROR"
                    print("%s [ %s ]%s" % (task.description, status, extra),
                          file=self.console.file)

    def refresh(self, *args, **kwargs):
        if self._interactive:
            return Progress.refresh(self, *args, **kwargs)

Ancestors

  • rich.progress.Progress
  • rich.jupyter.JupyterMixin

Methods

def complete_task(self, task_id, status_extra='')

Complete a task with OK status.

Args

task_id : TaskID
a task ID.
status_extra : str
a little extra message to add.
def complete_task_nok(self, task_id, status_extra='')

Complete a task with ERROR status.

Args

task_id : TaskID
a task ID.
status_extra : str
a little extra message to add.
def complete_task_warning(self, task_id, status_extra='')

Complete a task with WARNING status.

Args

task_id : TaskID
a task ID.
status_extra : str
a little extra message to add.
def make_tasks_table(self, tasks)

Get a table to render the Progress display.

Args

tasks : Iterable[Task]
An iterable of Task instances, one per row of the table.

Returns

Table
A table instance.
def refresh(self, *args, **kwargs)

Refresh (render) the progress information.

def start(self, *args, **kwargs)

Start the progress display.

def stop(self)

Stop the progress display.