もう定番のじゃんけん学校プロジェクトをはるかに超えています。早速、その内容に飛び込んでみましょう。
Trello ボード管理用の Python CLI プログラムを作成する方法 (パート 1)では、Trello SDK と対話するビジネス ロジックを正常に作成しました。
ここでは、CLI プログラムのアーキテクチャを簡単に要約します。
このチュートリアルでは、機能要件と非機能要件に焦点を当てて、プロジェクトを CLI プログラムに変換する方法を見ていきます。
一方で、プログラムをPyPI上のパッケージとして配布する方法も学びます。
以前は、 trelloservice
モジュールをホストするためのスケルトンをセットアップすることができました。今回は、さまざまな機能のモジュールを含むcli
フォルダーを実装したいと思います。
この考え方は、コマンド グループごとに、そのコマンドが独自のモジュールに保存されるということです。 list
コマンドに関しては、どのコマンド グループにも属さないため、メイン CLI ファイルに保存します。
一方、フォルダー構造のクリーンアップを見てみましょう。より具体的には、ディレクトリが乱雑にならないようにすることで、ソフトウェアのスケーラビリティを考慮することから始める必要があります。
フォルダー構造に関する提案は次のとおりです。
trellocli/ __init__.py __main__.py trelloservice.py shared/ models.py custom_exceptions.py cli/ cli.py cli_config.py cli_create.py tests/ test_cli.py test_trelloservice.py assets/ images/ README.md pyproject.toml .env .gitignore
assets
フォルダーがあることに気づきましたか?これはREADME
の関連アセットを保存するために使用されますが、 trellocli
には新しく実装されたshared
フォルダーがあり、ソフトウェア全体で使用されるモジュールを保存するために使用します。
まず、エントリ ポイント ファイル__main__.py
を変更しましょう。インポート自体を見ると、関連するモジュールを独自のサブフォルダーに保存することにしたため、そのような変更に対応する必要があります。一方で、メインの CLI モジュールcli.py
には、実行できるapp
インスタンスがあることも想定しています。
# trellocli/__main__.py # module imports from trellocli import __app_name__ from trellocli.cli import cli from trellocli.trelloservice import TrelloService # dependencies imports # misc imports def main(): cli.app(prog_name=__app_name__) if __name__ == "__main__": main()
cli.py
ファイルに早送りしてください。ここにapp
インスタンスを保存します。アイデアは、ソフトウェア全体で共有されるようにTyper
オブジェクトを初期化することです。
# trellocli/cli/cli.py # module imports # dependencies imports from typer import Typer # misc imports # singleton instances app = Typer()
この概念を進めて、 pyproject.toml
を変更してコマンド ライン スクリプトを指定しましょう。ここでは、パッケージの名前を指定し、エントリ ポイントを定義します。
# pyproject.toml [project.scripts] trellocli = "trellocli.__main__:main"
上記のサンプルに基づいて、パッケージ名としてtrellocli
定義し、 trellocli
モジュールに保存されている__main__
スクリプト内のmain
関数が実行時に実行されます。
ソフトウェアの CLI 部分が設定されたので、CLI プログラムをより適切に提供できるようにtrelloservice
モジュールを変更しましょう。思い出したように、 trelloservice
モジュールは、承認されるまでユーザーの承認を再帰的に求めるように設定されています。許可が与えられない場合にプログラムが終了し、ユーザーにconfig access
コマンドの実行を促すようにこれを変更します。これにより、プログラムがより明確になり、命令に関してより説明的なものになります。
これを言葉で表すと、次の関数を変更します。
__init__
__load_oauth_token_env_var
authorize
is_authorized
__init__
関数から始めて、ここでクライアントのセットアップを処理する代わりに、空のクライアントを初期化します。
# trellocli/trelloservice.py class TrelloService: def __init__(self) -> None: self.__client = None
チャレンジ コーナー💡 ユーザーの承認を再帰的に求めるプロンプトが表示されないように、 __load_oauth_token_env_var
関数を変更できますか?ヒント: 再帰関数とは、それ自体を呼び出す関数です。
authorize
およびis_authorized
ヘルパー関数に移ります。この概念は、 authorize
__load_oauth_token_env_var
関数を利用してクライアントをセットアップするビジネス ロジックを実行するのに対し、 is_authorized
関数は単に認可が与えられたかどうかのブール値を返すだけであるということです。
# trellocli/trelloservice.py class TrelloService: def authorize(self) -> AuthorizeResponse: """Method to authorize program to user's trello account Returns AuthorizeResponse: success / error """ self.__load_oauth_token_env_var() load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): return AuthorizeResponse(status_code=TRELLO_AUTHORIZATION_ERROR) else: self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) return AuthorizeResponse(status_code=SUCCESS) def is_authorized(self) -> bool: """Method to check authorization to user's trello account Returns bool: authorization to user's account """ if not self.__client: return False else: return True
__load_oauth_token_env_var
とauthorize
の違いは、 __load_oauth_token_env_var
が環境変数として認可トークンを保存する内部向けの関数であるのに対し、 authorize
公開向けの関数であり、必要な認証情報をすべて取得して Trello クライアントを初期化しようとすることです。
チャレンジ コーナー💡 authorize
関数がAuthorizeResponse
データ型を返す方法に注目してください。 status_code
属性を持つモデルを実装できますか? Trello ボード管理用の Python CLI プログラムの作成方法のパート 1を参照してください (ヒント: モデルの作成方法を見てください)。
最後に、モジュールの下部にシングルトンTrelloService
オブジェクトをインスタンス化しましょう。完全なコードがどのように見えるかを確認するには、このパッチを参照してください: trello-cli-kit
# trellocli/trelloservice.py trellojob = TrelloService()
最後に、プログラム全体で共有されるいくつかのカスタム例外を初期化します。これは、イニシャライザで定義されたERRORS
とは異なります。これらの例外はBaseException
のサブクラスであり、一般的なユーザー定義の例外として機能しますが、 ERRORS
は 0 から始まる定数値として機能します。
例外を最小限に抑え、最も注目に値するいくつかの一般的な使用例を見てみましょう。
# trellocli/shared/custom_exceptions.py class TrelloReadError(BaseException): pass class TrelloWriteError(BaseException): pass class TrelloAuthorizationError(BaseException): pass class InvalidUserInputError(BaseException): pass
パート I で述べたように、このチュートリアルでは単体テストについては詳しく説明しません。そのため、必要な要素のみを扱います。
このアイデアは、期待される結果をテストするためのshell
のようなコマンド ライン インタープリターを模擬することです。 Typer
モジュールの優れている点は、独自のrunner
オブジェクトが付属していることです。テストの実行に関しては、 pytest
モジュールと組み合わせます。詳細については、 Typer による公式ドキュメントを参照してください。
最初のテスト、つまりアクセスを構成するテストを一緒にやってみましょう。関数が適切に実行されるかどうかをテストしていることを理解してください。そのために、システムの応答と、終了コードがsuccess
(別名 0) であるかどうかを確認します。終了コードとは何か、およびシステムがそれを使用してプロセスを通信する方法については、 RedHat による優れた記事を参照してください。
# trellocli/tests/test_cli.py # module imports from trellocli.cli.cli import app # dependencies imports from typer.testing import CliRunner # misc imports runner = CliRunner() def test_config_access(): res = runner.invoke(app, ["config", "access"]) assert result.exit_code == 0 assert "Go to the following link in your browser:" in result.stdout
チャレンジ コーナー💡要点は理解できたので、他のテスト ケースを自分で実装できますか? (ヒント: 失敗ケースのテストも考慮する必要があります)
これがメインのcli
モジュールになることを理解してください。すべてのコマンド グループ (config、create) のビジネス ロジックは、読みやすさを高めるために独自の別個のファイルに保存されます。
このモジュールでは、 list
コマンドを保存します。コマンドをさらに深く掘り下げると、次のオプションを実装する必要があることがわかります。
config board
事前に設定されていない場合は必須
これを実現するには、board_name 必須オプションをはじめ、いくつかの方法があります。そのうちの 1 つは、コールバック関数を使用する方法 (詳細については、公式ドキュメントを参照してください)、または単純にデフォルトの環境変数を使用する方法です。ただし、このユースケースでは、条件が満たされない場合にInvalidUserInputError
カスタム例外を発生させることで、処理をわかりやすくしましょう。
コマンドを構築するには、オプションの定義から始めましょう。 Typer では、公式ドキュメントで述べられているように、オプションを定義するための重要な要素は次のとおりです。
たとえば、次の条件でdetailed
オプションを作成します。
コードは次のようになります。
detailed: Annotated[bool, typer.Option(help=”Enable detailed view)] = None
全体として、必要なオプションを指定してlist
コマンドを定義するには、 list
Python 関数として扱い、そのオプションを必須パラメーターとして扱います。
# trellocli/cli/cli.py @app.command() def list( detailed: Annotated[bool, Option(help="Enable detailed view")] = None, board_name: Annotated[str, Option(help="Trello board to search")] = "" ) -> None: pass
ファイルの先頭に向かって初期化されたapp
インスタンスにコマンドを追加していることに注意してください。公式 Typer コードベースを自由に移動して、好みに合わせてオプションを変更してください。
コマンドのワークフローとしては、次のようなものを予定しています。
board_name
オプションが指定されているかどうかを確認してください)detailed
オプションが選択されているか確認)
注意すべき点がいくつかあります…
SUCCESS
以外のステータス コードを生成した場合に例外を発生させる必要があります。 try-catch
ブロックを使用すると、プログラムの致命的なクラッシュを防ぐことができます。board_id
に基づいて Trello ボードを使用できるように設定しようとします。したがって、次のユースケースをカバーしたいと考えています。get_all_boards
関数を使用して一致をチェックすることにより、 board_name
明示的に指定されている場合は、 board_id
を取得します。board_name
オプションが使用されなかった場合に、環境変数として保存されているboard_id
を取得するrich
パッケージのTable
機能を使用してフォーマットされます。 rich
の詳細については、公式ドキュメントを参照してください。
すべてをまとめると、次のようになります。免責事項: TrelloService
にはまだ実装されていない機能がいくつかある可能性があります。実装にヘルプが必要な場合は、このパッチを参照してください: trello-cli-kit
# trellocli/cli/cli.py # module imports from trellocli.trelloservice import trellojob from trellocli.cli import cli_config, cli_create from trellocli.misc.custom_exceptions import * from trellocli import SUCCESS # dependencies imports from typer import Typer, Option from rich import print from rich.console import Console from rich.table import Table from dotenv import load_dotenv # misc imports from typing_extensions import Annotated import os # singleton instances app = Typer() console = Console() # init command groups app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations") app.add_typer(cli_create.app, name="create", help="COMMAND GROUP to create new Trello elements") @app.command() def list( detailed: Annotated[ bool, Option(help="Enable detailed view") ] = None, board_name: Annotated[str, Option()] = "" ) -> None: """COMMAND to list board details in a simplified (default)/detailed view OPTIONS detailed (bool): request for detailed view board_name (str): board to use """ try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # if board_name OPTION was given, attempt to retrieve board id using the name # else attempt to retrieve board id stored as an env var board_id = None if not board_name: load_dotenv() if not os.getenv("TRELLO_BOARD_ID"): print("[bold red]Error![/] A trello board hasn't been configured to use. Try running `trellocli config board`") raise InvalidUserInputError board_id = os.getenv("TRELLO_BOARD_ID") else: res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving boards from trello") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # retrieve all board id(s) and find matching board name if board_name not in boards_list: print("[bold red]Error![/] An invalid trello board name was provided. Try running `trellocli config board`") raise InvalidUserInputError board_id = boards_list[board_name] # configure board to use res_get_board = trellojob.get_board(board_id=board_id) if res_get_board.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when configuring the trello board to use") raise TrelloReadError board = res_get_board.res # retrieve data (labels, trellolists) from board res_get_all_labels = trellojob.get_all_labels(board=board) if res_get_all_labels.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError labels_list = res_get_all_labels.res res_get_all_lists = trellojob.get_all_lists(board=board) if res_get_all_lists.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving data from board") raise TrelloReadError trellolists_list = res_get_all_lists.res # store data on cards for each trellolist trellolists_dict = {trellolist: [] for trellolist in trellolists_list} for trellolist in trellolists_list: res_get_all_cards = trellojob.get_all_cards(trellolist=trellolist) if res_get_all_cards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving cards from trellolist") raise TrelloReadError cards_list = res_get_all_cards.res trellolists_dict[trellolist] = cards_list # display data (lists count, cards count, labels) # if is_detailed OPTION is selected, display data (name, description, labels) for each card in each trellolist print() table = Table(title="Board: "+board.name, title_justify="left", show_header=False) table.add_row("[bold]Lists count[/]", str(len(trellolists_list))) table.add_row("[bold]Cards count[/]", str(sum([len(cards_list) for cards_list in trellolists_dict.values()]))) table.add_row("[bold]Labels[/]", ", ".join([label.name for label in labels_list if label.name])) console.print(table) if detailed: for trellolist, cards_list in trellolists_dict.items(): table = Table("Name", "Desc", "Labels", title="List: "+trellolist.name, title_justify="left") for card in cards_list: table.add_row(card.name, card.description, ", ".join([label.name for label in card.labels if label.name])) console.print(table) print() except (AuthorizationError, InvalidUserInputError, TrelloReadError): print("Program exited...")
ソフトウェアの動作を確認するには、ターミナルでpython -m trellocli --help
を実行するだけです。デフォルトでは、Typer モジュールは独自に--help
コマンドの出力を設定します。そして、パッケージ名としてtrellocli
呼び出す方法に注目してください。これが以前にpyproject.toml
でどのように定義されていたかを思い出してください。
少し早送りして、 create
コマンド グループとconfig
コマンド グループも初期化しましょう。これを行うには、 app
オブジェクトでadd_typer
関数を使用するだけです。考え方としては、コマンド グループが独自のapp
オブジェクトを持ち、それをコマンド グループの名前とヘルパー テキストとともにcli.py
のメインapp
に追加するだけです。このように見えるはずです
# trellocli/cli/cli.py app.add_typer(cli_config.app, name="config", help="COMMAND GROUP to initialize configurations")
チャレンジコーナー💡create create
グループを自分でインポートできますか?ヘルプが必要な場合は、このパッチを参照してください: trello-cli-kit
create
のコマンド グループを設定するには、それぞれのコマンドを独自のモジュールに保存します。設定はcli.py
の設定と似ていますが、Typer オブジェクトをインスタンス化する必要があります。コマンドに関しては、カスタム例外を使用する必要性も遵守したいと考えています。さらに取り上げたいトピックは、ユーザーがCtrl + C
を押したとき、つまりプロセスを中断したときです。 list
コマンドでこれを説明しなかった理由は、ここでの違いは、 config
コマンド グループが対話型コマンドで構成されているためです。対話型コマンドの主な違いは、継続的なユーザー対話が必要であることです。もちろん、直接コマンドの実行には長い時間がかかるとします。潜在的なキーボード割り込みを処理することもベスト プラクティスです。
access
コマンドから始めて、最後にTrelloService
で作成したauthorize
関数を使用します。 authorize
関数は構成をすべて独自に処理するため、プロセスの実行を確認するだけで済みます。
# trellocli/cli/cli_config.py @app.command() def access() -> None: """COMMAND to configure authorization for program to access user's Trello account""" try: # check authorization res_authorize = trellojob.authorize() if res_authorize.status_code != SUCCESS: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except AuthorizationError: print("Program exited...")
board
コマンドに関しては、ユーザー対話用のターミナル GUI を表示するシンプル ターミナル メニューなど、優れたユーザー エクスペリエンスを提供するためにさまざまなモジュールを利用します。主なアイデアは次のとおりです。
# trellocli/cli/cli_config.py @app.command() def board() -> None: """COMMAND to initialize Trello board""" try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # retrieve all boards res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving trello boards") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # for easy access to board id when given board name # display menu to select board boards_menu = TerminalMenu( boards_list.keys(), title="Select board:", raise_error_on_interrupt=True ) boards_menu.show() selected_board = boards_menu.chosen_menu_entry # set board ID as env var dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_BOARD_ID", value_to_set=boards_list[selected_board] ) except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except (AuthorizationError, TrelloReadError): print("Program exited...")
最後に、ソフトウェアの中核となる機能要件、つまり Trello ボードのリストに新しいカードを追加する作業に移ります。 list
コマンドからボードからデータを取得するまでは同じ手順を使用します。
それとは別に、新しいカードを適切に構成するために対話的にユーザー入力を要求します。
ユーザーがリストから選択する必要があるすべてのプロンプトについては、以前と同様にSimple Terminal Menu
パッケージを使用します。他のプロンプトや、テキスト入力やユーザーの確認の必要性などのその他の項目については、単純にrich
パッケージを使用します。オプションの値を適切に処理する必要があることに注意することも重要です。
# trellocli/cli/cli_create.py @app.command() def card( board_name: Annotated[str, Option()] = "" ) -> None: """COMMAND to add a new trello card OPTIONS board_name (str): board to use """ try: # check authorization res_is_authorized = trellojob.is_authorized() if not res_is_authorized: print("[bold red]Error![/] Authorization hasn't been granted. Try running `trellocli config access`") raise AuthorizationError # if board_name OPTION was given, attempt to retrieve board id using the name # else attempt to retrieve board id stored as an env var board_id = None if not board_name: load_dotenv() if not os.getenv("TRELLO_BOARD_ID"): print("[bold red]Error![/] A trello board hasn't been configured to use. Try running `trellocli config board`") raise InvalidUserInputError board_id = os.getenv("TRELLO_BOARD_ID") else: res_get_all_boards = trellojob.get_all_boards() if res_get_all_boards.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving boards from trello") raise TrelloReadError boards_list = {board.name: board.id for board in res_get_all_boards.res} # retrieve all board id(s) and find matching board name if board_name not in boards_list: print("[bold red]Error![/] An invalid trello board name was provided. Try running `trellocli config board`") raise InvalidUserInputError board_id = boards_list[board_name] # configure board to use res_get_board = trellojob.get_board(board_id=board_id) if res_get_board.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when configuring the trello board to use") raise TrelloReadError board = res_get_board.res # retrieve data (labels, trellolists) from board res_get_all_labels = trellojob.get_all_labels(board=board) if res_get_all_labels.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving the labels from the trello board") raise TrelloReadError labels_list = res_get_all_labels.res labels_dict = {label.name: label for label in labels_list if label.name} res_get_all_lists = trellojob.get_all_lists(board=board) if res_get_all_lists.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when retrieving the lists from the trello board") raise TrelloReadError trellolists_list = res_get_all_lists.res trellolists_dict = {trellolist.name: trellolist for trellolist in trellolists_list} # for easy access to trellolist when given name of trellolist # request for user input (trellolist, card name, description, labels to include) interactively to configure new card to be added trellolist_menu = TerminalMenu( trellolists_dict.keys(), title="Select list:", raise_error_on_interrupt=True ) # Prompt: trellolist trellolist_menu.show() print(trellolist_menu.chosen_menu_entry) selected_trellolist = trellolists_dict[trellolist_menu.chosen_menu_entry] selected_name = Prompt.ask("Card name") # Prompt: card name selected_desc = Prompt.ask("Description (Optional)", default=None) # Prompt (Optional) description labels_menu = TerminalMenu( labels_dict.keys(), title="Select labels (Optional):", multi_select=True, multi_select_empty_ok=True, multi_select_select_on_accept=False, show_multi_select_hint=True, raise_error_on_interrupt=True ) # Prompt (Optional): labels labels_menu.show() selected_labels = [labels_dict[label] for label in list(labels_menu.chosen_menu_entries)] if labels_menu.chosen_menu_entries else None # display user selection and request confirmation print() confirmation_table = Table(title="Card to be Added", show_header=False) confirmation_table.add_row("List", selected_trellolist.name) confirmation_table.add_row("Name", selected_name) confirmation_table.add_row("Description", selected_desc) confirmation_table.add_row("Labels", ", ".join([label.name for label in selected_labels]) if selected_labels else None) console.print(confirmation_table) confirm = Confirm.ask("Confirm") # if confirm, attempt to add card to trello # else, exit if confirm: res_add_card = trellojob.add_card( col=selected_trellolist, name=selected_name, desc=selected_desc, labels=selected_labels ) if res_add_card.status_code != SUCCESS: print("[bold red]Error![/] A problem occurred when adding a new card to trello") raise TrelloWriteError else: print("Process cancelled...") except KeyboardInterrupt: print("[yellow]Keyboard Interrupt.[/] Program exited...") except (AuthorizationError, InvalidUserInputError, TrelloReadError, TrelloWriteError): print("Program exited...")
チャレンジコーナー💡 add
プロセスの進行状況バーを表示できますか?ヒント: rich
のステータス機能の使用方法を見てください。
ここからが楽しい部分です。PyPI でソフトウェアを正式に配布します。これを行うには、次のパイプラインに従います。
詳細な説明については、 Ramit Mittal による Python パッケージングに関するこの素晴らしいチュートリアルを参照してください。
pyproject.toml
に必要な最後の詳細は、パッケージ自体を格納するモジュールを指定することです。私たちの場合、それはtrellocli
になります。追加するメタデータは次のとおりです。
# pyproject.toml [tool.setuptools] packages = ["trellocli"]
README.md
に関しては、使用ガイドラインや開始方法など、何らかのガイドを提供することをお勧めします。 README.md
に画像を含めた場合は、その絶対 URL を使用する必要があります。通常は次の形式です。
https://raw.githubusercontent.com/<user>/<repo>/<branch>/<path-to-image>
build
ツールとtwine
ツールを使用して、パッケージをビルドして公開します。ターミナルで次のコマンドを実行して、パッケージのソース アーカイブとホイールを作成します。
python -m build
TestPyPI でアカウントがすでにセットアップされていることを確認し、次のコマンドを実行します。
twine upload -r testpypi dist/*
ユーザー名とパスワードを入力するように求められます。 2 要素認証が有効になっているため、API トークンを使用する必要があります (TestPyPI API トークンの取得方法の詳細については、ドキュメントへのリンクを参照してください)。次の値を入力するだけです。
それが完了したら、TestPyPI に移動して、新しく配布されたパッケージをチェックアウトできるようになります。
目標は、タグに基づいてパッケージの新しいバージョンを継続的に更新する手段として GitHub を利用することです。
まず、GitHub ワークフローのActions
タブに移動し、新しいワークフローを選択します。 GitHub Actions によって作成されたPublish Python Package
ワークフローを使用します。ワークフローでリポジトリ シークレットからの読み取りがどのように必要であるかに注目してください。 PyPI トークンが指定された名前で保存されていることを確認してください (PyPI API トークンの取得は TestPyPI の取得と似ています)。
ワークフローが作成されたら、コードをタグ v1.0.0 にプッシュします。バージョン命名構文の詳細については、Py-Pkgs による優れた説明を参照してください:ドキュメントへのリンク
通常のpull
、 add
、 commit
コマンドを実行するだけです。次に、次のコマンドを実行して、最新のコミットのタグを作成します (タグの詳細については、ドキュメントへのリンクを参照してください)。
git tag <tagname> HEAD
最後に、新しいタグをリモート リポジトリにプッシュします。
git push <remote name> <tag name>
さらに詳しく知りたい場合は、Karol Horosin による CI/CD と Python パッケージの統合に関する素晴らしい記事を参照してください。しかし今は、落ち着いて最新の成果をお楽しみください 🎉。パッケージを PyPI に配布するときに、GitHub Actions ワークフローとして魔法が解き明かされるのを自由に観察してください。
長くなりました😓。このチュートリアルを通じて、 Typer
モジュールを使用してソフトウェアを CLI プログラムに変換し、パッケージを PyPI に配布する方法を学びました。さらに深く理解するために、コマンドとコマンド グループを定義し、対話型 CLI セッションを開発し、キーボード割り込みなどの一般的な CLI シナリオに取り組む方法を学習しました。
あなたはすべてを乗り越えた絶対的な魔法使いでした。オプション機能を実装するパート 3 に参加しませんか?