安全なPythonパッケージを作成するための完全ガイド
Gourav Singh Bais
2024年5月8日
0 分で読めますPythonパッケージの作成には、適切なディレクトリ構造の検討、パッケージファイルの作成、デプロイ前のパッケージメタデータの設定など、いくつかの作業が必要です。テスト用のサブディレクトリを作成し、分かりやすいドキュメントを用意するなど、ほかにもいくつかの手順があります。パッケージの準備ができたら、さまざまな配布アーカイブに公開できます。これで、ほかの人がインストールして使えるPythonパッケージが完成します。
このガイドでは、最新のPythonパッケージを作成するためのすべての手順を詳しく解説します。このチュートリアルに必要なコードはこちらのGitHubリポジトリで確認できます。このチュートリアルを実践すると、既存のパッケージを使うのと同じくらい簡単にPythonパッケージを作成できることが分かるでしょう。まずは、Pythonでパッケージを使う方法から見ていきましょう。
Pythonパッケージの作成方法
Pythonパッケージを作成する前に、さまざまなインストール方法やインポート方法、パッケージの基本的な構造を確認しておきましょう。
Pythonパッケージのインストール
Pythonパッケージを使うには、PyPIで公開されているパッケージや、組織内の専有コードを含むプライベートインデックスを利用できます。
Pythonパッケージをインストールする方法は、次の2つです。
PyPIを利用する
PyPIは、公開されているすべてのPythonパッケージを収録した公式リポジトリです。
プライベートインデックスを利用する
PyPIで公開されているパッケージと、プライベートインデックスでのみ利用できるパッケージの両方が必要になる場合があります。プライベートインデックスは通常、ソースコードを公開しない専有プロジェクトを扱う大規模な組織で使われます。組織内でPythonパッケージを保存し、配布することができます。
プライベートインデックスからパッケージをインストールするには、pip installコマンドに-- index-urlフラグを付け、続けてプライベートインデックスのURLとパッケージ名を指定します。パッケージのインストールには、次のようなコマンドを実行します。
セキュリティ上の理由から、プライベートパッケージインデックスではTransport Layer Security(TLS)を使用することをおすすめします。プライベートインデックスに適切なTLS証明書がない場合は、pipが有効なTLS証明書のないインデックスを信頼できないと判断するため、そこからパッケージをインストールするにはpip installコマンドで-- trusted-hostフラグを指定する必要があります。
Pythonパッケージのインポート
Pythonでは、import文を使ってパッケージをインポートできます。Pythonインタープリターは、キャッシュ、組み込みライブラリ、インストール済みのパスからパッケージを検索します。パッケージが見つからない場合は、ImportErrorが発生します。
次のようにfromキーワードを使って、パッケージから特定のモジュールをインポートすることもできます。
次のコマンドを使えば、パッケージから複数またはすべてのモジュールをインポートすることもできます。
asキーワードを使って、パッケージに別名を付けることもできます。たとえば、`pandas`パッケージをpdという別名で使うには、次のようにします。
Pythonパッケージの構成要素
Pythonパッケージは、次のファイルやサブディレクトリを含むディレクトリ構造に従う必要があります。
パッケージ名のサブディレクトリと初期化ファイル
__init__.pyを含むsrcディレクトリ。srcディレクトリには、パッケージのソースコードを含むmodule.pyファイルも1つ以上配置しますパッケージのさまざまな機能をテストするスクリプトを含む
testsディレクトリ。このディレクトリをパッケージに追加するかどうかは任意です。パッケージの設定情報と依存関係を記述する
pyproject.tomlファイル。パッケージの利用条件を定める
LICENSEファイル。選択できるライセンスは数多くあり、独自のライセンスを作成することもできます。パッケージの利用者に役立つ情報とともに、分かりやすいドキュメントや使用手順を記載する
README.mdファイル。
ここまでに説明したファイルとディレクトリは、パッケージ作成に必要な基本要件です。さらに複雑なパッケージでは、ファイルやサブモジュール、テストが追加されることもあります。
コードのパッケージ化
Pythonパッケージを一般公開するには、まずパッケージを作成する必要があります。このチュートリアルでは、キロメートル(km)をマイル(mi)に変換するシンプルなPythonプログラムを作成します。
Pythonパッケージの命名
Pythonパッケージに名前を付けるときは、PyPIを確認して、選んだ名前がすでに使われていないか調べる必要があります。通常、開発者はPyPIとパッケージの名前を同じにしますが、別の名前を選んでもかまいません。
パッケージをインストールするときはPyPI上の名前を使い、インポートするときはパッケージ名を使います。
Pythonパッケージの設定
前述の要件に従って、パッケージのディレクトリ構造を整えます。km2miles.pyファイルにこちらのコードを配置します。こちらのコードを使って__init__.pyファイルを作成できます。
km2miles.pyファイルには、NumPyライブラリを使ってキロメートルをマイルに変換する関数が含まれています。__init__.pyファイルはメインモジュールをインポートし、パッケージのバージョンを指定します。
パッケージを公開する前に、pyproject.tomlファイルを設定する必要があります。ここではビルドシステムも指定します。設定方法は次に説明します。
ビルドシステムの設定
Pythonパッケージで公開するファイルを生成するには、ビルドシステムが必要です。pipなどのビルドフロントエンドや、setuptools、Flit、Hatchling、PDMなどのビルドバックエンドを利用できます。
ここではsetuptoolsを使います。既存のPythonパッケージの依存関係を自動的に解決するビルドツールだからです。実際の用途に合わせてPythonパッケージを作成する際に役立ちます。
ビルドシステムはpyproject.tomlファイルを使用します。このファイルには、プロジェクトの配布パッケージを作成するためのバックエンドツールへの指示が記述されています。ビルドシステムの仕様は次のように定義します。
pyproject.tomlファイルを使わず、代わりにsetup.pyファイルでPythonパッケージを設定するチュートリアルを見かけることがあります。以前は、setuptoolsが複雑なパッケージのインストール処理にsetup.pyファイルを使っていたためです。しかし、現在のプロジェクトでは、Pythonプロジェクトの設定にはsetup.pyやsetup.cfgではなく、一般にpyproject.tomlファイルを使うことが推奨されています。
Pythonパッケージの設定
ビルドシステムを設定したら、パッケージ固有の情報を設定します。これらの設定情報は、.tomlファイルのビルドシステム情報の下に記述します。
通常、プロジェクトの設定には次の情報を含めます。すべてのパッケージに含めることをおすすめしますが、必須ではない項目もあります。
名前:(必須)PyPIで使用するパッケージ名
バージョン:(必須)パッケージの現在のバージョン。パッケージのPyPIメインページにも表示されます。
説明:(必須)PyPIページに表示するパッケージの追加情報。
作者:(必須)パッケージの開発者に関する情報。
Readme:(任意)パッケージの説明と使い方を含むMarkdown形式の情報。パッケージのPyPIメインページに表示されます。
ライセンス:(任意ですが、強く推奨)ほかの人がパッケージを利用する際の条件。
分類子:(任意)対象ユーザー、プラットフォームの互換性、パッケージの成熟度を示すカテゴリです。PyPIで検索されやすくなります。使用可能な分類子の一覧はこちらで確認できます。
キーワード:(任意)関連する語句で検索したユーザーがPyPIでパッケージを見つけやすくするための単語一覧です。たとえば、マイルをキロメートルに変換するパッケージを探す場合、「converter」「miles」「kms」などのキーワードで検索することが考えられます。
依存関係の指定
プロジェクトの`pyproject.toml`)ファイルで依存関係を指定するには、プロジェクトの説明の後にdependenciesフィールドを追加します。
これにより、プロジェクトがNumPyライブラリに依存していることを指定できます。プロジェクトをインストールすると、pipがこれらの依存関係を解決します。依存関係を指定する際は、次の点に注意してください。
サードパーティパッケージがすでに対応している依存関係は指定しないでください。
特定のバージョンで追加された新機能に必要な依存関係の下限を指定するには、
>=を使います。メジャーバージョンの更新時に互換性の問題を引き起こす可能性がある依存関係の上限を指定するには、
<を使います。
オプションの依存関係は、project.optional-dependenciesフィールドを使って追加できます。
これらの依存関係は自動ではインストールされませんが、pip install package_name[optional_dependency_name]という構文でプロジェクトのインストール時に指定できます。
パッケージのバージョン管理
前述のとおり、Pythonパッケージにはバージョン番号を付け、パッケージのドキュメントに記載することでバージョン管理できます。バージョン番号には、一般的に使われるセマンティックバージョニング形式MAJOR.MINOR.PATCHを使用します。MAJORはプロジェクトの大幅な変更、MINORは新機能、PATCHはバグ修正やパフォーマンスの向上を表します。
設定ファイルでバージョンを追加する方法は次のとおりです。
これにより、プロジェクトのバージョンが1.0.0に設定されます。新しいバージョンをリリースする際に、バージョン番号を更新できます。
パッケージのテスト
Pythonパッケージをテストする方法はいくつかあります。小規模なプロジェクトでは手動テストで十分な場合もありますが、手作業を介さずにコードが一定の基準を満たしていることを確認できるよう、ほかの種類の自動テストも導入するのが望ましいです。自動テストを利用すれば、パッケージの信頼性に対する確信を高められます。
単体テスト: `robot`、`pytest`、`testify`、`nose`などのテストフレームワークを使い、コードの特定部分を切り分けてテストします。
統合テスト: テスト環境を構築してコードを実行し、パッケージの各部分がどのように連携するかをテストします。統合テストには`tox`などのパッケージを利用できます。
手動テスト: コードを実行し、期待どおりの結果になるか確認します。任意のPythonクライアントを使って手動テストを実行できます。
これらのテストはすべて、このチュートリアルで前述したtestsフォルダに格納できます。複雑なプロジェクトでは、テストの分析・可視化レポートやカバレッジレポートも必要になることがあります。その場合は、coverage.pyや`pytest-cov`などのパッケージを利用できます。
パッケージへのリソースファイルの追加
データファイル、バイナリ、マニュアル、設定ファイルなど、ソースコード以外のファイルをPythonパッケージに含めるには、MANIFEST.inというリソースファイルを作成します。このファイルのルールを使って、パッケージのビルド時に含めるファイルや除外するファイルを指定できます。
たとえば、次のコードを使うと、src/mileConverterディレクトリにあるすべての.tomlファイルが含まれます。
この目的のために、別の.tomlファイルを指定することもできます。
パッケージへのライセンスの付与
Pythonパッケージにライセンスを含め、ほかの人が利用できるようにするには、パッケージディレクトリにLICENSEというファイルを作成し、choosealicense.comなどにある選択肢から既存のライセンスを選びます。
pyproject.tomlファイルで、ライセンスをPyPIページに表示するには、license変数にLICENSEファイル名を指定する必要があります。これは、パッケージのpyproject.tomlファイルの設定を開始したときに行ったことです。
最も人気のあるオープンソースライセンスには、MIT License、Apache License、GNU General Public License(GPL)などがありますが、ほかにも多くの選択肢があります。
パッケージをローカルにインストールする
ここまでで、必要なファイルをすべて作成し、パッケージに必要な設定もすべて完了しました。PyPIに公開する前に、pipを使ってローカルにインストールし、エラーがないか確認しましょう。以下のコマンドのように、パッケージをローカルにインストールすることを示す-eまたは- editableタグを指定するだけです。
パッケージをローカルにインストールできたので、PyPIに公開する準備が整いました。
パッケージをPyPIに公開する
パッケージを公開するには、まずビルドする必要があります。
パッケージをビルドする
buildパッケージを使って、Pythonパッケージのwheelファイルを生成します。ビルドフロントエンドをインストールするには、次のコマンドを実行します。
次に、pyproject.tomlファイルがあるディレクトリに移動し、以下のコマンドを実行します。
これにより、.tomlファイルと同じ場所にdist/フォルダが作成され、次の2つのファイルが格納されます。
`.whl`ファイルはビルド済み配布物で、tar.gzファイルはソース配布物です。これらのファイルは最終的にPyPIにアップロードできますが、まずはテストが必要です。
パッケージをテストする
パッケージをテストするには、PyPIへのアップロードにも使えるライブラリ`twine`をインストールします。次のコマンドでインストールできます。
twineをインストールしたら、次のコマンドを使って、パッケージの説明がPyPIで正しく表示されるか確認できます。
前のコマンドを実行すると、次のような出力が表示されます。
すべてのテストに合格すれば、パッケージの準備は完了です。
パッケージをTestPyPIサービスにアップロードする
パッケージを一般公開する前に、TestPyPIサービスを使ってテストと検証を行いましょう。パッケージをTestPyPIにアップロードすればテストと検証ができますが、その前にPyPIのウェブサイトでアカウントを作成する必要があります。続いてtwineを使い、次のコマンドを実行してパッケージをアップロードします。PyPIのユーザー名とパスワードが必要です。
コマンドが正常に実行されると、結果を確認するためのURLが表示されます。TestPyPIのウェブサイトでパッケージを検索することもできます。TestPyPIサービスからパッケージをダウンロードするには、次のコマンドを使います。
ダウンロード後、パッケージが正しく動作するかテストできます。
パッケージを正式なPyPIサービスにアップロードする
正式なPyPIサービスへのアップロード方法は、TestPyPIサービスへのアップロード方法と同様です。前の手順でパッケージを検証したら、正式なPyPIサービスにアップロードできます。ここでもtwineツールを使って、パッケージファイルをPyPIにアップロードします。
ターミナルで次のコマンドを実行し、dist/*をパッケージファイルへのパスに置き換えます。
パッケージをアップロードしたら、ウェブサイトにアクセスして検索できます。
パッケージをインストールする
次に、パッケージをテストします。次のコマンドでパッケージをダウンロードできます。
パッケージをインストールしたら、任意のPythonエディターまたはPythonランタイムを開き、パッケージが動作するかテストします。
次のような出力が表示されます。
これでパッケージがPyPIにアップロードされ、pipを使って任意のPython環境から利用できるようになりました。
パッケージをプライベートPythonインデックスに公開する
プライベートPythonインデックスを使ってパッケージを配布する組織がある理由を、先ほど説明しました。また、Pythonパッケージをプライベートインデックスに公開するのは簡単です。次の手順に従うだけです。
配布用の
.whlファイルまたは.tar.gzファイルを含むディレクトリ構造を、パッケージ用に設定します。自動インデックス作成を有効にして、パッケージのルートディレクトリを配信するようウェブサーバーを設定します。
twineを使い、プライベートウェブサーバーのURLを指定して、パッケージをプライベートインデックスにアップロードします。$ twine upload - repository-url your_web_server milesConverter/*プライベートインデックスからパッケージをインストールするには、ウェブサーバーのURLを指定してpipコマンドを使います(
$ pip install - extra-index-url your_web_server milesConverter)。
これらの手順を完了すると、プライベートウェブサーバー上でパッケージを利用できるようになり、あなたやチームで使用できます。
Pythonパッケージ作成時のセキュリティのベストプラクティス
パッケージの公開方法がわかったところで、安全かつ正しく動作する状態で公開するために、いくつかのベストプラクティスを検討しましょう。
依存関係に注意する
プロジェクトでサードパーティの依存関係を使う前に、そのドキュメントを確認し、セキュリティチェックや脆弱性チェックの仕組みがあるか確認しましょう。多くのライブラリでは、既知のセキュリティ問題やその対処方法について、ドキュメントに専用のセクションが設けられています。こうした情報があれば、サードパーティ製パッケージを選ぶ際の安心感につながります。
ドキュメントがない場合は、Snykなどのツールを使って、利用を検討しているサードパーティライブラリの脆弱性を特定できます。
文字列と生のSQLの扱いに注意する
Pythonで文字列を扱う際、特にコードで生のSQLを使う場合は、細心の注意が必要です。この部分の処理を誤ると、文字列インジェクションやSQLインジェクション攻撃の危険にさらされる可能性があります。攻撃者が文字列、より一般的にはSQLクエリに悪意のあるコードを注入すると、データベースで予期しない任意のSQLコマンドが実行されるおそれがあります。
こうした攻撃を防ぐには、特にユーザー入力を扱う場合、文字列のフォーマット方法を慎重に検討してください。文字列のフォーマットには、f-string機能を活用して標準ライブラリを使うのが最善です。
生のSQLを使う場合は、ユーザー入力をSQL文字列に直接連結するのではなく、必ずプレースホルダーを使ったクエリを使用してください。ユーザー入力が適切にエスケープ、引用符処理、検証されるため、SQLインジェクション攻撃の防止に役立ちます。
デシリアライズは慎重かつ安全に行う
Pythonのpickleモジュールは安全性が高いとは言えず、pickle bombなどの攻撃に対して脆弱な可能性があります。パッケージの安全性を確保するには、pickleを基盤とする安全なラッパーである`dill`など、別のデシリアライズ用ライブラリの利用を検討してください。
pickleの脆弱性の1つは、デシリアライズ中に任意のコードが実行される可能性があることです。この場合、攻撃者はデシリアライズ時に実行されるコードを含む悪意のあるpickleファイルを注入できます。dillでは、unpickleを許可するモジュールや関数を指定できるため、デシリアライズ中の任意コード実行を防止できます。そのため、dillはpickleより安全です。
シークレットの削除も忘れずに
機密データを扱う際は、シークレットを安全に保存することが非常に重要です。安全に保存されていないシークレットは攻撃者にアクセスされ、不正にシステムやデータへアクセスするために悪用される可能性があります。keyring、passlib、pycryptodome、pycryptoなどのシークレット管理ライブラリを使えば、シークレットを暗号化して保存し、こうした事態を防げます。
さらに、シークレットは定期的にローテーションする必要があります。これにより漏えいのリスクを抑えられます。上記のツールの一部にはこの機能があり、HashiCorp Vaultなどのツールでも利用できます。
ツールを使ってコードの脆弱性を定期的にスキャンする
ここまで見てきたように、Pythonプロジェクトに影響を及ぼすさまざまな脅威、特に依存関係の管理、文字列のフォーマット、生のSQLクエリ、デシリアライズに関する脅威を把握することが重要です。こうした問題をすべて自力で解決することも可能かもしれませんが、おすすめはできません。プロジェクトの脆弱性をスキャンし、問題を解決できるツールを使うほうが効果的です。
Snykのようなツールは、コードの脆弱性を定期的にスキャンして、作業を効率化します。Snykを使えば、Pythonプロジェクトで起こりうる問題を特定して修正できます。プロジェクトの安全性を高められるだけでなく、アプリケーションのバグや機能上の問題も減らせます。
まとめ
このチュートリアルでは、Pythonパッケージの作成方法を紹介しました。また、パッケージがコードを整理するうえで役立つ理由や、コードの再利用性と持続可能性を高める方法についても説明しました。
このチュートリアルでは、プロジェクトを損なうさまざまな脅威や脆弱性を把握することがいかに重要かについても解説しました。コードを追加して、脆弱性や脅威の一部を自分で対処する方法も学びました。しかし、すべての組織やチームにそれを実行する余力があるわけではなく、たとえ余力があっても、自分たちで対処すべきではありません。脆弱性や脅威に継続的に対応し、作業を効率化する、標準的で包括的な製品を使うのが効果的です。
Snykrのような製品は、コードを徹底的にスキャンして脆弱性を特定し、一部の問題を自動修正することで、こうした脅威の軽減に役立ちます。
Pythonアプリのセキュリティ対策を始めましょう
Snykを使って、Pythonの脆弱性を無料で検出・修正できます。
クレジットカードの登録は不要です。
またはAzure AD、Docker ID、Bitbucketで登録
Snykを利用すると、利用規約およびプライバシーポリシーを含む各種ポリシーに同意したものとみなされます。



