여태껏 의존성 관리, 패키징을 하기 위해 setuptools를 사용해왔는데 언제부턴가 poetry(pyproject.toml) 가 종종 보이더니 이젠 poetry가 대세인 것 같은 모양새가 되었다.
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 offers a lockfile to ensure repeatable installs, and can build your project for distribution.
Poetry의 사용법에 대해 간단하게 알아보자.
1. Installation
Official site: https://python-poetry.org/docs#installation
개발환경: Linux(Rocky Linux 8.6), Ubuntu 22.04
1.1 Install Poetry
Installed directory: ~/.local/share/pypoetry
$ curl -sSL https://install.python-poetry.org | python3 -
1.2 Add Poetry to your PATH
$ echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
1.3 Uninstall
$ curl -sSL https://install.python-poetry.org | python3 - --uninstall
2. Dependency Managing
2.1 Update Poetry
$ poetry --version
$ poetry self update
2.2 Enable tab completion for Bash, Fish, or Zsh
왜인지 poetry가 shell completion을 지원해준다.
$ poetry completions bash >> ~/.bash_completion
2.3 Project setup
2.3.1 Initializing
$ poetry new poetry-demo
poetry-demo
├── pyproject.toml
├── README.md
├── poetry_demo
│ └── __init__.py
└── tests
└── __init__.py
$ cd pre-existing-project
$ poetry init
2.3.2 Specifying dependencies
Dependency를 알려주는 pyproject.toml 의 기본구성은 다음과 같다.
각 항목에 대한 자세한 내용은 여기로.
[tool.poetry]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [{include = "poetry_demo"}]
[tool.poetry.dependencies]
python = "^3.10"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.dependencies] 에 package dependency를 추가할 수 있다.
이를 위해 2가지 방법을 소개하고 있는데,
- 직접
pyproject.toml수정
패키지명과 버전을 형식에 맞춰 적어준다.# pyproject.toml [tool.poetry.dependencies] python = "^3.10" numpy = "^1.24.2" -
poetry add
다음과 같은 명령어는 두 가지 작업을 수행한다.$ poetry add numpy- 최신 패키지 버전을 찾아 설치 혹은 업데이트
pyproject.toml에 해당 패키지의 버전을 추가
버전을 지정하는데 caret/tilde/wildcard/inequality/exact requirements 등을 사용할 수 있다.
이와 관련된 몇 가지 예시를 살펴보자. 자세한 내용은 여기로.
- Caret requirements
가장 왼쪽의 non-zero digit을 수정하지 않는 버전을 허용REQUIREMENT VERSIONS ALLOWED ^1.2.3 >=1.2.3 <2.0.0 ^1.2 >=1.2.0 <2.0.0 ^1 >=1.0.0 <2.0.0 ^0.2.3 >=0.2.3 <0.3.0 ^0.0.3 >=0.0.3 <0.0.4 ^0.0 >=0.0.0 <0.1.0 ^0 >=0.0.0 <1.0.0 -
Tilde requirements
major.minor.patch 혹은 major.minor 형식으로 지정한 경우, patch 변화만 허용
major 형식으로 지정한 경우, minor.patch 변화만 허용REQUIREMENT VERSIONS ALLOWED ~1.2.3 >=1.2.3 <1.3.0 ~1.2 >=1.2.0 <1.3.0 ~1 >=1.0.0 <2.0.0 -
Wildcard requirements
*를 사용한다.REQUIREMENT VERSIONS ALLOWED * >=0.0.0 1.* >=1.0.0 <2.0.0 1.2.* >=1.2.0 <1.3.0 - Inequality requirements
>= 1.2.0 > 1 < 2 != 1.2.3 - Multiple requirements
>= 1.2, < 1.5 - Exact requirements
==를 이용하나, 사실 생략해도 무방하다.1.2.3 ==1.2.3
2.3.3 Package collision
대부분의 경우, 한 번에 모든 패키지들을 설치하지 않고 개발이 진행됨에 따라 필요한 패키지들을 차례차례 설치하게 된다.
기존 패키지에 대한 의존성 체크뿐만 아니라, 호환되는 버전까지 알고 싶다면 @* 태그를 추가하면 된다.
$ poetry add numpy # 최신 버전 설치
$ poetry add numba # 최신 버전 설치 (호환성 고려 X)
$ poetry add numba@* # 호환성이 맞는 버전 설치
가령, numpy에 대한 의존성을 가지고 있는 numba는 numpy 버전이 맘에 들지 않으면 poetry add numba로 설치할 수 없다.
이와 관련된 몇 가지 예제들을 살펴보자.
poetry add numpy→poetry add numba
버전을 지정하지 않으면 가장 최신 버전을 설치한다.(tmp) root@DESKTOP-HOME:~# poetry add numpy Using version ^1.24.3 for numpy Updating dependencies Resolving dependencies... (0.1s) Writing lock file Package operations: 1 install, 0 updates, 0 removals • Installing numpy (1.24.3) (tmp) root@DESKTOP-HOME:~# poetry add numba Using version ^0.56.4 for numba Updating dependencies Resolving dependencies... (0.0s) Because no versions of numba match >0.56.4,<0.57.0 and numba (0.56.4) depends on numpy (>=1.18,<1.24), numba (>=0.56.4,<0.57.0) requires numpy (>=1.18,<1.24). So, because computer depends on both numpy (^1.24.3) and numba (^0.56.4), version solving failed.poetry add numpy→poetry add numba@*
@*태그는 호환성이 맞는 버전을 자동으로 찾아서 설치한다.(tmp) root@DESKTOP-HOME:~# poetry add numpy Using version ^1.24.3 for numpy Updating dependencies Resolving dependencies... (0.1s) Writing lock file Package operations: 1 install, 0 updates, 0 removals • Installing numpy (1.24.3) (tmp) root@DESKTOP-HOME:~# poetry add numba@* Updating dependencies Resolving dependencies... (0.2s) Writing lock file Package operations: 2 installs, 0 updates, 0 removals • Installing llvmlite (0.34.0) • Installing numba (0.51.2)# pyproject.toml ... [tool.poetry.dependencies] python = ">=3.8,<3.11" numpy = "^1.24.3" numba = "*" ...poetry add numpy→poetry add numba@* --dry-run→poetry add numba=0.51.2
위의 방법에서 의존성 정보는poetry.lock에 저장되어 있기 때문에 큰 문제가 없지만,pyproject.toml에 패키지 버전이 나오지 않아 맘에 들지 않았다.
호환성이 맞는 버전을 설치없이 확인만 하고 싶을 때,--dry-run옵션을 사용할 수 있다.(tmp) root@DESKTOP-HOME:~# poetry add numba@* --dry-run Updating dependencies Resolving dependencies... (0.1s) Package operations: 2 installs, 0 updates, 0 removals, 2 skipped • Installing llvmlite (0.34.0) • Installing numba (0.51.2) • Installing numpy (1.24.3): Skipped for the following reason: Already installed • Installing setuptools (67.7.1): Skipped for the following reason: Already installed (tmp) root@DESKTOP-HOME:~# poetry add numba=0.51.2 Updating dependencies Resolving dependencies... (0.1s) Writing lock file Package operations: 2 installs, 0 updates, 0 removals • Installing llvmlite (0.34.0) • Installing numba (0.51.2)- 그래도 문제가 발생하는 녀석들이 종종 있다.
결국, 많은 패키지 종속성을 가지고 있는 무거운 패키지부터 설치하는 것이 가장 좋다.(tmp) root@DESKTOP-HOME:~# python -c "import numba" /root/.pyenv/versions/tmp/lib/python3.8/site-packages/numba/core/types/__init__.py:108: FutureWarning: In the future `np.long` will be defined as the corresponding NumPy scalar. long_ = _make_signed(np.long) Traceback (most recent call last): File "<string>", line 1, in <module> File "/root/.pyenv/versions/tmp/lib/python3.8/site-packages/numba/__init__.py", line 16, in <module> from numba.core import types, errors File "/root/.pyenv/versions/tmp/lib/python3.8/site-packages/numba/core/types/__init__.py", line 108, in <module> long_ = _make_signed(np.long) File "/root/.pyenv/versions/tmp/lib/python3.8/site-packages/numpy/__init__.py", line 320, in __getattr__ raise AttributeError("module {!r} has no attribute " AttributeError: module 'numpy' has no attribute 'long'(tmp) root@DESKTOP-HOME:~# poetry add numba Using version ^0.56.4 for numba Updating dependencies Resolving dependencies... (0.2s) Writing lock file Package operations: 6 installs, 0 updates, 0 removals • Installing zipp (3.15.0) • Installing importlib-metadata (6.6.0) • Installing llvmlite (0.39.1) • Installing numpy (1.23.5) • Installing setuptools (67.7.1) • Installing numba (0.56.4) (tmp) root@DESKTOP-HOME:~# python -c "import numba"
2.3.4 Specifying dependencies using PIP
선호되는 방법은 아니지만, pip로 설치된 패키지들을 직접 추가할 수도 있다.
$ poetry add $(pip freeze)
2.4 Installing dependencies
$ poetry install
pyproject.toml 에 기록한 종속성들을 install 명령어로 설치할 수 있다.
이때, poetry.lock 이 존재하는지 여부에 따라 수행되는 작업이 달라진다.
poetry.lock이 없는 경우
pyproject.toml의 종속성들을 설치하고poetry.lock에 저장하여 프로젝트를 해당 버전에lock한다.You should commit the
poetry.lockfile to your project repo so that all people working on the project are locked to the same versions of dependencies.poetry.lock이 있는 경우
pyproject.toml의 종속성들을 설치하는데poetry.lock에 저장된 특정 버전으로 설치한다.
2.4.1 Commit poetry.lock to version control
프로젝트를 공유하는 모든 사람들이 동일한 버전의 종속성들을 사용할 수 있도록 poetry.lock 을 commit해야 한다.
2.5 Managing environments
종속성 관리와 함께 프로젝트 환경을 분리하는 것 역시 poetry의 핵심 기능이다.
가상환경이 저장되는 기본 디렉터리는 ~/.cache/pypoetry/virtualenvs 이나 프로젝트 내부에 가상환경을 저장하도록 바꿀 수 있다.
다음 명령어로 프로젝트 내부의 .venv 위치에 가상환경을 저장할 수 있게된다. (기본값은 null)
$ poetry config virtualenvs.in-project true
참고로 다음 명령어로 poetry와 관련된 설정들을 확인할 수 있다.
$ poetry config --list
- Generate environment
사용하고자 하는pythonpath를 이용하여 가상환경을 만들 수 있다.$ poetry env use $PYTHON_PATH - List and print info of environments
생성한 가상환경들과activated가상환경이 무엇인지 다음의 명령어를 통해 알 수 있다.$ poetry env list $ poetry env info $ poetry env info -p # Virtualenv.Path만 출력 - Run in environment
activated가상환경에서 프로그램을 실행시키기 위해poetry run을 사용한다.$ poetry run python -m pytest $ poetry run pip install numpy $ poetry run test.sh - Delete environment
가상환경을 제거하기 위해선 python interpreter path를 사용하는 것이 편하다.$ poetry env remove $(poetry env info -p)/bin/python # 현재 activated 가상환경을 제거 - Change shell
현재activated가상환경 기반의 shell로 넘어갈 수도 있다.$ poetry shell
2.5.1 Test code example
pyenv를 이용하여 여러 버전의 python interpreter를 설치하고 poetry로 가상환경을 생성한 후, pytest로 코드를 검증하는 shell script이다.
# test.sh
#!/bin/bash
for version in 3.8.16 3.9.16 3.10.10
do
pyenv install -s $version
poetry env use ~/.pyenv/versions/$version/bin/python
poetry install --no-root
poetry run python -m pytest
poetry env remove $(poetry env info -p)/bin/python
done
3. Script Managing
poetry는 실행 스크립트들을 관리할 수도 있다.
# computer/main.py
import argparse
def fn(args):
pass
def main():
parser = argparse.ArgumentParser()
parser.add_argument('value1', type=int, help='Value 1')
parser.add_argument('value2', type=int, help='Value 2')
parser.add_argument('--mode', type=str, help='Mode')
args = parser.parse_args()
fn(args)
if __name__ == '__main__':
main()
# pyproject.toml
...
[tool.poetry.scripts]
main = 'computer.main:main' # computer/main.py의 main()
$ poetry run main 1 2 --mode sum