How to manage dependencies in your Python project
in a production-ready manner

Mikalai Saskavets, Software Engineer, iTechArt

How to manage dependencies in your Python project
in a production-ready manner

Mikalai Saskavets

2019-02-15, 📍 EventSpace

План

Проблематика Pipenv
как решение
DataScience?

Проблематика

Как это делается сейчас?


echo celery > requirements.txt

virtualenv .venv

source .venv/bin/activate

pip install -U -r requirements.txt

pip freeze
amqp==2.4.0
billiard==3.5.0.5
celery==4.2.1
kombu==4.2.2.post1
pytz==2018.9
vine==1.2.0

↖ зависимости тянут за собой свои зависимости

Концептуальные проблемы текущего подхода


echo celery > requirements.txt

pip install -U -r requirements.txt

Практические проблемы текущего подхода


echo celery > requirements.txt

pip install -U -r requirements.txt

Всё может сильно измениться за месяц-два

Celery [ноябрь 2018 : январь 2019]

  amqp==2.3.2         |   amqp==2.4.0
  billiard==3.5.0.5   |   billiard==3.5.0.5
  celery==4.2.1       |   celery==4.2.1
  kombu==4.2.2        |   kombu==4.2.2.post1
  pytz==2018.7        |   pytz==2018.9
  vine==1.1.4         |   vine==1.2.0
            

Всё может сильно измениться за месяц-два

Celery [ноябрь 2018 : январь 2019]

  amqp==2.3.2         |   amqp==2.4.0
  billiard==3.5.0.5   |   billiard==3.5.0.5
  celery==4.2.1       |   celery==4.2.1
  kombu==4.2.2        |   kombu==4.2.2.post1
  pytz==2018.7        |   pytz==2018.9
  vine==1.1.4         |   vine==1.2.0
            

Новобранец будет рад СВЕЖАЙШИМ пакетам

Celery [январь 2019 : февраль 2019]

     amqp==2.4.0             |       amqp==2.4.1
     billiard==3.5.0.5       |       billiard==3.5.0.5
     celery==4.2.1           |       celery==4.2.1
     kombu==4.2.2.post1      |       kombu==4.3.0
     pytz==2018.9            |       pytz==2018.9
     vine==1.2.0             |       vine==1.2.0

cat ~/celery/requirements/default.txt

pytz>dev
billiard>=3.5.0.2,<3.6.0
kombu>=4.2.0,<5.0

Всё становится сложнее, когда пакетов больше

 1 +-- 15 lines: amqp==2.4.0    +   1 +-- 15 lines: amqp==2.4.0
16 coreschema==0.0.4               16 coreschema==0.0.4
17 coverage==4.5.2                 17 coverage==4.5.2
18 cryptography==2.4.2             18 cryptography==2.5
19 decorator==4.3.2                19 decorator==4.3.2
20 Django==2.1.5                   20 Django==2.1.5
21 +--  7 lines: django-bulk-up +  21 +--  7 lines: django-bulk-u
28 django-timezone-field==3.0      28 django-timezone-field==3.0
29 djangorestframework==3.9.1      29 djangorestframework==3.9.1
30 docker==3.6.0                   30 docker==3.7.0
31 docker-pycreds==0.4.0           31 docker-pycreds==0.4.0
32 docutils==0.14                  32 docutils==0.14
33 ecdsa==0.13                     33 ecdsa==0.13
   ----------------------------    34 entrypoints==0.3
34 factory-boy==2.11.1             35 factory-boy==2.11.1
35 Faker==1.0.1                    36 Faker==1.0.2
36 flake8==3.6.0                   37 flake8==3.7.3
37 flake8-quotes==1.0.0            38 flake8-quotes==1.0.0
38 +-- 12 lines: flower==0.9.2  +  39 +-- 12 lines: flower==0.9.2
50 Jinja2==2.10                    51 Jinja2==2.10
51 jmespath==0.9.3                 52 jmespath==0.9.3
52 jsondiff==1.1.1                 53 jsondiff==1.1.1
53 jsonpickle==1.0                 54 jsonpickle==1.1
54 kombu==4.2.2.post1              55 kombu==4.2.2.post1
55 MarkupSafe==1.1.0               56 MarkupSafe==1.1.0
56 mccabe==0.6.1                   57 mccabe==0.6.1
57 mock==2.0.0                     58 mock==2.0.0
58 moto==1.3.7                     59 moto==1.3.7
59 openapi-codec==1.3.2            60 openapi-codec==1.3.2
60 opentracing==1.3.0              61 opentracing==1.3.0
61 opentracing-instrumentation>    62 opentracing-instrumentatio>
62 parameterized==0.6.3            63 parameterized==0.6.3
63 parso==0.3.2                    64 parso==0.3.2
64 pbr==5.1.1                      65 pbr==5.1.2
65 pexpect==4.6.0                  66 pexpect==4.6.0
66 pickleshare==0.7.5              67 pickleshare==0.7.5
67 prompt-toolkit==2.0.8           68 prompt-toolkit==2.0.8
68 psycopg2==2.7.7                 69 psycopg2==2.7.7
69 ptyprocess==0.6.0               70 ptyprocess==0.6.0
70 pyaml==18.11.0                  71 pyaml==18.11.0
71 pycodestyle==2.4.0              72 pycodestyle==2.5.0
72 pycountry==18.12.8              73 pycountry==18.12.8
73 pycparser==2.19                 74 pycparser==2.19
74 pycryptodome==3.7.2             75 pycryptodome==3.7.3
75 pyflakes==2.0.0                 76 pyflakes==2.1.0
            

«Решение» (↼_↼) Наивное решение

Пихаем выхлоп pip freeze в requirements.txt!

pip install celery

pip freeze > requirements.txt

...

pip install -r requirements.txt

Минусы pip-freeze решения

Решение requirement.in


echo 'celery'>requirements.in

pip install -U -r requirements.in

pip freeze > requirements.txt


pip install -r requirements.txt

pip freeze | diff requirements.txt - && echo 'Идентичны!'

Идентичны!

Всё ещё есть минусы

Недостатки таких «ручных» подходов, как «решение requirement.in» и «pip-freeze решение»:

Минус: много ручной работы

Последовательность команд для обновления «запиненных» зависимостей

cat requirements.in

celery>=4.2.1,<4.3

virtualenv ./temp-venv

./temp-venv/bin/python -m pip install -r requirements.in

./temp-venv/bin/python -m pip freeze > requirements.txt

rm -rf ./temp-venv

./.venv/bin/python -m pip install -r requirements.txt

Pipenv
как решение

Pipenv, почему?

Pipenv is a tool that aims to bring the best of all packaging worlds to the Python world.
Kenneth Reitz,
создатель Pipenv,
а также библиотеки requests.

Что такое Pipenv на самом деле?

Инструмент для приложений, не библиотек, который может:

Pipenv: Pipfile + Pipfile.lock

Pipenv: Pipfile

Список необходимых для установки пакетов декларируется в файле Pipfile, вместо привычного всем requirements.txt.

Pipfile:

Pipfile: что внутри?


[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
celery = "*"
[dev-packages]
"flake8" = "*"
[requires]
python_version = "3.6"

Pipfile: можно указывать свой PyPI


[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
celery = "*"
[dev-packages]
"flake8" = "*"
[requires]
python_version = "3.6"

Pipfile: указывается версия питона для проекта


...
[dev-packages]
"flake8" = "*"
[requires]
python_version = "3.6"
        

Pipfile: диапазоны версий, как в requiements.txt


...
[packages]
django = "*"
celery = "*"
elasticsearch = "<6.0.0,>=5.0.0"
[dev-packages]
"flake8" = "*"
moto = "==1.3.4"
[requires]
python_version = "3.6"

Pipfile: свой синтаксис для Git-ссылок


...
[packages]
django = "*"
celery = "*"
elasticsearch = "<6.0.0,>=5.0.0"
api_client = {git =
    "ssh://git@gitlab.com/something/api-client.git",
    ref = "v1.0.2"}
[dev-packages]
"flake8" = "*"
moto = "==1.3.4"

Pipfile: ещё раз структура файла


[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
celery = "*"
[dev-packages]
"flake8" = "*"
[requires]
python_version = "3.6"

Pipfile: несколько PyPI репозиториев


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

[[source]]
url = "http://pypi.famous.org/simple"
verify_ssl = false
name = "famous"

Pipfile: несколько PyPI репозиториев


[packages]
celery = {
    version="*",
    index="pypi"}
api_base_client = {
    version="*",
    index="famous"}
api_client = {
    git="ssh://git@gitlab.com/something/api-client.git",
    ref = "v1.0.2"}

Pipfile.lock, что внутри?

Pipfile.lock, что внутри? Пустой проект:

{ "_meta": {
    "hash": { "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7" },
    "pipfile-spec": 6,
    "requires": { "python_version": "3.6" },
    "sources": [
      { "name": "pypi",
        "url": "https://pypi.org/simple",
        "verify_ssl": true
      }
    ]
  },
  "default": {},
  "develop": {}
}

Pipfile.lock, что внутри? Пустой проект:

{ "_meta": {
    "hash": { "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7" },
    "pipfile-spec": 6,
    "requires": { "python_version": "3.6" },
    "sources": [
      { "name": "pypi",
        "url": "https://pypi.org/simple",
        "verify_ssl": true
      }
    ]
  },
  "default": {},
  "develop": {}
}

Pipfile.lock, что внутри? Пустой проект:

{ "_meta": {
    "hash": { "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7" },
    "pipfile-spec": 6,
    "requires": { "python_version": "3.6" },
    "sources": [
      { "name": "pypi",
        "url": "https://pypi.org/simple",
        "verify_ssl": true
      }
    ]
  },
  "default": {},
  "develop": {}
}

Pipfile.lock, что внутри? Пустой проект:

{ "_meta": {
    "hash": { "sha256": "415dfdcb118dd9bdfef17671cb7dcd78dbd69b6ae7d4f39e8b44e71d60ca72e7" },
    "pipfile-spec": 6,
    "requires": { "python_version": "3.6" },
    "sources": [
      { "name": "pypi",
        "url": "https://pypi.org/simple",
        "verify_ssl": true
      }
    ]
  },
  "default": {},
  "develop": {}
}

Pipfile.lock: Секция default для celery

"default": {
  "amqp": {
    "hashes": [ "sha256:9f181e4aef6562e6f9f45660578fc1556150ca06e836ecb9e733e6ea10b48464",
                "sha256:c3d7126bfbc640d076a01f1f4f6e609c0e4348508150c1f61336b0d83c738d2b"
    ],
    "version": "==2.4.0"
  },
  "billiard": {
    "hashes": [ "sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e"
    ],
    "version": "==3.5.0.5"
  },
  "celery": {
    "hashes": [ "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678",
                "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13"
    ],
    "index": "pypi",
    "version": "==4.2.1"
  },
  "kombu": {
    "hashes": [
      "sha256:1ef049243aa05f29e988ab33444ec7f514375540eaa8e0b2e1f5255e81c5e56d",
      "sha256:3c9dca2338c5d893f30c151f5d29bfb81196748ab426d33c362ab51f1e8dbf78"
    ],
    "version": "==4.2.2.post1"
  },
  "pytz": {
    "hashes": [
      "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
      "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
    ],
    "version": "==2018.9"
  },
  "vine": {
    "hashes": [
      "sha256:3cd505dcf980223cfaf13423d371f2e7ff99247e38d5985a01ec8264e4f2aca1",
      "sha256:ee4813e915d0e1a54e5c1963fde0855337f82655678540a6bc5996bca4165f76"
    ],
    "version": "==1.2.0"
  }
},

"default": {
  "amqp": {
    "hashes": [ "sha256:9f181e4aef6562e6f9f45660578fc1556150ca06e836ecb9e733e6ea10b48464",
                "sha256:c3d7126bfbc640d076a01f1f4f6e609c0e4348508150c1f61336b0d83c738d2b"
    ],
    "version": "==2.4.0"
  },
  "billiard": {
    "hashes": [ "sha256:42d9a227401ac4fba892918bba0a0c409def5435c4b483267ebfe821afaaba0e"
    ],
    "version": "==3.5.0.5"
  },
  "celery": {
    "hashes": [ "sha256:77dab4677e24dc654d42dfbdfed65fa760455b6bb563a0877ecc35f4cfcfc678",
                "sha256:ad7a7411772b80a4d6c64f2f7f723200e39fb66cf614a7fdfab76d345acc7b13"
    ],
    "index": "pypi",
    "version": "==4.2.1"
  },
  "kombu": {
    "hashes": [ "sha256:1ef049243aa05f29e988ab33444ec7f514375540eaa8e0b2e1f5255e81c5e56d",
                "sha256:3c9dca2338c5d893f30c151f5d29bfb81196748ab426d33c362ab51f1e8dbf78"
    ],
    "version": "==4.2.2.post1"
  },
  "pytz": {
    "hashes": [ "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9",
                "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"
    ],
    "version": "==2018.9"
  },
  "vine": {
    "hashes": [ "sha256:3cd505dcf980223cfaf13423d371f2e7ff99247e38d5985a01ec8264e4f2aca1",
                "sha256:ee4813e915d0e1a54e5c1963fde0855337f82655678540a6bc5996bca4165f76"
    ],
    "version": "==1.2.0"
  }
},

Управление пакетами


pipenv --help | только-интересные

  install    Installs provided packages and adds them to Pipfile, or (if none is given), installs all packages.
  graph      Displays currently-installed dependency graph information.
  uninstall  Un-installs a provided package and removes it from Pipfile.
  clean      Uninstalls all packages not specified in Pipfile.lock.
  lock       Generates Pipfile.lock.
  update     Runs lock, then sync.
  sync       Installs all packages specified in Pipfile.lock.
            

Управление пакетами: установка


pipenv install celery

...
Installing celery…
✔ Installation Succeeded 
Pipfile.lock (c07081) out of date, updating to (ca72e7)…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
✔ Success! 
Updated Pipfile.lock (c07081)!
Installing dependencies from Pipfile.lock (c07081)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 6/6 — 00:00:01

Управление пакетами: pipenv install

Два варианта.
  1. Указываем пакет, который нужно установить: pipenv install celery
    • добавляет celery в Pipfile
    • устанавливает celery
    • обновляет Pipfile.lock
  2. Не указываем пакет: pipenv install
    • устанавливает всё из Pipfile
    • обновляет/создаёт Pipfile.lock

Управление пакетами: удаление

pipenv uninstall celery

Uninstalling celery…
Uninstalling celery-4.2.1:
  Successfully uninstalled celery-4.2.1

pipenv run pip freeze

amqp==2.4.0
billiard==3.5.0.5
kombu==4.2.2.post1
pytz==2018.9
vine==1.2.0

Управление пакетами: удаление, чистое

pipenv uninstall celery

Uninstalling celery…
Uninstalling celery-4.2.1:
  Successfully uninstalled celery-4.2.1

pipenv clean

Uninstalling vine…
Uninstalling pytz…
Uninstalling kombu…
Uninstalling billiard…
Uninstalling amqp…

pipenv run pip freeze

Управление пакетами: pipenv graph


pipenv graph

celery==4.2.1
  - billiard [required: >=3.5.0.2,<3.6.0, installed: 3.5.0.5]
  - kombu [required: >=4.2.0,<5.0, installed: 4.2.2.post1]
    - amqp [required: >=2.1.4,<3.0, installed: 2.4.0]
      - vine [required: >=1.1.3, installed: 1.2.0]
  - pytz [required: >dev, installed: 2018.9]

            

Управление пакетами: pipenv graph 🕵 🔎


pipenv install factory-boy python-slugify==2.0.0

pipenv graph | grep -iE 'unidecode|'

factory-boy==2.11.1
  - Faker [required: >=0.7.0, installed: 1.0.2]
    - python-dateutil [required: >=2.4, installed: 2.7.5]
      - six [required: >=1.5, installed: 1.12.0]
    - six [required: >=1.10, installed: 1.12.0]
    - text-unidecode [required: ==1.2, installed: 1.2]
python-slugify==2.0.0
            

Управление пакетами: ещё немного CLI

Управление окружениями

Работа в виртуальном окружении

run   🤔   shell

Работа в виртуальном окружении: run

Единоразовое выполнение: pipenv run %SOMETHING%

pipenv run какой-то--скрипт.py

pipenv run pip freeze

pipenv run env

Работа в виртуальном окружении: shell

Интерактивный режим: pipenv shell

pipenv shell

какой-то--скрипт.py

pip freeze

env

pipenv.readthedocs.io

DataScience?

Anaconda

Почему Anaconda?

8/10 — поддеживают
5/10 — рекомендуют

Conda
менеджер пакетов и окружений для Anaconda

Conda
терминология
recipes , packages , channels

Conda: установка пакетов в окружение


conda create -n venv

conda activate venv

conda install pandas

или


conda install -m -n venv2 pandas

Conda: «фиксация» окружения


conda env export > environment.yml

cat environment.yml

name: venv
channels:
  - defaults
dependencies:
  - blas=1.0=mkl
  - ca-certificates=2019.1.23=0
  - certifi=2018.11.29=py37_0
  - intel-openmp=2019.1=144
  - libedit=3.1.20181209=hc058e9b_0
  - libffi=3.2.1=hd88cf55_4
  - libgcc-ng=8.2.0=hdf63c60_1
  - libgfortran-ng=7.3.0=hdf63c60_0
  - libstdcxx-ng=8.2.0=hdf63c60_1
  - mkl=2019.1=144
  - mkl_fft=1.0.10=py37ha843d7b_0
  - mkl_random=1.0.2=py37hd81dba3_0
  - ncurses=6.1=he6710b0_1
  - numpy=1.15.4=py37h7e9f1db_0
  - numpy-base=1.15.4=py37hde5b4d6_0
  - openssl=1.1.1a=h7b6447c_0
  - pandas=0.24.1=py37he6710b0_0
  - pip=19.0.1=py37_0
  - python=3.7.2=h0371630_0
  - python-dateutil=2.7.5=py37_0
  - pytz=2018.9=py37_0
  - readline=7.0=h7b6447c_5
  - setuptools=40.7.3=py37_0
  - six=1.12.0=py37_0
  - sqlite=3.26.0=h7b6447c_0
  - tk=8.6.8=hbc83047_0
  - wheel=0.32.3=py37_0
  - xz=5.2.4=h14c3975_4
  - zlib=1.2.11=h7b6447c_3
  - pip:
    - amqp==2.4.1
    - billiard==3.5.0.5
    - celery==4.2.1
    - kombu==4.3.0
    - vine==1.2.0
prefix: /opt/conda/envs/venv

            

Conda: не все пакеты есть в «каналах»


conda install -m -n venv2 celery

Solving environment: failed

PackagesNotFoundError: The following packages are not available from current channels:
                
  - celery
...
            

Conda: не обойтись без pip?


conda install -m -n venv pip

conda activate venv

pip install celery

↖ но зачем нам celery?

Pipenv?

Conda
несовместим с
Pipenv

Conda: что если не обойтись без pip?


conda env export | tail -n 9

  - xz=5.2.4=h14c3975_4
  - zlib=1.2.11=h7b6447c_3
  - pip:
    - amqp==2.4.1
    - billiard==3.5.0.5
    - celery==4.2.1
    - kombu==4.3.0
    - vine==1.2.0
prefix: /opt/conda/envs/venv

Conda: «реплицируем» окружение?





🤔

conda env export > v1.yml

conda env create -f v1.yml

CondaValueError: prefix already exists: /opt/conda/envs/venv

Conda: «реплицируем» окружение!





🤓

conda env export > v1.yml

head -1 v1.yml && tail -n 1 v1.yml

name: venv
prefix: /opt/conda/envs/venv

conda env create -f v1.yml -n venv2

Выводы

💪 Попробуйте сегодня Pipenv

🔭 Anaconda + Conda + Pip — путь датасаентистов


shurph      @

{