Codebeez

uv als vervanger voor poetry/pyenv/pipx

Dit is grotendeels een update op delen van de reproduceerbare Python-ontwikkelomgeving die ik eerder heb beschreven. Toch is deze blogpost prima als losstaand artikel te lezen.

Destijds gebruikte ik een combinatie van poetry om verschillende virtual environments voor verschillende projecten te beheren, pyenv om verschillende Python-versies af te handelen, en pipx om Python-CLI’s los van een project te beheren. Met de recente updates aan uv (van dezelfde makers als ruff, waar je vast al van weet - maar zo niet, dan raad ik het sterk aan als vervanger voor flake8, isort en black) is de functionaliteit van al deze tools echter samengebracht in één enkele tool.

uv werd al een tijd geprezen als een snelle vervanger van pip, maar onlangs is de reikwijdte ervan flink uitgebreid (door de integratie van rye-functionaliteit, een tool die door Astral is overgenomen). Dit biedt een mooie gelegenheid om de Python-toolchain op te schonen.

Dit wordt geen gedetailleerde review van de uv-functionaliteit, want die kun je gewoon nalezen in de documentatie. Maar ik wilde mijn vorige blog wel bijwerken nu uv gebruikt kan worden om veel (zo niet alle) Python-tools voor dependencybeheer te vervangen.

Dependencybeheer

uv vervangt poetry als systeem voor dependencybeheer. Er zijn meerdere bestanden waarin dependencies opgegeven kunnen worden, maar met het commando uv init om een skeletproject aan te maken (vergelijkbaar met poetry init) wordt een pyproject.toml-bestand gegenereerd. Daarin voegt uv add (vergelijkbaar met poetry add) standaard dependencies toe.

Omdat beide tools pyproject.toml gebruiken, zou je kunnen aannemen dat migreren van poetry naar uv makkelijk is, maar de configuratie verschilt tussen de tools, dus houd daar rekening mee voordat je migreert. We krijgen ook een uv.lock-bestand in plaats van een poetry.lock, en die twee zijn niet compatibel.

Uit de documentatie kun je waarschijnlijk goed genoeg afleiden hoe je deze tool bestuurt, dus ik wilde alleen een aantal verschillen met poetry uitlichten die ik persoonlijk echt prettig vind:

  • Standaard plaatst uv de virtual environment bij de repositorymap onder .venv. Naar verluidt heeft poetry hier ook een configuratie-optie voor, maar die heb ik nooit gevonden. Ik geef hieraan de voorkeur omdat het poetry shell overbodig maakt.

Met uv run (de tegenhanger van poetry run) werkt het en wordt deze virtual environment gebruikt, maar ik heb het volgende in mijn config.fish staan om het wisselen tussen virtual environments makkelijker te maken:

abbr -a venv source .venv/bin/activate.fish
  • De --dev-flag bestaat voor uv add en is niet deprecated. Er zijn ook optionele dependencies die op weer een andere manier opgegeven kunnen worden (namelijk onder de node [project.optional-dependencies]). Dit is een beetje redundant, maar het gemak bevalt me.

  • Net als in poetry kun je lokale dependencies opgeven, maar die vallen onder hun eigen configuratie. Een voorbeeld ziet er zo uit:

[tool.uv.sources]
stockallocationmodel = { path = "../stockallocationmodel", editable = true }
network_planner = { path = "../networkplanning/network_planner", editable = true}

De uv-documentatie zelf raadt aan om workspaces te gebruiken voor projecten die uit meerdere Python-packages bestaan, maar ik heb die zelf nog niet de kans gehad om uit te proberen.

  • uv heeft geen eigen build-backend, terwijl poetry die wel heeft. Met uv init krijgen we standaard de setup voor hatchling. Zorg dat je deze regels opneemt, want anders zullen builds volgens mij waarschijnlijk om duistere redenen mislukken:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Doordat het in Rust is geschreven en daarmee blazingly fast is, adverteert het met de mogelijkheid om dependencies veel sneller op te halen en te installeren dan de concurrent. Persoonlijk valt alles wat langer dan 10 seconden duurt in mijn agenda onder koffiepauze, dus voor dat argument ben ik niet zo gevoelig.

Python-versiebeheer

Ik houd persoonlijk erg van poetry en zou niet overstappen alleen voor iets snellere installatietijden. poetry heeft echter geen ingebouwde manier om verschillende Python-versies te beheren.

Hiervoor kunnen we pyenv gebruiken, maar dat beviel me niet zo vanwege het redundante .python-version-bestand. Ik worstelde vaak met het handmatig synchroniseren van de twee en met pyenv interacteren om de versies goed te krijgen.

Met uv werkt het wisselen van Python-versies gewoon. Veel commando’s hebben een --python-flag om versies op te geven, en het commando uv python heeft een hele set subcommando’s die zijn gewijd aan het beheren van Python-versies.

Er is ingebouwde ondersteuning voor cpython, uiteraard, maar ook voor pypy. Er wordt fuzzy search geboden, dus uv python install pypy geeft me de nieuwste pypy-versie voor mijn architectuur.

In pyproject.toml is er een speciale requires-python-instelling waarmee je minimale, maximale en exacte Python-versies kunt opgeven. Complexere versiegrenzen, zoals '>=3.8,<3.10', zijn ook mogelijk. Maar wat anders is, is dat de rest van de uv-commando’s daadwerkelijk proactief die specifieke Python-versie zullen aanbieden, in tegenstelling tot poetry, dat je alleen verbiedt door te gaan totdat je zelf uitvogelt hoe je aan de juiste versie komt.

Deze functionaliteit is voor mij de belangrijkste reden om over te stappen, want het is enorm handig om niet meer met Python-versies te hoeven worstelen. Het feit dat het twee tools tot één samenvoegt, betekent dat het pyenv overbodig maakt.

Je kunt ook uv python pin gebruiken om een .python-version-bestand aan te maken, en uv als pyenv-vervanger inzetten voor poetry-projecten (als je dat om de een of andere reden echt zou willen).

Python-CLI-tools

Voor tools in Python die je niet aan een specifieke virtual environment wilt koppelen, bestaat de set commando’s uv tool. Dit is in feite een pipx-vervanger, en uv heeft de alias uvx om dat punt nog eens te onderstrepen.

De belangrijkste commando’s werken zoals verwacht. Dat wil zeggen, vergelijkbaar met pipx, die op hun beurt vergelijkbaar werken met pip (uv heeft ook uv pip, wat de pip-vervanger is). list, install, upgrade en uninstall zijn allemaal aanwezig, zoals verwacht. Helaas is pipx inject dat niet (dit is een commando waarmee je extra packages kunt toevoegen aan de virtual environments die pipx rond een tool opzet, wat handig kan zijn als je meer controle wilt over de dependencies van een Python-tool).

Op unix-systemen plaatst uv symbolische links in je .local/bin-map die verwijzen naar de tools die het installeert, zodat deze tools direct vanuit de shell beschikbaar zijn. Je kunt echter ook uvx {toolname} gebruiken als je het liever zo aanroept, of in geval van path-conflicten.

Een interessant punt om op te merken is dat uvx {toolname} automatisch tools ophaalt die het lokaal niet kan vinden. Bijvoorbeeld: uvx ruff zou ruff uit zijn eigen cache halen of downloaden, en het als eenmalig commando uitvoeren. Dit voegt het niet toe aan je lijst met tools (uv tool list). Persoonlijk ben ik geen fan van commando’s die op deze manier meerdere dingen doen, maar het is wel slim hoe uv zijn eigen cache benut om tools direct beschikbaar te maken.

Conclusie

Dit was bedoeld als aanbevelings- en updateblog in plaats van een volledige feature-review.

Niet genoemd hier is dat uv veel andere Python-tools vervangt, zoals pip of de virtualenv-package, die op hun beurt al vervangers hebben die volledig of gedeeltelijk overlappen met de functionaliteit van de tools die ik in deze blog noemde. Met dat in gedachten zou je kunnen stellen dat uv weer een nieuwe incarnatie is van het concurrerende standaarden-probleem, waardoor tools zich oneindig blijven vermenigvuldigen.

Toch was ik vrijwel vanaf de dag dat ik uv begon te gebruiken overtuigd. Ik geef toe dat ik makkelijk te overtuigen was, omdat de interop tussen poetry en pyenv me al een tijd irriteerde, maar ik ben nog steeds erg onder de indruk van het gebruiksgemak van uv zodra ik de commando’s die essentieel zijn voor mijn workflow eenmaal doorhad.

Bovendien heeft astral met ruff al laten zien dat het in staat is om nuttige maar niet opgeblazen vervangende tools te leveren, en ook dat is voor mij en vele anderen een must-have in alle Python-projecten. Dat in aanmerking genomen, samen met de relatieve volledigheid van uv, lijkt het een veilige gok om te leren en te gebruiken voor je Python-projecten.

Blog