Poetryを使ってPythonパッケージ(CLI)作成

# 概要
Python用のパッケージ管理ツールPoetryを使って郵便番号から住所を取得するPythonパッケージを作成しコマンドラインから実行できるようにしてみる。

## 作業ディレクトリ作成
```bash
mkdir -p /tmp/$(date +%Y%m%d) && cd $_
pwd # /tmp/$(date +%Y%m%d)
```

## 開発環境構築
```bash
python3 -m venv venv
source venv/bin/activate
```

## poetry インストール
pipのバージョンが低いとコケるのでpipをアップデートしてからインストールを行う

```bash
pip install --upgrade pip
pip install poetry
```

## 新しいPythonプロジェクト zip_code_app の作成
```
poetry new --src zip_code_app
```

### プロジェクト作成後のディレクトリ構造
```
/tmp/$(date +%Y%m%d)/zip_code_app
├── README.rst
├── pyproject.toml
├── src
│   └── zip_code_app
│       └── __init__.py
└── tests
    ├── __init__.py
    └── test_zip_code_app.py
```

## requests モジュールインストール
住所検索APIに問い合わせを行うために requests をインストール

```
cd /tmp/$(date +%Y%m%d)/zip_code_app
poetry add requests
```
> requests がインストールされ `pyproject.toml`ファイルに `requests` が追記される。
>
```diff:pyproject.toml
 [tool.poetry.dependencies]
 python = "^3.7"
+requests = "^2.25.1"
>
 [tool.poetry.dev-dependencies]
 pytest = "^5.2"
```

## 郵便番号から住所を取得するプログラムを作成

```
vi /tmp/$(date +%Y%m%d)/zip_code_app/src/zip_code_app/core.py
```

```python:core.py
import json
import requests
import sys

URL = 'http://zipcloud.ibsnet.co.jp/api/search?zipcode={}'

def zip_api(zip_code):
    r = requests.get(URL.format(zip_code))
    return r.json()

def main():
    zip_code = sys.argv[1]
    zip_json = zip_api(zip_code)
    print(json.dumps(zip_json, ensure_ascii=False))

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

### core.py 作成後のディレクトリ構造
```
/tmp/20210710/zip_code_app
├── README.rst
├── poetry.lock
├── pyproject.toml
├── src
│   └── zip_code_app
│       ├── __init__.py
│       └── core.py ■ これが作成された
└── tests
    ├── __init__.py
    └── test_zip_code_app.py
```

### 実行してみる
```
poetry run python /tmp/$(date +%Y%m%d)/zip_code_app/src/zip_code_app/core.py 100-0001 | jq .
```
>
```json:実行結果
{
  "message": null,
  "results": [
    {
      "address1": "東京都",
      "address2": "千代田区",
      "address3": "千代田",
      "kana1": "ﾄｳｷｮｳﾄ",
      "kana2": "ﾁﾖﾀﾞｸ",
      "kana3": "ﾁﾖﾀﾞ",
      "prefcode": "13",
      "zipcode": "1000001"
    }
  ],
  "status": 200
}
```

## テストコード作成
```
vi /tmp/$(date +%Y%m%d)/zip_code_app/tests/test_zip_code_app.py
```

```python:test_zip_code_app.py
from zip_code_app import __version__
from zip_code_app import core

def test_version():
    assert __version__ == '0.1.0'

def test_zip_api():
    expect = {
        'message': None,
        'results': [{'address1': '東京都',
              'address2': '千代田区',
              'address3': '千代田',
              'kana1': 'ﾄｳｷｮｳﾄ',
              'kana2': 'ﾁﾖﾀﾞｸ',
              'kana3': 'ﾁﾖﾀﾞ',
              'prefcode': '13',
              'zipcode': '1000001'}],
        'status': 200
    }
    actual = core.zip_api('100-0001')
    assert expect == actual
```

### テスト実行

```
poetry install
poetry run pytest /tmp/$(date +%Y%m%d)/zip_code_app/tests/test_zip_code_app.py -vv
```
>```
======================================== test session starts ========================================
platform darwin -- Python 3.7.3, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- /private/tmp/$(date +%Y%m%d)/venv/bin/python3
cachedir: .pytest_cache
rootdir: /private/tmp/$(date +%Y%m%d)/zip_code_app
collected 2 items
>
tests/test_zip_code_app.py::test_version PASSED                                                 [ 50%]
tests/test_zip_code_app.py::test_zip_api PASSED                                                 [100%]
>
========================================= 2 passed in 0.37s =========================================
```

## スクリプトを登録

```
vi pyproject.toml
```

```toml:pyproject.toml
[tool.poetry.scripts]
zip-code = "zip_code_app.core:main"
```

## スクリプトを実行してみる
```
poetry run zip-code 100-0001 | jq .
```
>
```
{
  "message": null,
  "results": [
    {
      "address1": "東京都",
      "address2": "千代田区",
      "address3": "千代田",
      "kana1": "ﾄｳｷｮｳﾄ",
      "kana2": "ﾁﾖﾀﾞｸ",
      "kana3": "ﾁﾖﾀﾞ",
      "prefcode": "13",
      "zipcode": "1000001"
    }
  ],
  "status": 200
}
```

## パッケージをビルドする
```
poetry build
```

```
/tmp/20210710/zip_code_app
├── README.rst
|-- dist 
|   |-- zip_code_app-0.1.0-py3-none-any.whl ■ これが作成された
|   `-- zip_code_app-0.1.0.tar.gz ■ これが作成された
├── poetry.lock
├── pyproject.toml
├── src
│   └── zip_code_app
│       ├── __init__.py
│       └── core.py 
└── tests
    ├── __init__.py
    └── test_zip_code_app.py

```

### パッケージをインストール
#### ビルドしたパッケージからインストールする場合

```
pip install /tmp/20210710/zip_code_app/dist/zip_code_app-0.1.0-py3-none-any.whl
```

#### github で管理している場合のインストール
```
pip install git+https://github.com/mykysyk/zip-code.git
```


#### スクリプトを実行してみる
```
zip-code 100-0001 | jq .
```
>
```
{
  "message": null,
  "results": [
    {
      "address1": "東京都",
      "address2": "千代田区",
      "address3": "千代田",
      "kana1": "ﾄｳｷｮｳﾄ",
      "kana2": "ﾁﾖﾀﾞｸ",
      "kana3": "ﾁﾖﾀﾞ",
      "prefcode": "13",
      "zipcode": "1000001"
    }
  ],
  "status": 200
}
```