Zarządzanie pakietami w Pythonie I

Dependency hell - The situation when installed software packages have conflicting, incompatible dependencies on specific versions of other software packages.

Nowy projekt

Każdy z nas zna chyba ten scenariusz. Zaczynamy nowy czysty projekt. Tym razem wszystko będzie idealnie, całkowicie przemyślane i zrobione podręcznikowo. W planach mamy przy okazji przetestowanie nowego frameworka, narzędzia czy pakietu.

Life

Tworząc podstawy nowego projektu zaczynamy bardzo często od zainstalowania niezbędnych pakietów. Idealnymi przykładami są pytest, mypy albo flake8. Z czasem nasza aplikacja zaczyna wymagać coraz więcej zewnętrznych zależności. Pojawiają się pakiety, które przydają się tylko w procesie dewelopmentu (ipdb) i takie, które zawsze muszą być używane. Po chwili orientujemy się, że jakaś wersja potrzebnego nam pakiet koliduje z innym pakietem. W tym momencie zaczynają się pierwsze problemy związane z zarządzaniem tymi zależnościami.

  • Jak trzymać informacje o wymaganych pakietach i ich wersjach?
  • Jak rozdzielać pakiety na wersje produkcyjne i deweloperskie?
  • Jak w prosty sposób zreprodukować (deterministycznie) całkowicie środowisko programistyczne ?
  • Jak używać różnych wersji tego samego pakietu w tym samym systemie ?

Zarządzanie zależnościami

W ciągu paru ostatnich lat powstał zbiór narzędzi, które potrafią rozwiązać wyżej wymienione problemy na różne sposoby. Pierwszym typem przydatnych rozwiązań są menadżery pakietów jak pip czy easy_install. Drugą grupą są narzędzia do zarządzania środowiskiem takie jak virtualenv, czy venv. Omówmy więc teraz pokrótce te narzędzia, żeby zrozumieć jak one konkretnie działają i żeby zrozumieć jak mogą one nam pomóc w codziennej pracy.

1. Pip

Pip jest w tej chwili najbardziej popularnym narzędziem do zarządzania pakietami. Jest on instalowany wraz z pythonem od wersji 3.4. W starszych wersjach Pythona, żeby móc go używać należy ściągnąć installer i po prostu go uruchomić. Pip jest niczym innym niż prostym menadżerem instalowania pakietów. Zamiast samemu wyszukiwać w internecie odpowiednich pakietów, które są nam potrzebne, a następnie ręcznie dbać samemu o to, by je aktualizować, możemy wydać po prostu jedno polecenie, które zrobi to za nas. Cały proces wyszukiwania, konfiguracji i instalacji jest robiony automatycznie. Proces aktualizacji pakietów może zostać sprowadzony do wydania jednej komendy.

Przykładowo, wydając komendę:

pip install mypy

Zobaczymy:

Jak widzimy, pakiet został automatycznie wyszukany dla nas w repozytorium. Domyślnym repozytorium jest pypi (i w sumie nie posiada on żadnej sensownej konkurencji na tą chwile), w którym można znaleźć ponad 130000 pakietów. PyPi zostało założone kolo roku 2003 zeby stworzyc jeden wielki katalog z third-party pakietami dla Pythona. PyPi powinno być pierwszym miejsce do którego zagladamy, kiedy szukamy jakiegoś pakietu do zainstalowania (możemy nawet obejść wchodzenia na stronę i szukania pakietu - możemy sprawdzić interesujący nas pakiet za pomocą komendy pip search). Należy jednak pamiętać że z racji tego ze każda osoba może wrzucić tam pakiet to zdarzają się sytuacje na które powinniśmy być uczuleni i upewniać się że dany pakiet jest dokladnie tym, czego oczekujemy. Problemy mogą być związane z dwoma aspektami. Pierwszym jest opisana wyżej sytuacja tworzenia złośliwego pakietu, który wygląda bardzo podobnie (bardzo zbliżona nazwa do innej paczki), jednak robi troszkę coś innego. Często coś bardzo złego. Drugim problem może być zmiana/podmiana pakietu na jego inną wersję. Jak sobie radzić z tym drugim problem pokaże poniżej.

Pip po znalezieniu odpowiedniego pakietu potrafi automatycznie rozpakować taką paczkę, odpowiednio ją skonfigurować i zainstalować. Obecnie większość pakietów jest dystrybuowana w formacie .whl. Jest to teraz najbardziej popularny format zdefiniowany w PEP 427. O formacie .whl porozmawiamy jednak innym razem, bo jest to temat, który zasługuję również na swój wpis.

Pip sam w sobie nie posiada wiele więcej funkcjonalność. Poza zainstalowaniem, ściągnięciem i usunięciem pakietów mamy jeszcze dwie rzeczy, o których warto wspomnieć.

mariusz@hal:~/gits/ccMina/build/gateway$ pip

Usage:   
  pip <command> [options]

Commands:
  install                     Install packages.
  download                    Download packages.
  uninstall                   Uninstall packages.
  freeze                      Output installed packages in requirements format.
  list                        List installed packages.
  show                        Show information about installed packages.
  check                       Verify installed packages have compatible dependencies.
  search                      Search PyPI for packages.
  wheel                       Build wheels from your requirements.
  hash                        Compute hashes of package archives.
  completion                  A helper command used for command completion.
  help                        Show help for commands.

Wartą wspomnienia funkcją jest na pewno komenda pip freeze

Jest to lista wszystkich naszych pakietów, które zainstalowaliśmy za pomocą pipa. Widzimy informację o nazwie pakietu i o wersji która jest wykorzystywana aktualnie przez nas. Bardzo popularne jest zapisanie tych wszystkich informacji w pliku, który zwyczajowo nazywamy requirements.txt

pip freeze > requirements.txt

pip freeze daje nam wyniki w formacie, który jest znanym standardem w przechowywaniu zależności. Dodając plik requirements.txt do naszego projektu możemy bardzo szybko za pomocą komendy pip install -r requirements.txt odtworzyć środowisko potrzebne do działania naszej aplikacji. Jest to pierwszy krok do zarządzania pakietami w naszym projekcie. Warto wspomnieć w tym momencie o pipdeptree będącym ulepszoną wersją komendy pip freeze. Paczka ta pozwala nam posortować pakiety ze względu na zależności i daje nam większe rozeznanie wizualne, które pakiety są od siebie zależne.

Drugą wartą wspomnienia funkcją jest na pewno komenda pip hash. Najlepiej zobrazować jej przykładowe użycie

Za pomocą pip hash możemy policzyć hash danej paczki. Pozwoli to nam podczas instalacji być pewnym, że ściągamy dokładnie tę samą paczkę, o której chodzi w requirements.txt. Pomoże nam to zapobiec sytuacji, w której z jakiś bliżej nieokreślonych powodów dana wersja paczki zmieniła swoją zawartość (co przeważnie nie wróży nic dobrego). Instalując za pomocą pip nasze zależności możemy dodać flagę require-hashes np.

pip install --require-hashes -r requirements.txt

Nasz plik z wymaganiami powinien przechowywać dodatkowo informacje na temat hashy naszych wymagań i będzie wyglądać wtedy np. tak:

six==1.10.0 --hash=sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb

2. Virtualenv i venv

Korzystając z pip możemy bardzo szybko zauważyć, że pip freeze zwraca informacje, które pakiety są zainstalowane w naszym systemie. Wszystkie pakiety, nawet te, które nie są bezpośrednio używane przez aplikację, którą obecnie tworzymy. Można próbować rozwiązać ten problem poprzez ręczne czyszczenia requirements.txt. Takie rozwiązanie jest męczące i prędzej czy później doprowadzi do sytuacji dodania czegoś zbędnego albo co gorsze jakiś pakiet “zaginie” i nasze środowisko nie zadziała poprawnie. Klasyczne - u mnie działa. Dodatkowo możemy się spotkać z sytuacją, w której potrzebujemy konkretnej wersji danego pakietu w naszej aplikacji, ale nasz system używa wersji nowszej, albo starszej. Taki problem z zależnościami nazywamy - dependency hell.

Do rozwiązania takich problemów został stworzony virtualenv i venv. Dobrze od razu wyjaśnić, że różnica pomiędzy virtualenv i venv sprowadza się głównie do tego, że venv jest “nowszą” wersja, która jest obecnie dostarczana z Pythonem od wersji 3.3. Tak więc do wersji 3.3 powinniśmy używać virtualenv (bo venv po prostu nie ma dla starszych wersji ;)), a potem już venv. Jak więc działają te wirtualne środowiska?

Zasada działania jest bardzo prosta. Wirtualne środowisko to nic innego jak miejsce, w którym możemy zainstalować potrzebne nam pakiety i wmówić interpreterowi pythona, że ma korzystać z tego miejsca, kiedy będzie potrzebował zaimportować zewnętrzną bibliotekę albo zainstalować coś nowego. Tworząc wirtualne środowiska modyfikujemy ścieżkę, w której Python wyszukuje potrzebne sobie pakiety. Ponieważ pakiety instalowane w wirtualnym środowisku są trzymane w zupełnie innym miejscu niż domyślne miejsce w naszym systemie, możemy robić z nimi, na co tylko mamy ochotę bez bania się o to, że jakaś część naszego systemu, która wymaga danej paczki przestanie działać. Możemy zdecydować nawet jaka wersja Pythona będzie w tym środowisko wykorzystywana. Sama zasada działania wirtualnych środowisk jest oczywiście troszkę bardziej skomplikowana i bardzo się zmieniała wraz z czasem rozwoju Pythona (idealny przykład jak core deweloperzy wzięli dobry pomysł od społeczności i zrobili z niego oficjalny standard). Więcej informacji na technicznym działaniu wirtualnych środowisk można znaleźć tutaj (prezentacja nie jest najnowsza, ale nadal jest ciekawa)

Zobaczmy jak możemy użyć naszego venv

Na początku stworzyłem po prostu nowe środowisko example-blog za pomocą komendy python3 -m venv example-blog. Żeby aktywować środowisko i zacząć z niego korzystać możemy użyć komendy source example-blog/bin/activate. Komenda ta ustawia nam zmienną środowiskową w terminalu VIRTUAL_ENV co powoduje, że od teraz nasz interpreter będzie korzystał z naszego wirtualnego środowiska. Kiedy aktywujemy nasze nowe środowisko, możemy upewnić się, że jest ono puste (np. za pomocą pip freeze) i zainstalować jakiś przykładowy pakiet. Kiedy chcemy zakończyć pracę wystarczy, że użyjemy deactivate i znowu zaczynamy korzystać z pakietów, które zostały zainstalowane w naszym systemie operacyjnym.

Przeglądając zawartość naszego wirtualnego środowiska możemy zauważyć, że w środku została zainstalowana paczka mypy. Dowolna zmiana jej kodu nie wpłynie zupełnie na integralności naszego systemu operacyjnego, bo nie jest ona używana nigdzie poza aplikacjami, które uruchomimy w środku.

3. Część I - Podsumowanie

Korzystanie z pip i venv to dopiero pierwszy krok do pełnego zrozumienia jak działa nowoczesne zarządzanie pakietami w Pythonie. Pip mimo tego, że jest dość prostym narzędziem w większości wypadków jest zupełnie wystarczający. Najważniejszą rzeczą podczas zarządzania pakietami w jakimkolwiek systemie czy języku jest możliwość łatwego instalowania, wyszukiwania i aktualizacji naszych pakietów. Pip sprawdza się w tym idealnie. Jednak istnieją też inne narzędzia, które podchodzą do problemu zarządzania pakietami w zupełnie inny sposób. Dobrym przykładem jest na przykład pipenv, który jest połączeniem pip i venv w jedno. O tym jednak (i o wielu innych narzędziach) następnym razem.