Python, de la zero Lecția 17 / 60

Managementul dependențelor: pip, poetry, uv - cum o alegi pe a ta

Cele patru opțiuni reale, compromisurile și de ce majoritatea proiectelor noi din 2026 pornesc cu uv.

Orice proiect Python răspunde, până la urmă, la două întrebări despre dependențele sale. Prima e ce îi trebuie: o listă de nume precum httpx, pydantic, polars. A doua e care versiuni exacte: httpx==0.28.1, pydantic==2.10.4, polars==1.18.0. Prima listă e scurtă și citibilă de oameni. A doua e mult mai lungă pentru că include fiecare dependență tranzitivă și trebuie să fie reproductibilă: fiecare dezvoltator, fiecare rulare CI, fiecare deploy în producție are nevoie de aceleași versiuni, altfel obții faimoasul „merge pe mașina mea”.

Lucrul care rezolvă a doua întrebare este un lockfile. Diferite unelte îl numesc diferit: requirements.txt, poetry.lock, uv.lock, dar treaba e identică: să fixeze fiecare pachet și fiecare sub-pachet la o singură versiune rezolvată, cu checksum-uri, ca instalarea să fie reproductibilă bit cu bit.

Lecția asta e despre cele patru opțiuni reale pentru a gestiona toate astea în 2026 și cum o alegi pe una.

Ce înseamnă de fapt „managementul dependențelor”

Un proiect Python modern are, la minim:

  1. O listă de dependențe directe: ce importă codul tău. Asta trăiește în pyproject.toml (modern) sau requirements.in (workflow-ul mai vechi pip-tools).
  2. Un lockfile: fiecare pachet din arborele rezolvat, versiune exactă, hash-uri. Ăsta e input-ul pentru o instalare reproductibilă.
  3. O modalitate de a instala acele versiuni exacte într-un mediu virtual.
  4. O modalitate de a le actualiza când vrei versiuni proaspete.
  5. O modalitate de a le audita pentru probleme de securitate cunoscute.

Uneltele diferă prin felul în care tratează fiecare pas, prin cât de rapide sunt și prin cât te încurcă în drumul tău.

Opțiunea 1: pip + requirements.txt (clasicul)

# Direct deps you maintain by hand
echo "httpx" > requirements.txt
echo "pydantic>=2,<3" >> requirements.txt

pip install -r requirements.txt
pip freeze > requirements.txt   # now it has resolved versions

Asta e ce arată orice tutorial Python. Funcționează. Are o problemă mare: pip singur nu separă dependențele directe care te interesează de cele tranzitive. După pip freeze, fișierul tău le are pe ambele, lipite, și nu poți zice care-i care. Să bumpezi o singură dependență directă înseamnă să reconstruiești manual toată lista.

Nu are nici concept de hash-uri (fără --require-hashes), nici separare între dependențele de dev și cele de prod, nici poveste de upgrade dincolo de pip install -U.

Tot okay pentru un script într-un singur fișier. Nu e okay pentru un proiect pe care-l va atinge altcineva.

Opțiunea 2: pip + pip-tools (clasicul disciplinat)

pip-tools adaugă stratul lipsă peste pip-ul simplu. Ții un requirements.in cu dependențele directe și lași pip-compile să rezolve și să blocheze totul.

pip install pip-tools

# requirements.in — what you actually want
cat > requirements.in <<EOF
httpx
pydantic>=2,<3
EOF

# requirements.txt — generated, locked, with hashes
pip-compile --generate-hashes requirements.in

# Install the locked set
pip-sync requirements.txt

requirements.txt-ul de output e complet rezolvat, adnotat cu comentarii („via httpx”) și cu hash-uri. Pentru upgrade:

pip-compile --upgrade requirements.in            # everything
pip-compile --upgrade-package httpx requirements.in   # one package

Dependențele de dev stau într-un fișier separat:

pip-compile requirements.in
pip-compile dev-requirements.in   # references requirements.txt as a constraint

Dacă ai un codebase mai vechi care trăiește pe pip pur, ăsta-i drumul de upgrade cel mai ieftin. Niciun concept nou de packaging, niciun format nou de fișier. Doar două fișiere unde înainte era unul.

Opțiunea 3: poetry (bundler-ul cu opinii)

Poetry a fost prima unealtă adoptată pe scară largă care a pus totul, virtualenv, declararea dependențelor, lockfile, build backend, publish, în spatele unui singur CLI.

poetry init                # interactive pyproject.toml
poetry add httpx
poetry add --group dev pytest ruff
poetry install             # creates venv, installs from poetry.lock
poetry update              # bumps within your declared ranges
poetry run pytest          # run inside the env

pyproject.toml-ul pe care-l produce are propria secțiune [tool.poetry] (Poetry exista înainte ca PEP 621 să standardizeze [project], și se vede). Lockfile-ul e poetry.lock. Dependențele de dev/test/lint merg în grupuri cu nume.

Ce le place oamenilor la Poetry: o singură unealtă face totul, experiența de dev e consistentă, lockfile-ul e solid. Ce nu le place: resolverul a fost istoric lent pe arbori mari, schema [tool.poetry] diverge de standardul PEP 621 la care s-au mutat majoritatea celorlalte unelte și a fost lent în adoptarea standardelor mai noi. Rămâne o alegere perfect ok, mai ales pe proiecte existente care deja îl folosesc.

Opțiunea 4: uv (noul default)

L-am întâlnit pe uv în lecția 15. Pentru managementul dependențelor acoperă același teren ca pip-tools și Poetry combinate, dar într-un binar Rust care rezolvă și instalează cu un ordin de mărime mai rapid.

uv init my-project
cd my-project

uv add httpx pydantic
uv add --dev pytest ruff mypy

uv sync                # install from uv.lock
uv lock --upgrade      # refresh everything
uv lock --upgrade-package httpx
uv run pytest          # run inside the project env

Câteva detalii care fac din uv alegerea recomandată pentru proiecte noi în 2026:

  • Folosește metadate standard PEP 621 [project]. pyproject.toml-ul tău e portabil către orice altă unealtă PEP 621.
  • Lockfile-ul (uv.lock) e universal: un singur fișier rezolvă pentru toate platformele și versiunile de Python pe care le susții, nu un fișier per OS.
  • Dependențele de dev merg în [tool.uv.dev-dependencies] sau, mai portabil, în grupuri [project.optional-dependencies].
  • Gestionează interpretorul Python însuși dacă vrei (uv python install 3.13).

Dacă pornești un proiect azi și nu ai vreun motiv organizațional să alegi altceva, începe cu uv.

O notă despre conda

conda (și mai ușoarele mamba/pixi) e propriul lui ecosistem. Gestionează Python și binare non-Python, CUDA, MKL, GDAL, R, prin propriile canale de pachete. Dacă trăiești în Python-ul științific și ai nevoie de un BLAS specific sau de un toolkit CUDA fixat lângă numpy-ul tău, conda rezolvă probleme pe care pip nu le poate. Pentru un proiect tipic web/data/automation e exagerat. Îl menționăm și mergem mai departe.

Întrebarea cu lockfile-ul

Orice unealtă modernă produce o formă de lockfile. Ar trebui să-l tratezi ca pe un fișier sursă de prim rang:

  • Aplicație? Comite mereu lockfile-ul. Tot rostul e ca deploy-ul tău să instaleze aceleași versiuni ca laptop-ul tău.
  • Bibliotecă? Nu comite hash-urile lockfile-ului în pachetul publicat: utilizatorii tăi au propriul resolver. Dar tot e util în repo pentru dezvoltare și CI. Convenția acum e să comiți lockfile-ul și să lași publicarea să dezbrace ce nu aparține wheel-ului.

Lockfile-ul e și locul unde verifici ce s-a schimbat între deploy-uri. Diff-ul a două versiuni de uv.lock îți spune exact ce pachete s-au mutat și cu cât. Fă code review pe lockfile cum faci review pe o migrare.

Dependențe de dev vs dependențe de producție

Dependențele de producție sunt ce importă aplicația ta în runtime. Dependențele de dev sunt pytest, ruff, mypy, mkdocs, orice există doar ca să te ajute să scrii codul. Nu le livra.

În pyproject.toml-ul modern:

[project]
name = "my-app"
dependencies = [
  "httpx>=0.28,<0.29",
  "pydantic>=2,<3",
]

[project.optional-dependencies]
test = ["pytest>=8", "pytest-asyncio"]
lint = ["ruff>=0.7", "mypy>=1.13"]

[tool.uv]
dev-dependencies = ["pytest>=8", "ruff>=0.7", "mypy>=1.13"]

pip install my-app[test] aduce grupul opțional test. uv sync instalează automat dev-dependencies; uv sync --no-dev nu.

Specificatori de versiune: pinning vs intervale

O să vezi multe de astea:

httpx                # any version
httpx>=0.28          # at least 0.28
httpx>=0.28,<0.29    # 0.28.x only
httpx~=0.28.1        # >=0.28.1, <0.29 (compatible release)
httpx==0.28.1        # exactly this

Două reguli de bun-simț care te scapă de multă durere:

  1. Pentru biblioteci, declară intervale, nu pin-uri. Dacă biblioteca ta fixează httpx==0.28.1 și o altă bibliotecă fixează httpx==0.28.2, niciuna nu poate fi instalată în același mediu. Folosește >=0.28,<0.29 și lasă aplicația să rezolve versiunea exactă.
  2. Pentru aplicații, lockfile-ul e pin-ul tău. Declari intervale în pyproject.toml (ca update-urile să fie ușoare), iar lockfile-ul fixează versiunea exactă rezolvată (ca deploy-urile să fie reproductibile). Nu fixa manual == în pyproject.toml pentru o aplicație: lasă lockfile-ul să-și facă treaba.

Actualizarea dependențelor

O cadență rezonabilă e o dată la câteva săptămâni pentru proiecte mici, sau ori de câte ori apare un CVE:

uv lock --upgrade                      # bump everything within ranges
uv lock --upgrade-package httpx        # bump just one
poetry update
pip-compile --upgrade requirements.in

Rulează testele. Diff pe lockfile. Commit. Gata.

Când apare o versiune majoră și intervalul tău o exclude (<0.29), bumpează intervalul deliberat, citește changelog-ul și rulează din nou testele. Ăsta e momentul să afli dacă s-a stricat ceva.

Audituri de securitate

Lockfile-urile sunt și ce citesc scanerele de securitate. Cele mai folosite două unelte:

pip install pip-audit
pip-audit                               # scans the active env
pip-audit -r requirements.txt           # scans a lock file

pip install safety
safety check

Dependabot-ul de la GitHub și unelte conștiente de uv precum Renovate vor deschide automat pull request-uri când un CVE se potrivește cu una dintre versiunile tale blocate. Conectează una dintre ele în prima zi a oricărui proiect care manipulează date de utilizator. Costul e zero, costul neaplicării e un incident la 2 dimineața.

Cum o alegi pe a ta

O diagramă scurtă:

  • Proiect nou, fără constrângeri? uv.
  • Proiect existent pe Poetry, fără durere? Rămâi pe Poetry.
  • Proiect existent pe pip simplu, gata pentru un mic upgrade? pip-tools.
  • Stack științific cu CUDA/MKL/GDAL? conda sau pixi.

Restul modulului presupune uv, dar conceptele, dependențe directe, lockfile, separare dev, intervale, audituri, sunt identice în oricare dintre ele. Alege o unealtă, învață-i cele trei comenzi și mergi mai departe.

Caută