3 ポイント 投稿者 GN⁺ 2023-12-10 | 1件のコメント | WhatsAppで共有
  • Gooey は Python 3 のコンソールアプリケーションを、エンドユーザーが使いやすい GUI アプリケーションに変換し、argparse 宣言のある関数にデコレータを1行付けるだけで動作する
  • 基本的なインストールは pip install Gooey で可能で、@Gooey デコレータと ArgumentParser を一緒に使うと、Gooey が引数を読み取って WX コンポーネント ベースの UI を構成する
  • 標準のウィジェット選択で不足する場合は、GooeyParserArgumentParser の代替として使い、widget 引数を指定して FileChooserDateChooserPasswordFieldSlider などの カスタムウィジェット を選べる
  • 対象ユーザーは CLI を直接扱う開発者向けツールよりも、オフィス向けの「実行して完了」スクリプトや非プログラマ向けツールであり、高度/基本/設定なしの実行モード、メニュー、進捗表示、国際化、アイコン変更、パッケージ化をサポートする
  • 動的バリデーションとライフサイクルイベントは実験的機能として扱われており、optparse は現在サポートされず、ArgumentParser ベースのコードと任意のイベント購読を前提としている

コンソールプログラムを GUI に変える方法

  • Gooey はコンソールアプリケーションを、エンドユーザーが扱いやすい GUI アプリケーションへ変換する
  • 基本的な使い方は、argparse 宣言を含む関数に @Gooey デコレータを付けること
from gooey import Gooey

@Gooey
def main():
  parser = ArgumentParser(...)

# rest of code
  • スタイルと動作はデコレータ引数で調整できる
    • advanced: 高度な設定画面を表示するかどうか
    • language: JSON ベースの翻訳を選択
    • auto_start: 設定画面をスキップして即時実行
    • target: サブプロセスとして実行するコマンドを指定
    • program_name, program_description: GUI に表示する名前と説明
    • default_size: GUI の初期サイズ
    • dump_build_config, load_build_config: Gooey ビルド設定 JSON の保存/読み込み
    • monospace_display: 出力画面で等幅フォントを使用

インストールと例

  • 最も簡単なインストール方法は pip
pip install Gooey
  • リポジトリをクローンして setup.py を実行してインストールすることもできる
git clone https://github.com/chriskiehl/Gooey.git
python setup.py install
  • 用意されたサンプルスクリプトは Gooey Examples リポジトリから入手できる
  • サンプルリポジトリは、Gooey のさまざまなレイアウト、ウィジェット、機能を確認するために提供されている

なぜ必要なのか

  • Gooey は、コマンドプロンプトに不慣れなユーザーへそれを見せる問題を減らすために作られた
  • プログラムが1つ以上の処理を行うにはオプションを受け取る必要があり、従来は GUI を自作するか、コンソールアプリケーションに引数を渡す方法を説明しなければならなかった
  • Gooey は、開発者が慣れたやり方で構成可能なプログラムを作りつつ、表示と操作を GUI として提供できるようにする

向いているユーザーと向かないケース

  • 自分自身や他のプログラマ向けのユーティリティ、他のコンソールアプリケーションへパイプする結果を生成するツールには Gooey は適していない場合がある
  • オフィス向け実行スクリプト、拠点 A から B へデータを移すツール、非プログラマ向けプログラムには適している
  • 複雑なアプリケーションを作りながら、GUI 側を追加コストなしで得ることが目標である

内部動作と argparse のマッピング

  • Gooey は実行時に Python スクリプト内の ArgumentParser 参照をパースする
  • optparse は現在サポートされない
  • 抽出された引数は、指定された action に応じて component type が割り当てられ、GUI 構成に使われる
  • ArgumentParser._actions は基本的に次の WX コンポーネントへマッピングされる
    • store: TextCtrl
    • store_const, store_true, store_False, version: CheckBox
    • append: TextCtrl
    • count: DropDown
    • Mutually Exclusive Group: RadioGroup
    • choice: DropDown

GooeyParser とカスタムウィジェット

  • 標準のウィジェット選択で十分でない場合は、ArgumentParser のドロップイン置換である GooeyParser を使用できる
  • GooeyParser は追加のキーワード引数 widget を提供し、表示するコンポーネント名を指定できる
from gooey import Gooey, GooeyParser

@Gooey
def main():
  parser = GooeyParser(description="My Cool GUI Program!")
  parser.add_argument('Filename', widget="FileChooser")
  parser.add_argument('Date', widget="DateChooser")
  • サポートされるカスタムウィジェットには次が含まれる
    • DirChooser, FileChooser, MultiFileChooser
    • FileSaver, MultiFileSaver
    • DateChooser, TimeChooser
    • PasswordField
    • Listbox
    • BlockCheckbox
    • ColourChooser
    • FilterableDropdown
    • IntegerField, DecimalField
    • Slider
  • DateChooserTimeChooser がアプリケーションへ渡す値は常に ISO format であり、GUI の一部にはエンドユーザー設定に応じたローカライズ値が表示されることがある
  • BlockCheckbox は、長いヘルプテキストがある場合に標準のインラインチェックボックスより見やすくするため、テキストブロックを通常位置へ移し、コントロールの横には短い block_label を表示する

国際化

  • Gooey はデコレータの language 引数で表示言語を選択する
@Gooey(language='russian')
def main():
    ...
  • すべてのプログラムテキストは外部の JSON ファイル に保存される
  • 新しい言語サポートは gooey/languages/ ディレクトリにキー/値を追加する方式
  • 現在は 18 以上の翻訳が標準で用意されている

グローバル設定と UI 構成

  • Gooey の全体的な見た目と動作はデコレータ引数で調整する
  • 主な設定には次が含まれる
    • encoding: 表示文字エンコーディング、デフォルトは utf-8
    • advanced: 完全な設定画面または簡易画面を選択
    • auto_start: 設定なしで即時実行
    • target: Gooey が自身を再実行する方法を指定
    • suppress_gooey_flag: カスタム target 使用時に追加 CLI パラメータの注入を防ぐ
    • fullscreen: 全画面で開始
    • image_dir: カスタム画像/アイコンディレクトリ
    • language_dir: カスタム言語ファイルディレクトリ
    • disable_stop_button, show_stop_warning: 実行中の停止ボタンと警告を制御
    • show_success_modal, show_failure_modal: 成功/失敗サマリーモーダルを表示するかどうか
    • run_validators: プログラム呼び出し前にバリデーションを実行するかどうか
    • progress_regex, progress_expr: 進捗テキストの解析と変換
    • navigation: TABBED または SIDEBAR
    • body_bg_color, header_bg_color, footer_bg_color, sidebar_bg_color: 色設定
    • terminal_font_family, terminal_font_size, terminal_font_color: ターミナル表示フォント設定
    • menus: カスタムメニューグループと項目
    • clear_before_run: 再実行時に前回の出力を削除

レイアウトと実行モード

  • Gooey は入力をデフォルトで positionaloptional の2グループに分ける
  • argparseadd_argument_group() を使うと、入力を論理グループにまとめ、各グループのレイアウトを調整できる
parser = ArgumentParser()
search_group = parser.add_argument_group(
    "Search Options",
    "Customize the search options"
)
search_group.add_argument(
    '--query',
    help='Base search string'
)
  • サポートされる全体レイアウトオプションには show_sidebar=True, show_sidebar=False, navigation='TABBED', tabbed_groups=True が含まれる
  • Advanced

    • デフォルト画面は full/advanced 設定画面
    • ほとんどのアプリケーションでは CLI 構造に合ったフラットレイアウトが適している
    • 複数のパス、または小さなツール群の組み合わせからなる CLI にはカラムレイアウトが適している
    • カラムレイアウトでは、基本パスを左列に、対応する引数を右側に表示する
    • 現在のレイアウトはパラメータで明示指定できず、コードに subparsers があるかどうかで構成される
  • Basic

    • advanced=False を設定すると基本表示に切り替わる
    • ユーザーがコンソールアプリケーションには慣れているが、単純なターミナルより洗練された画面を求める場合に適している
    @gooey(advanced=False)
    def main():
    
    # rest of code
    
  • No Config

    • auto_start=True を設定すると設定画面なしでそのまま表示領域へ移動し、ホストプログラムを実行する
    • 小さな単発スクリプトの見た目を改善するのに使われる
    @Gooey(auto_start=True)
    def main():
        ...
    

メニュー機能

  • Gooey 1.0.2 以降、上部に Menu Bar を追加できる
  • メニューは @Gooey(menu=[{}, {}, ...]) 形式のマップ一覧で指定する
  • 各メニューグループは nameitems を持つ
  • 各メニュー項目は常に typemenuTitle を含む
  • サポートされるメニュー項目タイプは次のとおり
    • AboutDialog: 名前、バージョン、ライセンスなどのプログラム情報を表示
    • MessageDialog: ユーザーに情報メッセージを表示
    • Link: 指定 URL をデフォルトブラウザで開く
    • HtmlDialog: 制限付き HTML サブセットを使ったダイアログを表示

動的バリデーション

  • Dynamic Validation は実験的機能であり、API が変更または削除される可能性がある
  • Gooey はユーザー入力をプログラムへ渡す前に、任意で事前バリデーションを実行できる
  • この機能は、ほとんどの Argparse 引数型で使える type パラメータを活用する
def must_be_exactly_ten(value):
    number = int(value)
    if number == 10:
        return number
    else:
        raise TypeError("Hey! you need to provide exactly the number 10!")
  • バリデーションはデフォルトでは実行されない
  • 有効化するには VALIDATE_FORM イベントを購読する必要がある
from gooey import Gooey, Events

@Gooey(use_events=[Events.VALIDATE_FORM])
def main():
    ...
  • この機能は内部で強い Monkey Patching を使うため、現在はオプトイン方式になっている

ライフサイクルイベントと UI 制御

  • Gooey 1.2.0 以降、プログラムに ライフサイクルフック を公開している
  • この機能も実験的であり、API が変更または削除される可能性がある
  • 現在の主なフックは2つ
    • on_success
    • on_error
  • どちらのフックもプロセス完了後に実行される
  • ハンドラはパース済みの Argparse オブジェクトと現在の Gooey UI 状態を受け取り、更新された状態を返すと UI に反映できる
  • ハンドラは GooeyParser 作成時に接続する
parser = GooeyParser(
    on_success=my_success_handler,
    on_failure=my_failure_handler)
  • 使用するにはイベントを明示的に購読する必要がある
from gooey import Gooey, Events

@Gooey(use_events=[Events.ON_SUCCESS, Events.ON_ERROR])
def main():
    ...

進捗と時間表示

  • Gooey は既存のテキスト進捗出力を読み取って Progress Bar を駆動できる
  • 単純なケースでは Progress 83% のような文字列を正規表現でマッチさせて進捗へ変換する
@Gooey(progress_regex=r"^progress: (\d+)%$")
  • より複雑な出力は progress_expr で正規表現マッチ結果を変換できる
@Gooey(progress_regex=r"^progress: (?P<current>\d+)/(?P<total>\d+)$",
       progress_expr="current / total * 100")
  • hide_progress_msg=True を指定すると、進捗にマッチしたテキスト出力をコンソールから隠せる
  • timing_options を使うと経過時間と残り時間を表示できる
  • 時間表示は progress_regexprogress_expr を使う場合にのみ動作する

アイコンとパッケージ化

  • Gooey は標準で6つのアイコンを提供し、image_dir 引数でカスタム画像ディレクトリを指定して上書きできる
  • 画像はファイル名ベースで検索され、上書き可能なファイル名は次のとおり
    • program_icon.png
    • success_icon.png
    • running_icon.png
    • loading_icon.gif
    • config_icon.png
    • error_icon.png
  • 実行ファイルのパッケージ化では PyInstaller の使用例が提供されている
  • 概要手順は、アプリケーションルートに build.spec を置き、APPPNAMEnamepathex の値をプロジェクトに合わせて修正してから実行すること
pyinstaller -F --windowed build.spec

1件のコメント

 
GN⁺ 2023-12-10
Hacker News のコメント
  • あ、これ自分のプロジェクトなんだけど ^_^ なぜ HN のトップに上がっているんだろう?
    argparse に関するコメントに短く答えると、Gooey はもうかなり古いプロジェクトで、開始当時は argparse が堅実な選択肢だった
    最近の Gooey 自体は JSON で動作し、argparse からは切り離されているが、別のインターフェースを作った人がほとんどいないので、argparse が今も主な「公式」インターフェースとして残っている
    面白い点としては、Python だけでなく 任意の実行ファイル も呼び出せるのでかなり便利: https://chriskiehl.com/article/gooey-as-a-universal-frontend
    最後のコミットが 2 年前だという話については、年を取り優先順位が変わると、ニッチなソフトウェアを無料で維持し続ける理由を見つけるのが難しくなる :( 慰めになるかは分からないけど、いつも罪悪感は感じている
    不思議なことに、住んでいる街のあちこちに「GOOEY」のグラフィティがあって、放置された issue トラッカーを思い出させる常時通知みたいに見える haha

    • argparse は今でもかなり良い選択肢。非常に広く使われていて、ドキュメントもよく整っており、比較的簡単
      Typer と Click も使い勝手は良いが、Typer の「チュートリアル」式のドキュメントは、Typer.App() がどんな引数を受け取るのか、といった答えを探すにはかなり検索しづらいと感じる
      今作業しているプログラムは、ユーザーが自分で引数解析を始める構造なので argparse がよく合う。ユーザーにテキストベース UI を選択肢として提供したいが、Textualize は Click が必要だった気がするし、Textual をコードに直接つないで UI を作ろうとしても数時間使ったのに始めることすらできなかった
    • 本当に素晴らしいアイデア。API から分離されたインターフェースが好き
      最近はアプリケーションをプログラムから制御できないことで生じる苛立ちが多すぎる。正直、すべてのアプリケーション機能には API があるべきだという法律があればいいのにと思う
    • 素晴らしいプロジェクト! 気になるのだけど、なぜ sys.argv を現在の作業ディレクトリのローカルファイルにダンプしているの? https://github.com/chriskiehl/Gooey/blob/be4b11b8f27f500e732...
      tmp.txt は一意な名前とはほど遠いので、何か見落としているのか気になっている
    • 「最近の Gooey 自体は JSON で動作し、argparse からは切り離されている」という部分が素晴らしい
      タイトルだけ見てクリックする前は「きっと argparse に強く依存しているんだろう」と思っていたので、良い意味で驚いた
      私は熱心な getopt ファンで、開発者の都合よりユーザーの都合の方がはるかに重要だと考えているから。ユーザーは、開発者がどのプログラミング言語やオプション解析ライブラリを選んだかを気にする必要があってはならない
      argparse、Click、Go の flag/X11 スタイルなど、多くのライブラリは、半世紀にわたって積み上がった集団的な筋肉記憶に刻まれた慣習を壊している。だが中間レイヤーがあれば、二兎を得ることができそうに見える
    • Gooey がとても古い理由は、放置していたから。責めたいわけではなく、ただ現実がそうだという意味
      さらに悪いのは、それを置き換えられる実行可能な代替案も出ておらず、2 つのフォークのどちらも traction を得られなかったこと
  • これは「argparse ベース」と限定すべきでは?
    argparse は単純な用途には良いが、Click ベースの CLI も多く、人気のある CLI ライブラリのかなりの数が Click の上に構築されている。argparse や Click ではない Python CLI ツールもあるが、この 2 つがおそらく最も人気のある部類ではある
    Click でも動作することは確認されているのか? 今は argparse 関連の内容しか見えない。答えがノーなら、「ほぼすべて」という表現は明らかに誤りで、より正確なタイトルは「ほぼすべての argparse ベースの Python コマンドラインプログラムを完全な GUI アプリケーションに変える」になる
    最後の意味のあるコミットが 2 年以上前という点もある。それ自体が悪いわけではないが、未解決 issue の数を見ると、プロジェクトへの信頼はあまり湧かない
    それでも素晴らしいプロジェクト。こういうものをもっと見たい。README で述べているように力を倍増させるツールなので、誰かが CLI で自動化を作り、技術職ではないオフィスの人たちに簡単に共有できる。Access がデータベースに対して果たした役割に似ている
    Access と同じように、規模や複雑さが増すと限界にぶつかるだろうが、小規模なオフィス用途には十分に「良い程度」になり得る

    • argparse にどんな不足を見ているのか気になる。自分の好みは外部依存を最小化する方向
    • 前職でまさにこの用途に使っていた。多くの作業を自動化し、チーム向けのツールを作ったが、ほとんどが Python プログラマーではなかったので、Gooey でスクリプトの上に単純な GUI を載せた。とてもよく動いた
    • 具体的に argparse でできないことって何があるの?
  • 関連記事:
    Gooey: ほぼすべての Python コマンドラインプログラムを GUI アプリケーションに変える - https://news.ycombinator.com/item?id=27490291 - 2021年6月、コメント 115 件
    Gooey: コマンドラインプログラムを GUI アプリケーションに変える - https://news.ycombinator.com/item?id=8218785 - 2014年8月、コメント 74 件

  • OSやシェルが特定のパースライブラリに依存しなくても、プログラムの実行方法をもっとよく理解できるといいのに。
    プログラム同士が型付きのJSON/proto形式で通信し、期待する入力・出力の型を覗くだけで、シェルコマンドの構造化・自動補完や完全なGUIを得られるといい。
    今のところは、慎重にストローを差し込んで作った程度のものが最善に見える。プログラムごとに複数のシェル用補完ファイルを出力しなければならず、プログラムやパースライブラリごとにフラグのスタイルが大きく異なり、当然GUIもない。

    • いまはPowerShellというものがある。もう少し真面目に答えると、こういうことを本当に試みたOSはおそらくPlan 9くらいだったと思う。
    • OSネイティブ対応のある汎用的なgRPC交換/CLI APIのようなものを言っているのかな? それはかなり素敵なものになり得る。
      ただ、gRPCの主な問題は、多くの開発者――自分も含めて――が、言語の中で自然に使う解決策に比べると少し扱いが面倒だと感じる点だと思う。
    • PowerShellを試してみるといい。基本的にはMicrosoftの**.NETエコシステム**を対話型コマンドラインとして形にしたものだ。
      PowerShellが中核にある静的型を完全に活用できるのかはよく分からないが、コマンドラインでオブジェクトをやり取りする能力はほぼ唯一無二だ。
      Linuxではjc(https://github.com/kellyjonbrazil/jc)とjq(https://jqlang.github.io/jq/)を組み合わせてコマンドラインをつなげられるが、PowerShellの組み込み機能に比べると、まだ余計に2段階の処理が必要になる。
  • GUIは、ときどきコンフォートフードのように感じる。めったに使わないCLIで生じがちな認知負荷なしに、GUIインターフェースをただ見て回れるからだ。
    少し話は違うが、数か月前にここでCUDATEXTエディタを知った。単一ファイルのPython APIで、MENUやINPUTSのような任意のGUI要素を使えるようにしてくれる。これらの要素はエディタ自身も使っているものだ。
    今はそうした単純なGUI要素で設定を行い、エディタ内からそのままブログを生成している。

  • 併せて見るとよさそうなもの:
    “Textual: Python向けの軽量アプリケーションフレームワーク。シンプルなPython APIで洗練されたユーザーインターフェースを作り、アプリをターミナルとWebブラウザで実行” https://github.com/textualize/textual/

    • Gooeyに似たものもある: https://github.com/Textualize/trogon “Click CLIを強力なターミナルアプリケーションに簡単に変える”
    • うわ、これはどうやって実現しているんだ? ターミナルの例が想像していたよりずっと細かく作り込まれている。
    • camplyにTextual対応を追加するのはかなり簡単だったようだ。実装したのは自分ではなく、たまにプロジェクトに貢献しているだけだけど: https://juftin.com/camply/command_line_usage/#tui
      camply: https://juftin.com/camply/
  • この素晴らしいソフトウェアを作ってくれてありがとうと言いたい。Gooeyが好きで、いくつものプログラムでよく使ってきた。
    自分にとっては、Pythonスクリプトが重要な仕事をしていて、それを今度は非プログラマーにも使えるようにしなければならない、まさにその領域にぴったり合う。

  • これはnaked objectsを思い出させる。Javaクラスにいくつかのアノテーションとテーマを定義するだけで、GUI全体、あるいはWebフロントエンドアプリケーションが生成される、というアイデアだった。
    とてもクールなアイデアだったが、私の知る限り、結局うまくはいかなかった。

    • Djangoにも似た哲学がある。モデルを定義するとデータベーステーブルにそのまま反映され、そのモデルからフォームを自動生成できる。
      私が思うに、問題はプレゼンテーション、ドメイン、インフラといった垂直方向の関心事が互いに結合してしまう点だ。ドメインモデルの画面上の表現とデータベース上の表現が単純なうちは問題ない。
      だが便利機能の多い複雑なインターフェースを作りたいなら、その要件がドメイン層へ漏れ出さざるを得ない。逆に複雑なドメインモデルを作りたいなら、言語レベルで不変条件を強制するのは非現実的だ。Model参照を持つどんなコードでも任意に触れてしまえるからだ。
  • 逆方向にやってくれるものもあるといい。

    • GUIでargparseから得た情報をもとにパラメータスイープを設定し、その結果を適切に実行・保存するシェルスクリプトへ変換し直せるといい。
    • WYSIWYGエディタがCLIを吐き出す、ということを言っているのかな? かなり面白そうだが、必然的にある程度の制約はありそうだ。誰かがそういうアプローチを試したのか気になるけれど、思い当たる例はない。
  • 昔のMacintosh Programmer's Workshop、つまり80〜90年代のMac OS向けテキストベースシェルであるMPWには、ほぼすべてのコマンドに対してCommandoという似た機能があった。
    Commandoの情報はcmdoリソースに保存されていた。拡張属性のサポートがまちまちでなければ、今日でも似たようなことができたはずだ。
    調べてみると、A/UXにもCommandoがあった。FinderでターミナルコマンドをダブルクリックするとCLIオプションを選ぶCommandoボックスが開き、その後シェルウィンドウで実行された。最近のFinderでターミナルコマンドを実行すると、引数なしでシェルウィンドウ内でただ実行されるだけだ。
    https://cohost.org/boredzo/post/804893-i-still-want-a-moder
    http://toastytech.com/guis/aux3.html