こんにちは、遊びましょう!ファイターを選択してください (ジャンケン): ロック
日常的なソフトウェアの多くは、CLI プログラムとしてラップされています。たとえば、 vim
テキスト エディターを考えてみましょう。このツールは、UNIX システムに付属しており、ターミナルでvim <FILE>
実行するだけでアクティブ化できます。
たとえば、コア セクションでproject
プロパティを設定する場合は、 gcloud config set project <PROJECT_ID>
を実行します。
口論 | コンテンツ |
---|---|
引数0 | gクラウド |
引数1 | 構成 |
… | … |
前の例に基づいて、 gcloud config set project <PROJECT_ID>
を実行して、コア セクションにproject
プロパティを設定します。
つまり、 set
はコマンドです。
gcloud config
コマンドに戻ると、公式ドキュメントに記載されているように、 gcloud config
プロパティを変更できるコマンド グループです。使用方法は次のとおりです。
gcloud config GROUP | COMMAND [GCLOUD_WIDE_FLAG … ]
ここで、 COMMAND はset
、 list
などになります… ( GROUP はconfig
であることに注意してください)
gcloud config
コマンド グループの使用法に戻ると、この場合のオプションはGCLOUD_WIDE_FLAG
です。
たとえば、コマンドの詳細な使用法と説明を表示したい場合は、 gcloud config set –help
を実行します。つまり、 --help
がオプションです。
もう 1 つの例は、特定のプロジェクトのコンピューティング セクションでゾーン プロパティを設定する場合、 gcloud config set compute <ZONE_NAME> –project=<PROJECT_ID>
を実行することです。つまり、 --project
値<PROJECT_ID>
を保持するオプションです。
たとえば、dataproc クラスタを作成する場合は、 gcloud dataproc clusters create <CLUSTER_NAME> –region=<REGION>
を実行します。そして、使用方法のドキュメントに記載されているように:
gcloud dataproc clusters create (CLUSTER: –region=REGION)
--region
フラグは、事前に構成されていない場合は必須です。
短いオプションは-
で始まり、その後に 1 つの英数字が続きます。一方、長いオプションは--
で始まり、その後に複数の文字が続きます。短いオプションは、ユーザーが何を望んでいるのかがわかっている場合のショートカットとして考えてください。一方、長いオプションは読みやすいものです。
ロックを選んだんですね!コンピューターが選択を行います。
あなたのチームは Trello を使用してプロジェクトの問題と進捗状況を追跡しています。あなたのチームは、ターミナルを介して新しい GitHub リポジトリを作成するのと同じような、ボードと対話するためのより簡素化された方法を探しています。チームは、ボードの「To Do」列に新しいカードを追加できるという基本要件を備えた CLI プログラムの作成をあなたに依頼しました。
機能要件
非機能要件
オプションの要件
Ps 最後の 2 つの列については心配しないでください。それについては後ほど説明します…
単体テスト
トレロ
CLI
ユーティリティ (その他)
パート1
py-trello
ビジネスロジックの実装パート2
パート 3
コンピューターがハサミを選んだのです!誰がこの戦いに勝つか見てみましょう…
trellocli/ __init__.py __main__.py models.py cli.py trelloservice.py tests/ test_cli.py test_trelloservice.py README.md pyproject.toml .env .gitignore
trellocli
: ユーザーが使用するパッケージ名として機能します (例: pip install trellocli
__init__.py
: パッケージのルートを表し、フォルダーを Python パッケージとして準拠させます__main__.py
: エントリ ポイントを定義し、ユーザーが-m
フラグを使用してファイル パスを指定せずにモジュールを実行できるようにします。たとえば、 python -m <module_name>
でpython -m <parent_folder>/<module_name>.py
を置き換えます。models.py
: グローバルに使用されるクラス (API 応答が準拠すると予想されるモデルなど) を格納します。cli.py
: CLI コマンドとオプションのビジネス ロジックを保存します。trelloservice.py
: py-trello
と対話するビジネス ロジックを保存します。tests
: プログラムの単体テストを保存します。test_cli.py
: CLI 実装の単体テストを保存します。test_trelloservice.py
: py-trello
との対話のための単体テストを保存します。README.md
: プログラムのドキュメントを保存します。pyproject.toml
: パッケージの構成と要件を保存します.env
: 環境変数を保存します.gitignore
: バージョン管理中に無視する (追跡しない) ファイルを指定します。
パッケージ内の__init__.py
ファイルから始めます。このファイルには、アプリ名やバージョンなどのパッケージの定数と変数が保存されます。この例では、次のものを初期化したいと考えています。
# trellocli/__init__.py __app_name__ = "trellocli" __version__ = "0.1.0" ( SUCCESS, TRELLO_WRITE_ERROR, TRELLO_READ_ERROR ) = range(3) ERRORS = { TRELLO_WRITE_ERROR: "Error when writing to Trello", TRELLO_READ_ERROR: "Error when reading from Trello" }
__main__.py
ファイルに進むと、プログラムのメイン フローがここに保存されます。この例では、 cli.py
に呼び出し可能な関数があると想定して、CLI プログラムのエントリ ポイントを保存します。
# trellocli/__main__.py from trellocli import cli def main(): # we'll modify this later - after the implementation of `cli.py` pass if __name__ == "__main__": main()
パッケージのセットアップが完了したので、 README.md
ファイル (メインのドキュメント) の更新を見てみましょう。従う必要がある特定の構造はありませんが、優れた README は次の内容で構成されます。
<!--- README.md --> # Overview # Getting Started # Usage # Architecture ## Data Flow ## Tech Stack # Running Tests # Next Steps # References
# pyproject.toml [project] name = "trellocli_<YOUR_USERNAME>" version = "0.1.0" authors = [ { name = "<YOUR_NAME>", email = "<YOUR_EMAIL>" } ] description = "Program to modify your Trello boards from your computer's command line" readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] dependencies = [] [project.urls] "Homepage" = ""
リストの次は、API シークレットやキーなどの環境変数を保存する.env
ファイルです。このファイルには機密情報が含まれているため、Git で追跡しないように注意することが重要です。
私たちの場合、Trello 認証情報をここに保存します。 Trello でパワーアップを作成するには、に従ってください。具体的には、 py-trello
による使用法に基づいて、アプリケーションに OAuth を使用する予定であるため、Trello と通信するには次のものが必要になります。
API キーとシークレットを取得したら、それらを.env
ファイルにそのまま保存します。
# .env TRELLO_API_KEY=<your_api_key> TRELLO_API_SECRET=<your_api_secret>
最後になりましたが、あるテンプレート Python .gitignore
を使用してみましょう。これは、 .env
ファイルが決して追跡されないようにするために重要であることに注意してください。ある時点で.env
ファイルが追跡された場合、たとえ後の手順でファイルを削除したとしても、損害は発生し、悪意のある攻撃者が以前のファイルを追跡できるようになります。機密情報のパッチ。
セットアップが完了したので、変更を GitHub にプッシュしましょう。 pyproject.toml
で指定されているメタデータに応じて、それに応じてライセンスとホームページの URL を忘れずに更新してください。より良いコミットの書き方については、 参照してください。
機能要件に基づいて、私たちの主な関心事は、ユーザーが新しいカードを追加できるようにすることです。 py-trello
のメソッドを参照します: 。これを行うには、 List
クラスからadd_card
メソッドを呼び出す必要があります。List クラスは、 Board
クラスのget_list
関数から取得できます。
コードに入る前に、 pyproject.toml
ファイルを変更して、単体テストの作成/実行に必要な依存関係を含めましょう。
# pyproject.toml [project] dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1" ]
次に、 virtualenv をアクティブにしてpip install .
依存関係をインストールします。
# tests/test_trelloservice.py # module imports from trellocli import SUCCESS from trellocli.trelloservice import TrelloService from trellocli.models import * # dependencies imports # misc imports def test_get_access_token(mocker): """Test to check success retrieval of user's access token""" mock_res = GetOAuthTokenResponse( token="test", token_secret="test", status_code=SUCCESS ) mocker.patch( "trellocli.trelloservice.TrelloService.get_user_oauth_token", return_value=mock_res ) trellojob = TrelloService() res = trellojob.get_user_oauth_token() assert res.status_code == SUCCESS
サンプル コードでは、 GetOAuthTokenResponse
models.py
にまだ設定されていないモデルであることに注意してください。これは、よりクリーンなコードを作成するための構造を提供します。後でこれを実際に見ていきます。
テストを実行するには、 python -m pytest
を実行するだけです。テストがどのように失敗するかに注目してください。しかし、それは問題ありません。最終的にはうまくいきます。
チャレンジ コーナー💡 自分でもっとテストを書いてみませんか?私のテストがどのようなものかを確認するには、を参照してください。
ここでは、 trelloservice
を構築しましょう。新しい依存関係、つまりpy-trello
ラッパーを追加することから始めます。
# pyproject.toml dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1", "py-trello==0.19.0" ]
もう一度、 pip install .
依存関係をインストールします。
さて、モデルを構築することから始めましょう - trelloservice
で期待される応答を制御します。この部分については、単体テストとpy-trello
ソース コードを参照して、予想される戻り値の種類を理解するのが最善です。
たとえば、ユーザーのアクセス トークンを取得したいとします。 py-trello
のcreate_oauth_token
関数 ( ) を参照すると、戻り値は次のようなものになることがわかります。
# trellocli/models.py # module imports # dependencies imports # misc imports from typing import NamedTuple class GetOAuthTokenResponse(NamedTuple): token: str token_secret: str status_code: int
一方で、競合する命名規則に注意してください。たとえば、 py-trello
モジュールにはList
という名前のクラスがあります。この問題を回避するには、インポート時にエイリアスを指定します。
# trellocli/models.py # dependencies imports from trello import List as Trellolist
# trellocli/models.py class GetBoardName(NamedTuple): """Model to store board id Attributes id (str): Extracted board id from Board value type """ id: str
チャレンジ コーナー💡 自分でもっとモデルを書いてみませんか?私のモデルがどのように見えるかを確認するには、を参照してください。
モデルは終了です。正式にtrelloservice
のコーディングを開始しましょう。繰り返しますが、作成した単体テストを参照する必要があります。たとえば、現在のテストのリストではサービスを完全にカバーしていないため、必要に応じて常にテストを返し、テストを追加します。
通常どおり、すべての import ステートメントを先頭の方に含めます。次に、予想どおりTrelloService
クラスとプレースホルダー メソッドを作成します。このアイデアは、 cli.py
でサービスの共有インスタンスを初期化し、それに応じてそのメソッドを呼び出すというものです。さらに、私たちはスケーラビリティを目指しているため、広範囲にわたるカバレッジが必要になります。
# trellocli/trelloservice.py # module imports from trellocli import TRELLO_READ_ERROR, TRELLO_WRITE_ERROR, SUCCESS from trellocli.models import * # dependencies imports from trello import TrelloClient # misc imports class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: pass def get_user_oauth_token() -> GetOAuthTokenResponse: pass def get_all_boards() -> GetAllBoardsResponse: pass def get_board() -> GetBoardResponse: pass def get_all_lists() -> GetAllListsResponse: pass def get_list() -> GetListResponse: pass def get_all_labels() -> GetAllLabelsResponse: pass def get_label() -> GetLabelResponse: pass def add_card() -> AddCardResponse: pass
Ps は、今回テストを実行すると、テストがどのようにパスするかに注目してください。実際、これは私たちが正しい道を歩むことを確実にするのに役立ちます。ワークフローは、関数を拡張し、テストを実行し、合否を確認し、それに応じてリファクタリングする必要があります。
__init__
関数から始めましょう。ここでget_user_oauth_token
関数を呼び出し、 TrelloClient
を初期化するという考え方です。繰り返しになりますが、このような機密情報は.env
ファイルにのみ保存する必要があることを強調し、 python-dotenv
依存関係を使用して機密情報を取得します。 pyproject.toml
ファイルをそれに応じて変更した後、認証手順の実装を開始しましょう。
# trellocli/trelloservice.py class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: self.__load_oauth_token_env_var() self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) def __load_oauth_token_env_var(self) -> None: """Private method to store user's oauth token as an environment variable""" load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): res = self.get_user_oauth_token() if res.status_code == SUCCESS: dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_OAUTH_TOKEN", value_to_set=res.token ) else: print("User denied access.") self.__load_oauth_token_env_var() def get_user_oauth_token(self) -> GetOAuthTokenResponse: """Helper method to retrieve user's oauth token Returns GetOAuthTokenResponse: user's oauth token """ try: res = create_oauth_token() return GetOAuthTokenResponse( token=res["oauth_token"], token_secret=res["oauth_token_secret"], status_code=SUCCESS ) except: return GetOAuthTokenResponse( token="", token_secret="", status_code=TRELLO_AUTHORIZATION_ERROR )
この実装では、予見可能なエラー (たとえば、ユーザーが認証中にDeny
をクリックしたとき) を処理するためのヘルパー メソッドを作成しました。さらに、有効な応答が返されるまで再帰的にユーザーの承認を求めるように設定されています。実際には、ユーザーがアプリにアカウント データへのアクセスを承認しない限り、続行できないからです。
チャレンジ コーナー💡 TRELLO_AUTHORIZATION_ERROR
に注意してください ?このエラーをパッケージ定数として宣言できますか?詳細については、「セットアップ」を参照してください。
# trellocli/trelloservice.py def get_all_boards(self) -> GetAllBoardsResponse: """Method to list all boards from user's account Returns GetAllBoardsResponse: array of user's trello boards """ try: res = self.__client.list_boards() return GetAllBoardsResponse( res=res, status_code=SUCCESS ) except: return GetAllBoardsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_board(self, board_id: str) -> GetBoardResponse: """Method to retrieve board Required Args board_id (str): board id Returns GetBoardResponse: trello board """ try: res = self.__client.get_board(board_id=board_id) return GetBoardResponse( res=res, status_code=SUCCESS ) except: return GetBoardResponse( res=None, status_code=TRELLO_READ_ERROR )
リスト (列) を取得するには、 py-trello
のBoard
クラスをチェックアウトする必要があります。つまり、 Board
値タイプの新しいパラメーターを受け入れる必要があります。
# trellocli/trelloservice.py def get_all_lists(self, board: Board) -> GetAllListsResponse: """Method to list all lists (columns) from the trello board Required Args board (Board): trello board Returns GetAllListsResponse: array of trello lists """ try: res = board.all_lists() return GetAllListsResponse( res=res, status_code=SUCCESS ) except: return GetAllListsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_list(self, board: Board, list_id: str) -> GetListResponse: """Method to retrieve list (column) from the trello board Required Args board (Board): trello board list_id (str): list id Returns GetListResponse: trello list """ try: res = board.get_list(list_id=list_id) return GetListResponse( res=res, status_code=SUCCESS ) except: return GetListResponse( res=None, status_code=TRELLO_READ_ERROR )
チャレンジコーナー💡 get_all_labels
関数とget_label
関数を自分で実装できますか? py-trello
のBoard
クラスを修正します。私の実装がどのようなものかを確認するには、を参照してください。
# trellocli/trelloservice.py def add_card( self, col: Trellolist, name: str, desc: str = "", labels: List[Label] = [] ) -> AddCardResponse: """Method to add a new card to a list (column) on the trello board Required Args col (Trellolist): trello list name (str): card name Optional Args desc (str): card description labels (List[Label]): list of labels to be added to the card Returns AddCardResponse: newly-added card """ try: # create new card new_card = col.add_card(name=name) # add optional description if desc: new_card.set_description(description=desc) # add optional labels if labels: for label in labels: new_card.add_label(label=label) return AddCardResponse( res=new_card, status_code=SUCCESS ) except: return AddCardResponse( res=new_card, status_code=TRELLO_WRITE_ERROR )
おめでとう!あなたは勝ちました。もう一度プレイしますか (y/N)?