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__.py:from .main import apptui/__init__.py:from .app import mainscreens/__init__.py:公開したい Screen クラスだけ exportcore/__init__.py:共通ロジック(run_job など)を export
- トップ:
という感じに「外からどう見えるか」を整える場所として使うと綺麗です。
2.まとめ
- Typer のサブコマンドの 1 つとして curses アプリを起動する
- どちらからも呼ばれる共通処理は core/ などに分離する
- 「入口は Typer、中身のインタラクティブな画面は curses」という役割分担にするときれい
