🗂

はじめてPyPIにPythonライブラリを登録した話

2022/01/27に公開約6,000字

はじめに

先日、生まれてはじめてPyPIにPythonライブラリを登録しました。PyPIというのはPython Package Indexの略で、ここにパッケージを登録するとpip installでインストールできるようになります。

経緯は以下の通りです。

  1. もともとIMAP4をJSON化するAPIをローカルで動かすメール受信システムがあった
  2. AWS LambdaとAPI Gatewayの組み合わせでサーバーレス化することになった
  3. Flaskで書かれたAPIサーバー部分とメール受信機能を実装した箇所が密結合していたので分離した
  4. メール受信機能をpip installでインストールできたら便利じゃない?

PyPIへの登録は一度やってみたかったので、良い機会だと思い挑戦してみました。n番煎じのネタですが、ドキュメント化しておかないと忘れそうなので、未来の自分のために記事にしました。

PyPIに登録した結果はこちらです。

https://pypi.org/project/imap2dict/

ソースコードはこちらです。

https://github.com/sikkimtemi/imap2dict

手順は主にこちらの記事を参考にしました。

PyPI登録手順

ディレクトリ構成

今回登録するパッケージはimap2dictという名前にしました。IMAP4で受信したメールデータをPython辞書形式に変換するという意味を込めています。ディレクトリ構成は以下の通りです。

.
├── .gitignore ... Gitから除外したいファイルを指定
├── LICENSE ... ライセンス定義ファイル
├── README.md ... READMEファイル
├── imap2dict ... モジュールを配置するディレクトリ
│   ├── __init__.py ... モジュール検索や名前検索の初期化等を司るファイル
│   └── mail_client.py ... 機能を提供するモジュール
├── requirements.txt ... 依存関係を記述するファイル
└── setup.py ... PyPIの設定ファイル

init.pyの作成

__init__.py
from .mail_client import MailClient

参考にした記事では__init__.pyにバージョン情報を記載していましたが、それをするとsetup.pyを実行したときに依存関係を解消できずにエラーが発生したのでやめました。

ライセンス定義ファイルの生成

ライセンス定義ファイルはリポジトリ作成時にGitHub上で作るのが楽です。今回はMITライセンスにしました。

README.mdの作成

最低限必要なものを書いておこうと思い、概要とインストール方法、使い方だけを記載しました。

メインモジュールの修正

ディレクトリ構成に合わせてメインモジュールのmail_client.pyを修正しました。Pythonのお作法がいまいちよくわかっていないので、ファイル名とクラス名を決めるのに悩みましたが、機能をベースとして考えて以下のような構成にしました。

mail_client.pyの一部
class MailClient():
    '''
    メールクライアントクラス。
    '''

    host_name = ''
    user_id = ''
    password  = ''


    def __init__(self, host_name, user_id, password):
        self.host_name = host_name
        self.user_id = user_id
        self.password = password

requirements.txtの作成

requirements.txtpipreqsで自動生成してから手で修正しました。

pipreqsのインストール
pip install pipreqs
requirements.txtの自動生成
pipreqs .
自動生成されたrequirements.txt
pytz==2020.1
修正したrequirements.txt
pytz>=2020.1

setup.pyの作成

setup.pyには以下の情報を記載しました。

setupメソッドの引数 内容
name ライブラリ名
version バージョン
description 短い説明文
long_description 長い説明文(README.mdの内容をファイルから読み込むようにした)
long_description_content_type 長い説明文の形式(今回はマークダウンなのでtext/markdownと記載する)
author 作者名
author_email 作者のメールアドレス
maintaner メンテナーの名前(今回は作者と同じ)
maintaner_email メンテナーのメールアドレス(今回は作者と同じ)
url ホームページのURL(今回はGitHubリポジトリのURL)
download_url ダウンロード用URL(今回はGitHubリポジトリのURL)
packages パッケージ構成
classifiers 分類情報(ライセンス情報とプログラミング言語を記載)
license ライセンス情報
keywords 検索でヒットさせたいキーワード
install_requires 依存するパッケージの情報

実際のコードはこちらです。

setup.py
# Author: TAKAHASHI Taro <takahashi.taro@takedasystem.com>
# Copyright (c) 2022- TAKAHASHI Taro
# Licence: MIT

from setuptools import setup

DESCRIPTION = 'imap2dict: Receiving and deleting email on an IMAP4 server.'
NAME = 'imap2dict'
AUTHOR = 'TAKAHASHI Taro'
AUTHOR_EMAIL = 'takahashi.taro@takedasystem.com'
URL = 'https://github.com/sikkimtemi/imap2dict'
LICENSE = 'MIT'
DOWNLOAD_URL = URL
VERSION = '0.1.0'
PYTHON_REQUIRES = '>=3.6'
INSTALL_REQUIRES = [
    'pytz>=2020.1'
]
PACKAGES = [
    'imap2dict'
]
KEYWORDS = 'imap imap4 json'
CLASSIFIERS=[
    'License :: OSI Approved :: MIT License',
    'Programming Language :: Python :: 3.6'
]
with open('README.md', 'r', encoding='utf-8') as fp:
    readme = fp.read()
LONG_DESCRIPTION = readme
LONG_DESCRIPTION_CONTENT_TYPE = 'text/markdown'

setup(
    name=NAME,
    version=VERSION,
    description=DESCRIPTION,
    long_description=LONG_DESCRIPTION,
    long_description_content_type=LONG_DESCRIPTION_CONTENT_TYPE,
    author=AUTHOR,
    author_email=AUTHOR_EMAIL,
    maintainer=AUTHOR,
    maintainer_email=AUTHOR_EMAIL,
    url=URL,
    download_url=URL,
    packages=PACKAGES,
    classifiers=CLASSIFIERS,
    license=LICENSE,
    keywords=KEYWORDS,
    install_requires=INSTALL_REQUIRES
)

ライブラリのビルド

事前にWheelをインストールします。

pip install wheel

ソースコード配布物をビルドします。

python setup.py sdist

distディレクトリとimap2dict.egg-infoディレクトリが生成します。

Wheelパッケージをビルドします。

python setup.py bdist_wheel

buildディレクトリが生成します。

最終的に以下のようなディレクトリ構成になります。

.
├── LICENSE
├── README.md
├── build
│   ├── bdist.macosx-12-x86_64
│   └── lib
│       └── imap2dict
│           ├── __init__.py
│           └── mail_client.py
├── dist
│   ├── imap2dict-0.1.0-py3-none-any.whl
│   └── imap2dict-0.1.0.tar.gz
├── imap2dict
│   ├── __init__.py
│   └── mail_client.py
├── imap2dict.egg-info
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   ├── dependency_links.txt
│   ├── requires.txt
│   └── top_level.txt
├── requirements.txt
└── setup.py

.gitignoreの作成

ビルドによって生成したファイルやディレクトリをGitで管理しないように.gitignoreを作成します。私はこちらをもとに必要に応じて修正して使うことが多いです。

PyPIのユーザー登録

PyPIには本番環境とテスト環境があります。どちらにもユーザー登録しましょう。

本番環境はこちら。

https://pypi.org/account/register/

テスト環境はこちら。

https://test.pypi.org/account/register/

ユーザー登録が完了したらホームディレクトリに.pypircを作成します。

.pypirc
[distutils]
index-servers =
  pypi
  testpypi

[pypi]
repository: https://upload.pypi.org/legacy/
username: 本番環境のPyPIユーザ名
password: 本番環境のPyPIパスワード

[testpypi]
repository: https://test.pypi.org/legacy/
username: テスト環境のPyPIユーザ名
password: テスト環境のPyPIパスワード

TwineでPyPIに登録

PyPIにライブラリを登録するツールのひとつであるTwineをインストールします。

pip install twine

まずテスト環境にアップロードしてみましょう。

twine upload --repository testpypi dist/*

成功するとURLが返ってきます。

https://test.pypi.org/project/imap2dict/

URLにアクセスするとpipによるテストコマンドが記載されているので、テストしてみます。

pip install -i https://test.pypi.org/simple/ imap2dict

PyPIのテスト環境に存在するpytzモジュールのバージョンが古いので依存関係でエラーになりましたが、事前にpytzをインストールした状態でコマンドを打つとインストールに成功しました。

ではいよいよ本番環境にアップロードします。

twine upload --repository pypi dist/*

URLが返ってきたら表示を確認します。

https://pypi.org/project/imap2dict/

pipでインストールできることを確認します。

pip install imap2dict

GitHubにタグをつける

最後に下図の手順でGitHubにバージョンのタグをつけます。

これで一連の作業は完了です。

Discussion

ログインするとコメントできます