Pythonプロジェクトの共通のひな形を作る
こんにちは、バックエンドエンジニアの金子です。
コロナ禍により世界が一変してしまいましたが皆様は無事にお過ごしでしょうか?
私はリモートワーク中心になったことで引きこもり化が加速しました。先日久しぶりに2、3km程度歩いただけなのにふくらはぎの筋肉痛に苦しんでいます。
今回は、前々からやろうやろうと思って放置していたことについて書こうと思います。
はじめに
昨今Pythonのプロジェクトの管理にpipenvまたはpoetryを使われている方が多いと思います。
$ mkdir spamproject && cd spamproject
$ pipenv install
$ pipenv shell
または
$ poetry new spamproject && cd spamproject
$ poetry shell
で、プロジェクトを作り、依存ライブラリやプロジェクト情報の管理ができる。便利ですね。
個人がコードを書く分にはこれで十分だと思います。しかし、チームやOSSで他の人の協力が必要な時はどうでしょうか?
- エディターの設定を共通化したい
- linter/formatterの設定を入れたい
- テストを自動で走らせたい
- カバレッジを取りたい
等々……いろいろな設定をする必要があると思います。
毎回プロジェクトを作るたびに既存のプロジェクトからコピーして改変するのも面倒です。
そこで登場するのがcookiecutterというツールです。
これは、あらかじめGitHub等に用意したテンプレートをコピーし、環境に応じたパラメーターを入力することでカスタマイズされたプロジェクトを作成してくれる優れたツールです。
似たツールではnode.jsのdegitがあります。こちらはsvelteやrollupの作者のRich Harrisさんが作られたものなので、svelteを使われたことがある方ならプロジェクトの作成で触ったことがあるかと思います。
cookiecutterを使ってみる
インストール
それでは早速使ってみましょう。まずはおなじみのpipでインストールします。
$ pip install cookiecutter
特に詰まることはない……はず。
プロジェクトの作成
続いてREADMEに書いてある通りAudrey Roy Greenfeldさんのテンプレート、cookiecutter-pypackage
からプロジェクトを作ってみます。
$ cookiecutter gh:audreyr/cookiecutter-pypackage
ここでプレフィクスの gh:
はGitHub上から取得することを示しています。
他にも bb:
ならBitbucket、gl:
ならGitLabから取得するようになっています。しかし、BitBucketの場合はMercurialリポジトリからの取得になっているので注意が必要です。
また、2020年7月をもってBitBucketでのMercurialサポートは終了しているため、bb:<ユーザー名>/<リポジトリ名>
は使えませんでした。
BitBucket上のGitリポジトリにあるテンプレートを使用したい場合は次のようになります(これはあくまで例で、このテンプレートは実際には存在しません)
$ cookiecutter git@bitbucket.org:taro_kabuku/cookiecutter-pypackage.git
プロジェクト固有の情報の入力
前項の「プロジェクトの作成」のコマンドを入力すると、次のように対話形式でプロジェクトの情報を入力します。
cookiecutter-pypackage
では、フルネーム、メールアドレス、GitHubのユーザー名、プロジェクト名とスラグ(識別子)、短い説明……等々が聞かれます。
$ cookiecutter gh:audreyr/cookiecutter-pypackage
full_name [Audrey Roy Greenfeld]: Taro Kabuku
email [audreyr@example.com]: taro.kabuku@example.com
github_username [audreyr]: taro_kabuku
project_name [Python Boilerplate]: My Boilerplate
project_slug [my_boilerplate]:
project_short_description [Python Boilerplate contains all the boilerplate you need to create a Python package.]:
pypi_username [taro_kabuku]:
version [0.1.0]:
use_pytest [n]: y
use_pypi_deployment_with_travis [y]: n
add_pyup_badge [n]:
Select command_line_interface:
1 - Click
2 - Argparse
3 - No command-line interface
Choose from 1, 2, 3 [1]: 2
create_author_file [y]: n
Select open_source_license:
1 - MIT license
2 - BSD license
3 - ISC license
4 - Apache Software License 2.0
5 - GNU General Public License v3
6 - Not open source
Choose from 1, 2, 3, 4, 5, 6 [1]: 6
この時、固定で入力したい項目は~/.cookiecutterrc
に次のように書いておくと、プロジェクト作成時のデフォルト値を差し替えてくれます。
default_context:
full_name: "Taro Kabuku"
email: "taro.kabuku@example.com"
github_username: "taro_kabuku"
この通り!
$ cookiecutter gh:audreyr/cookiecutter-pypackage
full_name [Taro Kabuku]:
email [taro.kabuku@example.com]:
github_username [taro_kabuku]:
実際にできあがったプロジェクトはこんな感じです。
$ ls my_boilerplate/
CONTRIBUTING.rst MANIFEST.in README.rst my_boilerplate setup.cfg
tests HISTORY.rst Makefile docs requirements_dev.txt
setup.py tox.ini
自前のテンプレートを作る
続いて自前のテンプレートとして、Cloud FunctionsのHTTPトリガーのテンプレートを作ってみます。
1から作っても良いのですが、ちょうどよくPaweł Żukowskiさん作のcookiecutter-gcp-cloud-function-pythonがあったのでこちらをforkして作ります。
テンプレートの構成
テンプレートをGitHubリポジトリからクローンすると、テンプレートの構成は次のスクリーンショットの通りになっています。
見ると{{ cookiecutter.project_slug }}
という不思議な名前のディレクトリがあります。
このテンプレートから入力を全てデフォルト値としたmy_gcp_cloud_function
という名前の新しいプロジェクトを作ってみます。
するとcookiecutterは{{ cookiecutter.project_slug }}
フォルダーをコピーしてmy_gcp_cloud_function
という名前のディレクトリを作成します。
続いて{{ cookiecutter.project_slug }}/main.py
を見てみると、次のようなコード片があります。
def {{ cookiecutter.code_entry_point }}(req: Request):
return render_template("index.html")
{{ cookiecutter.code_entry_point }}
という不思議な名前の関数がありますね。
当然このままでは実行できません。
それではmy_gcp_cloud_function/main.py
を見てみます。
def on_request_received(req: Request):
return render_template("index.html")
{{ cookiecutter.code_entry_point }}
がon_request_received
に差し替えられています。
{{}}
で囲まれた文字列は、どこにある定義で差し替えられているのでしょうか?
テンプレートを見ると、cookiecutter.json
というファイルがあります。これを開くと次の通りになっています。
{
"project_name": "My GCP Cloud Function",
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_') }}",
"gcp_project": "",
"gcp_function_region": [
"asia-east2",
"asia-northeast1",
// (中略)
"code_entry_point": "on_request_received",
// (中略)
}
ということは、自前のテンプレートを作成したい時はcookiecutter.json
と{{ cookiecutter.project_slug }}
フォルダー以下を編集すれば良いということがわかります。
ここまで来るとお気づきの方もいらっしゃると思いますが、cookiecutterのテンプレートにはjinjaが使われています。
カスタマイズしてみる
実際にカスタマイズしてみましょう。変更するポイントは大まかに次の通りになります。
- プロジェクトの管理をpoetryからpipenvに変える
- linter、formatterとしてflake8、black、isortをインストールし、設定ファイルを用意する
- mypyで型のチェックを行う
- pytestによるテストを追加し、カバレッジを取る
- コミットする前にlinterやformatter、mypyでstage状態のファイルをチェックする(huskyを使う)
- gitignoreをgiboのPythonとnode.jsのものに置き換える
- エントリーポイント関数の戻り値はHTMLテンプレートのレンダリングをやめ、ただ
ok
とだけ返す - デフォルトのリージョンを
asia-northeast1(Tokyo)
にする
だいぶ長くなってきたので詳細は端折りますが、カスタマイズした結果がこちらになります。
https://github.com/kabuku/cookiecutter-gcp-cloud-function-python
おわりに
このようにcookiecutterはプロジェクトを作る時に便利かつカスタマイズしやすいツールです。
枯れたツールなので、多くのテンプレートがあります。また、PythonだけでなくTypeScriptやGoのプロジェクトのテンプレートもあったりするので毎回同じ作業をするのに疲れた時は探してみると良いと思います。
この記事はShodo ( https://shodo.ink ) を使って校正してみました。
その他の記事
Other Articles
関連職種
Recruit