Where to Discuss?

Local Group

Preface

Goal: Single Panel Watchfiles with Rich Live

We will do this step by step:

  1. Simple watch task using table.
  2. Prepare single watch task with panel.
  3. Dual task concurrently with split layout.
  4. Class refactoring for further use.

1: Simple Watchfiles Using Table

Python Source Code

You can visit my github, and download the sourse. So you can run a complete script.

Class: Skeleton

We start with only two methods.

class SingleWatch:
  filepath = '/home/epsi/awatch/code-02-enh'

  def generate_table(self, changes) -> Table:
  async def main(self):

Python Rich: Simple Watchfiles: Skeleton

Required Package

Do not forget to import your friendly neighbourhood awatch. This will act as a backend process, while the rich is terinal frontend.

import asyncio

from watchfiles import awatch

from rich.live import Live
from rich.table import Table

Python Rich: Simple Watchfiles: Brief

Constructor

No need.

We need constructor later. But not now.

Async Main Method: Live

This is the heart of our script project. Instead of updating the layout, we update the live directly.

  async def main(self):
    with Live(None, refresh_per_second=4) as live:
      watchloop = awatch(
        self.filepath, recursive=True)
      async for changes in watchloop:
        live.update(self.generate_table(changes))

Python Rich: Simple Watchfiles: Main

But why?

Because of the block building scope in live. As we can see when thing got complex later.

Renderable: Table

In this example, we start from simple renderable. It is the table without panel nor padding.

  def generate_table(self, changes) -> Table:
    # Make a new table
    table = Table(expand=True)
    table.add_column("Type")
    table.add_column("File")

    if changes != None:
      for change in changes:
        table.add_row(
          *[str(change[0]),change[1]])

    return table

Python Rich: Simple Watchfiles: Table

Watchfiles Changes

Each changes has two property. And each change always return those two values.

  • Change type
  • File name (full path)
    if changes != None:
      for change in changes:
        table.add_row(
          *[str(change[0]),change[1]])

In order to setup table with empty change, I add None value to changes. So you can do something like this:

    with Live(self.generate_table(None)) as live:
      ...

I reallyt hink the flow is clear with simple setup. Let’s get it o and see the output.

TUI in CLI

With this simple setup, we can already display this output:

Python Rich: Simple Watchfiles: TUI in CLI

Just change something on folder, such as save excel files in libreoffice, or else. The terminal will immediately show any folder changes.


2: Live with Single Task

This would be very similar to previous example. There is not much changes, except that we have panel.

Python Source Code

It is always nice, when we have access to complete source code of working script.

Class: Skeleton

class SingleWatch:
  filepath = '/home/epsi/awatch/code-02-enh'

  def generate_table(self, changes) -> Table:
  def get_panel(self, changes) -> Panel:
  async def main(self):

Python Rich: Live with Single Task: Skeleton

Constructor

No need.

Brief

No such changes. Except Panel package.

import asyncio

from watchfiles import awatch

from rich.live import Live
from rich.table import Table
from rich.panel import Panel

class SingleWatch:
  filepath = '/home/epsi/awatch/code-02-enh'
  ...

# Program Entry Point
single = SingleWatch()
asyncio.run(single.main())

Python Rich: Live with Single Task: Brief

Renderable: Table

Skip

No such changes.

  def generate_table(self, changes) -> Table:
    # Make a new table
    table = Table(expand=True)
    table.add_column("Type")
    table.add_column("File")

    if changes != None:
      for change in changes:
        table.add_row(
          *[str(change[0]),change[1]])

    return table

Python Rich: Live with Single Task: Table

Renderable: Panel

Nothing special, just another simple panel.

  def get_panel(self, changes) -> Panel:
    dataTable = self.generate_table(changes)
    panel = Panel(
      dataTable, title="A Single Panel",
      subtitle="A single [yellow]Thank you")
    return panel

Python Rich: Live with Single Task: Panel

We wrapped the renderable table inside the panel.

    panel = Panel(dataTable)

We can wrapped panel inside the layout. And the layout inside the live.

Async Main Method: Live

The same as previous, as you can see below. Except using renderable panel inside live, instead of table.

  async def main(self):
    with Live(
        self.get_panel(None),
        refresh_per_second=4
      ) as live:
        watchloop = awatch(
          self.filepath, recursive=True)
        async for changes in watchloop:
          live.update(self.get_panel(changes))

Python Rich: Live with Single Task: Main

In the next section we can wrapped layout inside live. But meanwhile, enjoy the view result.

TUI in CLI

It is much more prettier with box.

Again, change something on folder, delete or move file. The terminal will immediately show any folder changes.

Python Rich: Live with Single Task: TUI in CLI

Many changes to ahead

Prepare

Understanding panel is necessary, before we step into the next section. I know there is not much to see in this section. All we do is just adding panel.

Now that we know the concept, we are ready to make more changes.


What is Next 🤔?

More panel please

In real life we need more than one watchfiles panel. We need to deal with the asyncio complexity.

Consider continue reading [ Python - Rich - Dual Watchfiles ].