こんにちは!日々の開発、本当にお疲れ様です。
Pythonのパッケージ管理、皆さんは何を使っていますか?長年 `pip` と `requirements.txt` の組み合わせで苦労してきたチームにとって、依存関係の解決を美しく自動化してくれる Poetry は、まさに救世主のような存在ですよね。
さて、チーム開発の規模が大きくなると、こんな悩みが出てきませんか?
「うっかりセキュリティ脆弱性のある古いバージョンのライブラリを指定してしまった」
「社内規定で禁止されているライセンスのパッケージが混入してしまった」
これらをCI/CDのパイプライン(GitHub Actionsなど)で検知するのも大事ですが、「そもそも開発者のローカルPCで `poetry add` や `poetry install` を実行した瞬間に弾いてくれたら、どれだけ楽か」と思いませんか?
実は、Poetryには強力なプラグイン機構が備わっています。今回は、Poetryの内部挙動にフックし、チーム独自の依存関係ポリシー(ルール)を強制する「カスタムプラグイン」の作り方を、誰よりも分かりやすく解説します。
これをマスターすれば、あなたのチームのコード品質の「防波堤」が手に入り、レビュー時の無駄な指摘が劇的に減りますよ。一緒に手を動かしてみましょう!
—
1. Poetryプラグインの仕組みとアーキテクチャ
Poetryのプラグインは、Pythonの標準機能である `importlib.metadata`(エントリーポイント)の仕組みを利用して動いています。
Poetryが起動する際、インストールされているプラグインを動的にスキャンし、特定のイベント(コマンドの実行前など)に独自の処理を割り込ませます(これをイベントリスナーやフックと呼びます)。
今回は、`poetry add` や `poetry install` が実行された直後に、`pyproject.toml` に記述されている依存関係を読み込み、「特定の禁止パッケージが含まれていないか」を検証するプラグインを作ります。
—
2. 開発環境のセットアップとプロジェクト構造
まずは、プラグイン自体を開発するための環境を作ります。プラグインも独立したPythonパッケージとして作成し、Poetryに読み込ませる形をとります。
プラグイン用プロジェクトの作成
任意のディレクトリで、新しいPoetryプロジェクトを立ち上げます。ここでは `poetry-plugin-guardian` という名前にしましょう。
プラグイン用のPoetryプロジェクトを新規作成
poetry new poetry-plugin-guardian
ディレクトリへ移動
cd poetry-plugin-guardian
作成されたプロジェクトのディレクトリ構造を、以下のように整えていきます。
poetry-plugin-guardian/
├── README.md
├── poetry.lock
├── pyproject.toml
└── poetry_plugin_guardian
├── __init__.py
└── plugin.py # ここにプラグインのロジックを書きます
—
3. pyproject.toml の設定(ここが最重要!)
Poetryのプラグインとして認識させるためには、`pyproject.toml` の設定に秘密があります。通常のプロジェクト設定に加え、「エントリーポイント」を定義する必要があります。
以下の内容で `pyproject.toml` を書き換えてください。
[tool.poetry]
name = “poetry-plugin-guardian”
version = “0.1.0”
description = “チームの依存関係を監視するカスタムPoetryプラグイン”
authors = [“Your Name
packages = [{include = “poetry_plugin_guardian”}]
[tool.poetry.dependencies]
python = “^3.8”
依存関係としてpoetry本体を指定(プラグイン開発に必須)
poetry = “^1.2.0”
[tool.poetry.plugin]
Poetryに「これはプラグインですよ」と教えるためのエントリーポイント
guardian = “poetry_plugin_guardian.plugin:GuardianPlugin”
[build-system]
requires = [“poetry-core>=1.0.0”]
build-backend = “poetry.core.masonry.api”
ここがポイント:
`[tool.poetry.plugin]` セクションにある `guardian = “poetry_plugin_guardian.plugin:GuardianPlugin”` が、Poetryにこのプラグインを認識させる魔法の呪文です。Poetryは起動時にこの定義を読み込み、指定されたクラスをインスタンス化します。
—
4. 独自のバリデーションロジックを書く(HelloWorldの実装)
それでは、実際にプラグインのコードを書いていきましょう。
`poetry_plugin_guardian/plugin.py` を開き、以下のコードを記述してください。
from poetry.plugins.plugin import Plugin
from poetry.poetry import Poetry
from cleo.events.event_dispatcher import EventDispatcher
from cleo.events.console_events import COMMAND
from cleo.events.console_command_event import ConsoleCommandEvent
from poetry.console.application import Application
import sys
class GuardianPlugin(Plugin):
“””
Poetryの起動時に呼び出されるプラグインのエントリーポイントクラス。
“””
def activate(self, poetry: Poetry, application: Application) -> None:
# イベントディスパッチャを取得し、コマンド実行前のイベントにフックを設定する
application.event_dispatcher.add_listener(COMMAND, self.validate_dependencies)
def validate_dependencies(self, event: ConsoleCommandEvent, event_name: str, dispatcher: EventDispatcher) -> None:
“””
Poetryのコマンドが実行される直前に走るバリデーション関数
“””
command = event.command
# 今回は ‘add’ または ‘install’ コマンドが実行された時だけチェックする
if command.get_name() not in [“add”, “install”]:
return
# pyproject.tomlに定義されている依存関係を取得
# (実際にはここでpoetryオブジェクトから依存関係ツリーを解析します)
print(“\n[GuardianPlugin] 🛡️ 依存関係のセキュリティ&ポリシーチェックを実行中…”)
# — 【デモ用のバリデーションルール】 —
# 例として、セキュリティ上の理由で禁止したいパッケージ名リスト
forbidden_packages = [“unsafe-lib”, “legacy-parser”]
# 簡易的に現在のプロジェクトの依存関係を取得してチェック
requires = event.command.poetry.package.requires
for dependency in requires:
if dependency.name in forbidden_packages:
print(f”\n[ERROR] 🚫 禁止されているパッケージ ‘{dependency.name}’ が検出されました!”)
print(“チームのセキュリティポリシーにより、このパッケージの導入は許可されていません。”)
# 異常終了させてコマンドの実行をブロックする
sys.exit(1)
print(“[GuardianPlugin] ✅ チェック完了:ポリシー違反はありません。\n”)
このコードでは、開発者が `poetry add` や `poetry install` を叩いた瞬間、コマンドが実行される一瞬前にイベントを割り込ませ(`COMMAND`イベント)、禁止されたパッケージが含まれていないかをチェックしています。含まれていた場合は `sys.exit(1)` で強制終了させ、不正な依存関係の侵入を防ぎます。
—
5. プラグインをローカル環境にインストールして動作確認
自分で作ったプラグインを、Poetryに認識させてみましょう。
プラグイン開発時は、開発中のパッケージをローカルで直接Poetryにインストール(self-install)させると非常にスムーズです。
ターミナルでプラグインのプロジェクトディレクトリを開き、以下のコマンドを実行します。
現在のプロジェクトをPoetryのプラグインとしてローカルに組み込む
poetry self add .
うまくインストールが完了すると、Poetryのプラグインリストにこのプラグインが追加されます。確認してみましょう。
poetry self show plugins
実行ログのイメージ:
poetry-plugin-guardian (0.1.0)
見事に認識されていますね!
動作確認:バリデーションが機能するか試す
別の適当なPythonプロジェクト(またはテスト用プロジェクト)に移動し、わざと禁止されているパッケージ(例: `unsafe-lib`)を追加してみましょう。
poetry add unsafe-lib
期待される出力結果:
[GuardianPlugin] 🛡️ 依存関係のセキュリティ&ポリシーチェックを実行中…
[ERROR] 🚫 禁止されているパッケージ ‘unsafe-lib’ が検出されました!
チームのセキュリティポリシーにより、このパッケージの導入は許可されていません。
お見事です!`poetry add` が実行される前にプラグインが割り込み、エラーメッセージを出力して処理を完全にストップさせることができました。
—
6. さらに実務で活かすためのアーキテクトからの助言
今回作成したプラグインは非常にシンプルなものですが、実務の現場では以下のような拡張を行うことで、さらに強力なツールへと進化させることができます。
1. 外部APIとの連携:
社内の脆弱性データベースや、CVE(共通脆弱性情報)のAPIに非同期で問い合わせ、最新の脆弱性があるバージョンを指定していないかを動的にチェックする。
2. ライセンス監査:
依存パッケージのライセンス(MIT, GPL, Apache-2.0など)をメタデータから自動検出し、社内法務が許可していないライセンスが含まれていたら弾く。
3. チーム間での共有:
このプラグイン自体をプライベートなGitリポジトリや社内PyPIサーバーでホストし、`poetry self add git+https://…` のようにしてチームメンバー全員のPCに一括導入する。
—
まとめ
今回は、Poetryのプラグイン機構を利用して、独自の依存関係バリデーションツールを作る方法を解説しました。
- Poetryのイベントフックを使えば、コマンド実行前に独自のチェックを挟むことができる。
- `pyproject.toml` の `[tool.poetry.plugin]` でエントリーポイントを定義する。
- CI/CDに頼る前にローカルの `poetry add` などの段階で防ぐことで、手戻りをゼロにできる。
これをマスターすれば、あなたのチームのコード品質とセキュリティは一段上のステージへと引き上げられます。ぜひ、あなたのチーム独自のルールをコード化して、快適で安全なPython開発環境を作ってみてくださいね!