Python, dalle fondamenta Lezione 17 / 60

Gestione delle dipendenze: pip, poetry, uv, scegliere la propria

Le quattro opzioni reali, i compromessi, e perché nel 2026 quasi ogni progetto nuovo parte con uv.

Ogni progetto Python prima o poi risponde a due domande sulle proprie dipendenze. La prima è cosa gli serve: una lista di nomi tipo httpx, pydantic, polars. La seconda è quali versioni esatte: httpx==0.28.1, pydantic==2.10.4, polars==1.18.0. La prima lista è corta e leggibile da un essere umano. La seconda è molto più lunga perché include ogni dipendenza transitiva, e deve essere riproducibile: ogni sviluppatore, ogni run di CI, ogni deploy in produzione ha bisogno delle stesse versioni, altrimenti ti ritrovi con il famoso “sulla mia macchina funziona”.

La cosa che risolve la seconda domanda è un lockfile. Diversi tool lo chiamano in modo diverso (“requirements.txt”, “poetry.lock”, “uv.lock”) ma il compito è identico: pinnare ogni package e ogni sotto-package a una singola versione risolta, con i checksum, in modo che l’install sia riproducibile bit per bit.

Questa lezione è sulle quattro opzioni reali per gestire tutto questo nel 2026, e su come sceglierne una.

Cosa significa davvero “gestione delle dipendenze”

Un progetto Python moderno ha, come minimo:

  1. Una lista di dipendenze dirette: cosa importa il tuo codice. Vive in pyproject.toml (moderno) o requirements.in (workflow pip-tools più vecchio).
  2. Un lockfile: ogni package nell’albero risolto, con versione esatta e hash. È l’input per un install riproducibile.
  3. Un modo per installare quelle versioni esatte in un virtual environment.
  4. Un modo per aggiornarle quando vuoi versioni fresche.
  5. Un modo per fare audit alla ricerca di problemi di sicurezza noti.

I tool si differenziano per come gestiscono ogni passo, per quanto sono veloci, e per quanto ti stanno tra i piedi.

Opzione 1: pip + requirements.txt (il classico)

# Dipendenze dirette mantenute a mano
echo "httpx" > requirements.txt
echo "pydantic>=2,<3" >> requirements.txt

pip install -r requirements.txt
pip freeze > requirements.txt   # ora ha le versioni risolte

Questo è quello che mostra ogni tutorial Python. Funziona. Ha un grosso problema: pip da solo non separa le dipendenze dirette a cui tieni da quelle transitive. Dopo pip freeze il tuo file le ha entrambe, incollate insieme, e non sai distinguerle. Aggiornare una dipendenza diretta significa ricostruire l’intera lista a mano.

Inoltre non ha alcun concetto di hash (senza --require-hashes), nessuna separazione tra dipendenze di dev e di prod, e nessuna storia di upgrade oltre pip install -U.

Ancora accettabile per uno script in un singolo file. Non accettabile per un progetto che toccherà qualcun altro.

Opzione 2: pip + pip-tools (il classico disciplinato)

pip-tools aggiunge il layer mancante sopra il pip puro. Mantieni un requirements.in di dipendenze dirette e lasci che pip-compile risolva e blocchi tutto.

pip install pip-tools

# requirements.in: cosa vuoi davvero
cat > requirements.in <<EOF
httpx
pydantic>=2,<3
EOF

# requirements.txt: generato, lockato, con gli hash
pip-compile --generate-hashes requirements.in

# Installa il set lockato
pip-sync requirements.txt

Il requirements.txt di output è completamente risolto, annotato con commenti (“via httpx”) e con gli hash. Per aggiornare:

pip-compile --upgrade requirements.in            # tutto
pip-compile --upgrade-package httpx requirements.in   # un solo package

Le dipendenze di dev vivono in un file separato:

pip-compile requirements.in
pip-compile dev-requirements.in   # usa requirements.txt come constraint

Se hai una codebase più vecchia che vive su pip puro, questo è il path di upgrade più economico. Nessun nuovo concetto di packaging, nessun nuovo formato di file. Solo due file dove prima ce n’era uno.

Opzione 3: poetry (il bundler con opinioni)

Poetry è stato il primo tool ampiamente adottato a mettere tutto (virtualenv, dichiarazione delle dipendenze, lockfile, build backend, publish) dietro un’unica CLI.

poetry init                # pyproject.toml interattivo
poetry add httpx
poetry add --group dev pytest ruff
poetry install             # crea il venv, installa da poetry.lock
poetry update              # aggiorna nei range dichiarati
poetry run pytest          # esegue dentro l'env

Il pyproject.toml che produce ha la sua propria sezione [tool.poetry] (Poetry esisteva prima che PEP 621 standardizzasse [project], e si vede). Il lockfile è poetry.lock. Le dipendenze di dev/test/lint vanno in gruppi nominati.

Cosa piace di Poetry: un solo tool fa tutto, la dev experience è coerente, il lockfile è solido. Cosa non piace: il resolver è stato storicamente lento su alberi grandi, lo schema [tool.poetry] diverge dallo standard PEP 621 a cui la maggior parte degli altri tool è passata, ed è stato lento ad adottare gli standard più recenti. Resta comunque una scelta perfettamente valida, soprattutto su progetti esistenti che lo usano già.

Opzione 4: uv (il nuovo default)

uv lo abbiamo incontrato nella lezione 15. Per la gestione delle dipendenze copre lo stesso terreno di pip-tools e Poetry combinati, ma in un binario Rust che risolve e installa di un ordine di grandezza più velocemente.

uv init my-project
cd my-project

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

uv sync                # installa da uv.lock
uv lock --upgrade      # aggiorna tutto
uv lock --upgrade-package httpx
uv run pytest          # esegue dentro l'env del progetto

Alcuni dettagli che rendono uv la scelta consigliata per nuovi progetti nel 2026:

  • Usa i metadati standard PEP 621 [project]. Il tuo pyproject.toml è portabile verso qualsiasi altro tool PEP 621.
  • Il lockfile (uv.lock) è universale: un solo file risolve per tutte le piattaforme e le versioni Python che supporti, non un file per OS.
  • Le dipendenze di dev vanno in [tool.uv.dev-dependencies] o, in modo più portabile, in gruppi [project.optional-dependencies].
  • Gestisce anche l’interprete Python stesso, se vuoi (uv python install 3.13).

Se stai partendo con un progetto oggi e non hai motivi organizzativi per scegliere altro, parti con uv.

Una nota su conda

conda (e i più leggeri mamba/pixi) è un ecosistema a sé. Gestisce Python e binari non-Python (CUDA, MKL, GDAL, R) tramite i propri canali di package. Se vivi nel Python scientifico e ti serve uno specifico BLAS o un toolkit CUDA pinnato accanto al tuo numpy, conda risolve problemi che pip non risolve. Per un tipico progetto web/data/automation è eccessivo. Lo menzioniamo e andiamo avanti.

La questione del lockfile

Ogni tool moderno produce qualche forma di lockfile. Devi trattarlo come un file sorgente di prima classe:

  • Applicazione? Committa sempre il lockfile. Lo scopo è che il tuo deploy installi le stesse versioni del tuo portatile.
  • Libreria? Non committare gli hash del lockfile nel package pubblicato: i tuoi utenti hanno il proprio resolver. Ma è comunque utile averlo nella repo per sviluppo e CI. La convenzione adesso è committare il lock e lasciare che la pubblicazione tolga ciò che non appartiene al wheel.

Il lockfile è anche dove controlli cos’è cambiato tra un deploy e l’altro. Fare il diff di due versioni di uv.lock ti dice esattamente quali package si sono mossi e di quanto. Fai code review del lock allo stesso modo in cui fai review di una migration.

Dipendenze di dev vs dipendenze di produzione

Le dipendenze di produzione sono quelle che la tua applicazione importa a runtime. Le dipendenze di dev sono pytest, ruff, mypy, mkdocs, tutto quello che esiste solo per aiutarti a scrivere il codice. Non spedirle.

In un pyproject.toml moderno:

[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] tira dentro il gruppo opzionale test. uv sync installa automaticamente le dev-dependencies; uv sync --no-dev no.

Specifier di versione: pin vs range

Ne vedrai parecchi così:

httpx                # qualsiasi versione
httpx>=0.28          # almeno 0.28
httpx>=0.28,<0.29    # solo 0.28.x
httpx~=0.28.1        # >=0.28.1, <0.29 (compatible release)
httpx==0.28.1        # esattamente questa

Due regole pratiche che ti risparmieranno un sacco di dolore:

  1. Per le librerie, dichiara range, non pin. Se la tua libreria pinna httpx==0.28.1 e un’altra libreria pinna httpx==0.28.2, nessuna delle due si può installare nello stesso environment. Usa >=0.28,<0.29 e lascia che sia l’applicazione a risolvere la versione esatta.
  2. Per le applicazioni, il lockfile è il tuo pin. Dichiari range in pyproject.toml (così gli aggiornamenti sono facili) e il lockfile pinna la versione esatta risolta (così i deploy sono riproducibili). Non pinnare manualmente con == in pyproject.toml per un’app: lascia fare al lock.

Aggiornare le dipendenze

Una cadenza ragionevole è una volta ogni paio di settimane per progetti piccoli, oppure ogni volta che esce una CVE:

uv lock --upgrade                      # aggiorna tutto nei range
uv lock --upgrade-package httpx        # aggiorna solo uno
poetry update
pip-compile --upgrade requirements.in

Esegui i test. Fai il diff del lockfile. Committa. Fatto.

Quando esce una major version e il tuo range la esclude (<0.29), aggiorna il range deliberatamente, leggi il changelog, e rilancia i test. Quello è il momento per scoprire se qualcosa si è rotto.

Audit di sicurezza

I lockfile sono anche quello che leggono gli scanner di sicurezza. I due tool più comuni:

pip install pip-audit
pip-audit                               # scansiona l'env attivo
pip-audit -r requirements.txt           # scansiona un lockfile

pip install safety
safety check

Dependabot di GitHub e tool consapevoli di uv come Renovate aprono pull request automatiche quando una CVE matcha una delle tue versioni lockate. Collegane uno il primo giorno di qualsiasi progetto che gestisce dati utente. Il costo è zero, il costo di non farlo è un incidente alle 2 di notte.

Scegliere la tua

Un piccolo flowchart:

  • Progetto nuovo, nessun vincolo? uv.
  • Progetto esistente su Poetry, nessun dolore? Resta su Poetry.
  • Progetto esistente su pip puro, pronto per un piccolo upgrade? pip-tools.
  • Stack scientifico con CUDA/MKL/GDAL? conda o pixi.

Il resto di questo modulo dà per scontato uv, ma i concetti (dipendenze dirette, lockfile, split dev, range, audit) sono identici in tutti. Scegli un tool, impara i suoi tre comandi, e vai avanti.

Cerca