🐍

Pythonパッケージ管理@Windows(2021年8月版)

2021/08/21に公開

最近 pipenv から poetry に乗り換えることにした。
その備忘録を兼ねて、なぜ乗り換えることにしたのかまとめておく。
※普段の開発は Windows10 で行うが、運用環境は AWS Lambda などのコンテナ環境を前提とする。
※ 前提とする環境は Python 3.9.6(x64)/3.8.8(x64), pip 21.2.4, pipenv 2021.5.29, poetry 1.1.8
※ 複数 Python をインストールして、 py.exe を使ってプロジェクトごとにバージョンは切り替えている。どのバージョンの python.exe も PATH は通してない。

パッケージ管理の歴史などはこちらを参考に。

pipenv の不満点

最新だといくつか解決してるっぽいが、解決できてないものもある。

  • pipenv 環境下でコマンド履歴が使えない。
    • 地味にストレス。
    • .venv\Scripts\activate で venv 環境を有効化すれば履歴が使える。
    • SET PIPENV_VERBOSITY=-1 がないと毎回警告が出る。
  • docker イメージビルド時に pipenv sync --system が使えない。 → v2020.11.4 で修正されたようだ
  • pipenv install mysqlclient==1.4.6 で失敗する。
    • wheel を探してきてインストールすれば解決可能だった。
    • pip 20.x あたりで解決したっぽい

よく言われる lock が遅いのはそこまで感じたことがない。
上の不満点を解決できるのが poetry だった。

pipenv → poetry による改善

  • py -3.9 -m poetry shell でコマンド履歴は使える。
  • poetry.lock をコピーしておけば poetry install --no-dev でバージョン固定でインストールできる。
  • wheel がないとインストールできないライブラリも問題なくインストール可能

poetry を使った開発の始め方

python はインストール済みという前提で書く。以下のコマンドを実行して python のバージョンが表示されれば問題ない。

py -3.9 --version
--> Python 3.9.6

ちなみにこれから記述する手順を全部手動で実行するのは面倒なので、最後にバッチファイルを用意しておいた。新しく入ったメンバーにはリポジトリクローン後、そのバッチを実行してもらえば開発環境は準備できる。

pip のアップグレード

まずは以下のコマンドで pip を最新版にアップグレードする。

py -3.9 -m pip install --upgrade pip

poetry のインストール

次に poetry をインストールする。

py -3.9 -m pip install poetry
py -3.9 -m poetry config virtualenvs.in-project true

2つ目のコマンドは同じディレクトリに仮想環境を作成するための設定、ほぼ必須。

poetry プロジェクト開始

poetry プロジェクトは以下のコマンドで開始する。

py -3.9 -m poetry init

これだけだと pyproject.toml が作成されるだけで仮想環境は作成されない。
shell コマンドや add コマンドでも仮想環境は作成されるが、手動で作成しておく。

py -3.9 -m venv .venv

仮想環境のアクティベートは

py -3.9 -m poetry shell

または

.venv\Scripts\activate

のどちらかで実行できる。

仮想環境へのライブラリインストール

仮想環境は pip のアップグレードも poetry のインストールもされていないのでインストールしておく。

pip install --upgrade pip
pip install poetry
poetry install

poetry install コマンドは pipenv といろいろと挙動が異なる。

  • 開発用ライブラリもインストールする。 pipenv sync --dev と同じ動き。
    • 開発用ライブラリをインストールしたくない場合は poetry install --no-dev とする。
  • poetry.lock があった場合、そちらを優先してインストールする。なければ pyproject.toml を使用してインストールする。
    • pipenv installPipfile を使い、 pipenv syncPipfile.lock を使うのとは異なる。

おまけ

python 開発時にインストールしておいたほうがいいだろうライブラリもインストールしておく。 poetry add -D とすると開発用としてインストールされる。

poetry add -D pytest flake8 black mypy

その他

その他 Python に役立つかもしれないメモ。

py.exe

いわゆる pyenv の Windows 版。インストーラを使って Python をインストールしたら使えるはず。インストーラでは launcher と表示される。

py -3.9 -m pip --version
py -3.9 -m venv .venv

のようにバージョン指定しつつ -m で通常の Python コマンドを続ける。

venv の作成場所変更

  • pipenv の場合は SET PIPENV_VENV_IN_PROJECT=true
  • Poetry の場合は poetry config virtualenvs.in-project true

pipenv の開始手順

SET PIPENV_VENV_IN_PROJECT=true
py -3.9 -m pipenv --python 3.9
py -3.9 -m pipenv shell
python -m pip install --upgrade pip
pip install pipenv

poetry の開始手順

py -3.9 -m poetry config virtualenvs.in-project true
py -3.9 -m poetry init
py -3.9 -m poetry shell
python -m pip install --upgrade pip
pip install poetry

requirements.txt 出力

pip 向けに requirements.txt を生成するコマンドは以下の通り。

poetry export -f requirements.txt --output requirements.txt

反対に requirements.txt からインストールする方法はないっぽい。

仮想環境を作成しない

Docker イメージなんかで使うかもしれない。

poetry config virtualenvs.create false

poetry 環境構築バッチ

毎回こういう手順を行うのも面倒なのでバッチを作成した。

@echo off
@setlocal enabledelayedexpansion
SET CURRENT=%~dp0
SET PYTHON_BASE_DIR=%CURRENT%
CD /d %PYTHON_BASE_DIR%
SET VENV_DIR=%PYTHON_BASE_DIR%\.venv
SET VENV_BAT=%VENV_DIR%\Scripts\activate
SET PYTHON_VER=3.9
SET PIP_VER=21.2.4
SET POETRY_VER=1.1.8


REM set variable for ColorText, CALL :ColorText %GREEN% "message"
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
  set "DEL=%%a"
)
SET GREEN=0a
SET RED=0C
SET BLUE=09


:CLEAN_VENV
IF "%1" equ "clean" (
  CALL :ColorText %BLUE% "Clean venv."
  IF EXIST "%VENV_DIR%" RMDIR /S /Q "%VENV_DIR%"
  SHIFT
)


:SETUP_PYTHON
IF NOT EXIST %VENV_DIR%\* (
  CALL :CHECK_SYSTEM_PYTHON
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
  CALL :CHECK_SYSTEM_PIP
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
  CALL :CHECK_SYSTEM_POETRY
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
  CALL :ACTIVATE_VENV
  CALL :CHECK_VENV_PIP
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
  CALL :CHECK_VENV_POETRY
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
  CALL :CHECK_SYNC
  IF NOT !ERRORLEVEL! equ 0 GOTO :END
)
cmd /k %VENV_BAT%
EXIT

:END
cmd /k
EXIT


:CHECK_SYSTEM_PYTHON
CALL :CHECK_BIN "Python" py %PYTHON_VER%
EXIT /b %ERRORLEVEL%


:CHECK_SYSTEM_PIP
CALL :CHECK_PYSCRIPT "pip" pip %PIP_VER% "pip install --upgrade pip"
EXIT /b %ERRORLEVEL%


:CHECK_SYSTEM_POETRY
CALL :CHECK_PYSCRIPT "poetry" poetry %POETRY_VER% "pip install poetry"
CALL :ColorText %BLUE% "Configuring poetry."
py -%PYTHON_VER% -m poetry config virtualenvs.in-project true
CALL :ColorText %GREEN% "  Done."
EXIT /b %ERRORLEVEL%


:CHECK_PYSCRIPT
CALL :ColorText %BLUE% "Checking System %~1 version..."
py -%PYTHON_VER% -m %2 --version | find /i "%3" > nul
IF %ERRORLEVEL% equ 0 (
  CALL :ColorText %GREEN% "  OK. %~1 is ready"
  EXIT /b 0
)
IF "%~4" equ "" (
  CALL :ColorText %RED% "  NG, Please install %~1 v%3"
  EXIT /b -1
) ELSE (
  CALL :ColorText %RED% "  NG, Now trying to install %~1 v%3"
  py -%PYTHON_VER% -m %~4
)
EXIT /b %ERRORLEVEL%


:CHECK_BIN
CALL :ColorText %BLUE% "Checking %~1 version..."
where %2 2> nul > nul
IF %ERRORLEVEL% equ 0 (
  IF "%2" equ "py" (
    py -%PYTHON_VER% --version | find /i "%3" > nul
  ) ELSE (
    %2 --version | find /i "%3" > nul
  )
  IF !ERRORLEVEL! equ 0 (
    CALL :ColorText %GREEN% "  OK. %~1 is ready"
    EXIT /b 0
  )
)
IF "%~4" equ "" (
  CALL :ColorText %RED% "  NG, Please install %~1 v%3"
  EXIT /b -1
) ELSE (
  CALL :ColorText %RED% "  NG, Now trying to install %~1 v%3"
  %~4
)
EXIT /b %ERRORLEVEL%


:ACTIVATE_VENV
IF NOT EXIST .venv (
  CALL :ColorText %BLUE% "Creating python venv."
  py -%PYTHON_VER% -m venv .venv
  CALL :ColorText %BLUE% "  Done."
)
CALL :ColorText %BLUE% "Activating Python venv."
CALL %VENV_BAT%
CALL :ColorText %GREEN% "Python venv is activated."
EXIT /b %ERRORLEVEL%


:CHECK_VENV_PIP
CALL :CHECK_BIN "pip" pip %PIP_VER% "python -m pip install --upgrade pip"
EXIT /b %ERRORLEVEL%


:CHECK_VENV_POETRY
CALL :CHECK_BIN "poetry" poetry %POETRY_VER% "pip install poetry"
EXIT /b %ERRORLEVEL%


:CHECK_SYNC
CALL :ColorText %BLUE% "Synchronizing venv library."
IF EXIST poetry.lock (
  poetry install
) ELSE (
  IF EXIST pyproject.toml (
    poetry install
  ) ELSE (
    poetry init
    CALL :ColorText %BLUE% "Install libraries for developing."
    poetry add -D pytest flake8 black mypy
  )
)
IF NOT %ERRORLEVEL% equ 0 (
  CALL :ColorText %RED% "  Sync has error!!"
) ELSE (
  CALL :ColorText %GREEN% "  Done."
)
EXIT /b %ERRORLEVEL%


:ColorText
echo off
<nul set /p ".=%DEL%" > "%~2"
findstr /v /a:%1 /R "^$" "%~2" nul
del "%~2" > nul 2>&1
ECHO.
EXIT /b 0

Discussion