Obsah
1. PDM: moderní správce balíčků a virtuálních prostředí Pythonu
3. První spuštění nástroje PDM
4. Vytvoření kostry projektu bez dalších závislostí
5. Struktura nově vytvořeného projektu, soubor pyproject.toml
6. Přidání dalšího balíčku do projektu, zobrazení grafu závislostí
8. Malá odbočka: vliv zvolené minimální verze Pythonu
10. Nová podoba souboru pdm.lock
11. Explicitní specifikace verze balíčku
12. Strategie výběru verzí balíčku
13. Instalace balíčků používaných při vývoji, testování a ladění
14. Správa virtuálních prostředí z PDM
15. Využití adresářové struktury dle PEP-582 namísto virtuálního prostředí
16. Nová instalace všech balíčků (PEP-582)
17. Specifikace nových příkazů spouštěných přes pdm run
18. Spouštění jednotkových testů přes pdm run
19. Repositář s demonstračními příklady
1. PDM: moderní správce balíčků a virtuálních prostředí Pythonu
V současnosti pro ekosystém Pythonu existuje a je používáno hned několik správců balíčků a virtuálních prostředí. Pravděpodobně nejznámější je projekt Pipenv, který kombinuje možnosti pip a venv. Ovšem existují i další nástroje, například Poetry (ten používá pyproject.toml, ovšem není zcela kompatibilní s PEP-621) nebo Hatch (ten už je s PEP-621 kompatibilní). Dnes se ovšem seznámíme s jiným správcem, který se jmenuje PDM. Zajímavé je, že PDM umožňuje jak práci s virtuálním prostředím, tak i s lokální instalací balíčku podle PEP-582 (což je dnes oficiálně „zavržená“, ovšem stále užitečná vlastnost). Repositář projektu PDM naleznete na adrese https://github.com/pdm-project/pdm.
2. Instalace nástroje PDM
Existuje hned několik variant instalace nástroje PDM. Jedna z nabízených možností spočívá ve stažení instalačního skriptu (naprogramovaného v Pythonu) z webové stránky projektu PDM a v jeho následném spuštění. Tuto operaci lze provést jediným příkazem:
$ curl -sSL https://pdm.fming.dev/install-pdm.py | python3 -
Lepší je však si nejprve ověřit kontrolní součet stahovaného instalačního skriptu. Postup je snadný a nalezneme ho na stránkách samotného projektu PDM:
$ curl -sSLO https://pdm.fming.dev/install-pdm.py $ curl -sSL https://pdm.fming.dev/install-pdm.py.sha256 | shasum -a 256 -c - $ python3 install-pdm.py [options]
Jelikož je balíček PDM dostupný i na PyPi, je jeho instalace možná i přes nástroj pip, resp. pip3. Podívejme se, jak může tato forma instalace vypadat v praxi. Instalaci můžeme provést buď pro celý systém (PDM bude dostupný pro všechny uživatele) nebo pouze pro aktuálně přihlášeného uživatele:
$ pip3 install --user pdm
Jak je z následujícího výpisu prováděných operací patrné, závisí PDM na poměrně velkém množství dalších balíčků. Ovšem celková velikost nainstalovaných souborů nepřesáhne několik megabajtů:
Collecting pdm Downloading pdm-2.9.3-py3-none-any.whl (238 kB) |████████████████████████████████| 238 kB 2.1 MB/s Collecting resolvelib>=1.0.1 Downloading resolvelib-1.0.1-py2.py3-none-any.whl (17 kB) Collecting pyproject-hooks Downloading pyproject_hooks-1.0.0-py3-none-any.whl (9.3 kB) Collecting unearth>=0.10.0 Downloading unearth-0.11.1-py3-none-any.whl (41 kB) |████████████████████████████████| 41 kB 585 kB/s Collecting importlib-metadata>=3.6; python_version < "3.10" Downloading importlib_metadata-6.8.0-py3-none-any.whl (22 kB) Collecting findpython<1.0.0a0,>=0.4.0 Downloading findpython-0.4.0-py3-none-any.whl (20 kB) Collecting rich>=12.3.0 Downloading rich-13.6.0-py3-none-any.whl (239 kB) |████████████████████████████████| 239 kB 11.2 MB/s Collecting requests-toolbelt Downloading requests_toolbelt-1.0.0-py2.py3-none-any.whl (54 kB) |████████████████████████████████| 54 kB 1.5 MB/s Collecting cachecontrol[filecache]>=0.13.0 Downloading cachecontrol-0.13.1-py3-none-any.whl (22 kB) Collecting installer<0.8,>=0.7 Downloading installer-0.7.0-py3-none-any.whl (453 kB) |████████████████████████████████| 453 kB 2.4 MB/s Collecting python-dotenv>=0.15 Downloading python_dotenv-1.0.0-py3-none-any.whl (19 kB) Requirement already satisfied: blinker in /usr/lib/python3/dist-packages (from pdm) (1.4) Requirement already satisfied: importlib-resources>=5; python_version < "3.9" in ./.local/lib/python3.8/site-packages (from pdm) (6.1.0) Requirement already satisfied: tomli>=1.1.0; python_version < "3.11" in ./.local/lib/python3.8/site-packages (from pdm) (2.0.1) Requirement already satisfied: certifi in /usr/lib/python3/dist-packages (from pdm) (2019.11.28) Collecting shellingham>=1.3.2 Downloading shellingham-1.5.3-py2.py3-none-any.whl (9.7 kB) Collecting tomlkit<1,>=0.11.1 Downloading tomlkit-0.12.1-py3-none-any.whl (37 kB) Collecting platformdirs Downloading platformdirs-3.11.0-py3-none-any.whl (17 kB) Collecting virtualenv>=20 Downloading virtualenv-20.24.5-py3-none-any.whl (3.7 MB) |████████████████████████████████| 3.7 MB 6.6 MB/s Collecting packaging!=22.0,>=20.9 Downloading packaging-23.2-py3-none-any.whl (53 kB) |████████████████████████████████| 53 kB 1.9 MB/s Collecting requests>=2.25 Downloading requests-2.31.0-py3-none-any.whl (62 kB) |████████████████████████████████| 62 kB 1.2 MB/s Requirement already satisfied: zipp>=0.5 in ./.local/lib/python3.8/site-packages (from importlib-metadata>=3.6; python_version < "3.10"->pdm) (3.17.0) Collecting markdown-it-py>=2.2.0 Downloading markdown_it_py-3.0.0-py3-none-any.whl (87 kB) |████████████████████████████████| 87 kB 2.6 MB/s Requirement already satisfied: typing-extensions<5.0,>=4.0.0; python_version < "3.9" in ./.local/lib/python3.8/site-packages (from rich>=12.3.0->pdm) (4.7.1) Collecting pygments<3.0.0,>=2.13.0 Downloading Pygments-2.16.1-py3-none-any.whl (1.2 MB) |████████████████████████████████| 1.2 MB 8.9 MB/s Collecting msgpack>=0.5.2 Downloading msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (534 kB) |████████████████████████████████| 534 kB 6.7 MB/s Collecting filelock>=3.8.0; extra == "filecache" Downloading filelock-3.12.4-py3-none-any.whl (11 kB) Collecting distlib<1,>=0.3.7 Downloading distlib-0.3.7-py2.py3-none-any.whl (468 kB) |████████████████████████████████| 468 kB 5.7 MB/s Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/lib/python3/dist-packages (from requests>=2.25->unearth>=0.10.0->pdm) (1.25.8) Requirement already satisfied: idna<4,>=2.5 in /usr/lib/python3/dist-packages (from requests>=2.25->unearth>=0.10.0->pdm) (2.8) Collecting charset-normalizer<4,>=2 Downloading charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (137 kB) |████████████████████████████████| 137 kB 14.2 MB/s Collecting mdurl~=0.1 Downloading mdurl-0.1.2-py3-none-any.whl (10.0 kB) Installing collected packages: resolvelib, pyproject-hooks, packaging, charset-normalizer, requests, unearth, importlib-metadata, findpython, mdurl, markdown-it-py, pygments, rich, requests-toolbelt, msgpack, filelock, cachecontrol, installer, python-dotenv, shellingham, tomlkit, platformdirs, distlib, virtualenv, pdm Successfully installed cachecontrol-0.13.1 charset-normalizer-3.3.0 distlib-0.3.7 filelock-3.12.4 findpython-0.4.0 importlib-metadata-6.8.0 installer-0.7.0 markdown-it-py-3.0.0 mdurl-0.1.2 msgpack-1.0.7 packaging-23.2 pdm-2.9.3 platformdirs-3.11.0 pygments-2.16.1 pyproject-hooks-1.0.0 python-dotenv-1.0.0 requests-2.31.0 requests-toolbelt-1.0.0 resolvelib-1.0.1 rich-13.6.0 shellingham-1.5.3 tomlkit-0.12.1 unearth-0.11.1 virtualenv-20.24.5
3. První spuštění nástroje PDM
Po doufejme že úspěšné instalaci by mělo být možné spustit příkaz pdm. Nejnovější a v době vydání článku aktuální verze PDM je 2.9.3, což si můžeme velmi snadno ověřit (ostatně tato verze se vypisovala již při instalaci balíčků provedené v rámci předchozí kapitoly):
$ pdm --version PDM, version 2.9.3 Usage: pdm [-h] [-V] [-c CONFIG] [-v] [-I] [--pep582 [SHELL]] ... ____ ____ __ ___ / __ \/ __ \/ |/ / / /_/ / / / / /|_/ / / ____/ /_/ / / / / /_/ /_____/_/ /_/ Options: -h, --help Show this help message and exit. -V, --version Show the version and exit -c CONFIG, --config CONFIG Specify another config file path [env var: PDM_CONFIG_FILE] -v, --verbose Use `-v` for detailed output and `-vv` for more detailed -I, --ignore-python Ignore the Python path saved in .pdm-python. [env var: PDM_IGNORE_SAVED_PYTHON] --pep582 [SHELL] Print the command line to be eval'd by the shell Commands: add Add package(s) to pyproject.toml and install them build Build artifacts for distribution cache Control the caches of PDM completion Generate completion scripts for the given shell config Display the current configuration export Export the locked packages set to other formats fix Fix the project problems according to the latest version of PDM import Import project metadata from other formats info Show the project information init Initialize a pyproject.toml for PDM install Install dependencies from lock file list List packages installed in the current working set lock Resolve and lock dependencies publish Build and publish the project to PyPI remove Remove packages from pyproject.toml run Run commands or scripts with local packages loaded search Search for PyPI packages self (plugin) Manage the PDM program itself (previously known as plugin) show Show the package information sync Synchronize the current working set with lock file update Update package(s) in pyproject.toml use Use the given python version or path as base interpreter venv Virtualenv management
4. Vytvoření kostry projektu bez dalších závislostí
Podívejme se nyní, jakým způsobem můžeme s využitím nástroje PDM vytvořit kostru jednoduchého Pythonovského projektu. První projekt nebude mít žádné závislosti, ovšem jeho ostatní nastavení a metadata budou odpovídat skutečnému projektu.
Kostru projektu lze vytvořit interaktivně, tj. odpovědmi na několik otázek, které PDM položí. Pro tento účel v novém (prázdném) adresáři zavoláme následující příkaz:
$ pdm init
Nástroj PDM se v prvním kroku pokusí nalézt všechny nainstalované verze Pythonu a následně se zeptá, na které verzi bude projekt postaven (resp. na které bude závislý). Na mém nově nainstalovaném systému je nalezen pouze Python 3.11 (jediná verze), kterou ovšem PDM kvůli použití symlinků nabídne jako tři nezávislé verze:
Creating a pyproject.toml for PDM... Please enter the Python interpreter to use 0. /usr/bin/python (3.11) 1. /usr/bin/python3.11 (3.11) 2. /usr/bin/python3 (3.11) Please select (0): 2
$ ls -l /usr/bin/python* lrwxrwxrwx. 1 root root 9 Aug 28 02:00 /usr/bin/python -> ./python3 lrwxrwxrwx. 1 root root 10 Aug 28 02:00 /usr/bin/python3 -> python3.11 -rwxr-xr-x. 1 root root 16040 Aug 28 02:00 /usr/bin/python3.11
Mimochodem: na systému s Pythonem 2.x i Pythonem 3.x může výběr vypadat například následovně:
0. /usr/bin/python (3.8) 1. /usr/bin/python3.8 (3.8) 2. /usr/bin/python2.7 (2.7)
Dále se PDM zeptá, zda se má vytvořit virtuální prostředí Pythonu (virtualenv). Prozatím odpovíme, že ano, ovšem později se vrátíme k alternativě:
Would you like to create a virtualenv with /usr/bin/python3? [y/n] (y): y Virtualenv is created successfully at /home/ptisnovs/project1/.venv
Další otázka se týká struktury projektového souboru. Odpovíme, že se prozatím nebude jednat o (instalovatelnou) knihovnu:
Is the project a library that is installable? If yes, we will need to ask a few more questions to include the project name and build backend [y/n] (n): n
Následují otázky týkající se licence a autora aplikace či knihovny:
License(SPDX name) (MIT): MIT Author name (): Pavel Author email (): pavel@nowhere.com
A konečně musíme odpovědět na otázku, jaká minimální verze Pythonu bude vyžadována. Poté již bude vytvořena kostra projektu:
Python requires('*' to allow any) (>=3.11): >=3.7 Project is initialized successfully
Obrázek 1: Vytvoření kostry projektu.
5. Struktura nově vytvořeného projektu, soubor pyproject.toml
Adresář s kostrou projektu, který byl vytvořen po upřesňujících dotazech, o nichž jsme se zmínili ve čtvrté kapitole, by měl vypadat následovně:
. ├── __pycache__ ├── pyproject.toml ├── README.md ├── src │ └── example_package │ ├── __init__.py │ └── __pycache__ └── tests ├── __init__.py └── __pycache__ 7 directories, 4 files
Projekt obsahuje především soubor pyproject.toml, v němž jsou zapsána metadata o projektu, která odpovídají specifikaci PEP 621 – Storing project metadata in pyproject.toml. Pokud jste na otázky ve čtvrté kapitole odpovídali stejně jako autor článku, měl by projektový soubor mít následující obsah:
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] dependencies = [] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"}
Dále v projektu najdeme obligátní soubor .gitignore a taktéž soubor .pdm-python s odkazem na interpret Pythonu, který se bude používat:
/home/ptisnovs/pdb/project1/.venv/bin/python
Další informace o vytvořeném projektu lze získat tímto příkazem:
$ pdm info
Výsledek by mohl vypadat následovně (cesty se pochopitelně budou lišit):
PDM version: 2.9.3 Python Interpreter: /home/ptisnovs/project1/.venv/bin/python (3.11) Project Root: /home/ptisnovs/project1 Local Packages:
6. Přidání dalšího balíčku do projektu, zobrazení grafu závislostí
Nyní se pokusme do projektu přidat nějakou závislost, tj. knihovnu, kterou budeme v projektu používat. Tato operace je snadná, protože nám postačuje použít příkaz pdm add, kterému předáme název knihovny a popř. i požadovanou verzi. Pokud verzi neuvedeme, bude použita poslední dostupná validní verze (ovšem viz další text – ještě je totiž možné specifikovat takzvanou strategii):
$ pdm add sympy
Adding packages to default dependencies: sympy 🔒 Lock successful Changes are written to pyproject.toml. All packages are synced to date, nothing to do. 🎉 All complete!
Po provedení této operace se podívejme do projektového souboru pyproject.toml. Objevila se v něm nová sekce nazvaná příznačně dependencies:
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] dependencies = [ "sympy>=1.10.1", ] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"}
Seznam závislostí ovšem můžeme získat i z příkazového řádku bez nutnosti znát strukturu projektového souboru:
$ pdm list
Výsledek bude obsahovat i tranzitivní závislost – knihovnu mpmath, kterou sympy pro svoji činnost vyžaduje:
╭────────┬─────────┬──────────╮ │ name │ version │ location │ ├────────┼─────────┼──────────┤ │ mpmath │ 1.3.0 │ │ │ sympy │ 1.10.1 │ │ ╰────────┴─────────┴──────────╯
Přímé i tranzitivní závislosti si můžeme nechat zobrazit i formou stromové struktury, což je ukázáno na dalším příkladu:
$ pdm list --graph
S výsledkem:
sympy 1.10.1 [ required: >=1.10.1 ] └── mpmath 1.3.0 [ required: >=0.19 ]
Obrázek 2: Výsledek příkazu pdm list na terminálu, který dokáže zobrazovat obarvený text.
7. Soubor pdm.lock
V průběhu přidání (či aktualizace nebo odebrání) nějakého balíčku, na kterém projekt závisí, se vytvoří i soubor pojmenovaný pdm.lock. V tomto souboru jsou uloženy přesné informace o konkrétních verzích přímých i tranzitivních závislostí, včetně otisků souborů s archivy těchto balíčků. Jedná se o velmi důležité informace, které mohou pomoci k tomu, aby se na jiném počítači (třeba na stage či produkčním serveru) nainstalovaly ty samé balíčky, jako na počítači vývojáře a/nebo testera. A v neposlední řadě to vede k větší bezpečnosti při sestavování projektů (omezí se útoky typu typosquatting atd.).
Zajímavé bude zjistit, jak tento soubor vypadá:
# This file is @generated by PDM. # It is not intended for manual editing. [metadata] groups = ["default"] cross_platform = true static_urls = false lock_version = "4.3" content_hash = "sha256:c19bb0a4a0a179b5e92d875ea3704d5564d319483f9497918302f97aa96a7d83" [[package]] name = "mpmath" version = "1.3.0" summary = "Python library for arbitrary-precision floating-point arithmetic" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, ] [[package]] name = "sympy" version = "1.10.1" requires_python = ">=3.7" summary = "Computer algebra system (CAS) in Python" dependencies = [ "mpmath>=0.19", ] files = [ {file = "sympy-1.10.1-py3-none-any.whl", hash = "sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130"}, {file = "sympy-1.10.1.tar.gz", hash = "sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b"}, ]
8. Malá odbočka: vliv zvolené minimální verze Pythonu
Připomeňme si, že seznam přímých i tranzitivních závislostí vypadá u našeho projektu následovně:
╭────────┬─────────┬──────────╮ │ name │ version │ location │ ├────────┼─────────┼──────────┤ │ mpmath │ 1.3.0 │ │ │ sympy │ 1.10.1 │ │ ╰────────┴─────────┴──────────╯
To je zajímavé, protože při pohledu na vydání knihovny sympy (viz https://github.com/sympy/sympy/releases) snadno zjistíme, že poslední stabilní verze je 1.12 a nikoli 1.10.1. Pokusme se tedy o explicitní instalaci novější verze:
$ pdm add sympy==1.12
V tomto případě se instalace (resp. ugrade) nepodaří, protože novější verze knihovny Sympy vyžadují i novější verzi samotného Pythonu:
Adding packages to default dependencies: sympy==1.12 🔒 Lock failed Unable to find a resolution because the following dependencies don't work on all Python versions in the range of the project's `requires-python`: >=3.7. python>=3.8 (from <Candidate sympy@1.12 from https://pypi.org/simple/sympy/>) A possible solution is to change the value of `requires-python` in pyproject.toml to >=3.8. See /tmp/pdm-lock-ssanlsgo.log for detailed debug log.
To znamená, že i když používáme Python 3.11, projekt je stále nastaven tak, že minimální kompatibilní verze Pythonu je 3.7:
requires-python = ">=3.7"
Změna souboru pyproject je snadná – postačuje totiž výše uvedený řádek změnit na:
requires-python = ">=3.8"
Nyní již můžeme spustit příkaz pro instalaci konkrétní verze knihovny sympy:
$ pdm add sympy==1.12
Ve skutečnosti u nového projektu můžeme použít i:
$ pdm add sympy
Instalace by měla proběhnout bez problémů, a to s verzí Sympy 1.12:
Adding packages to default dependencies: sympy 🔒 Lock successful Changes are written to pyproject.toml. Synchronizing working set with resolved packages: 2 to add, 0 to update, 0 to remove ✔ Install mpmath 1.3.0 successful ✔ Install sympy 1.12 successful 🎉 All complete!
9. Projekt s více závislostmi
Samozřejmě nám nic nebrání v tom, abychom si do projektu přidali další balíček, na kterém bude projekt záviset. Zvolit můžeme například známý balíček requests. Při jeho instalaci prozatím nebudeme specifikovat verzi:
$ pdm add requests
Průběh konfigurace projektu s přidáním nového balíčku:
Adding packages to default dependencies: requests 🔒 Lock successful Changes are written to pyproject.toml. Synchronizing working set with resolved packages: 5 to add, 0 to update, 0 to remove ✔ Install idna 3.4 successful ✔ Install requests 2.31.0 successful ✔ Install certifi 2023.7.22 successful ✔ Install urllib3 2.0.6 successful ✔ Install charset-normalizer 3.3.0 successful 🎉 All complete!
V projektovém souboru pyproject.toml se objeví pouze dvě přímé závislosti (podle očekávání):
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] dependencies = [ "sympy>=1.10.1", "requests>=2.31.0", ] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"}
Ve skutečnosti však naroste počet tranzitivních závislostí, což si opět můžeme ověřit jejich výpisem ve formě tabulky nebo zobrazením stromové struktury:
$ pdm list ╭────────────────────┬───────────┬──────────╮ │ name │ version │ location │ ├────────────────────┼───────────┼──────────┤ │ charset-normalizer │ 3.3.0 │ │ │ mpmath │ 1.3.0 │ │ │ sympy │ 1.10.1 │ │ │ idna │ 3.4 │ │ │ requests │ 2.31.0 │ │ │ certifi │ 2023.7.22 │ │ │ urllib3 │ 2.0.6 │ │ ╰────────────────────┴───────────┴──────────╯
Zobrazení přímých i tranzitivních závislostí formou stromové struktury:
$ pdm list --graph requests 2.31.0 [ required: >=2.31.0 ] ├── certifi 2023.7.22 [ required: >=2017.4.17 ] ├── charset-normalizer 3.3.0 [ required: <4,>=2 ] ├── idna 3.4 [ required: <4,>=2.5 ] └── urllib3 2.0.6 [ required: <3,>=1.21.1 ] sympy 1.10.1 [ required: >=1.10.1 ] └── mpmath 1.3.0 [ required: >=0.19 ]
10. Nová podoba souboru pdm.lock
Soubor pdm.lock se po přidání nového balíčku kvůli tranzitivním závislostem zvětšil až na 17kB. Z tohoto důvodu si uvedeme pouze jeho začátek a konec (s několika balíčky). Struktura souboru však zůstává stále stejná:
[metadata] groups = ["default"] cross_platform = true static_urls = false lock_version = "4.3" content_hash = "sha256:286f1c206e4a496e9dc40642617bcc789fa52bcac8f2dc52b9090d276c4162e4" [[package]] name = "certifi" version = "2023.7.22" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." files = [ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] name = "charset-normalizer" version = "3.3.0" requires_python = ">=3.7.0" summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." files = [ {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, ... ... ... [[package]] name = "urllib3" version = "2.0.6" requires_python = ">=3.7" summary = "HTTP library with thread-safe connection pooling, file post, and more." files = [ {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ]
11. Explicitní specifikace verze balíčku
Při přidávání balíčku do projektu můžeme explicitně specifikovat požadovanou verzi tohoto balíčku. Formát zápisu verze je v ekosystému Pythonu již standardizován – konkrétní verze se zapisuje za znaky rovnosti:
$ pdm add requests==2.0.0
Projektový soubor pyproject.toml bude nyní vypadat následovně:
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] dependencies = [ "sympy>=1.10.1", "requests==2.0.0", ] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"}
Vzhledem k tomu, že requests nemá žádné tranzitivní závislosti (na rozdíl od novějších verzí tohoto balíčku), bude soubor pdm.lock stále velmi jednoduchý:
# This file is @generated by PDM. # It is not intended for manual editing. [metadata] groups = ["default"] cross_platform = true static_urls = false lock_version = "4.3" content_hash = "sha256:b5aed5d117e640360335e67f9b9a815150e717d992ecfdf1663587226da6b6b7" [[package]] name = "mpmath" version = "1.3.0" summary = "Python library for arbitrary-precision floating-point arithmetic" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, ] [[package]] name = "requests" version = "2.0.0" summary = "Python HTTP for Humans." files = [ {file = "requests-2.0.0-py2.py3-none-any.whl", hash = "sha256:2ef65639cb9600443f85451df487818c31f993ab288f313d29cc9db4f3cbe6ed"}, {file = "requests-2.0.0.tar.gz", hash = "sha256:78536038f54cff6ade3be6863403146665b5a3923dd61108c98d8b64141f9d70"}, ] [[package]] name = "sympy" version = "1.10.1" requires_python = ">=3.7" summary = "Computer algebra system (CAS) in Python" dependencies = [ "mpmath>=0.19", ] files = [ {file = "sympy-1.10.1-py3-none-any.whl", hash = "sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130"}, {file = "sympy-1.10.1.tar.gz", hash = "sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b"}, ]
Požadovanou verzi balíčku lze ovšem specifikovat i dalšími způsoby:
minimální: >2.21.0 kompatibilní: >2.21.0,<3.0.0
12. Strategie výběru verzí balíčku
Výběr verze balíčku, který se do projektu přidává, závisí na zvolené strategii. Ta se specifikuje následujícími čtyřmi volbami (přepínači):
--save-minimum --save-compatible --save-exact --save-wildcard
Strategie „minimum“ ve k tomu, že se do projektového souboru uloží informace o tom, že bude vyžadována konkrétní stabilní verze či verze vyšší:
$ pdm add --save-minimum requests
Výsledek vypadá následovně:
dependencies = [ "requests>=2.31.0", ]
Strategie „compatible“ umožňuje specifikovat konkrétní verzi, popř. verze s ní kompatibilní:
$ pdm add --save-compatible requests
Výsledek:
dependencies = [ "requests~=2.31", ]
Nejjednodušší je strategie „exact“, která v důsledku vede k tomu, že bude specifikována jediná konkrétní verze balíčku:
$ pdm add --save-exact requests
Výsledek:
dependencies = [ "requests==2.31.0", ]
A konečně v poslední variantě „wildcard“ nemusí být ve výsledku verze balíčku vůbec uvedená:
$ pdm add --save-wildcard requests
Výsledek:
dependencies = [ "requests", ]
13. Instalace balíčků používaných při vývoji, testování a ladění
Mnoho balíčků, s nimiž se v projektu nějakým způsobem pracuje, je ve skutečnosti použito „pouze“ při vývoji, testování a ladění aplikace. Takové balíčky tedy není nutné (a ani to není vhodné) instalovat při nasazování aplikace na stage či produkční prostředí. Na druhou stranu by však projekt měl obsahovat informace o těchto balíčcích. V případě, že se používá nástroj PDM, je možné „vývojářské“ balíčky nainstalovat s použitím přepínače -d, kterým se tyto balíčky odliší od produkčních balíčků nutných pro běh aplikace.
Poměrně dobrým příkladem může být balíček pytest využívaný pro spouštění a analýzu jednotkových testů. Takový balíček nainstalujeme následujícím způsobem:
$ pdm add -d pytest
Výsledkem bude, že se v projektovém souboru pyproject.toml objeví nová sekce nazvaná tool.pdm.dev-dependencies (viz zvýrazněnou část):
[project] name = "" version = "" description = "" authors = [ {name = "Pavel Tisnovsky", email = "tisnik@nowhere.com"}, ] dependencies = [ "sympy>=1.12", ] requires-python = ">=3.11" readme = "README.md" license = {text = "MIT"} [tool.pdm.dev-dependencies] dev = [ "pytest>=7.4.2", ]
Naproti tomu soubor pdm.lock informaci o tomto balíčku obsahovat nebude:
# This file is @generated by PDM. # It is not intended for manual editing. [metadata] groups = ["default"] cross_platform = true static_urls = false lock_version = "4.3" content_hash = "sha256:5cd901a11186547468a967f40df61dd8cb8cf0b5bb3499bbf66125887548c0f7" [[package]] name = "mpmath" version = "1.3.0" summary = "Python library for arbitrary-precision floating-point arithmetic" files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, ] [[package]] name = "sympy" version = "1.12" requires_python = ">=3.8" summary = "Computer algebra system (CAS) in Python" dependencies = [ "mpmath>=0.19", ] files = [ {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, ]
14. Správa virtuálních prostředí z PDM
Ještě jednou se vraťme k příkazu pro zobrazení informací o projektu, zejména o jeho prostředí. Jedná se o příkaz:
$ pdm info
Výsledek by mohl vypadat následovně (cesty se pochopitelně budou lišit):
PDM version: 2.9.3 Python Interpreter: /home/ptisnovs/project1/.venv/bin/python (3.11) Project Root: /home/ptisnovs/project1 Local Packages:
V tuto chvíli je pro nás důležitý řádek s cestou k interpretru Pythonu. Ten nám totiž mj. říká, že při spouštění projektu (nebo jeho částí, popř. testů atd.) bude využito virtuální prostředí Pythonu. To je v našem případě umístěno v podadresáři .vent. PDM nám umožňuje nechat si vypsat virtuální prostředí, která jsou v daný okamžik dostupná, a to konkrétně příkazem:
$ pdm venv list Virtualenvs created with this project: * in-project: /home/ptisnovs/x/project1/.venv
Virtuální prostředí je do stromové struktury s projektem zakomponováno následovně:
├── __pycache__ ├── src │ └── example_package │ └── __pycache__ ├── tests │ └── __pycache__ └── .venv ├── bin ├── lib │ └── python3.11 │ └── site-packages │ ├── mpmath │ ├── mpmath-1.3.0.dist-info │ ├── __pycache__ │ ├── sympy │ └── sympy-1.10.1.dist-info ├── lib64 │ └── python3.11 │ └── site-packages └── share └── man └── man1
Možné je nechat si vytvořit i další virtuální prostředí. Pokud však již adresář .venv existuje, bude další virtuální prostředí vytvořeno v adresáři specifikovaném konfigurační volbou venv.location (tu jsme neměnili). Jméno nově vytvořeného podadresáře s virtuálním prostředím přitom bude mít tento formát: jméno_projektu-hash-verze_pythonu. Ostatně můžeme se pokusit o vytvoření ještě jednoho virtuálního prostředí pro náš projekt a pro zvolenou verzi Pythonu:
$ pdm venv create --name testing 3.11 Virtualenv /home/ptisnovs/.local/share/pdm/venvs/project1-MLVA-_ZK-testing is created successfully
Struktura tohoto nového virtuální prostředí bude shodná se strukturou „lokálního“ virtuálního prostředí:
/home/ptisnovs/.local/share/pdm/venvs └── project1-MLVA-_ZK-testing ├── bin ├── lib │ └── python3.11 │ └── site-packages └── lib64 └── python3.11 └── site-packages
Virtuální prostředí je možné i smazat:
$ pdm venv remove in-project Virtualenvs created with this project: Will remove: /home/ptisnovs/x/project1/.venv, continue? [y/n] (y): y Removed successfully!
15. Využití adresářové struktury dle PEP-582 namísto virtuálního prostředí
Správce PDM umožňuje namísto virtuálního prostředí (tedy vlastně typicky podadresáře .venv) pracovat s adresářovou strukturou definovanou v PEP-582. Toto PEP sice bylo oficiálně zamítnuto, což ale neznamená, že jím definovanou adresářovou strukturu nelze použít (ostatně PDM byl vytvořen právě proto, aby existovala implementace PEP-582).
Při vytváření nového projektu postačuje na otázku:
Would you like to create a virtualenv with /usr/bin/python? [y/n] (y): n
odpovědět záporně:
You are using the PEP 582 mode, no virtualenv is created. For more info, please visit https://peps.python.org/pep-0582/
Následně do projektu přidáme nějakou závislost:
$ pdm add sympy Adding packages to default dependencies: sympy 🔒 Lock successful Changes are written to pyproject.toml. Synchronizing working set with resolved packages: 2 to add, 0 to update, 0 to remove ✔ Install mpmath 1.3.0 successful ✔ Install sympy 1.12 successful 🎉 All complete!
Výsledkem by měla být následující adresářová struktura, v níž se namísto .venv objevuje podadresář __pypackages__:
. ├── __pycache__ ├── __pypackages__ │ └── 3.11 │ ├── bin │ ├── include │ ├── lib │ │ ├── mpmath │ │ ├── mpmath-1.3.0.dist-info │ │ ├── sympy │ │ └── sympy-1.12.dist-info │ └── share │ └── man ├── src │ └── example_package │ └── __pycache__ └── tests └── __pycache__
16. Nová instalace všech balíčků (PEP-582)
V případě potřeby, například pokud dojde k problémům s updaty balíčků (což se nastává příliš často), je možné jednoduše celý adresář __pypackages__ smazat:
$ rm -rf __pypackages__
Struktura projektu se nám zjednoduší na:
. ├── __pycache__ ├── src │ └── example_package │ └── __pycache__ └── tests └── __pycache__
Nová instalace všech balíčků v požadovaných verzích se provede příkazem:
$ pdm install -v STATUS: Resolving packages from lockfile... STATUS: Fetching hashes for resolved packages... Synchronizing working set with resolved packages: 2 to add, 0 to update, 0 to remove unearth.preparer: Using cached <Link https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl (from https://pypi.org/simple/mpmath/)> unearth.preparer: Using cached <Link https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl (from https://pypi.org/simple/sympy/)> ✔ Install mpmath 1.3.0 successful ✔ Install sympy 1.12 successful
Což povede k obnovení původní struktury projektu:
. ├── __pycache__ ├── __pypackages__ │ └── 3.11 │ ├── bin │ ├── include │ ├── lib │ │ ├── mpmath │ │ ├── mpmath-1.3.0.dist-info │ │ ├── sympy │ │ └── sympy-1.12.dist-info │ └── share │ └── man ├── src │ └── example_package │ └── __pycache__ └── tests └── __pycache__
17. Specifikace nových příkazů spouštěných přes pdm run
Do projektového souboru pyproject.toml můžeme přidat i specifikaci nových (prakticky libovolných) příkazů, které se spouští přes pdm run. Příkladem může být příkaz start pro spuštění aplikace (samozřejmě s nastaveným prostředím atd.):
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] dependencies = [] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"} [tool.pdm.scripts] start = "src/example_package/main.py"
Nový příkaz start nyní můžeme zavolat takto:
$ pdm run start
18. Spouštění jednotkových testů přes pdm run
Do projektového souboru pyproject.toml samozřejmě můžeme přidat i další vlastní příkazy, například příkaz test určený pro spuštění jednotkových testů. Úprava bude triviální:
[project] name = "" version = "" description = "" authors = [ {name = "Pavel", email = "pavel@nowhere.com"}, ] [tool.pdm.dev-dependencies] dev = [ "pytest>=7.4.2", ] requires-python = ">=3.7" readme = "README.md" license = {text = "MIT"} [tool.pdm.scripts] start = "src/example_package/main.py" test = "pytest"
A takto bude vypadat spuštění testů:
============================= test session starts ============================== platform linux -- Python 3.11.5, pytest-7.4.2, pluggy-1.2.0 rootdir: /home/ptisnovs/x/project7 collected 0 items ============================ no tests ran in 0.00s =============================
19. Repositář s demonstračními příklady
Všechny Pythonovské projekty, které jsme si vytvořili, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalovánu některou z podporovaných verzí Pythonu 3 a pochopitelně taktéž PDM:
20. Odkazy na Internetu
- Stránka projektu PDM
https://pdm.fming.dev/latest/ - PDF na GitHubu
https://github.com/pdm-project/pdm - PEP 582 – Python local packages directory
https://peps.python.org/pep-0582/ - PDM na PyPi
https://pypi.org/project/pdm/ - Which Python package manager should you use?
https://towardsdatascience.com/which-python-package-manager-should-you-use-d0fd0789a250 - How to Use PDM to Manage Python Dependencies without a Virtual Environment
https://www.youtube.com/watch?v=qOIWNSTYfcc - What are the best Python package managers?
https://www.slant.co/topics/2666/~best-python-package-managers - PEP 621 – Storing project metadata in pyproject.toml
https://peps.python.org/pep-0621/ - Pick a Python Lockfile and Improve Security
https://blog.phylum.io/pick-a-python-lockfile-and-improve-security/ - PyPA specifications
https://packaging.python.org/en/latest/specifications/ - Creation of virtual environments
https://docs.python.org/3/library/venv.html - How to Use virtualenv in Python
https://learnpython.com/blog/how-to-use-virtualenv-python/ - Python Virtual Environments: A Primer
https://realpython.com/python-virtual-environments-a-primer/ - virtualenv Cheatsheet
https://aaronlelevier.github.io/virtualenv-cheatsheet/ - Installing Python Modules
https://docs.python.org/3/installing/index.html