Pythonを触った当初は、とりあえず hoge.py にたくさんメソッドを書き、そのうちファイルが複数になり、ディレクトリが増えていきました。
しかしこのようにすると自作プログラムの中の依存関係の解決に失敗したり、どんどん訳の分からない構成になってしまい、あとで正すことも難しく感じました。
そのためよっぽど緊急なプログラミングでないかぎりは、最初に**「ライブラリ化しやすいようにディレクトリ、ファイルを準備する」**ことが重要かと思っています。
そのテンプレートを、本記事でつくってみます。
まずはsetup.pyから書いてみる
まずプロジェクトのフォルダとして python_package ディレクトリをつくって、setup.py を新規作成します。
プロジェクト直下は当然ながら下のようになります。
.
└── setup.py
setup.py は、自作のプログラムをライブラリとして提供するときに絶対に必要なものです。
とりあえず現状で入れられるものを埋めていきましょう。
自分が作ろうとしているプロジェクトの名前やソースの配置先を決めてしまいます。
from setuptools import setup
setup(
name="koboripackage",
version='1.0',
description='Pythonのディレクトリ構成のテスト用',
author='Kobori Akira',
author_email='private.beats@gmail.com',
url='https://github.com/koboriakira/python_package',
)
つくるプロダクトが明確であれば、この段階で README.md を作成しておいてもいいと思います。
とりあえずライブラリにしてみる
setup.py を書いたら、つぎにソースのディレクトリを準備します。
setupの name
に書いたのと同名のディレクトリを作成し、ディレクトリ名と同じファイル名のPythonファイルも作ります。これがライブラリとして利用されるモジュールだとしましょう。
.
├── koboripackage
│ └── koboripackage.py
└── setup.py
def hello(name):
print('Hello, %s!' % name)
作成が完了したら setup.py のあるディレクトリで python setup.py sdist
を実行します。
$ python setup.py sdist
running sdist
running egg_info
.
.
.
creating dist
Creating tar archive
removing 'koboripackage-1.0' (and everything under it)
すると色々とファイルが増えました。
.
├── dist
│ └── koboripackage-1.0.tar.gz
├── koboripackage
│ └── koboripackage.py
├── koboripackage.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ └── top_level.txt
└── setup.py
このうち、distに入っているtarが pip install
などで皆に使ってもらうライブラリです。
まだライブラリを公開していないため、さしあたりこのtarを直接指定して自分のローカルに入れてみましょう。
pip install dist/koboripackage-1.0.tar.gz
実際にライブラリとして利用できるか試してみましょう。CLIでPythonを実行します。
$ python
>>> from koboripackage import koboripackage
>>> koboripackage.hello('World')
Hello, World!
>>>
このように自分のつくったモジュール・関数をインポートして使うことができました。
なお pip uninstall koboripackage
でライブラリをアンインストールできます。
CLIでも使えるようにする
Pythonで作ったライブラリは、ターミナルからコマンドラインで実行することもできます。
そのための設定を追加しましょう。まずソースディレクトリ直下に cli.py を作成します。
def execute():
print('コマンドが実行されました!')
そしてsetup.pyを編集します。
-
version
を1.1にします - importに
find_packages
を追加して、packages=find_packages()
を追加します -
entry_points
を追加します。形式はコマンド名 = モジュール:メソッド
で書きます
from setuptools import setup, find_packages
setup(
name="koboripackage",
version='1.1',
description='Pythonのディレクトリ構成のテスト用',
author='Kobori Akira',
author_email='private.beats@gmail.com',
url='https://github.com/koboriakira/python_package',
packages=find_packages(),
entry_points="""
[console_scripts]
koboripackage = koboripackage.cli:execute
""",
)
完成したら再度 python setup.py sdist
を実行します。
するとdistに新しくv1.1のtarが作成され、以下のような構成になります。
.
├── dist
│ ├── koboripackage-1.0.tar.gz
│ └── koboripackage-1.1.tar.gz
├── find_package.txt
├── koboripackage
│ ├── cli.py
│ └── koboripackage.py
├── koboripackage.egg-info
└── setup.py
さきほど同様、 pip install dist/koboripackage-1.1.tar.gz
でライブラリをインストールしてみます。すると、
$ koboripackage
コマンドが実行されました!
上記のようにコマンド名だけで cli.pyのexecuteメソッドを実行できたことがわかります。
既存ライブラリの利用
cli.pyに既存ライブラリをimportしてみます。
import requests
def execute():
print('コマンドが実行されました!')
response = requests.get('https://www.google.com/')
print(response.status_code)
requestsを(ローカルで)利用するには pip install requests
でライブラリをインポートする必要があります。
同様にライブラリとして提供するにも、「何をインポートしないといけないか」を明記する必要があります。
このために準備するのが requirements.txt です。プロジェクト直下に配置しましょう。
requests==2.23.0
ちなみに必要なライブラリ、バージョンは pip freeze
コマンドで確認できます。
ただし現在インストールしているすべてのライブラリが出力されるので、そのままコピペしてしまうと不要なライブラリも入れてしまうかもしれません。
つぎに python setup.py sdist
でtarを作成するときに requirements.txt が正しく利用されるように、2つ設定を行います。
- MANIFEST.in を新規作成します
- setup.py に
install_requires
を追加します(ついでにバージョンを1.2に)
.
├── MANIFEST.in
├── dist
├── koboripackage
│ ├── cli.py
│ └── koboripackage.py
├── koboripackage.egg-info
├── requirements.txt
└── setup.py
include requirements.txt
from setuptools import setup, find_packages
setup(
name="koboripackage",
version='1.2',
description='Pythonのディレクトリ構成のテスト用',
author='Kobori Akira',
author_email='private.beats@gmail.com',
url='https://github.com/koboriakira/python_package',
packages=find_packages(),
entry_points="""
[console_scripts]
koboripackage = koboripackage.cli:execute
""",
install_requires=open('requirements.txt').read().splitlines(),
)
**MANIFEST.inは、「パッケージ化されるときに通常含まれないものを追加する(その逆も可)」**ことができます。
include requirements.txt
と記述したので、パッケージ化するときに requirements.txtを追加することになります。
そしてsetupに追加した install_requires
で、必要なライブラリを指定します。
直接ライブラリ名を配列で指定してもいいのですが、上記のようにrequirements.txtの中身を読み込ませるようにするのが一般的なようです。
※直接指定する場合
install_requires=['requests']
ここまで準備が終わったら python setup.py sdist
でファイルで、v1.2のtarを作成します。
pip install dist/koboripackage-1.2.tar.gz
で新バージョンのライブラリをインストールしたあとにコマンドを実行してみると、
$ koboripackage
コマンドが実行されました!
200
上記のようにライブラリを利用できていることがわかります。
PyPiにアップロード
ここまで準備しておければ、PyPi(Python Package Index)に登録することができます。
具体的な手順は https://blog.amedama.jp/entry/2017/12/31/175036 を参考にしてください。
うまくアップロードできれば、 pip install koboripackage
などでインストールすることが可能となります。
testsを準備
テストができるようにも準備しておきましょう。
プロジェクト直下にtestsフォルダを作成します。
ソースのあるディレクトリと同じ構成にして、テスト用のPythonファイルを作成します(プレフィクスにtestをつける)。
.
├── MANIFEST.in
├── dist
├── koboripackage
│ ├── cli.py
│ └── koboripackage.py
├── koboripackage.egg-info
├── requirements.txt
├── setup.py
└── tests
└── test_koboripackage.py
koboripackage.py、test_koboripackage.py それぞれの次のようにソースを書いて、multiply()
の挙動を確かめるようにしてみます。
def hello(name):
print('Hello, %s!' % name)
def multiply(a, b):
return a * b
from koboripackage import koboripackage
def test_multiply():
assert koboripackage.multiply(2, 3) == 6
pip install pytest
でpytestをインストールしておいてから、pytest
を実行します。
$ pytest
================================================================================================== test session starts ==================================================================================================
platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: ...............
collected 1 item
tests/test_koboripackage.py . [100%]
=================================================================================================== 1 passed in 0.02s ===================================================================================================
これで一般的なPythonプロジェクトを作ることができました。
Docker、gitを準備
最後にせっかくなのでDockerイメージの準備もしてみます。
必要なファイルを追加したうえで、あらかじめライブラリをインストールしたイメージをつくります。
FROM python:3.7.5-slim
WORKDIR /work
ADD koboripackage koboripackage
ADD tests tests
ADD requirements.txt requirements.txt
ADD setup.py setup.py
ADD MANIFEST.in MANIFEST.in
RUN pip install --upgrade pip \
&& pip install -r requirements.txt \
&& pip install pytest
CMD bash
docker build -t python_package .
でイメージを作成して、 docker run -it --rm python_package
でコンテナを作成します。
Dockerコンテナの中でも pytest
を実行することができるようになりました。
そのまま開発する場合は、次のようにしてソースを共有することも可能です。
docker run -it --rm -v $(pwd)/koboripackage:/work/koboripackage -v $(pwd)/tests:/work/tests python_package
最後にこれをgithubにpushします。
この工程の間に作られたファイルの中には、わざわざgithubに上げる必要のないものもあるので、.gitignoreを準備しておきます。
**/__pycache__
dist/
koboripackage.egg-info/
さいごに
こうして作成したパッケージが次の通りです。いちおうPyPiにもアップロードしておきました。
https://github.com/koboriakira/python_package
なお本来はsetup.pyにライセンスを記述することあるのですが、立ち入る自信がなかったので今回は避けました。
現状は「とりあえずMITにしておけばいいかな」程度の把握です。
このようにライブラリ化できる状態を最初から準備することで、適切なサイズのコンポーネントを意識的につくるのが容易になればと思います。
参考
- https://python-guideja.readthedocs.io/ja/latest/writing/structure.html
- https://rinatz.github.io/python-book/ch04-06-project-structures/
- https://www.rhoboro.com/2018/01/25/project-directories.html
- https://github.com/rhoboro/webapp_skeleton/blob/master/Makefile
- https://techblog.asahi-net.co.jp/entry/2018/06/15/162951#%E3%83%86%E3%82%B9%E3%83%88%E5%8F%AF%E8%83%BD%E3%81%AA%E6%9C%80%E5%B0%8F%E6%A7%8B%E6%88%90%E3%82%92%E4%BD%9C%E3%82%8B