【PyPI 】Pythonの自作ライブラリをpipに公開する方法

# 自作Pythonライブラリを全世界に公開
Pythonでライブラリをインストールする際は

```
pip install [ライブラリ名]
```
というコマンドを使うことが多いですが、
多くの人は以下のようなイメージを持っているかと思います。

**「pipで公開してるライブラリってシリコンバレーのランチ無料のオフィスで働いてる凄いエンジニアが作ったんでしょ？」**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/18f71b3b-0ccb-6ed9-b3df-954d451bdd29.png)

このように敷居が高そうに見えるpipですが、実は**一般人でもライブラリを公開することができます**

何を隠そう、一般人の味方！す○家で牛丼ランチ（500円）を食べている僕でもアップロードできました。
一般ピープルでも全世界にライブラリを公開できるのは感慨深いです。

公開方法を紹介している記事はいくつかありますが、どこかしらの必要工程が抜けているものが多いように感じたので、**本記事では一般的に必要とされている公開内容を網羅する**ことを心がけて紹介したいと思います。

# pipの仕組み
pipでインストール可能なライブラリは、**[PyPI](https://pypi.org/)**（パイピーアイと読むらしいです）というディストリビューションサービスにアップロードされています。

つまるところ、一般人でもPyPIにユーザ登録してライブラリをアップロードすれば、pipでインストールできてしまうわけです。

# 自作ライブラリ公開手順
公開するライブラリが既にGitHubリポジトリ上に存在するという前提で、以下の手順でPyPIに公開します。

**1. ＿init＿.pyを作成**
**2. ライセンスの追加**
**3. README.rstまたはREADME.mdを作成**
**4. setup.pyの追加**
**5. Twineを使ってPyPIに登録**
**6. APIドキュメントをSphinxで作成**
**7. APIドキュメントをGithub Pagesにアップロード**

本記事では、**私が作成した自作ライブラリ[「seaborn-analyzer」](https://qiita.com/c60evaporator/items/8cb774e65d4423fd93ee)を例に、pipへのライブラリ公開手順を紹介します**

全体の注意点として、ローカルで行う作業とリモート（GitHub）で行う作業があるので、両者の整合性がとれるよう**適宜PushとPullを実施してください**。

## 1. ＿init＿.pyを作成
モジュールのディレクトリ構造を定義する、`__init__.py`を作成します。
これにより、

```python
import seaborn_analyzer
seaborn_analyzer.custom_hist_plot.fit_dist()
```
というように階層構造を全て把握して記載する必要があったのが、

```python
import seaborn_analyzer
seaborn_analyzer.fit_dist()
```
のように記載を短くすることができ、ユーザ側から見て使いやすくなります。
（ディレクトリ構造の定義以外にも用途はありますが、[詳しくはこちらをご参照ください](https://qiita.com/msi/items/d91ea3900373ff8b09d7#3-%E3%83%AF%E3%82%A4%E3%83%AB%E3%83%89%E3%82%AB%E3%83%BC%E3%83%89-import-%E3%81%AE%E5%AF%BE%E8%B1%A1%E3%81%AE%E5%AE%9A%E7%BE%A9-__all__-%E3%81%AE%E5%AE%9A%E7%BE%A9)）

### ・ディレクトリ構成の適正化
`__init__.py`が機能するためには、適正なディレクトリ構成でモジュールを設置する必要があります。
シンプルに管理するのであれば、以下のようなディレクトリ構成が一般的です。

```
./ ... ルートディレクトリ
├─ LICENSE ... ライセンス定義ファイル（2章で作成）
├─ README.md ... READMEファイル（3章で作成）
├─ setup.py ... PyPIアップロード時の設定ファイル（4章で作成）
└─ seaborn_analyzer/ ... モジュールを設置するディレクトリ
    ├─ custom_hist_plot.py ...モジュールを構成する.pyファイルの一つ
    : ... ここにモジュールを構成する.pyファイルをまとめて設置
    :
    └─ __init__.py ... 本章で作成
```

ここで注意すべきが、モジュールを設置するディレクトリの名称です。
**ここで設定したディレクトリ名**が、そのまま
`import seaborn_analyzer`
のように、**importする際のモジュール名となります**

その他、モジュールを設定するディレクトリ名の制約条件として、
・ハイフン（-）は使用できない
・setup.pyのpackages引数（[4章参照](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#setup%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E5%90%84%E7%A8%AE%E6%83%85%E5%A0%B1%E3%82%92%E5%85%A5%E5%8A%9B)）には、このディレクトリ名を指定
にご注意ください。

### ・実際に作成したコード
上記ディレクトリ構成で[seabornライブラリの記載](https://github.com/mwaskom/seaborn/blob/master/seaborn/__init__.py)を参考に、以下のような`__init__.py`を作成しました

```__init__.py
from .custom_pair_plot import *
from .custom_hist_plot import *
from .custom_class_plot import *
from .custom_reg_plot import *

__version__ = '0.1.2'
```
階層構造の定義だけでなく、リリースのバージョン（[5章での指定と統一](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#github%E3%81%AB%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%BF%E3%82%B0%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B)）もここで管理します。


## 2. ライセンスの追加
GitHubリポジトリにオープンソースのライセンスを追加します。
これがあると、どの程度の流用が許されるライブラリなのか、ユーザが判断することができます。
（ライセンスが定義されていないと、制限を警戒されて使ってもらえない可能性もあります）

### ・ライセンスの種類
以下を参考に、ライセンスを選択してください。

https://qiita.com/bremen/items/c5aa9446e73aa4bc1de0

個人開発なら、迷ったらMIT、BSD3あたりでいいかと思います。

**業務で使うのであれば、テキトーに選ぶと後で[とんでもない事態に発展](https://www.ogis-ri.co.jp/otc/hiroba/technical/oss-license-violations/)する事もある**ので、責任者に必ず相談してください。

※余談ですが、GNU GPL v2, v3ライセンスのソフトウェアを業務で使う例が散見されますが、ソースコード開示義務が生じるので商品開発等では細心の注意を払って利用する必要があります（特に組込み系では要注意）

### ・GitHubリポジトリへのライセンス追加方法
リポジトリ作成時に追加する方法と、既に存在するリポジトリに追加する方法があります。

#### リポジトリ作成時に追加したい時
以下のリンクを参照ください

https://docs.github.com/ja/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository

#### 既に存在するリポジトリにライセンス追加したい時
以下のリンクを参照ください

https://qiita.com/medi-y-sato/items/c3d5c357ab020290dccc

## 3. README.rstまたはREADME.mdを作成
GitHubのリポジトリや[後述のPyPI](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#%E6%9C%AC%E7%95%AA%E7%94%A8pypi%E3%81%B8%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89)のトップ画面に表示されるREADMEファイルを作成します。
これがないとユーザがライブラリが何物なのかを判断する事ができないので、
下記リンクを参考に**必ず作成してください。**

https://howpon.com/8334

GitHubで認識可能なREADMEの形式には、**マークダウン形式（README.md）**と**reStructuredText形式（README.rst）**があります。
Qiitaを読んでいる時点でマークダウンに慣れている方が多いと思うので、手軽に作るならmd形式の方がお薦めです

rst形式のメリットには、後述のSphinxドキュメントとフォーマットを統一できることがあります。
rst形式の文法は以下を参照ください

https://planset-study-sphinx.readthedocs.io/ja/latest/04.html

[私の作ったライブラリのリポジトリ](https://github.com/c60evaporator/seaborn-analyzer)にも、README.md（日本語）とREADME.rst（英語）があるので、両者を比較すると分かりやすいかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/b8fe902a-9068-2f0b-dd9b-86de95b8bb9d.png)
（この記事やライブラリが役に立てば、右上のStarボタンも押して頂けると有難いです！）

## 4. setup.pyの追加
パッケージの様々な内容（パッケージ名、依存パッケージ、バージョン、作者名など）を定義する、setup.pyを作成します。
PyPIに公開するためには、この**setup.pyを正しい構文で記載する事が必須**となります。

### ・実際に作成したコード
今回は、[seabornライブラリの記載](https://github.com/mwaskom/seaborn/blob/master/setup.py)および[shapelyライブラリの記載（Readme.rstの読込部分）](https://github.com/Toblerity/Shapely/blob/master/setup.py)を参考に、以下のようなsetup.pyを作成しました。

```setup.py
# Author: Kenta Nakamura <c60evaporator@gmail.com>
# Copyright (c) 2020-2021 Kenta Nakamura
# License: BSD 3 clause

from setuptools import setup
import seaborn_analyzer

DESCRIPTION = "seaborn-analyzer: data visualization of regression, classification and distribution"
NAME = 'seaborn-analyzer'
AUTHOR = 'Kenta Nakamura'
AUTHOR_EMAIL = 'c60evaporator@gmail.com'
URL = 'https://github.com/c60evaporator/seaborn-analyzer'
LICENSE = 'BSD 3-Clause'
DOWNLOAD_URL = 'https://github.com/c60evaporator/seaborn-analyzer'
VERSION = seaborn_analyzer.__version__
PYTHON_REQUIRES = ">=3.6"

INSTALL_REQUIRES = [
    'matplotlib>=3.3.4',
    'seaborn>=0.11.0',
    'numpy >=1.20.3',
    'pandas>=1.2.4',
    'matplotlib>=3.3.4',
    'scipy>=1.6.3',
    'scikit-learn>=0.24.2',
]

EXTRAS_REQUIRE = {
    'tutorial': [
        'mlxtend>=0.18.0',
        'xgboost>=1.4.2',
    ]
}

PACKAGES = [
    'seaborn_analyzer'
]

CLASSIFIERS = [
    'Intended Audience :: Science/Research',
    'License :: OSI Approved :: BSD License',
    'Programming Language :: Python :: 3',
    'Programming Language :: Python :: 3.6',
    'Programming Language :: Python :: 3.7',
    'Programming Language :: Python :: 3.8',
    'Programming Language :: Python :: 3.9',
    'Programming Language :: Python :: 3 :: Only',
    'Topic :: Scientific/Engineering',
    'Topic :: Scientific/Engineering :: Visualization',
    'Topic :: Scientific/Engineering :: Artificial Intelligence',
    'Topic :: Multimedia :: Graphics',
    'Framework :: Matplotlib',
]

with open('README.rst', 'r') as fp:
    readme = fp.read()
with open('CONTACT.txt', 'r') as fp:
    contacts = fp.read()
long_description = readme + '\n\n' + contacts

setup(name=NAME,
      author=AUTHOR,
      author_email=AUTHOR_EMAIL,
      maintainer=AUTHOR,
      maintainer_email=AUTHOR_EMAIL,
      description=DESCRIPTION,
      long_description=long_description,
      license=LICENSE,
      url=URL,
      version=VERSION,
      download_url=DOWNLOAD_URL,
      python_requires=PYTHON_REQUIRES,
      install_requires=INSTALL_REQUIRES,
      extras_require=EXTRAS_REQUIRE,
      packages=PACKAGES,
      classifiers=CLASSIFIERS
    )
```

### ・setup.pyの作成手順
上記のようなsetup.pyは、以下の手順で作成します
・setuptoolsのインストール
・`from setuptools import setup`でsetupメソッドをインポート
・setupメソッドに各種情報を入力

#### setuptoolsのインストール

```
pip install setuptools
```

#### setupメソッドに各種情報を入力
setupメソッドの引数に各種情報を入力します。
主な引数は以下のようになります

|引数名|入力する情報|
|---|---|
|name|ライブラリの名前（PyPIにこの名前で登録される）|
|author|作者名|
|author_email|作者のEメールアドレス|
|maintainer|管理者（個人開発なら作者と同じ）|
|maintainer_email|管理者のEメールアドレス|
|description|ライブラリの簡単な説明|
|long_description|ライブラリの詳細説明。**[READMEを登録するとPyPIのトップ画面に表示される](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#%E3%83%86%E3%82%B9%E3%83%88%E7%94%A8pypi%E3%81%B8%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89)**|
|license|[手順2](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#2-%E3%83%A9%E3%82%A4%E3%82%BB%E3%83%B3%E3%82%B9%E3%81%AE%E8%BF%BD%E5%8A%A0)で作成したライセンス|
|url|URL（GitHubのURLを登録することが多い）|
|version|バージョン（二重管理防止のため＿init＿.pyから読込む事が望ましい）|
|download_url|ダウンロードURL（GitHubのURLを登録することが多い）|
|python_requires|**Pythonのバージョン要件**|
|install_requires|**必須パッケージ**|
|extras_require|推奨パッケージ|
|packages|パッケージ構成（複数登録することも可能）|
|classifiers|各種分類情報|

「**必須パッケージ**に何を書けばいいか分からない！」
という方も多いかと思いますが、最低限、**Importしているパッケージ**を記載しておけばよいかと思います。
バージョン制約も全て調べるときりがないので、最低限、自分が動作確認したときのバージョンを記載しておけばよいかと思います。

##### ※READMEとPyPIのトップ画面
long_description引数に[手順3で作成したREADME.rst](https://qiita.com/c60evaporator/items/e1ecccab07a607487dcf#3-readmerst%E3%81%BE%E3%81%9F%E3%81%AFreadmemd%E3%82%92%E4%BD%9C%E6%88%90)またはREADME.mdを登録すると、PyPIサイトのライブラリトップ画面に登録したREADMEが表示されます。
ユーザがググった結果PyPIサイトに誘導されることもあるので、この方法でREADMEを登録しておくと、ユーザに親切かと思います。

上のsetup.pyの例ではREADME.rstを登録していますが、同じ手順でREADME.mdを登録する事も可能です（例：[optunaのget_long_description()メソッド](https://github.com/optuna/optuna/blob/master/setup.py)）

### ※複数階層のパッケージを作成したい場合

ある程度大型のパッケージになると、複数階層でフォルダ分け、あるいは外部からの利用時も階層的にクラスや関数をインポートできるよう、整理したい場面も出てくるかと思います。

例えば、冒頭で例に挙げたフォルダ構成を、以下のように複数階層で整理するケースを考えます。

```console
./
├─ LICENSE
├─ README.md
├─ setup.py
└─ seaborn_analyzer/
    ├─ distribution/
    |    ├─ custom_hist_plot.py
    |    └─ custom_pair_plot.py
    ├─ supervised/
    |    ├─ cls/
    |    |    ├─ custom_class_plot.py
    |    |    └─ multiclass_fitparams.py
    |    └─ reg/
    |         └─ custom_reg_plot.py
    └─ __init__.py
```

このとき、サブフォルダ（`distribution`や`supervised`フォルダ、あるいはそのさらに内部の`cls`、`reg`フォルダ）内のモジュールも全てパッケージに含めたい場合、以下のようにサブフォルダを`setup()`メソッドの`packages`引数に含めてあげる必要があります。

```python:setup.py
 略
 :
PACKAGES = [
    'seaborn_analyzer'
    'seaborn_analyzer.distribution'
    'seaborn_analyzer.supervised'
]
 :
 略
 :
setup(name=NAME,
      :
      略
      :
      packages=PACKAGES,
      classifiers=CLASSIFIERS
    )
```

上記のように手動で記載する以外にも、`setuptools.find_packages()`メソッドを用いることでサブフォルダを自動で探索する方法もあります。この方法では、以下のようにパッケージに含めたいサブフォルダに空の`__init__.py`ファイルを作成した上で、

```console
./
├─ LICENSE
├─ README.md
├─ setup.py
└─ seaborn_analyzer/
    ├─ distribution/
    |    ├─ __init__.py ... 空のファイルとして作成
    |    ├─ custom_hist_plot.py
    |    └─ custom_pair_plot.py
    ├─ supervised/
    |    ├─ __init__.py ... 空のファイルとして作成
    |    ├─ cls/
    |    |    ├─ __init__.py ... 空のファイルとして作成
    |    |    ├─ custom_class_plot.py
    |    |    └─ multiclass_fitparams.py
    |    └─ reg/
    |         ├─ __init__.py ... 空のファイルとして作成
    |         └─ custom_reg_plot.py
    └─ __init__.py
```

以下のように`find_packages()`メソッドを適用します

```python:setup.py
from setuptools import setup, find_packages
 :
 略
 :
setup(name=NAME,
      :
      略
      :
      packages=find_packages()
    )
```

これにより`__init__.py`が含まれるサブフォルダを探索して自動でパッケージに含めてくれます。

このとき、本記事の手順を全て完了させてpip経由でインストールしたパッケージのインポートも、階層的に行います。
例えば上記フォルダ構成の`custom_class_plot.py`内の`class_proba_plot()`メソッドを使用する場合、

```python
from seaborn_analyzer.supervised.cls.custom_class_plot import class_proba_plot
```

のようにインポートすることができます。このように階層が深くなるとインポートの記述が長くなるため、これを避けるために上の階層の`__init__.py`を修正し、途中階層を省略したインポートを実現することもできます。
例えば、`supervised`フォルダ内の`__init__.py`に以下のような記述を追加することで、

```python
from .cls.custom_class_plot import class_proba_plot

__all__ = [
    "class_proba_plot",
]
```

以下のように途中の階層を省略したインポートが可能となります。

```python
from seaborn_analyzer.supervised import class_proba_plot
```

## 5. Twineを使ってPyPIに登録
ここでついにPyPIにライブラリを登録し、全世界に公開します。
PyPIにライブラリを登録するためのツールはいくつかありますが、今回はTwineを使います（[セキュリティ面でメリットがあるそうです](https://blog.amedama.jp/entry/2017/12/31/175036)）

### ・TwineとWheelのインストール
ソースコードのビルドに必要なツールである、TwineとWheelをインストールします

```
pip install twine
pip install wheel
```

### ・ライブラリのビルド
PyPIへの公開に必要なソースコード配布物およびWheelパッケージをビルドします。

#### ソースコード配布物のビルド
ライブラリのルートフォルダ（setup.pyがあるフォルダ）で以下のコマンドを実施し、ソースコード配布物をビルドします

```
python setup.py sdist
```
以下のように、「dist」フォルダおよび「パッケージ名.egg-info」フォルダができれば成功です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9ff97bf8-a479-b6b4-1b7b-eb3629fb7af7.png)

#### Wheelパッケージのビルド
ライブラリのルートフォルダ（setup.pyがあるフォルダ）で以下のコマンドを実施し、Wheelパッケージをビルドします

```
 python setup.py bdist_wheel
```
以下のように、「build」フォルダができれば成功です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5fa72939-c3fd-53f6-b0e5-628ecd200738.png)

### ・PyPIへのユーザ登録
PyPIへのライブラリアップロードには、ユーザ登録が必要となります。

#### テスト用PyPIと本番用PyPI
ここで注意すべきが、**PyPIにはテスト用と本番用のサイトが存在**することです。

PyPIは**一度アップロードすると同じバージョンで再アップロードできない**（再アップロードにはバージョンを上げる必要がある）ので、本番用PyPIでミスをして無駄にバージョンが上がることを防ぐため、**事前にテスト用PyPIで問題なくアップロードできることを確認**することが望ましいです。

#### テスト用PyPIのユーザ登録
以下のサイトに名前、メールアドレス、ユーザ名、パスワードを入力し、「Create acount」を押して届いたメールの指示に従うと、登録が完了します

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

#### 本番用PyPIのユーザ登録
以下のサイトに名前、メールアドレス、ユーザ名、パスワードを入力し、「Create acount」を押して届いたメールの指示に従うと、登録が完了します

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

#### 2段階認証の有効化

　2024/1より、PyPIの2段階認証が必須化されました。これに伴い、2段階認証を有効化しないとパッケージのアップロードができなくなりました。テスト用PyPI、本番用PyPI共に、以下操作で2段階認証を有効化してください

 2段階認証はPyPIにログイン後、以下の"アカウント設定"タブより有効化できます（リカバリコードを事前にダウンロードしていなかった場合、ダウンロードしてくださいとのメッセージが出てくるので、ダウンロードしたのちにコードの1つを入力欄に記載することでこの画面に進めます）
 
![スクリーンショット 2024-01-27 19.08.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/e7139d3d-09f4-eb79-bbef-627e955c713e.png)

　2段階認証の認証方法にはスマホ等のAuthenticatorアプリとUSBキーの2種類がありますが、今回はAuthenticatorアプリを使用します。上記「Add 2FA with authentication application」をクリックして表示されるQRコードを、[Google Authenticatorアプリ](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=ja)の`Scan a QR code`ボタンから読み込んで追加します。
　するとGoogle AuthenticatorにPyPIのワンタイムパスワードが表示されるようになるので、これをPyPIのサイトの「認証コード」欄に入力します（60秒ごとに更新されるのでご注意ください）

#### APIトークンの追加

　2024/1より、PyPIのユーザー/パスワード認証によるパッケージアップロードが無効になりました。これに伴い、APIトークンを追加して.pypircに記載しないとパッケージのアップロードができなくなりました。テスト用PyPI、本番用PyPI共に、以下操作でAPIトークンを追加してください。

 2段階認証と同様、"アカウント設定"タブにある以下の"APIトークンの追加"をクリックします。

 ![スクリーンショット 2024-01-27 19.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/63c78e0b-ee48-c99f-7b8f-bb2e41943f3e.png)

　トークン名、スコープ（全パッケージを対象とするか、特定のパッケージのみを対象とするか）を指定します。

 ![スクリーンショット 2024-01-27 19.24.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/a79533b4-0f6e-399b-53e0-b6918bac7ebb.png)

　"Create token"をクリックするとトークンが表示されます。**一度しか表示されないので必ずどこかにコピーを控えてください**（ただし忘れてしまっても再生成すればOKです）

### ・.pypircを作成
TwineによるPyPIアップロード時の設定ファイルである.pypircを作成します。
以下のように、セクションを分けてテスト用と本番用のPyPI登録情報を記載します

#### 2024年1月以降（APIトークン認証）

以下のように.pypircにAPIトークン記載してください（2023年以前は使用できたユーザ名/パスワードの記述は現在は使用できません）

```:.pypirc
[distutils]
index-servers =
  pypi
  testpypi

[pypi]
repository: https://upload.pypi.org/legacy/
username: __token__
password: 先ほど生成した本番用PyPIのAPIトークン

[testpypi]
repository: https://test.pypi.org/legacy/
username: __token__
password: 先ほど生成したテスト用PyPIのAPIトークン
```
.pypircの作成先ですがLinuxの場合はホームディレクトリに、
Windowsの場合はPowerShellで`$HOME`と打って出てきたフォルダに作成します。
`$HOME`が登録されていないときは、`$env:HOME = 'HOMEに登録したいフォルダ名'`と打つことで登録可能です（[参考](https://copdips.com/2019/07/troubleshooting-python-twine-cannot-upload-package-on-windows.html)）

#### 参考: パスワード認証の場合の.pypirc（2023年以前）

2023年以前は以下のユーザ名/パスワードの記述でもOKでしたが、**2024/1以降は使用できなくなりました**。

```:.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のパスワード
```

### ・テスト用PyPIへのライブラリアップロード
以下のコマンドで、テスト用PyPIへライブラリをアップロードします。

```
twine upload --repository testpypi dist/*
```
以下のようにテスト用PyPIにアップロードしたライブラリのURLが表示されれば成功です。
README.rst（またはREADME.md）がリンク先のトップ画面に表示されている事も確認しましょう。

```
View at:
https://test.pypi.org/project/seaborn-analyzer/0.1.3/
```

#### ※The description failed to render in the default format of reStructuredText.とエラーが出た時
README.rstをlong_descriptionに指定している場合、高確率でこのエラーが発生するかと思います。
これは、**README.rstのフォーマット違反**により、レンダリングが失敗することによって発生します。

厄介なのが、GitHubeで正常に表示できているように見えても、このエラーが発生しうることです。
PyPIで.rstファイルをレンダリングする際と同様のreadme-rendererが、pipで提供されているので、
これを使って原因箇所を特定するのが便利です。

```
pip install readme-renderer
```
でreadme-rendererをインストールし、

```
python -m readme_renderer README.rst -o README.html
```
でレンダリングを実行できます。
失敗した時は、以下のようにエラー箇所と内容が表示されます。

```
<string>:1: (WARNING/2) Title overline too short.

=======
seaborn-analyzer
=======
```
上の例では、タイトルの===がタイトル名に比べて短すぎることが原因だと分かります。
このような**エラーメッセージに応じてsetup.pyを修正し**、エラーが出なくなるまで繰り返す事で、正しいREADME.rstを作成することができます。
setup.py修正後は前述のソースコード配布物およびWheelパッケージのビルドもやり直しましょう。

また、**README.mdをlong_descriptionに指定しているとき、setup.pyに`description-content-type`引数を指定しないと**、同様のエラーが出るようです（[参考](https://pod.hatenablog.com/entry/2019/06/12/092708?utm_source=feed)）

上記以外にも、**当該ライブラリをローカルにpipでインストール済の場合、インストールされた古いバージョンのライブラリがアップロード対象となって失敗する**こともあるので、注意してください。この場合は古いバージョンのライブラリをアンインストールすればOKです。

### ・テスト用PyPIからpipでライブラリインストールを確認
以下のコマンドでテスト用PyPIからライブラリがインストールできることを確認します。
このタイミングでライブラリの動作確認も行う事が望ましいです。

```
pip install --index-url https://test.pypi.org/simple/ [パッケージ名]
```

動作確認が完了したら、下記コマンドでいったんライブラリをアンインストールします

```
pip uninstall [パッケージ名]
```

### ・本番用PyPIへのライブラリアップロード
テスト用PyPIで問題がないことを確認したら、以下のコマンドで、本番用PyPIへライブラリをアップロードします。

```
twine upload --repository pypi dist/*
```
以下のようにテスト用PyPIにアップロードしたライブラリのURLが表示されれば成功です。
README.rst（またはREADME.md）がリンク先のトップ画面に表示されている事も確認しましょう。

```
View at:
https://test.pypi.org/project/seaborn-analyzer/0.1.3/
```

### ・本番用PyPIからpipでインストール確認
以下のコマンドで、ライブラリがインストールできることを確認します。
ライブラリの簡単な動作確認も一緒に行いましょう

```
pip install [ライブラリ名]
```

### ・GitHubにバージョンタグをつける
以下の手順で、GitHubに＿init＿.pyと同じバージョンのタグをつけます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/7cd846c6-f003-8322-baa6-1ad5323ff65a.png)
下記赤枠の内容を記載し、「Publish release」を押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/95d27897-28f9-5a2e-e492-f47fd60c4811.png)
これでライブラリの公開自体は完了です。
以降の作業で、ドキュメントを充実させていきます。

## 6. SphinxでAPIドキュメントを作成
ある程度Pythonを使っている人であれば、公式のAPIドキュメントに助けられた経験が何度もあるかと思います。
そして使おうと思ったライブラリにまともなAPIドキュメントがなかったときの絶望感も‥

というわけで、せっかく作ったライブラリを使ってくれるユーザに同じ絶望感を味合わせないよう、**APIドキュメントを整備しましょう**。
Pythonではコード内で説明コメント（Docstring）を適切に記載しておけば、**自動でAPIドキュメントを作ってくれるSphinx**という素晴らしいライブラリがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/bb6b6234-bc41-e473-e949-b2f9a6b46852.png)
Sphinxに関しての詳細は、以下のページが分かりやすいです

https://qiita.com/pashango2/items/d1b379b699af85b529ce

### ・Docstringでメソッドやクラスの仕様を記載
docstringとは、Pythonのメソッドやクラスに対する説明文を記載するためのコメントのフォーマットで。
決められたフォーマットで記載することで、SphinxによりAPIドキュメントとして出力する事ができます。

具体的にはreStructuredText、Google style、Numpy styleの**3種類のフォーマット**があります。
詳しくは以下のサイトを参照ください

https://qiita.com/yokoc1322/items/ebf25c9cb779ff5ebc9c

私はNumpy Styleが好きなので、外部から呼び出すメソッド（先頭にアンダースコアのないメソッド）全てに対して、以下のようにNumpy StyleでDocstringを記載しました

```python:hist.fit_distメソッドのdocstring記載例
    def fit_dist(cls, data: pd.DataFrame, x: str=None, hue=None, dist='norm', ax=None, binwidth=None, bins='auto', norm_hist=True,
                  floc=None, sigmarange=4, linecolor='red', linesplit=200, hist_kws={}):
        """
        `ここにメソッドの処理概要を記載`
        Fit distributions by maximum likelihood estimation, and calculate fitting scores.

        `ここに引数一覧を記載`
        Parameters
        ----------
        data : pd.DataFrame, pd.Series, or pd.ndarray
            Input data structure. Either a long-form collection of vectors that can be assigned to named variables or a wide-form dataset that will be internally reshaped.
        x : str, optional
            Variables that specify positions on the x. Available only if data is pd.DataFrame.
        hue : str, pd.Series, or pd.ndarray, optional
            Semantic variable that is mapped to determine the color of plot elements. If data is pd.DataFrame, the argument must be key in data.
        dist : {'norm', 'lognorm', 'gamma', 't', 'expon', 'uniform', 'chi2', 'weibull'} or list, optional
            Type of fitting distribution or list of distrbutions.
        ax : matplotlib.axes.Axes, optional
            Pre-existing axes for the plot. Otherwise, call matplotlib.pyplot.gca() internally.
        binwidth : float, optional
            Width of each bin, overrides bins.
        bins : int, optional
            Generic bin parameter that can be the name of a reference rule, the number of bins, or the breaks of the bins. Passed to numpy.histogram_bin_edges().
        norm_hist : bool, optional
            If True, the histogram height shows a density rather than a count.
        floc : float, optional
            Hold location parameter fixed to specified value. If None, location parameter is fitted by maximum likelihood estimation except when dist is 'weibull' or expon'. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rv_continuous.fit.html#scipy.stats.rv_continuous.fit
        sigmarange : float, optional
            Set the x-axis view limits. The lower limit is -sigmarange * std(data) + mean(data). The higher limit is sigmarange * std(data) + mean(data).
        linecolor : str or List[str], optional
            Color of fitting line or colors of fitting lines. See https://matplotlib.org/stable/gallery/color/named_colors.html
        linesplit : int, optional
            Number of fitting line divisions.
        hist_kws : dict, optional
            Additional parameters passed to seaborn.histplot() other than the above arguments.
        
        `ここに戻り値一覧を記載`
        Returns
        ----------
        all_params : dict
            Parameters estimated by maximum likelihood estimation.
        all_scores : dict
            Fitting scores, which consist of RSS, AIC, and BIC.
        """
```


### ・Sphinx作業用フォルダを作成
Git管理対象のルートフォルダに、以下の2フォルダを作成します

|フォルダ名|用途|
|---|---|
|docs_src|Sphinxのプロジェクト用フォルダ（.rstファイルやconf.pyなどのソースを保持）|
|docs|作成したAPIドキュメントのhtmlファイル格納用（あとでgithub.ioで公開）|

### ・Sphinxのインストール
以下のコマンドでSphinxをインストールします。

```
pip install sphinx
```
Sphinxのページデザインはデフォルトだと味気ないので、人気のデザインテーマ「Read The Docs」も以下コマンドでインストールします

```
pip install sphinx_rtd_theme
```

### ・Sphinxプロジェクトを作成
以下コマンドでSphinxプロジェクト（Sphinxで作業をするためのディレクトリと設定ファイル）を作成します。

```
sphinx-quickstart docs_src
```
※docs_srcの部分は作業ディレクトリ名に合わせて変えてOKです

以下の質問が表示されたら、そのままEnterします

```
> ソースディレクトリとビルドディレクトリを分ける（y / n） [n]: 
```

以下の質問でにライブラリの情報を記載します

```
> プロジェクト名: [ライブラリ名を記載（例：seaborn_analyzer）]
> 著者名（複数可）: [著者名を記載]
> プロジェクトのリリース []: 0.1.3
```

以下の質問が表示されたら、そのままEnterします

```
> プロジェクトの言語 [en]: 
```
docs_srcというフォルダが作成され、その中にconf.py（Sphinxでのビルド時の設定ファイル）やindex.rst（ドキュメントのトップページのベースとなるrstファイル）が生成されれば成功です。

### ・sphinx-apidocでAPIドキュメントのrstファイルを作成
sphinx-apidocというSphinxのコマンドを使うことで、PythonのAPIドキュメントのベースとなるrstファイルを自動作成します。

```
sphinx-apidoc -o [出力先フォルダ] [仕様書対象コードのあるフォルダ]
```

```:実行例
sphinx-apidoc -f -o ./docs_src ./seaborn_analyzer
```
※-fオプションは、元々出力先のドキュメントが存在するときも、強制的に上書きする事を意味します

このコマンドにより、新たに対象ライブラリのモジュール構成が記載されたrstファイル（上のコマンドでの例：seaborn_analyzer.rst）が生成されます。

### ・conf.pyを編集してAPIドキュメントの自動作成を可能とする
conf.pyのうち、以下の部分を書き換えてDocstringによるAPIドキュメントのビルド設定を

#### プロジェクトのルートフォルダを指定
下記部分のコメントアウトを解除してパスを書き換えます

```python:conf.pyの一部
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
```

#### バージョンを自動読込できるよう修正
`release='*.*.*'`となっている部分を以下のように書き換えます

```python:conf.pyの一部
import seaborn_analyzer
release = seaborn_analyzer.__version__
```

#### docstringを自動読込できる拡張機能を追加
以下を追記します

```python:conf.pyの一部
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'
]
```
sphinx.ext.autodocはdocstringの自動読込、
sphinx.ext.napoleonはNumpyスタイルおよびGoogleスタイルのdocstringをパースするための拡張機能です

#### デザインテーマをRead The Docsテーマに変更
Sphinxデフォルトのデザインテーマ'alabaster'は[味気なく感じる人が多い](https://sphinx-themes.org/sample-sites/default-alabaster/)かと思うので、他のテーマに変更することをお勧めします。

今回は定番の人気テーマ[Read The Docs](https://sphinx-themes.org/sample-sites/sphinx-rtd-theme/)を使用します。

事前にpip install sphinx_rtd_themeしておき、
conf.pyの`html_theme = 'alabaster'`となっている部分を以下のように書き換えます

```python:conf.pyの一部
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
```

### ・ビルドしてhtmlファイルを生成
docs_srcフォルダに存在するconf.py（ビルド設定）や.rstファイル（sphinx-apidocで生成済）に基づいてSphinxによるビルドを実行し、docsフォルダにAPIドキュメントのhtmlファイルを生成します。

```
sphinx-build [conf.py等のあるフォルダ] [出力先フォルダ]
```

```:実行例
sphinx-build ./docs_src ./docs
```
なお出力先フォルダ名は次節で**GitHub Pagesに仕様書をアップロードしたい場合、「docs」という名前にする必要**があります。

ビルドによりSphinxが自動でDocstringをパースし、いい感じにドキュメントのWebページを生成してくれます。

**アンダースコアのついたメソッド、プロパティは自動で無視される**など、かなり芸が細かい解釈をしてくれます。

また、生成したドキュメントのスタイルシートやjavascriptファイルは、_staticフォルダ内に格納されます。

デザインをいじりたいときは、基本的にsphinx-apidocで生成したrstファイルを変更して再ビルドすれば良いです。
好みのデザインは人により異なるので、色々と試してみると良いかと思います。

## 7. APIドキュメントをGithub Pagesにアップロード
Docstringで生成したAPIドキュメントですが、そのままだとローカルでしか閲覧できません。

インターネット上で全世界に公開したい場合は、GitHubが提供しているウェブホスティングサービスである**Github Pages（github.io）**を使用すると便利です。

### ・.nojekyllを作成
Github Pagesでは、Jekyllの動作によりうまくスタイルシートを読み込んでくれないので、
ビルドしたhtmlファイルのあるフォルダ（上の例ではdocsフォルダ）に.nojekyllを作成します。
例えばLinuxでは、以下のコマンド

```
touch .nojekyll .docs
```

Windowsでは、VScodeなどのエディタやエクスプローラで右クリックし、空のファイルを作成すればよいかと思います。

### ・APIドキュメントをGitHubにPush
恐らくSphinxによるAPIドキュメントのビルド作業はローカルで行ったかと思うので、生成したファイル一式をGitHub上にPushします。

_staticフォルダ内のCSSやJavaScriptファイルも含め、docsフォルダ内のファイルは全てPushしてGitHub上にアップロードしましょう。

### ・GitHub PagesにAPIドキュメントをアップロード
以下の手順で、docsフォルダ内のWebページをGitHub Pagesに登録します。
反映までに数分かかることもあるので、気長に待ちましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/757af31e-da44-19ce-44d5-87f28cf583ad.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9aecea38-7f24-0cf7-3ecf-0d5af676bdcc.png)
うまくいけば、下のリンクのようなドキュメントのWebページが公開されるはずです

https://c60evaporator.github.io/seaborn-analyzer/seaborn_analyzer.html

自動で体裁が整ったドキュメントが生成するので、なかなか感動的です

# おわりに
以上で、pipへの自作ライブラリアップロードは完了となります。

有名どころのライブラリでも意外とドキュメントが整備されていなかったりするので、
ここまでやればシリコンバレーのつよつよエンジニアが見ても

**「個人開発にしては真面目にやってるな」**

と思ってもらえるのではと思います。
（外面を整えてもライブラリの内容自体がショボいと悲しいですが‥笑）