Zarządzanie pakietami w Pythonie II

Python is the “most powerful language you can still read” - Paul Dubois

W poprzednim poście omówiłem takie narzędzia jak pip i virtualenv. Dzięki nim możemy bez problemu zainstalować potrzebne nam biblioteki w wyizolowanych od siebie środowiskach. Te bazowe narzędzia do zarządzania pakietami w większości przypadków są wystarczające dla standardowych problemów. Możemy dzięki nim w bardzo prosty sposób zainstalować pakiet, usunąć go czy zaktualizować. Virtualenv pozwala nam w łatwy sposób ominąć problem trzymania różnych wersji pakietów w jednym systemie.

Life

Kiedy jednak aktywnie uczestniczmy w wiele różnych projektów, możemy napotkać kolejne problemy:

  • Jak zarządzać różnymi wersjami interpreterów w jednym systemie?
  • Jak trzymać zależności w bardziej czytelny (i bezpieczny) sposób niż requirements.txt?
  • Jak w prosty sposób zarządzać dużą ilością wirtualnych środowisk?

Problemy te można rozwiązać na wiele sposobów, jednak ja chciałbym przedstawić wam dwa nowe narzędzia - pyenv, pipenv. Różnią się one zupełnie tym, co oferują deweloperom, razem jednak rozwiążą wyżej wymienione problemy.

Pyenv

Pyenv jest prostym w obsłudze narzędziem, które służy do instalacji, usuwania i zarządzania różnymi wersjami interpretera Pythona w twoim systemie. Za pomocą prostego interfejsu możemy zmienić wersje interpretera bez żadnych problemów dla naszego komputera (ten, kto próbował usunąć pythona 2.7 z ubuntu i zastąpić go wersją 3, wie, o czym mówię). Dodatkowo automatycznie pyenv dba o to by inne komendy związane z naszym interpreterem, jak np. pip odwoływały się do swoich odpowiedników.

Sam proces instalacji pyenv nie jest tak prosty, jak innych wymienionych tutaj narzędzi (zwłaszcza jak jesteśmy przyzwyczajeni korzystając na co dzień z pip ;) ). Wynika to głównie z faktu, że pyenv jest narzędziem napisanym głównie w bashu. Cały proces instalacji nie jest jednak skomplikowany i poza skopiowaniem plików, wymaga od nas też małych zmian w naszym .bashrc (czy używanym przez nas odpowiedniku). Rzućmy okiem jak wygląda help:

❯ pyenv help                                                                                             mmasztalerczuk.github.io/git/master 
Usage: pyenv <command> [<args>]

Some useful pyenv commands are:
   commands    List all available pyenv commands
   local       Set or show the local application-specific Python version
   global      Set or show the global Python version
   shell       Set or show the shell-specific Python version
   install     Install a Python version using python-build
   uninstall   Uninstall a specific Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   version     Show the current Python version and its origin
   versions    List all Python versions available to pyenv
   which       Display the full path to an executable
   whence      List all Python versions that contain the given executable

Sprawdźmy, jakie mamy zainstalowane wersje i użyjmy innej wersji Pythona.

pyenv

Za pomocą pyenv versions możemy sprawdzić które wersje interpretera mamy obecnie pod kontrolą pyenv (wszystkie wspierane wersje możemy znaleźć za pomocą pyenv install --list). Zmiana interpretera następuje za pomocą pyenv global x (gdzie x to wersja, którą chcemy użyć). Zasada działania pyenv jest dość prosta. Kiedy wpisujemy komendę np. python nasz terminal zaczyna szukać pliku wykonywalnego w miejscach, które zostały określone w zmiennej środowiskowej PATH. Pyenv po prostu modyfikuje nam tą zmienną poprzez dodanie na sam początek pare folderów, w których trzyma tak zwane shims.

PATH=/home/mariusz/.pyenv/shims:/home/mariusz/.pyenv/bin:/usr/local/bin:/usr/local/sbin

W folderze nazwanym shims istnieją bardzo małe skrypty, które nazywają się jak pliki wykonywalne np. python. Z racji tego ze nazywają się tak samo, są one traktowane przez nasz shell jak to, co chcemy znaleźć. Parsują one argumenty i przesyłają je dalej uruchamiając pyenv który m. in za pomocą zmiennych środowiskowych decyduje o wersji interpretera. Cały proces jest dokładniej opisany w dokumentacji i polecam się z nim zapoznać, bo jest to dość ciekawe rozwiązanie problemu.

Dzięki takiej metodzie jesteśmy w stanie bardzo prosto oszukać nasz terminal i korzystać z wielu wersji pythona z minimalnym nakładem pracy.

Pipenv

Pipenv jest najnowszym z wymienionych przeze mnie narzędzi (co przekłada się na to, że jest on bardzo mocno w tej chwili rozwijany i cały czas dochodzą nowe funkcjonalności). Pipenv jest narzędziem, który łączy w sobie pip i virtualenv w jedno. Dodatkowo wspiera Pipfile, pomaga zarządzać zmiennymi środowiskowymi poprzez automatyczne wczytywanie plików .env (bardzo popularne rozwiązanie do trzymania wartości dla zmiennych środowiskowych). Dodatkowo posiada on wsparcie dla pyenv, co powoduje, że podczas tworzenia środowiska możemy automatycznie zainstalować wersje Pythona, która nas interesuje.

Proces instalacji jest prosty i można zainstalować pipenv za pomocą pip (a potem zapomnieć, że coś takiego jak pip istnieje). Zobaczmy jak wygląda help i pokrótce omówmy dostarczone nam funkcjonalności (skrocilem do najwazniejszego fragmentu):

Commands:
  check      Checks for security vulnerabilities and against PEP 508 markers
             provided in Pipfile.
  clean      Uninstalls all packages not specified in Pipfile.lock.
  graph      Displays currently–installed dependency graph information.
  install    Installs provided packages and adds them to Pipfile, or (if none
             is given), installs all packages.
  lock       Generates Pipfile.lock.
  open       View a given module in your editor.
  run        Spawns a command installed into the virtualenv.
  shell      Spawns a shell within the virtualenv.
  sync       Installs all packages specified in Pipfile.lock.
  uninstall  Un-installs a provided package and removes it from Pipfile.
  update     Runs lock, then sync.
Wirtualne środowiska

Chcąc stworzyć nowe środowisko do pracy, zaczynamy od uruchomienia np. komendy pipenv --python 3.6. Jeżeli posiadamy zainstalowane pyenv to w razie problemów z brakiem konkretnej wersji interpretera pipenv sam zadba o to, by ja ściągnąć i zainstalować. Nowe wirtualne środowisko, które zostanie stworzone domyślnie nie będzie jednak się znajdować w folderze z projektem, a cytując dokumentację w standardowej lokalizacji. U mnie jest to ścieżka .local/share/virtualenvs. Zachowanie to można zmienić poprzez odpowiednie ustawienie flagi ‘PIPENV_VENV_IN_PROJECT’, wtedy wirtualne środowisko powstanie w tym samym folderze. Ogólnie polecam zapoznać się z listą zmiennych środowiskowych, z których korzysta pipenv, ponieważ jest ona dość spora i daje nam spore możliwości konfiguracji zachowań aplikacji.

pyenv

Warto zwrócić uwagę na nazwę folderu z wirtualnym środowiskiem pipenv_blog-ldLvq6-K. Jest to nazwa folderu i hash ścieżki. Nazwa została w takich sposób skonstruowana, by w jednoznaczny sposób identyfikować w naszym projekcie środowisko wirtualne. Daje to nam możliwość nieprzejmowania się, jeżeli posiadamy więcej niż jeden projekt o tej samej nazwie. Na tę chwilę zarządzanie wirtualnymi środowiskami jest jeszcze troszkę problematyczne. Zwłaszcza jeżeli chodzi o usuwanie nieużywanych folderów. Możemy usunąć środowisko poprzez komendę pipenv --rm, musimy być jednak w folderze, którego to dotyczy. Jest to standardowe rozwiązanie i ma jedną niestety wadę. Trzeba pamiętać o tym przed usunięciem z dysku projektu. Bardzo kłopotliwe może być, że chcąc odbudować środowisko, usuniemy folder z projektem, po czym stworzymy go na nowo (np. robiąc git clone). Niestety w takim scenariuszu stare środowisko będzie nadal używane, bo ścieżka i nazwa nie zostały zmienione. Drugim problem może być też to, że potencjalna zmiana nazwy folderu, czy przeniesienie go w inne miejsce nie spowoduje automatycznie, że istniejące wirtualne środowisko będzie nadal używane. Będziemy musieli samemu pamiętać o tym, że należy je usunąć i stworzyć nowe.

Same środowiska możemy po prostu usunąć ręcznie z miejsca, gdzie są one trzymane albo możemy skorzystać z narzędzia pew. Jest ono automatycznie instalowane wraz z pipenvem i jest wykorzystywane przez niego do zarządzania pakietami. Za pomocą pew ls możemy sprawdzić, jakie mamy obecne zainstalowane środowiska, po czym za pomocą pew rm usunąć te, które wydają się nam już niepotrzebne.

Piplock

Instalacja pakietów jest równie prosta. Sprowadza się to do wykoniania komendy pipenv install mypy. Pokaże przykład w którym zainstalujemy dwa pakiety, tyle że jeden z nich z flagą --dev.

pyenv

Żeby zrozumieć, co się naprawdę wydarzyło musimy wiedzieć czym jest Pipfile i do czego służy. Pipfile jest taką lepszą alternatywą do requirements.txt. Podczas korzystania z pip bardzo często mamy problem z rozdzielaniem zależności od tych, które są realnie potrzebne do pracy projekty, a tych, które wykorzystujemy podczas procesu dewelopmentu. Przeważnie rozwiązuje się to trzymaniem paru plików takich jak requirements_dev.txt czy requirements_tests.txt w projekcie. Nie jest to rozwiązanie idealne i bardzo często pakiety zaczynają się mieszać i nie znajdują się w odpowiednim pliku.

Pipfile rozwiązuje te problemy poprzez swoją strukturę. Tak naprawdę istnieją dwa pliki - pipfile i pipfile.lock. W pierwszym z nich mamy nasze pakiety podzielone na dwie sekcje, w których możemy trzymać pakiety potrzebne do wersji produkcyjnej i tej deweloperskiej. Instalując requests z flagą --dev dodajemy właśnie ten pakiet do tej sekcji. Drugi z nich pipfile.lock jest zbiorem hashy, które potwierdzają wersje danego pakietu.Spójrzmy jak wygląda nasz plik pipfile:

Oczywiście jest to bardzo podstawowy plik, który można rozbudować o wiele rzeczy. Sama składnia określania interesującej nas paczki jest bardzo skomplikowana (w sumie jest to język, który nazywa się TOML), pozwala jednak bardzo dokładnie określić, czego chcemy używać w projekcie. Rzućmy okiem na troszkę bardziej skomplikowany przykład:

Drugim plikiem jest pipfile.lock. Jest to zbiór hashy które potwierdzają wersje danego pakietu. Omawiając pip wspominałem o fladze --hash. Generowanie hashy z plików pozwala upewnić się, że dany plik jest dokładnie tym samym plikiem, z którego korzystaliśmy, kiedy pierwotnie instalowaliśmy bibliotekę. W requirements.txt istniała możliwość przechowywania informacji o hashu, jednak było to bardzo niepraktyczne, bo czytelność takiego pliku bardzo się zmniejszała. Pipfile rozbija to na dwa pliki, dzięki czemu rzeczy, które nas interesują na co dzień (czyli zawartość pipfile) jest bardzo czytelna. Warto przy okazji wspomnieć też o komendzie graph. Działa ona tak samo, jak wspomniane w poprzednim wpisie pipdeptree. W bardziej przystępny sposób wyświetla nam pakiety zainstalowane w naszym środowisku, pokazując zależności pomiędzy nimi.

Żeby móc uruchomić program w danym środowisku możemy to zrobić na dwa sposoby. Możemy wejść do danego środowiska za pomocą komendy pipenv shell. Jest to dokładnie to samo co robiliśmy korzystając z source z venv czy virtualenv. Drugim rozwiązaniem jest składnia podobna do składni popularnego teraz dockera, czyli wykonanie jednej komendy w naszym wyizolowanym kontenerze. Za pomocą pipenv run możemy uruchomić coś, co będzie działało w kontekście naszego środowiska.

Dodatkowo bardzo ciekawą funkcjonalnością jest komenda check, która umożliwia nam w prosty sposób sprawdzenie poprawności markerów określających zależności pakietów opisanych w PEP 508 (czyli składnia wykorzystywana w pliku pipfile) i co ciekawsze sprawdzenia, czy nie ukazała się jakaś poprawka związana z bezpieczeństwem do którejś z naszych paczek. Dzięki temu będziemy wiedzieć, kiedy konieczne jest podbicie naszej paczki do najbardziej aktualnej wersji.

Podsumowanie

Pipenv jest bardzo ciekawym fragmentem kodu i poprzez zintegrowanie z paroma innymi rozwiązaniami nadaje się idealnie jako główne narzędzie w codziennej pracy python dewelopera. Dzięki temu, że wspiera on częściowo pyenv nie musimy się samemu martwić o to, który interpreter powinien zostać użyty. Dodatkowo obsługa pipfile, czy funkcjonalność, która całkowicie pokrywa zastosowanie pip powoduje, że jest on na tę chwilę najatrakcyjniejszym narzędzie do zarządzania pakietami w pythonie.