Poetry事始め

# Poetry事始め

ごく簡単なCLIアプリケーションの一連の作成手順を通して，Pythonプロジェクト管理ツールPoetryの基本的な使用方法をまとめる．
実行環境はMacOS/Ubuntuを想定．
Poetryのバージョンは`1.0.5`

# ゴール
以下のように動作する`greet`コマンドを作成する．
コマンドに必要なPythonパッケージを管理したり，ローカルの仮想環境にコマンドをインストールして使用できるようにしたりするためPoetryを使用する．  


```
$ greet hello John
Hello John!

$ greet goodnight John
Goodnight John...

$ greet goodnight --sleeping John
zzz
```

## 補足
Poetryを使用してローカル環境に直接パッケージをインストールするのは少し厄介．
簡単なので題材をCLIツールとしたが，これを普段使い用にPoetryでインストールするいい方法がわからなかった．
詳しくは本稿末 "Poetryの設定" の項目を参照してください．  


# Poetry


Pythonプロジェクトで使用するパッケージの依存関係やバージョンを管理するためのツール
(ここではパッケージを作成するために必要なディレクトリ構成とファイルをまとめてプロジェクトと呼ぶ)．  

pyproject.tomlファイルをもとにパッケージの情報を管理するので，pyproject.tomlファイルを共有すればプロジェクトに必要な環境を簡単に再現できる．pyproject.tomlファイルの雛形もPoetryが自動的に作成する．  


公式では  
> Poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.


# Poetryのインストール
予め[Pyenv](https://github.com/pyenv/pyenv)が使用可能であるとインストール時のトラブルが少なくて良い，

以降の説明ではPythonの実行を`python`コマンドによるものとする．  

MacOS/Linuxの場合，以下でPoetryをインストールする．  

```
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
```

MacOSの場合は`source ~/.bashrc`，Ubuntuの場合は`source ~/.profile`を実行して`poetry`コマンドのパスを通す．  
`poetry -h`を実行してきれいなヘルプが出力されればOK．  

`python3`コマンドでのPoetryのインストールは本稿末の "python3コマンドでのPoetryのインストール" の項目を参照してください．  


# 新規Pythonプロジェクトの作成
Poetryの`new`コマンドで新規Pythonプロジェクトを作成する．  
  
任意の場所で`poetry new greet`を実行し，新規Pythonプロジェクト`greet`を作成．
`greet`パッケージを作成するために必要なファイル一式が自動的に作成される．  

```
greet
├── greet
│   └── __init__.py
├── pyproject.toml
├── README.rst
└── tests
    ├── __init__.py
    └── test_greet.py
```

## pyproject.toml
後述する`poetry install`でプロジェクトからパッケージを作成する際に使用される．
コマンド実行時，よく見ているとPoetryが一時的にsetup.pyを作成しているのがわかる．  

[tool.poetry.dependencies], [tool.poetry.dev-dependencies]でプロジェクトで使用するパッケージのリストとそのバージョンが列挙されている．

依存するパッケージのインストール時に自動的にアップデートされる．  


# Pythonパッケージのインストール
`add`コマンドでプロジェクトで使用するPythonパッケージをインストールできる，  
デフォルトでは`virtualenv`でプロジェクト用の仮想環境が自動的に作成され，その中にインストールされる．  
  
`greet`ツールでは以下のPythonのパッケージを使用する．

- 動作に必須のパッケージ  
    - cleo
- 開発中のみ使用するパッケージ(dev用)
    - flake8
    - autopep8

  
`pyproject.toml`のある`greet`ディレクトリで以下を実行する．  

```
poetry add cleo
poetry add --dev flake8 autopep8
```

dev用は`add`コマンドに`--dev/-D`オプションをつけてインストールする．
これにより，`poetry install`でプロジェクトのパッケージ(ここでは`greet`パッケージのこと)をインストールする際，`--no-dev`オプションを指定して実行に必要のないパッケージのインストールをスキップできる．  

## 補足1

Ubuntuで`poetry add ...`を実行して次のようなエラーが出る場合は，エラーメッセージの通り`apt-get install python3-venv`を実行すると正常に動作するようになる．  

```
Creating virtualenv greet-b6grBBry-py3.6 in /root/.cache/pypoetry/virtualenvs
The virtual environment was not created successfully because ensurepip is not
available.  On Debian/Ubuntu systems, you need to install the python3-venv
package using the following command.

    apt-get install python3-venv

You may need to use sudo with that command.  After installing the python3-venv
package, recreate your virtual environment.

Failing command: ['/root/.cache/pypoetry/virtualenvs/greet-b6grBBry-py3.6/bin/python', '-Im', 'ensurepip', '--upgrade', '--default-pip']
```

## 補足2
VSCodeを使用している場合は，VSCodeで使用するPythonインタプリタにPoetryで作成された仮想環境のものを選択すること．
仮想環境のパスは`poetry env info`のPathで確認できるのでそれを指定． 
例えば以下の場合，`/root/.cache/pypoetry/virtualenvs/greet-b6grBBry-py3.6`を指定．

```
$ poetry env info

Virtualenv
Python:         3.6.9
Implementation: CPython
Path:           /root/.cache/pypoetry/virtualenvs/greet-b6grBBry-py3.6
Valid:          True

System
Platform: linux
OS:       posix
Python:   /usr
```

# greetツールの実装
pyproject.tomlのある`greet`ディレクトリで`poetry shell`を実行し，本プロジェクト用の仮想環境に入る．  
  
`greet/greet`ディレクトリに以下のapplication.pyを作成．  
[Cleo](https://github.com/sdispater/cleo)はPythonのCLI用パッケージであり，コマンドやオプションを簡単に追加できるほか，出力もきれいに配色されたものにできる．Poetryも使用している．  

```greet/greet/application.py
from cleo import Command, argument, option, Application


class HelloCommand(Command):

    name = 'hello'
    description = 'Say hello to someone'

    arguments = [argument('name', 'Name of a person to hello')]

    def handle(self):
        self.line(f'Hello {self.argument("name")}!')


class GoodnightCommand(Command):

    name = 'goodnight'
    description = 'Say goodnight to someone'

    arguments = [argument('name', 'Name of a person to goodnight')]
    options = [option('sleeping', 's', 'Sleeping...')]

    def handle(self):
        if self.option('s'):
            self.line('zzz')
        else:
            self.line(f'Goodnight {self.argument("name")}...')


application = Application()
application.add_commands(HelloCommand(), GoodnightCommand())


def main():
    application.run()

```

# pyproject.tomlファイルの編集

pyproject.tomlファイルに以下を追記．  

```greet/pyproject.toml
[tool.poetry.scripts]
greet = 'application:main'
```

`[tool.poetry.scripts]`はsetuptoolsのentry_pointsにあたるもので，`<command_name> = '<module_name>:<function_name>'`のように指定する．  
これにより，greetコマンドを実行したときapplication.pyのmain関数が実行される．  



# greetパッケージの作成・インストール

pyproject.tomlのある`greet`ディレクトリで`poetry install`を実行する．  

`poetry install --no-dev`とした場合，dev用パッケージはインストールされない． 
開発目的では`poetry install`を使用し，パッケージを使用するだけであれば`poetry install --no-dev`を使用する．  


# greetコマンドの動作確認

冒頭のゴールに示したように，仮想環境内で以下のように`greet`コマンドが実行できる．  

```
$ greet hello John
Hello John!

$ greet goodnight John
Goodnight John...

$ greet goodnight --sleeping John
zzz
```

`greet -h`を実行すると，Cleoによって配色されたきれいなヘルプが見れるはず．  

以上が本稿の趣旨．  

<hr>

# Appendix
## python3コマンドでのPoetryのインストール

`python`コマンドがない状態(`/usr/bin`に`python`がなく，`python3`のみの状態)を想定．  

`python3`コマンドを使用している場合は以下でインストールことができる．  

```
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3
```

必要であれば，`echo 'alias python="python3"' >> ~/.bashrc && source ~/.bashrc`を実行してPython3を`python`コマンドで実行可能にしてから`curl ... python`でPoetryをインストールできる．  


しかし，いずれの場合もインストール後にパスを通した後も以下のエラーでPoetryが実行できない．  

```
/usr/bin/env: ‘python’: No such file or directory
```

原因は，Poetryは実行される際インストールに使用されたインタプリタを使用するが， `~/.poetry/bin/poetry`の1行目に`#!/usr/bin/env python`がハードコーディングされており，`python3`でインストールした場合も`/usr/bin/python`を使用しようとするためで，これを`#!/usr/bin/env python3`に書き換えれば正常に使用できる．  
(issue: https://github.com/python-poetry/poetry/issues/1543)

`python`コマンドがない状態(`/usr/bin`に`python`がなく，`python3`のみの状態)の解決策として，`ln -s /usr/bin/python3 /usr/bin/python`でシンボリックリンクを作成してから`curl ... python`でPoetryをインストールしても問題なく動作する．  

ほかにも，`python`コマンドでPython2を，`python3`コマンドでPython3を実行している場合は注意が必要．  

Pyenvを使用し，3系に切り替えてPoetryをインストールするのが一番トラブルが少ないと思われる．  


## Poetryを使用した複数人での開発

pyproject.tomlファイルをリポジトリで共有することで，開発環境を揃えることができる．  
開発に新規参加する場合，リポジトリをcloneした後，pyproject.tomlファイルのあるディレクトリで`poetry install`を実行するだけで，このPythonプロジェクト用の仮想環境が切られ，その中に必要なパッケージがインストールされる．

## Poetryの設定
`poetry config --list`でPoetryの設定が列挙される．  

|        設定項目        | デフォルト値 |                                                                                                                          説明                                                                                                                          |
| :--------------------: | :----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|   virtualenvs.create   |     true     | `poetry add`や`poetry install`の実行時，仮想環境を使用するかどうか．falseにした場合直環境に作用するため注意．例えば，falseの状態で`poetry install --no-dev`を実行すると，pyproject.tomlに列挙されたdev用パッケージが直環境からアンインストールされる． |
| virtualenvs.in-project |    false     |                          `poetry virtualenvs.in-project true`でtrueに設定できる．仮想環境用のファイルがプロジェクト内に作成されるようになる．これにより，プロジェクトのディレクトリを削除したとき仮想環境も同時に削除できる．                          |

ローカル環境に直接パッケージをインストールするには`virtualenvs.create`をfalseにすれば良いが，`poetry install --no-dev`でパッケージをインストールすると，ローカル環境からdev用パッケージがアンインストールされてしまう．
一方，`poetry install`でパッケージをインストールするとdev用パッケージの不要なものまでインストールされてしまうので，ローカル環境全体に渡って使用するパッケージをPoetryでインストールするのは難しい．いい方法があったら教えてください．