Niedawno zadane zostało mi pytanie, dlaczego korzystam z Poetry
i w czym jest ono lepsze od pip
. W tym wpisie spróbuję odpowiedzieć na te pytania. Zanim przejdę dalej, warto nadmienić, czym w ogóle są oba narzędzia. Pip
1 to dobrze znany wszystkim programistom Pythona menadżer pakietów, Poetry
2 natomiast stanowi jego rozwinięcie, zapewniające dodatkowe funkcje i upraszczające sposób jego użytkowania.
pyproject.toml
Definiując zależności dla projektu, można podzielić je na dwie główne grupy, zależności potrzebne do działania programu i deweloperskie, takie jak np. test runnery, lintery itp. Nie ma sensu budować paczki z gotowym programem, zawierającej przykładowo framework pytest
, ponieważ dla użytkownika jest on kompletnie zbędny. W przypadku pip
, aby rozdzielić zależności można skorzystać z podejścia wykorzystującego dwa pliki requirements.txt
, w których odpowiednio będą zdefiniowane zależności deweloperskie i produkcyjne. Oczywiście oba pliki, ktoś będzie musiał utrzymywać i ręcznie aktualizować, co nie jest zbyt wygodne. W przypadku Poetry
sprawy mają się nieco inaczej, narzędzie to do przechowywania listy zależności wykorzystuje plik pyproject.toml
wprowadzony w PEP-5183, ponadto przechowywane są tam również inne informacje dotyczące konfiguracji projektu oraz jego budowania. Istotne jest to, że Poetry
aktualizuje wspomniany plik automatycznie po dodaniu do projektu nowych
zależności, co więcej mamy możliwość oznaczenia paczek jako deweloperskich, dzięki czemu znajdą się one na osobnej liście. A wszystko dzięki pojedynczej komendzie poetry add [--dev] <nazwa paczki>
. Przykładowy plik pyproject.toml
znajduje się poniżej.
[tool.poetry]
name = "django_app"
version = "0.1.0"
description = ""
authors = ["artur"]
[tool.poetry.dependencies]
python = "^3.10"
Django = "^4.0.5"
django-crispy-forms = "^1.14.0"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
pylint = "^2.14.4"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
rozwiązywanie zależności
Poetry automatycznie rozwiązuje poważny problem powtarzalnego budowania środowiska, zanim jednak przejdę do wyjaśnienia, w jaki sposób to robi, warto omówić sobie pewne kwestie związane z pip
. Korzystając z tego menadżera paczek, należy pamiętać, aby wykonać komendę pip freeze > requirements.txt
, po każdorazowym zainstalowaniu jakiejś paczki, jest to istotne, ponieważ dodając jakąś paczkę do naszego środowiska, dodajemy również jej zależności, które warto zapisać wraz z ich wersjami. Manualne uzupełnianie pliku requirements.txt
wprowadza ryzyko, że na różnych systemach mogą zostać zainstalowane inne wersje zależności instalowanych paczek (domyślnie pip instaluje najnowsze wersje paczek), co grozi konfliktami wersji, czy innymi błędami. Wracająć do Poetry
, jak już wspominałem, paczki wymagane przez nasz projekt zdefiniowane są w pliku pyproject.toml
, jednakże znajdują się tam tylko główne wymagane moduły. Poetry
wprowadza do projektu jeszcze jeden plik nazwany poetry.lock
, którego celem jest przechowywanie informacji o wszystkich zależnościach projektu, wraz ich wersjami oraz co istotne sumami kontrolnymi, do których wrócę w dalszej części wpisu. Podsumowując, wywołując poetry install
, mamy pewność, że zainstalowane zostaną paczki o konkretnej wersji.
Załóżmy teraz, że chcemy zaktualizować jakąś paczkę menadżerem pip
, jawnie instalujemy jej nową wersję, która okazuje się być niekompatybilna z inną wcześniej zainstalowaną paczką. Co w tym momencie zrobi pip
? Odpowiedź jest prosta, zainstaluje paczkę ! Na potrzeby poniższego przykładu zmieniłem wersję Django
z 4.0.5 na wersję 1.8, mając zainstalowany Dajngo Rest Framework
(3.13.1), przykład ten doskonale oddaje zachowanie podstawowego menadżera paczek pip
.
pip install django==1.8
Collecting django==1.8
Using cached Django-1.8-py2.py3-none-any.whl (6.2 MB)
Installing collected packages: django
Attempting uninstall: django
Found existing installation: Django 4.0.5
Uninstalling Django-4.0.5:
Successfully uninstalled Django-4.0.5
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
djangorestframework 3.13.1 requires django>=2.2, but you have django 1.8 which is incompatible.
Successfully installed django-1.8
W przypadku Poetry
opisana sytuacja nie może zaistnieć. W przypadku jakiejkolwiek niekompatybilności instalacja paczki zostanie zatrzymana, a w terminalu pojawi się stosowny komunikat, jak na przykładzie poniżej. Co więcej, Poetry
pozwala na przetestowanie operacji podczas wykonywania których rozwiązywane są zależności (np. update
, czy add
) poprzez podanie argumentu --dry-run
. Dzięki temu argumentowi narzędzie nie wykona żadnych faktycznych operacji, a jedynie wyświetli jakie kroki muszą zostać podjęte do ich wykonania.
poetry add django==1.8
Updating dependencies
Resolving dependencies... (0.0s)
SolverProblemError
Because djangorestframework (3.13.1) depends on django (>=2.2)
and no versions of djangorestframework match >3.13.1,<4.0.0, djangorestframework (>=3.13.1,<4.0.0) requires django (>=2.2).
So, because django-app depends on both Django (1.8) and djangorestframework (^3.13.1), version solving failed.
at ~/.local/lib/python3.10/site-packages/poetry/puzzle/solver.py:241 in _solve
237│ packages = result.packages
238│ except OverrideNeeded as e:
239│ return self.solve_in_compatibility_mode(e.overrides, use_latest=use_latest)
240│ except SolveFailure as e:
→ 241│ raise SolverProblemError(e)
242│
243│ results = dict(
244│ depth_first_search(
245│ PackageNode(self._package, packages), aggregate_package_nodes
bezpieczeństwo
Instalując paczki z poziomu menadżera pip
, ich sumy kontrolne nie są weryfikowane, co wprowadza potencjalne zagrożenie. Podczas pobierania zależności może wystąpić błąd sieciowy, przez co dane paczki zostaną uszkodzone i będzie ona wadliwa lub co gorsza, ktoś wprowadzi spreparowaną paczkę o tej samej wersji, ale wzbogaconą o złośliwy kod. Możliwe jest weryfikowanie hashy przy pomocy pip
, jednakże nie ma prostej metody na zapisanie ich wartości w pliku requirements.txt
. W przypadku Poetry
każda zainstalowana paczka, wraz z jej zależnościami jest zapisywana w pliku poetry.lock
razem z sumą kontrolną. Tak więc jeżeli przy ponownym instalowaniu paczek jakiś hash się zmieni, Poetry
poinformuje nas o zaistnieniu niespójności.
izolacja
Zakładając nowy projekt, zapewne korzystasz z virtualenv
4, aby odizolować środowisko projektu od jego hosta. Jeżeli nie izolujesz środowiska deweloperskiego, a paczki instalujesz za pomocą pip
, mogą spotkać Cię nieprzyjemne konsekwencje, w postaci, chociażby konfliktów pomiędzy wersjami paczek, których potrzebuje nowy projekt, a tym co aktualnie dostępne jest w systemie. Poetry
tworzy wirtualne środowisko Pythona za Ciebie. Nie musisz o niczym pamiętać, po prostu korzystasz z Poetry
, które w tle zajmuje się wszystkim. Virtualenv
stworzony przez Poetry
można aktywować przy pomocy komendy poetry shell
, czy uruchomić w nim moduł przy pomocy poetry run <moduł>
. Warto nadmienić, że Poetry
można zintegrować z IDE Pychram, czy IntelliJ poprzez plugin5, dzięki któremu, IDE będzie korzystać z virtualenv
stworzonego przez Poetry
.
Co więcej, Poetry
pozwala na zarządzanie różnymi wersjami Pythona, realizując podobne funkcje do projektu pyenv
6, dzięki czemu możliwe jest rozwijanie i testowanie kodu dla różnych wersji języka. Zmiany używanej wersji języka można dokonać przy pomocy komendy przedstawionej poniżej, należy jednak pamiętać, że podana wersja Pythona musi być dostępna na danym systemie.
poetry env use pypy3
dystrybucja paczek
Poetry pozwala na szybką i prostą budowę paczek oraz ich dystrybucję. Komenda build pozwala na zbudowanie paczek w formacie wheel
7 oraz sdist
8 na podstawie pliku pyproject.toml
. Niepotrzebne są do tego żadne dodatkowe narzędzia, takie jak np. setuptools
.
poetry build
Publikacja paczki jest równie prosta, po zbudowaniu paczki komendą build
wystarczy wywołać komendę publish
w celu wprowadzenia paczki do indeksu pypi9.
poetry publish
podsumowanie
Poetry
nie jest po prostu menadżerem paczek, to uniwersalne narzędzie, z którego poziomu możemy zarządzać zależnościami, wersjami Pythona, budową oraz publikacją paczek. Bez wątpienia można dzięki niemu zaoszczędzić sporo czasu. Czy w takim razie warto korzystać z Poetry
? Moim zdaniem, tak! Poetry
Warto wprowadzić do swojego projektu, jeżeli nadal korzysta on z czystego pip
i requirements.txt. Jeżeli nie podoba Ci się Poetry
, możesz skorzystać z innych rozwiązań takich jak Pipenv10, czy PDM11, które podobnie jak Poetry
, nie tylko skrupulatnie zarządzają zależnościami projektu, ale również je weryfikują. O PDM bez wątpienia napiszę w przyszłości, gdyż jest to bardzo ciekawa alternatywa dla opisanego menadżera paczek, zapewniająca izolację środowiska bez virtualenv
.
- podstawowy menadżer pakietów dla Python: https://pypi.org/project/pip/ [↩]
- nowoczesny menadżer pakietów dla języka Python: https://python-poetry.org/ [↩]
- Python Enhancement Proposal 518, dokumentacja dostępna pod adresem: https://peps.python.org/pep-0518/ [↩]
- narzędzie do izolacji środowiska Python, link do strony projektu: https://virtualenv.pypa.io/en/latest/ [↩]
- https://plugins.jetbrains.com/plugin/14307-poetry [↩]
- Menadżer wersji Pythona, link do projektu na Github: https://github.com/pyenv/pyenv [↩]
- format paczki, więcej w PEP-427: https://peps.python.org/pep-0427/ [↩]
- source distribution, format paczki, więcej w dokumentacji: https://docs.python.org/3/distutils/sourcedist.html [↩]
- Python Package Index – rejestr paczek dla języka Python [↩]
- menadżer paczek, strona projektu: https://pipenv.pypa.io/en/latest/ [↩]
- menadżer paczek, strona projektu: https://pdm.fming.dev/latest/ [↩]