Skip to main content

Terminal utilities for CLI, TUI, IO and general use

Project description

termz

Terminal utilities for CLI, TUI, IO and general use.

Overview

termz is a Python library that bundles reusable building blocks for terminal applications. It is organized into four sub-packages:

Package Contents
termz.cli Styled console output via Rich
termz.tui Textual TUI helpers — theme loading, custom widgets, modal screens
termz.io SQLite database abstraction, JSON app-state storage, file utilities
termz.util Datetime helpers, string utilities, singleton metaclass, debug decorators, logging setup

Requirements

Installation

pip install termz

termz.cli — Styled CLI Output

Provides helpers for printing color-coded panels using Rich and for clearing terminal output.

from termz import print_error, print_warning, print_success, print_info, clear_lines

print_success("File saved.")
print_warning("Disk space is low.")
print_error("Connection refused.")
print_info("Starting process...")

# Remove the last 4 rendered lines from the terminal
clear_lines(4)

Functions

Function Description
print_error(message) Red panel with ✗ prefix
print_warning(message) Yellow panel with ⚠ prefix
print_success(message) Green panel with ✓ prefix
print_info(message) Cyan panel with ℹ prefix
print_panel(message, color) Panel with bold colored text
print_custom_panel(formatted_message, panel_color) Panel with pre-formatted Rich markup
clear_lines(count) Move cursor up count lines and clear everything below
get_console() Returns the shared Rich Console instance

termz.tui — Textual TUI Helpers

ThemeLoader

Dynamically loads and registers Textual themes from a folder. Each theme lives in its own sub-directory and must expose a TEXTUAL_THEME variable of type textual.theme.Theme. Optional .css / .tcss files in the same folder are loaded automatically when the theme is activated.

termz ships 16 built-in themes:

classic-black-saturated, classic-black-v1, classic-black-v2, classic-blue, compact-gray, mnml-black, mnml-deepblack, pure-amber, pure-black, pure-blue, pure-green, pure-sweet16, xplore-black, xplore-blue, xplore-blue-muted, xplore-teal

from pathlib import Path
from termz import ThemeLoader

loader = ThemeLoader(
    theme_folder="themes",           # optional: path to custom themes
    include_standard_themes=True,    # include built-in termz themes
)

# In your Textual App.on_mount():
loader.register_themes_in_textual_app(app)
loader.set_previous_theme_in_textual_app(
    app,
    default_theme_name="TERMZ_xplore-blue",
    theme_config_file=Path("~/.config/myapp/theme.json").expanduser(),
)

# When the user changes theme:
loader.save_theme_to_config(app.theme, Path("~/.config/myapp/theme.json").expanduser())
loader.load_theme_css(app.theme, app)

# Cycle through themes with arrow keys:
loader.change_to_next_or_previous_theme(direction=1, app=app)

Theme name prefixes:

  • Built-in termz themes: TERMZ_ (e.g. TERMZ_xplore-blue)
  • Custom themes: CUSTOM_ (e.g. CUSTOM_mytheme)

Both prefixes can be customized via the ThemeLoader constructor.

QuestionScreen

A Textual ModalScreen that presents a yes/no dialog and returns a bool.

from termz import QuestionScreen, ButtonColor

async def confirm_delete(self):
    answer = await self.app.push_screen_wait(
        QuestionScreen(
            question="Delete this entry?",
            yes_button_color=ButtonColor.ERROR,
            no_button_color=ButtonColor.PRIMARY,
        )
    )
    if answer:
        self.do_delete()

ButtonColor values: DEFAULT, PRIMARY, ERROR, SUCCESS, WARNING.

CustomDataTable

A subclass of Textual's DataTable that supports flexible columns — columns that automatically fill the remaining width when the terminal is resized.

from termz.tui.custom_widgets.custom_data_table import CustomDataTable

table = CustomDataTable()
col_name = table.add_column("Name", width=20)
col_desc = table.add_column("Description")
table.flexible_columns = [col_desc]  # This column will stretch to fill space

CustomCheckbox

A subclass of Textual's Checkbox that shows a when checked and an empty box when unchecked, instead of the default X.

from termz.tui.custom_widgets.custom_checkbox import CustomCheckbox

yield CustomCheckbox("Enable feature", value=True)

CustomSelectionList

A subclass of Textual's SelectionList whose items show a when selected and an empty box when unselected, instead of the default X.

from termz.tui.custom_widgets.custom_selection import CustomSelectionList

yield CustomSelectionList(
    ("Option A", "a"),
    ("Option B", "b"),
)

MultiLineFooter

A drop-in replacement for Textual's built-in Footer that supports multiple rows of key bindings. Two modes are available:

  • auto_wrap=True (default) — bindings wrap automatically when the row is full.
  • auto_wrap=False — bindings are assigned to rows explicitly via row_map.
from termz.tui.custom_widgets.multiline_footer import MultiLineFooter

# Auto-wrap (default)
yield MultiLineFooter()

# Manual row assignment
yield MultiLineFooter(
    auto_wrap=False,
    row_map={
        'quit': 0,
        'save': 0,
        'toggle_dark': 1,
        'help': 1,
    },
)

CustomBindings

Loads keyboard bindings from a YAML file and exposes them as Textual Binding objects. Supports global bindings, tab-specific bindings, and screen-specific bindings.

Group naming conventions:

Group name Scope Action prefix
_global Always visible none (used as-is)
<name>_tab Shown when that tab is active <name>_tab_
<name>_screen Shown on that screen none (used as-is)

YAML example (bindings.yaml):

_global:
  - key: q
    action: quit
    description: Quit
    priority: true
    row: 1

tasks_tab:
  - key: a
    action: add_task
    description: Add
    row: 0

add_screen:
  - key: escape
    action: cancel
    description: Cancel
    row: 0

Usage:

from termz.tui.custom_bindings import CustomBindings

bindings = CustomBindings('bindings.yaml', sort_alphabetically=False)

# In your App or Screen:
BINDINGS = bindings.get_bindings()                        # all bindings
BINDINGS = bindings.get_bindings(tab_name='tasks_tab')    # tab-specific + global
BINDINGS = bindings.get_bindings(screen_name='add')       # screen-specific + global

# Row map for MultiLineFooter(auto_wrap=False):
row_map = bindings.get_row_map()

# In check_action to hide tab bindings that don't belong to the active tab:
def check_action(self, action, parameters):
    return bindings.handle_check_action(action, parameters, active_group=self.active_tab)

termz.io — IO Utilities

AppStateStorage

A JSON-backed singleton for persisting small application states (scroll position, last selection, command history, etc.).

from termz import AppStateStorage

# Initialize once (e.g. at startup)
storage = AppStateStorage(package_name="myapp")
# State file is created at ~/.local/state/myapp/state.json

# Read / write simple values
storage.set("last_tab", "settings")
tab = storage.get("last_tab", default_value="home")

# List operations
storage.list_insert("history", 0, "command_1")
storage.edit_list_item("history", 0, "label", "renamed")
storage.move_list_item("history", 0, 2)
storage.delete_list_item("history", 0)

Because AppStateStorage is a singleton, subsequent calls to AppStateStorage() anywhere in the application return the same instance. Supply an explicit state_file_path instead of package_name for a custom path.

Database

A lightweight SQLite abstraction.

from termz import Database, Condition, SQLComparisonOperator, SQLOrderByDirection, ColumnOrder

with Database("data.db") as db:
    # Fetch with conditions and ordering
    rows = db.fetch(
        table="tasks",
        columns=["id", "title", "due_date"],
        conditions=[
            Condition("done", SQLComparisonOperator.EQ, 0),
        ],
        orderby=[ColumnOrder("due_date", SQLOrderByDirection.ASC)],
        limit=50,
    )

    # Insert
    inserted = db.insert("tasks", [{"title": "Buy milk", "done": 0}])

    # Update
    db.update("tasks", [{
        "done": 1,
        "@conds": [Condition("id", SQLComparisonOperator.EQ, inserted[0]["id"])],
    }])

    # Delete
    db.remove("tasks", [Condition("id", SQLComparisonOperator.EQ, 42)])

    # Raw SQL
    db.query("CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY KEY, title TEXT, done INTEGER)")
    db.save()

File

Static helpers for file and folder operations.

from termz import File

# List folder contents (optionally filtered by extension and recursive)
items = File.folder_content("./data", extfilter="csv", withsubfolders=True)

# Copy a folder
File.copy_folder("./src_folder", "./dst_folder")

# File extension helpers
ext = File.extension("report.csv")          # "csv"
new_name = File.change_extension("report.csv", "txt")  # "report.txt"
folder = File.path("/home/user/docs/file.txt")          # "/home/user/docs"

Textfile

Simple UTF-8 text file read/write.

from termz import Textfile   # or: from termz.io.textfile import Textfile

content = Textfile.read("notes.txt")
lines   = Textfile.readlines("notes.txt")
Textfile.write("notes.txt", "New content")

termz.util — General Utilities

Logging

from termz import setup_logging
import logging

setup_logging("myapp", level=logging.INFO)
# Writes to ~/.local/state/myapp/app.log

Singleton

A metaclass that enforces the singleton pattern.

from termz import Singleton

class Config(metaclass=Singleton):
    def __init__(self):
        self.debug = False

a = Config()
b = Config()
assert a is b  # True

Datetime

from termz import timestamp_to_date, date_to_timestamp, date_diff, today_timestamp, today_date

ts = date_to_timestamp("01.04.2025")           # German format (default)
ts = date_to_timestamp("2025-04-01", english_format=True)

s  = timestamp_to_date(ts)                     # "01.04.2025"
s  = timestamp_to_date(ts, english_format=True) # "2025-04-01"

days = date_diff(ts1, ts2)                     # difference in days

ts_today = today_timestamp()                   # midnight UNIX timestamp
s_today  = today_date()                        # "02.04.2026"

String

from termz import linewrap, charpos

wrapped = linewrap("A long piece of text that should be wrapped.", linewidth=20)
positions = charpos("hello world", "l")  # [2, 3, 9]

Index Navigation

from termz import next_index

# Navigate a list of 5 items, wrapping around at edges
idx = next_index(current_index=4, max_index=5, direction=1)  # 0  (wraps)
idx = next_index(current_index=0, max_index=5, direction=-1) # 4  (wraps)

# Clamp at boundaries instead of wrapping
idx = next_index(current_index=4, max_index=5, direction=1, loop_behavior=False)  # 4

Validation

from termz import is_number

is_number("3.14")  # True
is_number("abc")   # False
is_number(None)    # False

Debug Decorators

from termz import print_arguments, timing

@print_arguments
def add(a: int, b: int) -> int:
    return a + b

add(3, 5)
# Function add called
# Args: (3, 5)
# Kwargs: {}
# Function add returns: 8


@timing()                  # seconds
@timing(use_ns_timer=True) # nanoseconds
def heavy_computation():
    ...

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

termz-0.1.1.tar.gz (38.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

termz-0.1.1-py3-none-any.whl (52.8 kB view details)

Uploaded Python 3

File details

Details for the file termz-0.1.1.tar.gz.

File metadata

  • Download URL: termz-0.1.1.tar.gz
  • Upload date:
  • Size: 38.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for termz-0.1.1.tar.gz
Algorithm Hash digest
SHA256 a6bfcb00c1a8f1d8111861537b9648cd6dd0f76c31f52c59f83d94082449e069
MD5 69495e4ad23c945bf6124405c01e879e
BLAKE2b-256 bb62dd143b7cefcce1fa6afe1f9aa28b0be22c2f6de95aa2bb19ecdb4182e110

See more details on using hashes here.

File details

Details for the file termz-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: termz-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 52.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for termz-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d8d96ffe6426dea88483251660bab6fe5f31a080419d98f4b9399fbea6311914
MD5 82c9251a7c18d982e3f962a9eb3d7694
BLAKE2b-256 ee31635f1a485881e6d1dd42757f7db12deb166f5d0d53d7547854d3f4686463

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page