Conversation
- サブコマンドのインポートを遅延させることで、ヘルプ表示を約50%高速化(1秒→0.5秒) - ヘルプ表示時は必要最小限のモジュールのみロード - サブコマンド実行時のみ、該当するモジュールをインポート - 後方互換性のためcreate_parser()関数は維持 パフォーマンス改善: - 元のバージョン: 平均1036ms - 遅延インポート版: 平均556ms - 改善率: 約46% これにより補完ツールでの使用も実用的な速度になりました。 AI(GitHub Copilot)が生成したコミットです。
Title遅延インポートによるヘルプ表示の高速化 Description
Changes walkthrough 📝
|
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
| # サブコマンドの簡易登録(遅延ロード用) | ||
| for cmd_name, cmd_info in SUBCOMMANDS.items(): | ||
| subparser = subparsers.add_parser(cmd_name, help=cmd_info["help"], add_help=False) | ||
| # 実際のサブコマンドは実行時にロードする |
There was a problem hiding this comment.
Suggestion: 遅延ロード用サブコマンドにヘルプオプションがないため、-h/--helpでサブコマンド固有のヘルプが表示できません。add_help=Falseを削除するか、サブパーサーに明示的に-h/--help引数を追加してヘルプを有効化してください。 [general, importance: 5]
| # サブコマンドの簡易登録(遅延ロード用) | |
| for cmd_name, cmd_info in SUBCOMMANDS.items(): | |
| subparser = subparsers.add_parser(cmd_name, help=cmd_info["help"], add_help=False) | |
| # 実際のサブコマンドは実行時にロードする | |
| for cmd_name, cmd_info in SUBCOMMANDS.items(): | |
| subparser = subparsers.add_parser(cmd_name, help=cmd_info["help"]) # add_help=False を削除 | |
| subparser.add_argument("-h", "--help", action="help", help="サブコマンドのヘルプを表示します。") | |
| subparser.set_defaults(_lazy_module=cmd_info["module"], _lazy_command=cmd_name) |
| else: | ||
| # 未知のサブコマンドの場合はヘルプを表示 | ||
| args.command_help() | ||
| # サブコマンドが見つからない場合 | ||
| parser_lazy.print_help() |
There was a problem hiding this comment.
Suggestion: 未知のサブコマンド実行時にヘルプを表示したまま正常終了してしまうため、エラーコードを返すようにしてください。sys.exit(1)を追加することで呼び出し元に失敗を伝えられます。 [general, importance: 7]
| else: | |
| # 未知のサブコマンドの場合はヘルプを表示 | |
| args.command_help() | |
| # サブコマンドが見つからない場合 | |
| parser_lazy.print_help() | |
| else: | |
| # サブコマンドが見つからない場合 | |
| parser_lazy.print_help() | |
| sys.exit(1) |
| # ヘルプ表示のみの場合は遅延パーサーを使う | ||
| if len(argv) == 0 or argv[0] in ["-h", "--help"]: | ||
| parser = create_parser_lazy() | ||
| args = parser.parse_args(argv) | ||
| if hasattr(args, "command_help"): | ||
| args.command_help() | ||
| return |
There was a problem hiding this comment.
ヘルプ表示/バージョン表示の分岐と遅延ロードの新しい制御フローが入っているため、最低限 tests/test____main__.py などで main([]) / main(["--help"]) / main(["task","--help"]) のようなケースが期待どおりに動く(SystemExit の有無や終了コードなど)ことをテストで担保したいです。現状この経路を直接検証するテストが見当たりません。
| parser = argparse.ArgumentParser(description="Command Line Interface for Annofab", formatter_class=annofabcli.common.cli.PrettyHelpFormatter) | ||
| parser.add_argument("--version", action="version", version=f"annofabcli {annofabcli.__version__}") | ||
| parser.set_defaults(command_help=parser.print_help) | ||
|
|
There was a problem hiding this comment.
create_parser_lazy() と load_subcommand_parser() と create_parser() で、トップレベルの ArgumentParser 生成(description/formatter/--version/defaults)が重複しています。将来どれか一方だけが更新されると挙動差分が出やすいので、共通のヘルパー(例: create_base_parser())に切り出して重複をなくすのが安全です。
| # バージョン表示 | ||
| if argv[0] in ["--version"]: | ||
| parser = create_parser_lazy() | ||
| args = parser.parse_args(argv) |
There was a problem hiding this comment.
--version 処理の分岐で args = parser.parse_args(argv) を代入していますが、その後 args を参照していないため Ruff の F841 (local variable 'args' is assigned to but never used) でlintエラーになります。代入せずに parser.parse_args(argv) を呼ぶか、未使用であることが明確な変数名(例: _args)にしてください。
| args = parser.parse_args(argv) | |
| parser.parse_args(argv) |
| # 未知のサブコマンドの場合はヘルプを表示 | ||
| args.command_help() | ||
| # サブコマンドが見つからない場合 | ||
| parser_lazy.print_help() |
There was a problem hiding this comment.
parse_known_args() の結果でサブコマンドが判定できなかった場合に無条件で print_help() してしまうため、例えば annofabcli --unknown-option のような入力でもエラー終了せずヘルプ表示(終了コード0)になってしまいます(以前は argparse の unrecognized arguments で終了コード2)。_remaining に未知の引数が残っている場合は parser_lazy.parse_args(argv) でエラーにする、もしくは parser_lazy.error(...) を呼ぶなどして従来どおりエラー扱いにしてください。
| parser_lazy.print_help() | |
| if len(_remaining) != 0: | |
| # 未知の引数が残っている場合は、従来どおりエラー扱いにする | |
| parser_lazy.parse_args(argv) | |
| else: | |
| parser_lazy.print_help() |
|
|
||
| if hasattr(args_lazy, "_lazy_module"): | ||
| # 実際のサブコマンドパーサーをロード | ||
| parser = load_subcommand_parser(args_lazy._lazy_module) # noqa: SLF001 |
There was a problem hiding this comment.
args_lazy._lazy_module のように先頭が _ の属性名を使っているため、アクセス側で # noqa: SLF001 が必要になっています。CLI内部用の値であれば lazy_module のような名前に変更して noqa を不要にすると、意図が明確になり lint抑止も減らせます。
| parser = load_subcommand_parser(args_lazy._lazy_module) # noqa: SLF001 | |
| lazy_module = getattr(args_lazy, "_lazy_module") | |
| parser = load_subcommand_parser(lazy_module) |
| args.subcommand_func(args) | ||
| except Exception as e: | ||
| logger.exception(e) # noqa: TRY401 | ||
| raise e # noqa: TRY201 |
There was a problem hiding this comment.
例外捕捉後に raise e で再送出すると traceback が書き換わり、元の例外発生箇所が追いづらくなります。ここは bare raise で再送出してください(ログ出力は現状のままでOKです)。
| raise e # noqa: TRY201 | |
| raise |
概要
ヘルプ表示を高速化するため、サブコマンドのインポートを遅延させる実装を追加しました。
変更内容
create_parser()関数は維持(テストコードで使用されている可能性があるため)パフォーマンス改善結果
動作確認
annofabcli --helpの表示が正常に動作することを確認annofabcli task --helpなど)が正常に動作することを確認影響範囲
AI(GitHub Copilot)が生成したPRです。