Gestire i pacchetti Python con Poetry

Gianluigi Tiesi

Cos'è poetry

Poetry è un tool per gestire le dipendenze e il packaging in Python.

Contrariamente a pip / requirements.txt, con poetry si possono dichiarare esclusivamente le dipendenze necessarie, poetry si occuperà di mantenere la lista delle versioni delle loro dipendenze.

Concettualmente si avvicina a npm o yarn, infatti anche poetry creerà un suo file .lock.

Installazione Linux / OS X

curl -sSL https://install.python-poetry.org | python3 -

Installerà poetry nella seguente cartella $HOME/.local/bin, dovrete aggiungerla al vostro path.

Se usate bash basta aggiungete la seguente riga al .bashrc:

export PATH=$PATH:$HOME/.local/bin

Aggiornamento

Se avete installato poetry con il suo installer basterà eseguire:

poetry self update

Configurazione

Per visualizzare le opzioni correnti basta eseguire:

poetry config --list
cache-dir = "/home/sherpya/.cache/pypoetry" experimental.new-installer = true installer.parallel = true virtualenvs.create = true virtualenvs.in-project = null virtualenvs.path = "{cache-dir}/virtualenvs" # /home/sherpya/.cache/pypoetry/virtualenvs

L'unica opzione che consiglio di cambiare è virtualenvs.in-project,

in questo caso poetry creerà il virtualenv nella cartella del progetto, con il nome .venv:

poetry config virtualenvs.in-project true

Ricordatevi di aggiungere /.venv al file .gitignore

Utilizzo

Vediamo adesso un esempio pratico: un progetto Django.

mkdir demo-django cd demo-django poetry init

Poetry ci farà qualche domanda, io ho cambiato solo la versione minima di Python in ^3.7

^3.7 significa che vanno bene tutte le major con 3 a partire dalla 3.7.

Package name [demo-django]: Version [0.1.0]: Description []: Author [Gianluigi Tiesi <sherpya@netfarm.it>, n to skip]: License []: Compatible Python versions [^3.9]: ^3.7 Would you like to define your main dependencies interactively? (yes/no) [yes] You can specify a package in the following forms: - A single name (requests) - A name and a constraint (requests@^2.23.0) - A git url (git+https://github.com/python-poetry/poetry.git) - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop) - A file path (../my-package/my-package.whl) - A directory (../my-package/) - A url (https://example.com/packages/my-package-0.1.0.tar.gz)
Search for package to add (or leave blank to continue): django Found 20 packages matching django Enter package # to add, or the complete package name if it is not listed: [0] Django [1] django-503 [2] django-scribbler-django2.0 [3] django-filebrowser-django13 [4] django-tracking-analyzer-django2 [5] django-jchart-django3-uvm [6] django-totalsum-admin-django3 [7] django-debug-toolbar-django13 [8] django-suit-redactor-django2 [9] django-django_csv_exports > 0 Enter the version constraint to require (or leave blank to use the latest version): ^3.2.10

Ho specificato la versione di Django altrimenti avrebbe preso l'ultima disponibile 4.0, non compatibile con python 3.7 (sospetto sia un bug).

Per terminare l'aggiunta interattiva, premere invio alla domanda:

Add a package:

Qui possiamo aggiungere ad esempio iPython

Would you like to define your development dependencies interactively? (yes/no) [yes] Search for package to add (or leave blank to continue): ipython Found 20 packages matching ipython Enter package # to add, or the complete package name if it is not listed: [0] ipython [1] twisted-ipython [2] ipython-futhark [3] ipython-cells [4] ipython-odoo [5] ipython2cwl [6] progressbar-ipython [7] ipython-ngql [8] ipython-elasticsearch [9] ipython-bg > 0 Enter the version constraint to require (or leave blank to use the latest version): Using version ^7.30.1 for ipython

Per terminare l'aggiunta interattiva, premere sempre invio alla domanda:

Add a package:

Ora poetry mostrerà un'anteprima del file che verrà generato:

[tool.poetry] name = "demo-django" version = "0.1.0" description = "" authors = ["Gianluigi Tiesi <sherpya@netfarm.it>"] [tool.poetry.dependencies] python = "^3.7" Django = "^3.2.10" [tool.poetry.dev-dependencies] ipython = "^7.30.1" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api"
Do you confirm generation? (yes/no) [yes]

A questo punto nella cartella troveremo il nostro file di configurazione del progetto pyproject.toml.

Per inizializzare il virtualenv e installare i pacchetti digitiamo il comando:

poetry install
Creating virtualenv demo in /tmp/demo/.venv Installing dependencies from lock file Package operations: 17 installs, 0 updates, 0 removals • Installing parso (0.8.3) • Installing ptyprocess (0.7.0) • Installing traitlets (5.1.1) • Installing wcwidth (0.2.5) • Installing asgiref (3.4.1) • Installing backcall (0.2.0) • Installing decorator (5.1.0) • Installing jedi (0.18.1) • Installing matplotlib-inline (0.1.3) • Installing pexpect (4.8.0) • Installing pickleshare (0.7.5) • Installing prompt-toolkit (3.0.24) • Installing pygments (2.10.0) • Installing pytz (2021.3) • Installing sqlparse (0.4.2) • Installing django (3.2.10) • Installing ipython (7.30.1)

Per eseguire un comando utilizzando il venv possiamo utilizzare:

poetry run ./manage.py

Ricordate il ./ perché altrimenti il comando non verrà trovato.

In alternativa è possibile attivare una shell simile al classico virtualenv:

poetry shell

Creiamo subito il nostro progetto Django anche se ovviamente possiamo aggiungere poetry ad un progetto esistente.

poetry shell django-admin startproject demo .

Se volessimo aggiungere una dipendenza:

poetry add djangorestframework # oppure poetry add djangorestframework@^3.12.4 # o ancora poetry add 'djangorestframework<4.0'
Using version ^3.12.4 for djangorestframework Updating dependencies Resolving dependencies... (0.1s) Writing lock file Package operations: 1 install, 0 updates, 0 removals • Installing djangorestframework (3.12.4)

Nel nostro file di configurazione sarà aggiunta la dipendenza:

[tool.poetry.dependencies] python = "^3.7" Django = "^3.2.10" djangorestframework = "^3.12.4"

Per rimuovere una dipendenza:

poetry remove djangorestframework
Updating dependencies Resolving dependencies... (0.1s) Writing lock file Package operations: 0 installs, 0 updates, 1 removal • Removing djangorestframework (3.12.4)

Il file poetry.lock bisogna versionarlo e committare ogni qual volta cambi per una modifica ad un pacchetto.

Per il deploy non occorre installare poetry sul server, possiamo farci generare un requirements.txt e versionarlo, così da poterlo utilizzare con pip.

poetry export --without-hashes > requirements.txt

il file generato:

asgiref==3.4.1; python_version >= "3.6" django==3.2.10; python_version >= "3.6" djangorestframework==3.12.4; python_version >= "3.5" pytz==2021.3; python_version >= "3.6" sqlparse==0.4.2; python_version >= "3.6" typing-extensions==4.0.1; python_version < "3.8" and python_version >= "3.6"

Conversione di un progetto esistente (Django)

Per la conversione di un progetto esistente occorre analizzare sia il file settings.py con i pacchetti di cui abbiamo bisogno, sia il file requirements.txt per le versioni.

INSTALLED_APPS = [ 'django.contrib.contenttypes', 'grappelli.dashboard', 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'oauth2_provider', 'corsheaders', 'rest_framework', 'django_filters', 'sequences.apps.SequencesConfig', 'tinymce', ... ]

I pacchetti da inserire nelle dipendenze sono i seguenti:

  • django (ovviamente)
  • grappelli
  • oauth2_provider -> django-oauth-toolkit
  • corsheaders -> django-cors-headers
  • rest_framework -> djangorestframework
  • django_filters -> django-filter
  • sequences.apps.SequencesConfig -> django-sequences
  • tinymce -> django-tinymce

ora guardiamo le versioni corrispondenti nel file requirements.txt

  • Django@3.2.5
  • django-grappelli@2.15.1
  • django-oauth-toolkit@1.5.0
  • django-cors-headers@3.7.0
  • djangorestframework@3.12.4
  • django-filter@2.4.0
  • django-sequences@2.6
  • django-tinymce@3.3.0

Procediamo con l'init e alla richiesta dei pacchetti inseriamoli come nella lista:

Search for package to add (or leave blank to continue): Django@3.2.5 Adding Django@3.2.5 Add a package: django-grappelli@2.15.1 Adding django-grappelli@2.15.1 Add a package: django-oauth-toolkit@1.5.0 Adding django-oauth-toolkit@1.5.0 Add a package: django-cors-headers@3.7.0 Adding django-cors-headers@3.7.0 Add a package: djangorestframework@3.12.4 Adding djangorestframework@3.12.4 Add a package: django-filter@2.4.0 Adding django-filter@2.4.0 Add a package: django-sequences@2.6 Adding django-sequences@2.6 Add a package: django-tinymce@3.3.0 Adding django-tinymce@3.3.0

Le dipendenze nel file pyproject.toml:

[tool.poetry.dependencies] python = "^3.7" Django = "3.2.5" django-grappelli = "2.15.1" django-oauth-toolkit = "1.5.0" django-cors-headers = "3.7.0" djangorestframework = "3.12.4" django-filter = "2.4.0" django-sequences = "2.6" django-tinymce = "3.3.0"

Ora

poetry install
Creating virtualenv django-demo in /tmp/django_demo/.venv Updating dependencies Resolving dependencies... (8.5s) Writing lock file Package operations: 24 installs, 0 updates, 0 removals • Installing pycparser (2.21) • Installing cffi (1.15.0) • Installing wrapt (1.13.3) • Installing asgiref (3.4.1) • Installing certifi (2021.10.8) • Installing charset-normalizer (2.0.9) • Installing cryptography (36.0.0) • Installing deprecated (1.2.13) • Installing idna (3.3) • Installing pytz (2021.3) • Installing sqlparse (0.4.2) • Installing urllib3 (1.26.7) • Installing django (3.2.5) • Installing jwcrypto (1.0) • Installing oauthlib (3.1.1) • Installing requests (2.26.0) • Installing six (1.16.0) • Installing django-cors-headers (3.7.0) • Installing django-filter (2.4.0) • Installing django-grappelli (2.15.1) • Installing django-oauth-toolkit (1.5.0) • Installing django-sequences (2.6) • Installing django-tinymce (3.3.0) • Installing djangorestframework (3.12.4)

dimenticavo...

poetry add psycopg2-binary
Using version ^2.9.2 for psycopg2-binary Updating dependencies Resolving dependencies... (3.6s) Writing lock file Package operations: 1 install, 0 updates, 0 removals • Installing psycopg2-binary (2.9.2)

Possiamo ora provare se funziona lanciando la shell oppure:

poetry run ./manage.py

Possiamo generare il file requirements.txt

poetry export --without-hashes > requirements.txt
asgiref==3.4.1; python_version >= "3.6" and python_version < "4.0" certifi==2021.10.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" cffi==1.15.0; python_version >= "3.6" charset-normalizer==2.0.9; python_full_version >= "3.6.0" and python_version >= "3" cryptography==36.0.0; python_version >= "3.6" deprecated==1.2.13; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" django-cors-headers==3.7.0; python_version >= "3.6" django-filter==2.4.0; python_version >= "3.5" django-grappelli==2.15.1 django-oauth-toolkit==1.5.0 django-sequences==2.6; python_version >= "3.5" and python_version < "4.0" django-tinymce==3.3.0 django==3.2.5; python_version >= "3.6" djangorestframework==3.12.4; python_version >= "3.5" idna==3.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.5" jwcrypto==1.0 oauthlib==3.1.1; python_version >= "3.6" psycopg2-binary==2.9.2; python_version >= "3.6" pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pytz==2021.3; python_version >= "3.6" and python_version < "4.0" requests==2.26.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" sqlparse==0.4.2; python_version >= "3.6" and python_version < "4.0" typing-extensions==4.0.1; python_version < "3.8" and python_version >= "3.6" urllib3==1.26.7; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" wrapt==1.13.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"

Aggiungiamo /.venv al file .gitignore e versioniamo pyproject.toml, poetry.lock e il requirements.txt generato.

Approfondimenti

Per ulteriori approfondimenti e la lista completa dei comandi, consiglio di consultare la documentazione ufficiale https://python-poetry.org/docs/