PoetryとGitとDockerを使ったPython開発フロー

# はじめに
この記事は以前書いた[pipenvとGitとDockerを使ったPython開発フロー](https://qiita.com/Aruneko/items/796d7eeb61e1f36ae4a0)の姉妹記事です。Pipenvの開発があまり活発ではなくなった現在、Pythonを使ったプロジェクトではPoetryを使う場面もぼちぼち出てくると思います。そこで改めてPoetryを使った開発フローについてまとめ直してみようと思います。

# 全体の流れ
1. 開発環境のインストール
2. プロジェクトの初期化
3. Docker化

# 前提環境
- Python 3.x
- UnixライクなOS

# 開発環境のインストール
## Poetryのインストール
たいていの場合お使いのOS付属のパッケージマネージャーからインストールすることができると思います。

### macOSの場合

```bash
$ brew install poetry
```

### Arch Linux

```bash
$ sudo pacman -S python-poetry
```

### Ubuntu 18.04
残念ながら標準リポジトリには登録されていないので、公式インストーラーからインストールします。

```bash
$ sudo apt install python3 python3-pip
$ curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3
```

インストールが完了したら `~/.poetry/bin` に PATH を通してください。お使いのシェルによると思いますが、`~/.zshrc` や `~/.bashrc` に以下のような設定を加えます。

```bash:~/.bashrc
export PATH=${HOME}/.poetry/bin:${PATH}
```

## その他ツールの導入
この先 Git と Docker / Docker Compose を使うので用意しておいてください。

# Python プロジェクトの初期化
ではいよいよ poetry を使って Python プロジェクトを初期化していきます。

## プロジェクトの作成
まずは適当なディレクトリを作ってその中に入っておきます。

```bash
$ mkdir sample-app
$ cd sample-app
```

次に `poetry init` を実行することでプロジェクトの初期化ができます。対話式になっているので、それぞれ答えていきましょう。途中依存パッケージをインストールするかどうかも聞かれますが、今回は個別にインストールしたいのでスキップしておきます。

```shell-session
$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [sample_app]:
Version [0.1.0]:
Description []:  Sample App
Author [Aruneko <webmaster@example.com>, n to skip]:
License []:  MIT
Compatible Python versions [^3.7]:

Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your development dependencies interactively? (yes/no) [yes] no
Generated file

[tool.poetry]
name = "sample_app"
version = "0.1.0"
description = "Sample App"
authors = ["Aruneko <aruneko99@gmail.com>"]
license = "MIT"

[tool.poetry.dependencies]
python = "^3.7"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"


Do you confirm generation? (yes/no) [yes]
```

対話環境を抜けると `pyproject.toml` ができあがっています。これでプロジェクトの初期化は完了です。

```bash
$  ls
pyproject.toml
```


## Poetryの 初期設定
通常は `~/Library/Caches/pypoetry/virtualenvs` や `~/.config/poetry` 以下に作成される Python 仮想環境が、以下の設定を追加しておくとプロジェクトルート直下に `.venv` ディレクトリを作って、そこで Python 環境を管理してくれるようになります。お好みで設定してください。

```bash
$ poetry config virtualenvs.in-project true --local
```

なお、この設定は `pyproject.toml` ではなく `poetry.toml` に記載されるので注意してください。

## パッケージの追加
Poetry 環境下では npm などと同じく `add` を使ってパッケージを追加します。サンプルとして FastAPI を使ったアプリケーションを開発してみましょう。

```bash
$ poetry add fastapi uvicorn
```

Poetry でパッケージをインストールするとその際に解決できた依存パッケージや、それぞれのパッケージのバージョン情報が `poetry.lock` に書き込まれます。

開発時にしか使わないパッケージはこれまた npm と同様に `-D` オプションを付けてインストールします。例えばコードフォーマッターである `black` をインストールしてみましょう。

```bash
$ poetry add -D black
```

ここまでのコマンドを実行すると、`pyproject.toml` の `tool.poetry.dependencies` セクションと `tool.poetry.dev-dependencies` セクションに今までインストールしたパッケージ群が追加されていることが確認できます。

```toml:pyproject.toml
[tool.poetry.dependencies]
python = "^3.7"
fastapi = "^0.52.0"
uvicorn = "^0.11.3"

[tool.poetry.dev-dependencies]
black = "^19.10b0"
```

今後は必要に応じたパッケージをその都度インストールしてください。

## Gitの初期化
続いてこのプロジェクトを Git で管理するために Git の初期化を行います。[gitignore.io](https://www.gitignore.io/) を利用して、まず `.gitignore` ファイルを作ってしまいましょう。今回は Python と virtualenv を指定して作ってもらいます。`.gitignore` に追加の設定が必要な方はここで編集しておいてください。

`.gitignore` ができたら手元の Git リポジトリを初期化して、ざっくり全ファイルを追加して commit しておきます。

```bash
$ curl -o .gitignore https://www.gitignore.io/api/python,virtualenv
$ git init
$ git add pyproject.toml poetry.lock poetry.toml .gitignore
$ git commit -m "Initial Commit"
```

## 仮想環境への入り方
Poetry で作成した Python 仮想環境に入るには `shell` サブコマンドを使います。仮想環境に入り忘れるとインストールしたはずのパッケージが見えないなどいろいろな不具合が起こりますので、よく確認するようにしましょう。なお、抜けるときは `exit` コマンドです。

```bash
$ poetry shell
(.venv)$ exit
```

# Docker 化
では一通り Poetry プロジェクトを作り終わったところでいよいよ Docker 化に取り組んでいきます。

## .dockerignore の準備
転送しなくて良いファイルは `.dockerignore` に記入するようにしましょう。特に `.venv` ファイルをプロジェクトルートに作るように設定している場合は必ず記述するようにします。その他にも `__pycache__` ディレクトリなどは転送しないようにしておくと良いでしょう。

```:.dockerignore
.venv/
__pycache__/
```

## Dockerfile の準備
ついに Dockerfile を書いていきます。今回は Multi Stage Build を使った方法を紹介します。前半で `requirements.txt` を作成、後半でそれを用いて Python アプリケーションの載った Docker Image を作成するという手順に分かれています。

そもそもなぜそうする必要があるのかというと、`poetry.lock` を読むためには Poetry をインストールする必要がある一方、アプリケーションが載るコンテナには Poetry を入れておく必要がないため、これらを切り離したいという意図があるからです。Poetry には `requirements.txt` を生成する機能があるため、これを活用していきます。

まず前半部分の説明から。Docker のキャッシュ戦略をうまく使うため、一番最初に Poetry をインストールしてしまいます。こうすることで、ビルドのたびに Poetry をインストールする手間が省けます。次に、`pyproject.toml` と `poetry.lock` だけをコピーして、`requirements.txt` を生成してしまいます。

続いて後半の説明です。できあがった `requirements.txt` を前半部分からコピーしてきた上で、`pip` コマンドを使ってそれらのパッケージを全てインストールします。もしこれらのファイルに変更がない場合はキャッシュが使われて自動で次のレイヤのビルドが行われるので、ビルドのたびに依存パッケージをダウンロードしてくるような動作を事前に防止することができます。これで圧倒的にビルド時間を節約できるのでオススメの構成です。インストールが終わった後に、各種 Python スクリプトを転送して、動かしたいコマンドなどを書いておけば完成です。

```Dockerfile
FROM python:3.8-slim as builder

WORKDIR /usr/src/app

RUN pip install poetry

COPY pyproject.toml poetry.lock ./

RUN poetry export -f requirements.txt > requirements.txt


FROM python:3.8-slim

ENV PYTHONUNBUFFERED=1

WORKDIR /usr/src/app

COPY --from=builder /usr/src/app/requirements.txt .

RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000
CMD [ "uvicorn", "main:app", "--host", "0.0.0.0" ]
```

ついでに Docker Compose の設定もしておくと良いでしょう。開発用にローカルファイルをマウントした上でオートリロードの設定などを入れておくとはかどるかもしれません。

```yaml:docker-compose.yml
version: '3'

services:
  app:
    build: .
    volumes:
      - ./:/usr/src/app
    ports:
      - "8000:8000"
    command: ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]
```

# FAQ
## パッケージの更新方法
`poetry update` が使えます。ただし `pyproject.toml` に記述したバージョンを超えてアップデートされることはないので、メジャーバージョンアップの際などは注意してください。

## パッケージの削除方法
`poetry remove PACKAGE_NAME` で削除できます。

## 途中から開発に参加した場合
`poetry install` で開発用依存パッケージごとインストールできます。

# おわりに
ここまで Poetry を使った Python プロジェクトの開発フローについて説明してきました。Poetry においても Pipenv と同様に `poetry.lock` を活用することによって開発者間でのバージョンの差異を取り除き、安定した開発環境を提供できるようになっていると思います。また、PEP で取り決められたフォーマットに従っているため、将来性も悪くないように感じられます。

一方で、Pipenv では実現できていたワンライナーを走らせる `script` 機能を欠いているなど、Python アプリケーションではなく **Python パッケージ開発者**のためのプロジェクト管理ツールであるという印象も受けます。Lint とかテストを実行するのに結構使うんですけどね、この機能。

ということで Poetry も Pipenv も一長一短な感じがあります。お使いのプロジェクトに合わせて選択していくのが現時点では無難なのかなと思うところです。それでは皆さん良い Python ライフをお送りください。