Pipenv - zarządzanie zależnościami i środowiskami wirtualnymi w Pythonie

6 minut(y)

Zależności

Programując w języku Python wcześniej niż później staniemy przed potrzebą skorzystania z zewnętrznych modułów bibliotek (zwanych też pakietami). Przed ich wykorzystaniem musimy je zainstalować (w odpowiednich wersjach) w środowisku Pythona, w którym będzie działał nasz program.

Możemy to zrobić ręcznie, poprzez pobranie kodu źródłowego dodatku, zbudowaniu go i zainstalowaniu w systemie. Jest to dość skomplikowany proces, gdyż musimy uwzględniać, że zewnętrzny pakiet będzie potrzebował innych zewnętrznych pakietów, które analogicznie będziemy musieli pobrać, zbudować i zainstalować.

Na szczęście Python daje nam kilka gotowych narzędzi ułatwiających proces instalacji zewnętrznych modułów wraz z ich zależnościami (setuptools, easy_install, pip, egg, wheel).

Środowiska wirtualne

Pracując przy kilku projektach równocześnie może okazać się, że potrzebujemy danego pakietu w różnych wersjach w różnych projektach. Tutaj z pomocą przychodzą nam wirtualne środowiska. Wirtualne środowisku pythona jest to odizolowany od systemu podsystem interpretera Python wraz własnym zestawem bibliotek, w którym można niezależnie instalować zewnętrzne moduły i uruchamiać własne programu. Programy uruchomione w takim środowisku korzystają tylko z komponentów i bibliotek w nim dostępnych.

I w tym miejscu również mamy do dyspozycji kilka narzędzi do zarządzania środowiskami wirtualnymi (pyenv, virtualenv, virtualenvwrapper, conda, venv)

Typowy proces pracy przy projekcie (pip + venv + requirements.txt)

Pokażę teraz w jaki sposób może przebiegać praca nad typowym projektem w Pythonie. Będziemy wykorzystywać Pythona w wersji co najmniej 3.4 (najlepiej w aktualnej obecnie wersji 3.7), gdyż począwszy od tej wersji mamy już wbudowany w środowisko Pythona program pip i nie musimy go dodatkowo instalować.

Zaczynamy od stworzenia środowiska wirtualnego dla projektu. Możemy to zrobić na wiele sposobów. Skorzystamy z modułu venv, który jest wbudowany w Pythona począwszy od wersji 3.3

$ python -m venv ~/.venvs/myproject

gotowe środowisko wirtualne aktywujemy poprzez:

$ source ~/venvs/myproject/bin/activate
(myproject) $

Korzystając z systemu Linux i powłoki fish w celu aktywowania środowiska wydajemy polecenie: source ~/venvs/myproject/bin/activate.fish, a w przypadku powłoki csh: source ~/venvs/myproject/bin/activate.csh

Przychodzi teraz pora na stworzenie katalogu dla projektu i edycję jego kodu w naszym ulubionym edytorze/IDE. Załóżmy, że będzie to projekt korzystający z frameworka flask. Zaczniemy banalnie:

# app.py
from flask import Flask
app = Flask(__name__)4

@app.route('/')
def hello():
  return "Hello World"

if __name__ == '__main__':
  app.run()

Z powyższego kodu wynika, że będziemy potrzebowali moduł flask. Wydajemy więc w konsoli polecenie jego instalacji:

(myproject) $ pip install flask

W wyniku wykonania tego polecenia zostanie w środowisku wirtualnym zainstalowany moduł flask w najnowszej wersji (w tym przypadku 1.0.2) wraz z innymi zależnościami.

Pip domyślnie pobiera paczki modułów z adresu https://pypi.org

Gdy potrzebujemy innej wersji modułu flask to musimy wyspecyfikować numer wersji do instalacji:

(myproject) $ pip install flask=="1.0.1"

Mając zainstalowany moduł flask, wraz z modułami zależnymi powinnyśmy wygenerować plik z zależnościami, który w przyszłości umożliwi nam łatwiejszą instalację zależności w odpowiednich wersjach np. gdy kolejne osoby będą dołączać do grona developerów naszego projektu, albo gdy będziemy ten projekt publikować na serwerze produkcyjnym.

(myproject) $ pip freeze > requirements.txt
(myproject) $ cat requirements.txt
Click==7.0
Flask==1.0.2
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
Werkzeug==0.14.1

Mając tak przygotowany plik requirements.txt możemy w każdym momencie odtworzyć stan instalacji wszystkich zewnętrznych modułów w odpowiednich wersjach:

(myproject) $ pip install -r requirements.txt

Dzięki takiemu sposobowi instalacji zależności nasz projekt będzie deterministyczny. Oznacza to, że niezależnie od momentu, w którym będziemy instalować zależności, zawsze otrzymamy w wyniku instalacji wszystkie moduły w wymaganych wersjach.

Rozważmy jeszcze hipotetyczną sytuację, że w pewnym momencie w jednym z modułów np. Jinja2 w wersji 2.10 wykryto lukę bezpieczeństwa (lub błąd), który usunięto w wersji 2.11. Edytujemy plik requirements.txt poprzez zmianę numeru wersji tego modułu, instalujemy ponownie zależności, uruchamiamy testy i jesteśmy gotowi na publikację projektu.

Warto jeszcze nadmienić, że bardzo częstą praktyką jest tworzenie w projekcie oddzielnych zestawów z zależnościami (plików) dla wersji developerskiej, produkcyjnej i dla testów.

Praca z pipenv

Narzędzie pipenv to swojego rodzaju połączenie pip i środowiska wirtualnego. Pipenv pozwala na łatwe zarządzanie środowiskami wirtualnymi i zależnościami wewnątrz nich. Dzięki temu narzędziu nie musimy już oddzielnie używać polecenia pip i venv/virtualenv - wystarczy jedno pelecenie: pipenv.

Pipenv do przechowywania informacji o zależnościach wykorzystuje pliki Pipfile oraz Pipfile.lock. W pliku Pipfile wykorzystywana jest składnia TOML do deklarowania zależności oraz ich źródeł. W pliku Pipfile.lock przechowywane są informacje o deterministycznym zestawie zależności, pozwalające na powtarzalny deployment projektu.

Pipenv jest zewnętrznym modułem Pythona, który instalujemy w globalnym środowisku Pythona:

$ sudo pip install pipenv

Teraz dla porównania zrealizujemy ten sam proces pracy co w poprzednim podpunkcie ale z wykorzystaniem pipenv:

$ cd ~
$ mkdir myproject
$ cd myproject
$ pipenv install flask
Creating a virtualenv for this project…
Pipfile: /home/maciej/myproject/Pipfile
Using /usr/bin/python (3.7.2) to create virtualenv…
⠙ Creating virtual environment...Already using interpreter /usr/bin/python
Using base prefix '/usr'
New python executable in /home/maciej/.local/share/virtualenvs/myproject-59kleYW3/bin/python
Installing setuptools, pip, wheel...done.

✔ Successfully created virtual environment!
Virtualenv location: /home/maciej/.local/share/virtualenvs/myproject-59kleYW3
Creating a Pipfile for this project…
Installing flask…
Adding flask to Pipfile's [packages]…
✔ Installation Succeeded
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success!
Updated Pipfile.lock (662286)!
Installing dependencies from Pipfile.lock (662286)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 6/6 — 00:00:00
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

Wydaliśmy tylko polecenie instalacji modułu flask, a pipenv wykonał znacznie więcej pracy dla nas. Najpierw sprawdził, czy istniało już środowisko wirtualne (na podstawie nazwy katalogu z projektem). W tym przypadku nie istniało, więc pipenv go utworzył. Kolejnym etapem było utworzenie pliku Pipfile i instalacja zależności. Zobaczmy jak wygląda plik Pipfile po zakończeniu działania polecenia:

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
flask = "*"

[requires]
python_version = "3.7"

Przy instalacji modułu flask nie określiliśmy wersji do instalacji, więc została zainstalowana najnowsza, a w pliku Pifpile jako numer wersji wstawiony został symbol *. Instalacja modułu w konkretnej wersji może być zrealizowana w następujący sposób:

$ pipenv install flask=="1.0.2"

Jeśli potrzebujemy skorzystać z wersji modułu bezpośrednio z github, to mamy taką możliwość:

$ pipenv install -e git+https://github.com/requests/requests.git#egg=requests

Aktywowanie utworzonego środowiska projektu odbywa się poprzez polecenie:

$ pipenv shell

Możemy również uruchamiać programy w wirtualnym środowisku z poziomu środowiska globalnego (bez wcześniejszego aktywowania):

$ pipenv run python app.py
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Korzystając z Pipenv nie musimy już tworzyć oddzielnych plików z zestawem zależności dla developmentu, produkcji, testów itp. W pliku Pipfile możemy tworzyć odpowiednie sekcje:

$ pipenv install pytest --dev

Możemy również w łatwy sposób zobaczyć jak wygląda drzewo zależności:

$ pipenv graph
Flask==1.0.2
  - click [required: >=5.1, installed: 7.0]
  - itsdangerous [required: >=0.24, installed: 1.1.0]
  - Jinja2 [required: >=2.10, installed: 2.10]
    - MarkupSafe [required: >=0.23, installed: 1.1.0]
  - Werkzeug [required: >=0.14, installed: 0.14.1]

Odinstalowanie modułu umożliwia nam komenda:

$ pipenv uninstall numpy

Pipenv jako nowoczesne narzędzie umożliwia nam dwukierunkową współpracę z plikami requirements.txt. Jeśli potrzebować będziemy zainstalować zależności na podstawie pliku requirements.txt to możemy zrobić to poleceniem:

$ pipenv install -r requirements.txt
(lub dla sekcji dev)
$ pipenv install -r dev-requirements.txt --dev

W drugą stronę, na podstawie zainstalowanych zależności możemy wygenerować plik requirements.txt:

$ pipenv lock -r > requirements.txt
(lub dla sekcji dev)
$ pipenv lock -r -d > dev-requirements.txt

Przedstawiłem tutaj zaledwie podstawy pracy z Pipenv, mając nadzieję na zachęcenia Was do poznania i wykorzystywania tego coraz bardziej popularnego narzędzia. Sam osobiście stosuję go od jakiegoś czasu w projektach i sprawdza się bardzo dobrze.

Zostaw komentarz