여태껏 의존성 관리, 패키징을 하기 위해 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.lock
file 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
사용하고자 하는python
path를 이용하여 가상환경을 만들 수 있다.$ 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