【Python中級】cursesとTyperを組み合わせた設計

Python中級

1.curses と Typer を「どう組み合わせて設計するか」

  • Typer → 「入口(コマンドライン)」を担当
  • curses → 「中の TUI 画面」を担当

という分担にすると収まりがいいです。

全体構成

mytool/
  __init__.py
  cli/
    __init__.py
    main.py        # Typerアプリの定義
  tui/
    __init__.py
    app.py         # cursesのメインループ
    screens/
      __init__.py
      menu.py
      detail.py
  core/
    logic.py       # 共通ロジック(CLI/TUI 両方から使う)

Typer 側:mytool/cli/main.py

import typer
from . import tui_commands

app = typer.Typer(help="サンプル管理ツール")

@app.command()
def tui():
    """テキストUIを起動する"""
    from mytool.tui.app import run_tui
    run_tui()

@app.command()
def batch(...):
    """バッチ処理"""
    from mytool.core.logic import run_batch
    run_batch(...)

# 他のサブコマンド…など

if __name__ == "__main__":
    app()

curses 側:mytool/tui/app.py

import curses
from .screens.menu import MenuScreen

def main(stdscr):
    curses.curs_set(0)
    screen = MenuScreen(stdscr)

    while True:
        screen.render()
        key = stdscr.getch()
        if not screen.handle_key(key):
            break  # False を返したら終了

def run_tui():
    curses.wrapper(main)

共通ロジック:mytool/core/logic.py

def list_items():
    # ファイルやDBを読む処理
    return ["item1", "item2", "item3"]

TUI の MenuScreen も、CLI の batch コマンドも、この list_items() を呼ぶようにする。
ロジックの再利用ができる設計

__init__.py

mytool/__init__.py(トップレベル)

# mytool/__init__.py
# 空でもOK(コメントだけでもOK)

#↓よくあるパターンはversionとエントリポイントだけを出す
<記載例>
# mytool/__init__.py
from .cli.main import app as cli_app
from .tui.app import main as tui_main  # 関数名は例

__all__ = ["cli_app", "tui_main"]
__version__ = "1.0.0"

#上記のようにすることでトップレベルから触りやすくなる。
<使用例>
import mytool

mytool.cli_app()   # Typer CLI を起動
mytool.tui_main()  # curses TUI を起動
print(mytool.__version__)

mytool/cli/__init__.py

ここも 空でOK ですが、「cli パッケージを使う人に何を見せたいか」で決めます。

<記載例>
# mytool/cli/__init__.py
from .main import app

__all__ = ["app"]

<使用例※Typerアプリをここからimportする>
from mytool.cli import app

app()  # Typer 起動

__init__.pyのまとめ

  • 全部の __init__.py は「空でも動く」
  • ただし、綺麗にしたいなら:
    • トップ:__version__ と、よく使う入口だけ export
    • cli/__init__.pyfrom .main import app
    • tui/__init__.pyfrom .app import main
    • screens/__init__.py:公開したい Screen クラスだけ export
    • core/__init__.py:共通ロジック(run_job など)を export

という感じに「外からどう見えるか」を整える場所として使うと綺麗です。

2.まとめ

  • Typer のサブコマンドの 1 つとして curses アプリを起動する
  • どちらからも呼ばれる共通処理は core/ などに分離する
  • 「入口は Typer、中身のインタラクティブな画面は curses」という役割分担にするときれい
タイトルとURLをコピーしました