7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

パッケージ開発と動作確認の手順 (最低限の構成編)

Last updated at Posted at 2020-12-16

前置き

Python で pip install できるパッケージの開発をしようとしています。
開発中にちょこちょこ動作確認することになるので、その手順を自分なりに確立しておきたいと思って、記事にまとめることにしました。

今回は**「最低限の構成編」**ですので、PyPI に登録するようなパッケージを作るのには向きません。
ただしテストコードは含むようにしています。

何をもって「最低限」かというと、他のパッケージに依存しない というのがひとつのポイントになります。
そのためいろいろ端折っていますが、ちょっとしたパッケージを GitHub で配布するだけならこれで大丈夫だと思います。
他のパッケージに依存するパッケージの構成については、またの機会に書きたいと思います。

動作確認した環境

Python 3.8.1
pip 20.3.1
setuptools 51.0.0

ステップ

  1. ディレクトリを構成する
  2. テストコードが動くようにする
  3. インストールする側を作る (動作確認用)
  4. 編集可能モードでインストールする
    1. pip コマンドによるインストール
    2. requirements ファイルを使ったインストール
  5. 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 を見ると、おまじないの様に決まった記述をすれば良いようです。

pyproject.toml
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

setup.cfg

setuptools が参照する、パッケージ情報です。多分これで最低限だと思います。

setup.cfg
[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" の中でそう書いているからです。

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 しています。

test_init_char.py
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") を参照するところも上手く動いています。

main_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") に、以下のスクリプトを書いて動かしてみます。
これは、CharacterMainCharacter というクラスを import して、ユーザに入力してもらったパラメタでインスタンスを作る、というスクリプトです。

make_chars.py
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 "ヒロ".

元のパッケージを変更してみる

ここで「編集可能モード」になっていることを確認するために、元のパッケージを編集して、デフォルト値を変えてみます。

main_character.py
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  # 追加

以下のように書きます。

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 ファイルでインストールする場合も、同様の書き方になります。

requirements.txt
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" の中には、こんな行が足されていました。

Pipfile
[packages]
min-pkg = {editable = true, path = "./../min_pkg"}

GitHub からインストール

GitHub からのインストールも試します。
まずは、先ほど入れたものをアンインストールします。

> pipenv uninstall min_pkg

アンインストールできたか確認するには、Pipenvrun コマンドで pip list します。

> pipenv run pip list

Pipenv で git リポジトリからインストールする時は、以下のようにします。
#egg=<パッケージ名> の部分を書かないと、警告が出てインストールに失敗します。

> pipenv install git+https://github.com/satamame/min_pkg#egg=min_pkg

このやり方でインストールすると、"Pipfile" に足される行はこのようになります。

Pipfile
[packages]
min-pkg = {git = "https://github.com/satamame/min_pkg"}

あとがき

本文の途中にもちょいちょい書きましたが、今後、もっと整った感じの配布用パッケージについても、手順を (勉強して) まとめてみようと思っています (こちらに書きました)。

「Pipenv を使うパターン」は、後から書き足しました。
Pipenv でローカルや GitHub からのインストールを試した時に、何度か "UnicodeDecodeError" で失敗したので、最初は書くのを断念していたのですが、パッケージを今の形にしてからエラーが出なくなりました。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?