Code of the Day
AdvancedText User Interfaces

Lab: TUI log viewer

Build a Textual TUI that displays a live-updating log table, supports keyboard navigation, and exports the current view to a file.

Lab · optionalUtilitiesAdvanced35 min
Recommended first
By the end of this lesson you will be able to:
  • Build a DataTable log viewer that populates from an in-memory buffer
  • Use set_interval to append a new log entry and refresh the table each second
  • Bind the E key to export the current table rows to a text file
  • Add a Footer widget that lists available keybindings

This lab ties together everything from the TUI module: a DataTable populated from an in-memory buffer, a one-second refresh timer, a keybinding that exports the current view to disk, and a Footer that keeps the user oriented. By the end you will have a self-contained log viewer you can adapt for any structured data source.

Textual requires a real terminal to display correctly. The runnable cells below use pip install textual and print the app structure. Copy the final app code to a .py file and run it locally with python log_viewer.py.

The data model

The viewer simulates a growing log buffer — a plain Python list that acts as an in-memory ring buffer. In production this list would be fed by a background thread reading from a file, a subprocess pipe, or a network socket.

Python — editable, runs in your browser

The buffer grows without bound here. A real viewer would cap it with _log_buffer = _log_buffer[-500:] to keep memory stable across long sessions.

Building the DataTable view

The DataTable widget shows the buffer contents. Columns are added once in on_mount; the refresh callback clears and repopulates rows from the current buffer state.

Python — editable, runs in your browser

A few decisions worth noting:

  • table.clear() + add_row() on each refresh is the right approach here because the rolling-window truncation can remove rows from the top, making keyed row updates unreliable. The table is small enough that a full rebuild does not flicker noticeably.
  • self._buffer = self._buffer[-200:] keeps memory bounded. Adjust the window to match the expected scroll depth.
  • self.notify(...) shows a brief toast message in the bottom-right corner when the export completes — useful feedback without interrupting the view.

The export action

The action_export method writes the buffer to a plain-text file. The naming convention matters: a method named action_<name> is automatically callable via BINDINGS — the ("e", "export", "Export") binding calls action_export without any extra wiring.

Python — editable, runs in your browser

Putting it together

Run the complete checklist locally:

  1. pip install textual in a virtual environment.
  2. Save the app code (from the second runnable above) to log_viewer.py.
  3. Run python log_viewer.py. The table should appear and gain one new row per second.
  4. Use the arrow keys and Page Down to scroll through older entries.
  5. Press E — a toast notification confirms the export. Check log_export.txt in the same directory.
  6. Press Q to quit cleanly.

Add BINDINGS = [..., ("c", "clear_buffer", "Clear")] and implement action_clear_buffer to empty self._buffer and rebuild the table. It is a common request in real log viewers — operators want to clear noise before an operation they are watching.

What you practised

You combined three patterns from the TUI module:

  • DataTable with a clear-and-rebuild refresh cycle for a variable-length buffer.
  • set_interval triggering a data ingestion step and a table redraw each second.
  • Action bindings wiring a key directly to a Python method with no boilerplate.

The resulting viewer handles live data, keyboard navigation, and file export in under 60 lines of application logic. That is the payoff for Textual's separation between layout declaration and event handling.

Where to go next

Next module: Plugin Systems — extending Python tools with discoverable, installable plugins using entry points and importlib.metadata.

Finished reading? Mark it complete to track your progress.

On this page