前置き
Python で pip install
できるパッケージの開発をしようとしています。
開発中にちょこちょこ動作確認することになるので、その手順を自分なりに確立しておきたいと思って、記事にまとめることにしました。
今回は**「最低限の構成編」**ですので、PyPI に登録するようなパッケージを作るのには向きません。
ただしテストコードは含むようにしています。
何をもって「最低限」かというと、他のパッケージに依存しない というのがひとつのポイントになります。
そのためいろいろ端折っていますが、ちょっとしたパッケージを GitHub で配布するだけならこれで大丈夫だと思います。
他のパッケージに依存するパッケージの構成については、またの機会に書きたいと思います。
動作確認した環境
Python 3.8.1
pip 20.3.1
setuptools 51.0.0
ステップ
- ディレクトリを構成する
- テストコードが動くようにする
- インストールする側を作る (動作確認用)
- 編集可能モードでインストールする
- pip コマンドによるインストール
- requirements ファイルを使ったインストール
- GitHub に Push して、GitHub からインストール
ディレクトリを構成する
パッケージ名を "min_pkg" として、以下のような構成にしました。
GitHub に置いてありますので、内容についてはそちらをご参照ください。
https://github.com/satamame/min_pkg
min_pkg/ (パッケージのリポジトリ)
├─ .git/
├─ min_pkg/
│ ├─ __init__.py
│ ├─ character.py
│ └─ main_character.py
├─ tests/
│ ├─ __init__.py
│ └─ test_init_char.py
├─ .gitignore
├─ pyproject.toml
├─ README.md
├─ setup.cfg
└─ setup.py
最低限という割にはファイルが多いですが、順番に見ていきましょう。
パッケージになる部分
内側の "min_pkg" フォルダが、パッケージになる部分です。
モジュールが2つあるのは、**「同じパッケージ内の別のモジュールを import する」**という状況を作って動作確認したいからです。
ソースは GitHub をご参照ください。
テストコード
"tests" フォルダ にテストコードを入れてあります。
「テストコードは要らない」という人は、フォルダごと無視して大丈夫です。
setuptools で使うファイル
パッケージを pip install
する時に setuptools が動きます (後述)。
以下のファイルは、setuptools で使うファイルになります。
pyproject.toml
setuptools の公式ドキュメントの Basic Use を見ると、おまじないの様に決まった記述をすれば良いようです。
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
setup.cfg
setuptools が参照する、パッケージ情報です。多分これで最低限だと思います。
[metadata]
name = min_pkg
version = 0.1
[options]
packages = min_pkg
今回の構成ですと "tests" フォルダも (__init__.py があるので) パッケージになっていますが、packages = min_pkg
で、"min_pkg" フォルダだけをインストールするよう指示しています。
setup.py
先ほど 「pip install
する時に setuptools が動く」と書きましたが、それは "setup.py" の中でそう書いているからです。
from setuptools import setup
setup()
setup.py を書くのは古いやり方?
上記の "setup.py" では、setuptools の setup()
を呼んでいるだけですが、"setup.py" の中に全部の情報を書くこともできます。ただし、それは distutils を使っていた時代のやり方で、互換性のために setuptools に引き継がれているようです。
さらに、この最低限の "setup.py" を書くことすらも、setuptools の Basic Use ではしていません。
setup()
だけの "setup.py" は、ある条件下では必要ない (省略できる) からです。それについても次回に説明したいと思います。
テストコードが動くようにする
最初からテストコードをばっちり揃えておく必要はありませんが、「テストコードを書きたくなったらいつでも書ける状態にしておくこと」が大事だと思います。
動作確認するたびに使ったコードを蓄積していけるので、今後の無駄を省くことにもなります。
サンプルでは unittest
で動くようにテストコードを書いたので、(リポジトリ・ルートから) 以下のコマンドで実行できます。
-v
は実行中の情報を出力するオプションです。
> python -m unittest -v
test_init_character (tests.test_init_char.TestInitChar) ... ok
test_init_main_character (tests.test_init_char.TestInitChar) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
テストコードからの import
テストコードからは、以下のようにパッケージを import
しています。
import unittest
from min_pkg.character import Character
from min_pkg.main_character import MainCharacter
class TestInitChar(unittest.TestCase):
'''各クラスのインスタンス初期化のテスト'''
def test_init_character(self):
# (以下、テストコード)
リポジトリ・ルートから実行することによって、リポジトリ・ルート直下にある "min_pkg" フォルダが (__init__.py があるので) パッケージとして認識され、このように import
できます。
パッケージとして認識されているので、"main_character.py" が自分と同じパッケージ内のモジュール ("character.py") を参照するところも上手く動いています。
from .character import Character # . (ドット) でパッケージ内を参照
class MainCharacter(Character):
def __init__(self, name='ヒロ', age=14):
self.name = name
super().__init__(age)
本当はインストールしてテストすべき?
テストコードの実行について、上のやり方でもあまり問題はない (と私は思う) のですが、より厳密にテストしたい場合は一回どこかへインストールして**「インストール済みパッケージとして」**テストする必要があります。
たとえば他のパッケージに依存するパッケージなら同じ仮想環境に入っている方をテストすべきですし、さらには、別のバージョンの Python や pip でインストールしたパッケージをテストしたい場合もあるかも知れません。
そのやり方はまた次回にでも書こうと思います。
今回は上のやり方でも大丈夫な構成になっています。
インストールする側を作る (動作確認用)
"min_pkg" をインストールして動作確認するための、"install_check" というプロジェクトを作りました。
どこに作っても良いのですが、今回は以下のように同じディレクトリに作っています。
.
├─ min_pkg/ (パッケージのリポジトリ)
│ └─ (略)
└─ install_check/
├─ .venv/
└─ make_chars.py
この段階で、".venv" ディレクトリは、素の仮想環境です。
念のため、これを作るコマンドも載せておきます。
> cd install_check
> python -m venv .venv
この状態で、仮想環境には Python, pip, setuptools がインストールされています。
編集可能モードでインストールする
ではこれから仮想環境に "min_pkg" をインストールします。
その前に「編集可能モード」についてご説明します。
「編集可能モード」でインストールすると、パッケージのソースを変更した時に、インストール先に即座に反映されます。
ビルドしたり再インストールしたりする手間が要らないので、開発中はこのモードを使います。
pip コマンドによるインストール
先ほどの続きで、"install_check" ディレクトリにいる前提で進めます。
まずは仮想環境に入ります (以下は Windows で PowerShell を使う場合)。
> .venv/Scripts/activate
pip
で編集可能モードでインストールするには、以下のようにします。
(.venv) > pip install -e <パッケージのパス>
今回は、今いるディレクトリのひとつ上のディレクトリにパッケージがあるので、以下のようにします (もちろんフルパスでもいいです)。
(.venv) > pip install -e ../min_pkg
インストールされていることを確認してみましょう。
(.venv) > pip list
Package Version
---------- -------
min-pkg 0.1
pip 20.3.1
setuptools 51.0.0
import して使ってみる
インストールする側のプロジェクト ("install_check") に、以下のスクリプトを書いて動かしてみます。
これは、Character
と MainCharacter
というクラスを import
して、ユーザに入力してもらったパラメタでインスタンスを作る、というスクリプトです。
from min_pkg.character import Character
from min_pkg.main_character import MainCharacter
def main():
print('Making a normal character')
age = input('Age? ')
if age.isdigit():
character = Character(int(age))
else:
character = Character()
print('Making a main character')
name = input('Name? ')
age = input('Age? ')
kwargs = {}
if name:
kwargs['name'] = name
if age.isdigit():
kwargs['age'] = int(age)
main_character = MainCharacter(**kwargs)
print(f'You made a {character.age}-year-old character,', end=' ')
print('and a {}-year-old character named "{}".'.format(
main_character.age, main_character.name))
if __name__ == '__main__':
main()
Enter キーを空打ちするとそのパラメタは使わないようになっているので、それぞれのクラスで定義しているデフォルト値が使われます。
実行してみます。
(.venv) > python make_chars.py
Making a normal character
Age? [Enter]
Making a main character
Name? [Enter]
Age? [Enter]
You made a 17-year-old character, and a 14-year-old character named "ヒロ".
元のパッケージを変更してみる
ここで「編集可能モード」になっていることを確認するために、元のパッケージを編集して、デフォルト値を変えてみます。
from .character import Character
class MainCharacter(Character):
def __init__(self, name='インディ', age=78): # デフォルト値を変更
self.name = name
super().__init__(age)
そして先ほどの仮想環境で、そのまま同じコマンドを実行してみます。
(.venv) > python make_chars.py
(中略)
You made a 17-year-old character, and a 78-year-old character named "インディ".
変更が即座に反映されたことが分かります。
※ちなみにこの状態でテストコードの方を実行すると失敗します (パラメタのデフォルト値が期待値になっていたので)。
requirements ファイルを使ったインストール
さらに、インストールする側のプロジェクトを共有することも考えて、requirements ファイルでもインストールできるようにします。
.
├─ min_pkg/ (パッケージのリポジトリ)
│ └─ (略)
└─ install_check/
├─ .venv/
├─ make_chars.py
└─ requirements.txt # 追加
以下のように書きます。
-e ../min_pkg
インストールされた状態から pip freeze
で作ることもできますが、パスがフルパスになるようです。
では、今はいっているのをアンインストールしてから、このファイルを使ってインストールしてみます。
(.venv) > pip uninstall min_pkg
(中略)
(.venv) > pip install -r requirements.txt
(中略)
Successfully installed min-pkg
GitHub からインストールする
GitHub で配布することを想定して、GitHub からのインストールも試してみます。
GitHub に Push するところは省略して、今回のサンプルのリポジトリからインストールしてみます。
まずはアンインストールして、
(.venv) > pip uninstall min_pkg
以下のようなコマンドでインストールできます。
(.venv) > pip install git+https://github.com/satamame/min_pkg
"git+" の後ろがリポジトリの URL になっていれば、GitHub でなくても行けると思います。
requirements ファイルでインストールする場合も、同様の書き方になります。
git+https://github.com/satamame/min_pkg
Pipenv を使うパターン
pip
ではなく Pipenv
を使うパターンでもインストールしてみました。
使った Pipenv
のバージョンは 2020.11.15 です。
まずは Pipenv
で素の仮想環境を作ります。
"install_check" ディレクトリで "make_chars.py" 以外を削除して、以下のコマンドで作ります。
> pipenv install
ディレクトリはこんな感じになります。
.
├─ min_pkg/ (パッケージのリポジトリ)
│ └─ (略)
└─ install_check/
├─ .venv/
├─ make_chars.py
├─ Pipfile
└─ Pipfile.lock
編集可能モードでインストール
Pipenv
の場合も、パッケージの指定の仕方は pip
の時と同じです。
> pipenv install -e ../min_pkg
ここで "UnicodeDecodeError" が出ることがあったのですが、原因が分からないまま出なくなりました。
パッケージのパスに日本語を入れたりはしない方が良いのかも知れません (他にも原因はあると思いますが…)。
そのまま "make_chars.py" を実行してみます。
> pipenv run python make_chars.py
(中略)
You made a 17-year-old character, and a 14-year-old character named "ヒロ".
ちなみに "Pipfile" の中には、こんな行が足されていました。
[packages]
min-pkg = {editable = true, path = "./../min_pkg"}
GitHub からインストール
GitHub からのインストールも試します。
まずは、先ほど入れたものをアンインストールします。
> pipenv uninstall min_pkg
アンインストールできたか確認するには、Pipenv
の run
コマンドで pip list
します。
> pipenv run pip list
Pipenv
で git リポジトリからインストールする時は、以下のようにします。
#egg=<パッケージ名>
の部分を書かないと、警告が出てインストールに失敗します。
> pipenv install git+https://github.com/satamame/min_pkg#egg=min_pkg
このやり方でインストールすると、"Pipfile" に足される行はこのようになります。
[packages]
min-pkg = {git = "https://github.com/satamame/min_pkg"}
あとがき
本文の途中にもちょいちょい書きましたが、今後、もっと整った感じの配布用パッケージについても、手順を (勉強して) まとめてみようと思っています (こちらに書きました)。
「Pipenv を使うパターン」は、後から書き足しました。
Pipenv
でローカルや GitHub からのインストールを試した時に、何度か "UnicodeDecodeError" で失敗したので、最初は書くのを断念していたのですが、パッケージを今の形にしてからエラーが出なくなりました。