フリーキーズ | 独学プログラミング

PythonのTextualでTUIアプリ開発!ターミナルでGUI並みの美しいツールを作ろう

最終更新日
リンドくん

リンドくん

たなべ先生、最近「TUI」という言葉をよく聞くんですけど、これって何なんですか?

たなべ

たなべ

TUI(Text User Interface)は、ターミナル上でGUI並みに美しいアプリが作れる技術なんだ。
今日は特にPythonの「Textual」というライブラリを使って、驚くほど簡単にリッチなツールが作れることを紹介するよ!

プログラミングを学んでいる皆さん、ターミナルでの作業は好きですか?
コマンドライン操作は確かに軽快で効率的ですが、時には「もっと視覚的に分かりやすくならないかな...」と思うことってありますよね。

実は今、そんな悩みを解決する革新的な技術が注目されています。
それがTUIです。特にPythonの「Textual」ライブラリを使えば、わずか100行程度のコードで、まるでGUIアプリのような美しいツールをターミナル上に作ることができるんです。

この記事では、TUIの基本概念から実際のアプリケーション開発まで、初心者の方でも理解しやすいよう丁寧に解説していきます。
最後には、システムリソースをリアルタイムで監視する、カラフルなダッシュボードの作り方もご紹介しますので、ぜひ最後までお読みください!

↓動画版↓

TUIとは

TUIとは、ターミナル上で動作するグラフィカルなユーザーインターフェースのことです。
これまでのCLI(コマンドライン)の軽さと高速性を保ちながら、GUI(グラフィカルユーザーインターフェース)のような視覚的な分かりやすさを実現した、まさに「いいとこ取り」の技術なんです。

TUIが注目される理由

従来のインターフェースと比較してみましょう。

CLI(Command Line Interface)の特徴

  • 軽量で高速
  • リソース消費が少ない
  • 学習コストが高い
  • 視覚的な情報が限定的

GUI(Graphical User Interface)の特徴

  • 直感的で分かりやすい
  • マウス操作が簡単
  • メモリ・CPU消費が大きい
  • 開発に時間がかかる

TUI(Text User Interface)の特徴

  • CLIの軽さとGUIの視覚性を両立
  • 色やレイアウトが使える
  • キーボード中心の高速操作
  • 開発が比較的簡単

TUIが活躍する場面

TUIが特に威力を発揮するのは以下のような場面です。

  • DevOps向けダッシュボード - サーバーリソースの監視やログ解析
  • 開発ツール - Git操作(tig)やファイル管理(ranger)
  • クラウド管理 - Docker操作(Lazydocker)やKubernetes管理(k9s)
  • 社内ツール - データベース管理やバッチ処理の監視

これらのツールは、SSH経由でリモートサーバーを操作する際にも非常に便利で、現代のクラウドネイティブな開発現場でも重宝されているんです。

PythonのTextualライブラリの魅力

リンドくん

リンドくん

PythonでTUIを作るには、どんなライブラリを使えばいいんですか?

たなべ

たなべ

Textualというライブラリが断然おすすめだよ!
Richライブラリの作者が開発したもので、Web開発者にも馴染みやすいCSSライクな記法でレイアウトを定義できるんだ。

Textualの主要機能

Textualは、Will McGugan氏によって開発されたモダンなTUIフレームワークです。
同氏は人気ライブラリ「Rich」の作者でもあり、TextualはRichの描画エンジンを基盤として開発されています。

主な特徴をご紹介します。

豊富な表現力

  • 絵文字やテーブル、Markdownの美しい表示
  • グラデーションを含む豊富な色表現
  • プログレスバー、グラフなどの視覚的要素

現代的なレイアウトシステム

  • CSSライクな記法でレイアウト定義
  • FlexboxやGridのような現代的手法
  • テーマの簡単な切り替え

リアクティブな仕組み

  • データ変更時の自動UI更新
  • イベント駆動のインタラクション
  • リアルタイムデータ表示

開発者に優しい機能

特に開発者にとって嬉しいのがDevToolsの存在です。
textual run --devコマンドでアプリケーションを起動すると、ブラウザの開発者ツールのような感覚でTUIアプリケーションをデバッグできるんです。

これは本当に便利で、DOM構造やイベントログを確認でき、ホットリロード機能により、コードを変更すると即座にアプリケーションに反映されます。

また、MITライセンスで提供されているため、商用利用も問題ありません。企業での利用や有償ソフトウェアへの組み込みも可能です。

Textualのインストールと最初のTUIアプリ

現代的なPython開発環境として、今回はuvを使用した方法をおすすめします。
もちろん、pipを使っても問題ありません。

環境準備とインストール

まず、プロジェクトディレクトリを作成し、Python環境を準備します。

# プロジェクトディレクトリの作成と初期化
mkdir my-tui-app
cd my-tui-app
uv init -p 3.13  # Python 3.13を指定

# Textualとdev用ツールのインストール
uv add textual textual-dev
uv sync

textual-devは開発用ツールが含まれており、デバッグ時に非常に便利なので必ず含めることをおすすめします。

最小構成のTUIアプリケーション

それでは、最小構成のTUIアプリケーションを作ってみましょう。
たった10行程度で動作するTUIアプリケーションが作れるんです。

from textual.app import App
from textual.widgets import Static


class Hello(App):
    def compose(self):
        yield Static("Hello, TUI!")


Hello().run()

このコードをmain.pyとして保存し、以下のコマンドで実行してみてください。

uv run textual run main.py

実行すると、ターミナル上に「Hello, TUI!」というテキストが表示されたウィンドウが現れます。
Ctrl+QまたはCtrl+Pを押してアプリケーションを終了できます。

開発モードでのデバッグ

開発モードを試してみましょう。これはデバッグ時に非常に便利です。

# 別のターミナルでコンソールを起動
uv run textual console

# メインターミナルで開発モードで実行
uv run textual run --dev main.py

これにより、TUI内部で発生しているイベントを確認でき、print関数の出力もコンソールに表示されます。

CSSライクなスタイリング

Textualの素晴らしい点は、CSSライクなスタイリングができることです。

from textual.app import App
from textual.widgets import Static

class StyledApp(App):
    CSS = """
    Static {
        background: blue;
        color: white;
        padding: 2;
    }
    """
    
    def compose(self):
        yield Static("スタイル付きTUIアプリ")

StyledApp().run()

また、CSS_PATH変数を使って、Pythonコードからスタイル部分を分離することも可能です。
公式では.tcssという拡張子を推奨しています。

実践的なシステム監視ダッシュボードの作成

ここからは、より実用的なTUIアプリケーションとして、システムリソースをリアルタイムで監視するダッシュボードの概要をご紹介します。

必要なライブラリ

システム情報を取得するために、psutilライブラリが必要です。

uv add psutil

ソースコード

# sys_view.py
import psutil
from textual.app import App, ComposeResult
from textual.color import Gradient
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import reactive
from textual.widgets import Footer, Header, ProgressBar, Sparkline, Static


# ---------- ウィジェット定義 ----------
class Gauge(Container):
    """ラベル + ProgressBar + Sparkline のセット"""

    percent = reactive(0.0)
    history = reactive(list)

    def __init__(self, title: str, *, gradient: Gradient, classes: str = "") -> None:
        super().__init__()
        self.title_text = title
        self.gradient = gradient
        self.classes = classes
        self.bar: ProgressBar = ProgressBar(
            total=100, show_eta=False, gradient=self.gradient
        )
        self.spark: Sparkline = Sparkline()

    def compose(self) -> ComposeResult:
        yield Static(self.title_text, classes="gauge-label")
        self.bar = ProgressBar(total=100, show_eta=False, gradient=self.gradient)
        self.spark = Sparkline()
        yield self.bar
        yield self.spark

    def watch_percent(self, value: float) -> None:
        self.bar.update(progress=value)

    def watch_history(self, data: list[int]) -> None:
        self.spark.data = data


# ---------- メイン部分 ----------
class SystemPulseApp(App):
    CSS = """
    Screen { layout: vertical; }
    Container.gauge { border: round #555555; padding: 1 2; height: auto; }
    .gauge-label { text-style: bold; margin-bottom: 1; }
    """

    def compose(self) -> ComposeResult:
        yield Header(show_clock=True)

        rainbow = Gradient.from_colors(
            "#881177",
            "#aa3355",
            "#cc6666",
            "#ee9944",
            "#eedd00",
            "#99dd55",
            "#44dd88",
            "#22ccbb",
            "#00bbcc",
            "#0099cc",
            "#3366bb",
            "#663399",
        )

        with Vertical():
            yield Static("CPU Usage (%)", classes="gauge-label")
            self.cpu_gauge = Gauge("CPU使用率 (%)", gradient=rainbow, classes="gauge")
            yield self.cpu_gauge
            self.mem_gauge = Gauge(
                "メモリ使用率 (%)", gradient=rainbow, classes="gauge"
            )
            yield self.mem_gauge
            with Container(classes="gauge"):
                yield Static("Network I/O (kB/s)", classes="gauge-label")
                self.net_up = Sparkline(id="up")
                yield self.net_up
                self.net_down = Sparkline(id="down")
                yield self.net_down
                with Horizontal():
                    yield Static("↑", classes="arrow")
                    yield self.net_up
                with Horizontal():
                    yield Static("↓", classes="arrow")
                    yield self.net_down

        yield Footer()

    # ---------- ランタイム ----------
    async def on_mount(self) -> None:
        self.cpu_hist, self.mem_hist = [], []
        self.net_hist_up, self.net_hist_down = [], []
        self.prev_net = psutil.net_io_counters()
        self.set_interval(1.0, self.refresh_stats)  # 1 秒ごと更新

    async def refresh_stats(self) -> None:
        # CPU / MEM
        cpu = psutil.cpu_percent()
        mem = psutil.virtual_memory().percent
        self.cpu_hist.append(cpu)
        self.mem_hist.append(mem)
        self.cpu_hist, self.mem_hist = self.cpu_hist[-60:], self.mem_hist[-60:]
        self.cpu_gauge.percent = cpu
        self.mem_gauge.percent = mem
        self.cpu_gauge.history = self.cpu_hist
        self.mem_gauge.history = self.mem_hist

        # NETWORK
        now = psutil.net_io_counters()
        up = (now.bytes_sent - self.prev_net.bytes_sent) / 1024  # kB/s
        down = (now.bytes_recv - self.prev_net.bytes_recv) / 1024
        self.prev_net = now
        self.net_hist_up.append(up)
        self.net_hist_down.append(down)
        self.net_hist_up, self.net_hist_down = (
            self.net_hist_up[-60:],
            self.net_hist_down[-60:],
        )
        self.net_up.data = self.net_hist_up
        self.net_down.data = self.net_hist_down


if __name__ == "__main__":
    SystemPulseApp().run()

アプリケーションの構成要素

このダッシュボードでは、以下の要素を組み合わせます。

カスタムGaugeウィジェット

  • ProgressBarで現在の使用率を表示
  • Sparklineで過去60秒の履歴をグラフ表示
  • ラベルで数値情報を表示

監視対象

  • CPU使用率
  • メモリ使用率
  • ネットワークトラフィック(上り・下り)

リアルタイム更新

  • 1秒ごとの自動更新
  • 虹色のプログレスバー(使用率に応じて色が変化)
  • 履歴データの蓄積と表示

実装のポイント

レイアウト設計

  • Verticalコンテナで各ゲージを縦に配置
  • Flexboxライクなレイアウトシステムを活用
  • レスポンシブなデザイン

データ取得と更新

  • psutilを使ったシステム情報の取得
  • reactive属性による自動UI更新
  • set_intervalメソッドでの定期実行

視覚的な表現

  • Gradient.from_colors()での虹色プログレスバー
  • 使用率に応じた警告色の表示
  • 心拍数モニターのようなSparkline表示

このようなシステム監視ツールは、実際の運用現場でも非常に有用です。
特に、SSH経由でリモートサーバーにアクセスして作業する際、軽量でありながら視覚的に分かりやすいツールは作業効率を大幅に向上させます。

まとめ

リンドくん

リンドくん

TUIって思った以上に可能性がありますね!これなら自分でも作ってみたくなりました。

たなべ

たなべ

その通り!TUIはターミナルの再発明とも言える技術なんだ。
重いGUIアプリを起動する必要もなく、CLIの味気ない画面に我慢する必要もない。まさに現代の開発者が求めていた技術だよね。

今回は、PythonのTextualライブラリを使ったTUI開発について詳しく見てきました。
重要なポイントをおさらいしましょう。

TUIの革新性

  • CLIの軽さとGUIの美しさを両立
  • 現代のクラウド環境に最適な技術
  • SSH接続でのリモート作業に最適

Textualの優位性

  • Web開発者にも馴染みやすいCSS記法
  • 少ないコードで本格的なアプリケーション
  • 充実した開発ツールとデバッグ機能

活用の幅広さ

  • システム監視ツール
  • データベース管理ツール
  • DevOps・SRE現場でのツール開発
  • 社内ツールの効率化

Textualの可能性は本当に無限大です。今回紹介したのは基本的な機能のほんの一部に過ぎません。
リアルタイムチャットアプリケーション、ゲーム要素を含んだツール、さらには最近話題のAI技術と組み合わせれば、更に面白いツールが作れそうですね。

あなたも今すぐTUI開発を始めてみませんか?
まずは今回紹介した簡単なHello Worldアプリから始めて、徐々に機能を追加していけば、きっと素晴らしいツールが作れるはずです。

関連するコンテンツ