CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: 環境ごとの依存ライブラリをセットアップスクリプトの extras_require で管理する

Python のパッケージを作っていると、特定の環境だけで必要となるパッケージが大抵はでてくる。 例えばデータベースを扱うアプリケーションなら、使う RDBMS によってデータベースドライバのパッケージが異なる。 あるいは、インストール先の Python のバージョンによっては標準ライブラリに用意されていないパッケージのバックポート版をインストールしなきゃいけない。 今回は、そんなときに便利なセットアップスクリプト (setup.py) の extras_require 引数を使ってみる。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.5
BuildVersion:   15F34
$ python --version
Python 3.5.1

下準備

まず最初に題材とするのはデータベースを扱うアプリケーションにしよう。 前述した通り、この状況では使う RDBMS によって異なるデータベースドライバをインストールしなきゃいけない。 今回は RDBMS に MySQL と Postgresql を使い分ける状況を想定しよう。

データベースドライバをビルドするために MySQL と Postgresql をインストールしておく。

$ brew install mysql postgresql

次に、題材とするアプリケーション本体のソースコード。 これには SQLAlchemy を使ってモデルを定義したモジュールを mydbapp という名前で保存しておく。 ただし、今回これはあくまで単なる例に過ぎないので実際に動かしたりすることはない。

$ cat < 'EOF' > mydbapp.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import BigInteger
from sqlalchemy.sql.sqltypes import Text


Base = declarative_base()


class User(Base):
    """データベースのスキーマの元になるモデル"""
    __tablename__ = 'users'

    id = Column(BigInteger, primary_key=True)
    name = Column(Text, nullable=False)
EOF

サンプルコードにセットアップスクリプトを書く

それでは、今回の本題となるセットアップスクリプト (setup.py) を書いてみることにする。

アプリケーションが共通で必要とするパッケージについては通常どおり install_requires に記述しよう。 今回においては SQLAlchemy がこれに当たる。 そして、環境に依存するデータベースドライバは extras_require に辞書の形で渡す。 辞書のキーは環境の名前で、バリューにはパッケージの入ったリストを指定することになる。 今回であれば mysql には mysqlclient を、そして postgresql には psycopg2 を指定している。

$ cat << 'EOF' > setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup


def main():
    setup(
        name='mydbapp',
        version='0.0.1',
        zip_safe=False,
        py_modules=['mydbapp'],
        install_requires=[
            # どのような環境でも SQLAlchemy は必要になる
            'SQLAlchemy',
        ],
        extras_require={
            # 使う RDBMS ごとに、それ専用のドライバが必要になる
            'mysql': ['mysqlclient'],
            'postgresql': ['psycopg2'],
        },
    )


if __name__ == '__main__':
    main()
EOF

これで mydbapp モジュールをインストールできるようになった。

環境を指定してインストールする

pip install サブコマンドでは setup.py のあるディレクトリを指定することで、そのパッケージ (モジュール) をインストールできる。 このとき extras_require を使ったものであれば、角括弧で環境を指定する。

それでは、環境として mysql を指定してインストールしてみよう。

$ pip install .[mysql]
...(省略)...
Running setup.py install for mydbapp ... done
Successfully installed SQLAlchemy-1.0.13 mydbapp-0.0.1 mysqlclient-1.3.7

インストールされたパッケージを確認すると SQLAlchemy や mydbapp に混じって mysqlclient が見つかる。

$ pip list
mydbapp (0.0.1)
mysqlclient (1.3.7)
pip (8.1.2)
setuptools (23.0.0)
SQLAlchemy (1.0.13)
wheel (0.29.0)

同じように postgresql を指定したときはどうなるだろうか。

$ pip install .[postgresql]
...(省略)...
Running setup.py install for mydbapp ... done
Successfully installed SQLAlchemy-1.0.13 mydbapp-0.0.1 psycopg2-2.6.1

先ほどとは異なり psycopg2 がインストールされている。 ちなみに Python の仮想環境は作りなおしている。

$ pip list
mydbapp (0.0.1)
pip (8.1.2)
psycopg2 (2.6.1)
setuptools (23.0.0)
SQLAlchemy (1.0.13)
wheel (0.29.0)

もちろん、環境の指定は pip install 以外のサブコマンドにも有効になっている。 例えば Wheel をビルドするときも指定すれば環境ごとの内容になる。

$ pip wheel .[mysql]
$ ls | grep whl$
SQLAlchemy-1.0.13-cp35-cp35m-macosx_10_11_x86_64.whl
mydbapp-0.0.1-py3-none-any.whl
mysqlclient-1.3.7-cp35-cp35m-macosx_10_11_x86_64.whl

Python のバージョンごとに依存ライブラリを切り替える

extras_require には、環境の名前を指定してインストールする以外にも便利な使い方がある。 例えば Python のバージョンごとにインストールする依存ライブラリを切り替えることができる。

それでは、例としてアプリケーションが ipaddress モジュールに依存している場合を考えてみよう。 ipaddress モジュールは Python 3.3 で新たに標準ライブラリの仲間入りを果たしたモジュールだ。 つまり、それ以前のバージョンでは使うことができない。 ただし、バックポート版を PyPI からダウンロードしてインストールすることはできる。

次のセットアップスクリプトでは Python のバージョンが 3.3 未満のときだけ ipaddress モジュールをインストールするようにしよう。 これには「:python_version<"3.3"」といった書式で extras_require のキーを指定する。

$ cat << 'EOF' > setup.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup


def main():
    setup(
        name='mydbapp',
        version='0.0.1',
        zip_safe=False,
        py_modules=['mydbapp'],
        install_requires=[
            # どのような環境でも SQLAlchemy は必要になる
            'SQLAlchemy',
        ],
        extras_require={
            # 使う RDBMS ごとに、それ専用のドライバが必要になる
            'mysql': ['mysqlclient'],
            'postgresql': ['psycopg2'],
            # Python 3.3 未満には ipaddress が標準ライブラリにない
            ':python_version<"3.3"': [
                'ipaddress',
            ],
        },
    )


if __name__ == '__main__':
    main()
EOF

それでは、上記を Python 2.7 の環境にインストールしてみよう。

$ python --version
Python 2.7.10
$ pip install .
...(省略)...
Running setup.py install for mydbapp ... done
Successfully installed SQLAlchemy-1.0.13 ipaddress-1.0.16 mydbapp-0.0.1

ipaddress モジュールがインストールされていることがわかる。

$ pip list
ipaddress (1.0.16)
mydbapp (0.0.1)
pip (8.1.2)
setuptools (23.0.0)
SQLAlchemy (1.0.13)
wheel (0.29.0)

次に Python 3.5 にもインストールしてみる。

$ python --version
Python 3.5.1
$ pip install .
...(省略)...
Running setup.py install for mydbapp ... done
Successfully installed SQLAlchemy-1.0.13 mydbapp-0.0.1

今度は ipaddress モジュールはインストールされていない!

$ pip list
mydbapp (0.0.1)
pip (8.1.2)
setuptools (23.0.0)
SQLAlchemy (1.0.13)
wheel (0.29.0)

ちなみに、上記で登場したバージョンの指定方法は PEP 426 という仕様で規定されているらしい。 具体的には、その中の Environment Markers だ。

Environment Markers については、次のブログ記事が詳しかった。 ちなみに、システムのプラットフォーム (Linux だとか Windows だとか) やアーキテクチャ (i386 や x86_64) まで判定できるようだ。

2014/07/10 PEP-0426 Environment Markers の調査 - 清水川Web