uv op macOS: Python-versies, projecten en tools beheren

Automatische vertaling Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.

Ik ben voor mijn Python-workflow overgestapt op uv. Daarmee verving ik het wisselen tussen pip, virtual environments, pip-tools, pipx en projectmanagers. Eén executable dekt nu het grootste deel van dat werk af.

De workflows zijn nog steeds verschillend. Voor Python-developers die op macOS overstappen van pip, virtual environments, pip-tools of pipx naar uv, hebben een project, een script met inline metadata, een eenmalige CLI en een geïnstalleerde CLI elk een eigen environment en lifecycle. Met de onderstaande commands kun je daartussen kiezen.

Snel starten

# Install uv with Homebrew
brew install uv

# Start a project
uv init example-app
cd example-app
uv add httpx
uv run python main.py

# Run an isolated CLI without installing it permanently
uvx ruff check .

TL;DR. In projecten gebruik ik uv add, uv lock, uv sync en uv run. Voor self-contained scripts gebruik ik PEP 723-metadata, voor eenmalige tools uvx en voor commands die op PATH moeten blijven uv tool install. In CI controleert --locked of de projectmetadata overeenkomt met uv.lock. --frozen vertrouwt op de bestaande lock zonder de actualiteit te controleren.

Installeer uv met één eigenaar

Homebrew is een handige installatiemethode op macOS. Zie uv’s installation guide voor de ondersteunde methoden:

brew install uv
uv --version

Als Homebrew uv heeft geïnstalleerd, moet Homebrew het ook upgraden:

brew upgrade uv

uv self update is bedoeld voor uv’s standalone-installatiemethode en is uitgeschakeld voor installaties via een package manager. Laat niet twee installers concurreren om dezelfde executable.

Handige identity-commands:

command -v uv
uv python dir
uv tool dir
uv cache dir

Managed Python-installaties, persistente tools en tijdelijke cache-items hebben afzonderlijke directories. De cache mag worden verwijderd en opnieuw opgebouwd. Deze is geen source of truth.

De vier uv-workflowgrenzenDe vier uv-workflowgrenzen

Projecten: declaraties, resolution en environment

Een uv-project heeft normaal gesproken drie artefacts:

  • pyproject.toml declareert projectmetadata en directe requirements.
  • uv.lock slaat uv’s cross-platform resolution op.
  • .venv is de lokaal geïnstalleerde environment en moet disposable zijn.

Inputs en afgeleide state in een uv-projectInputs en afgeleide state in een uv-project

Maak een applicatie aan en voeg runtime- en testrequirements toe:

uv init forecast-app
cd forecast-app

uv add httpx
uv add --group test pytest
uv run python main.py
uv run --group test pytest

De huidige uv-application-templates maken main.py, pyproject.toml, README.md en .python-version aan. Standaard definiëren ze geen build system. Gebruik uv init --lib voor een packaged library met een src-layout en build backend.

uv run controleert het project, werkt de lock bij wanneer dat nodig is, synct de vereiste dependencies en voert de command uit. Zie uv’s project layout and sync documentation voor deze lifecycle. De eerste projectoperatie maakt .venv en uv.lock aan wanneer dat nodig is. uv init zelf maakt alleen de projectbestanden aan.

Commit inputs, niet de environment

Commit pyproject.toml, uv.lock, source en een bewuste .python-version. Ignore .venv en uv’s caches.

De lock bevat een resolution voor de ondersteunde markers en platforms. Deze maakt native wheels niet identiek op macOS, Linux, Intel en Apple Silicon. Test elk deploymentplatform.

Lock-actualiteit: --locked is niet --frozen

Standaard kunnen projectcommands uv.lock bijwerken wanneer declaraties veranderen. Zie uv’s locking and syncing documentation voor de flags voor actualiteit en exactheid.

Gebruik --locked om te vereisen dat de lock actueel is ten opzichte van de projectmetadata:

uv lock --check
uv sync --locked --group test
uv run --locked --group test pytest

Als pyproject.toml en uv.lock niet overeenkomen, falen deze commands in plaats van een nieuwe lock te resolven. Dat is doorgaans precies de CI-gate die je wilt.

Gebruik --frozen alleen wanneer je uv bewust de bestaande lock wilt laten gebruiken zonder te controleren of deze actueel is:

uv sync --frozen

Dat kan nuttig zijn in een gecontroleerde build stage waarin de lock al is gevalideerd, maar het detecteert geen stale lock.

Exactheid is een aparte instelling van actualiteit. uv sync is standaard exact en verwijdert overbodige packages. uv run gebruikt standaard een inexacte sync, tenzij --exact wordt opgegeven.

Managed Python: een versieaanvraag, geen universele binary

uv kan Python-distributies downloaden en beheren:

uv python install 3.12 3.13
uv python list --only-installed
uv python pin 3.12

uv’s managed CPython-builds komen uit het project python-build-standalone. uv kan ook system-, Homebrew-, pyenv-, Conda- en andere interpreters detecteren. Zie uv’s Python version documentation voor de ondersteunde sources en het discoverygedrag.

.python-version is een version request die door uv en andere compatibele tools wordt gevonden. requires-python in pyproject.toml is het compatibility contract van het project. Houd beide bewust gekozen:

[project]
requires-python = ">=3.12,<3.14"

Het pinnen van 3.12 garandeert niet voor altijd dezelfde patch build of op elk operating system hetzelfde artefact. Vraag een exacte patchversie aan wanneer je die nodig hebt en laat CI de interpreter expliciet selecteren en rapporteren.

Gebruik een andere Python-manager wanneer een project een distributie of buildconfiguratie nodig heeft die uv niet levert. uv kan die interpreter nog steeds gebruiken via --python of via normale discovery.

Kies tussen uv run, uvx en geïnstalleerde tools

Kiezen tussen een projectenvironment, een tijdelijke toolenvironment of een persistente toolenvironmentKiezen tussen een projectenvironment, een tijdelijke toolenvironment of een persistente toolenvironment

Projectgekoppelde tools: uv run

Als pytest, mypy, een code generator of een andere tool het project moet importeren of de gelockte plugins moet gebruiken, declareer je die in een dependency group:

uv add --group lint ruff
uv run --group lint ruff check .

Als je die tool via uvx uitvoert, wordt deze geïsoleerd van het project en kan het geïnstalleerde package of de plugins die de tool nodig heeft verborgen blijven.

Ad-hoc-tools: uvx

uvx is een alias voor uv tool run. Dit maakt een geïsoleerde environment aan in de disposable uv-cache. De uv tools documentation beschrijft deze cache en de lifecycle van persistente tools:

uvx ruff@0.12.0 check .
uvx --from jupyterlab jupyter lab
uvx --with mkdocs-material mkdocs --help

Pin de toolversie in CI of in de documentatie. Bij een eerste invocation zonder versie wordt een actuele release geselecteerd; latere invocations kunnen de cache-state hergebruiken.

Persistente executables: uv tool install

Installeer een tool wanneer scripts buiten jouw beheer de command op PATH nodig hebben, of wanneer je machine-setupmanifest het beheer ervan moet overnemen:

uv tool install ruff
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

Persistente tools gebruiken nog steeds geïsoleerde environments. Mutateer die environments niet handmatig met pip.

Scripts: maak van één bestand de release-unit

PEP 723 definieert inline scriptmetadata. Een compatibele runner kan het commentaarblok lezen en een geïsoleerde environment opbouwen. Zie de uv scripts guide en PEP 723 voor het metadataformat en het gedrag van de runner.

Anatomie van een PEP 723-scriptAnatomie van een PEP 723-script

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx>=0.27,<1",
# ]
# ///
import httpx

response = httpx.get("https://example.com", timeout=10)
response.raise_for_status()
print(response.status_code)

Run het script en bewerk de metadata met uv:

uv run fetch.py
uv add --script fetch.py rich
uv remove --script fetch.py rich

Plain python fetch.py negeert de commentaarmetadata. Het script is daardoor afhankelijk van een compatibele runner, ook al blijft de Python-syntax geldig.

Als een script een resolution later moet kunnen reproduceren, maak je een aangrenzende lock aan:

uv lock --script fetch.py
uv run --script fetch.py

Dit schrijft fetch.py.lock. Een exclude-newer-timestamp kan de datums van kandidaat-distributies beperken, maar is zwakker dan een exact gelockte resolution en garandeert niet dat een artefact beschikbaar blijft.

Gebruik een project wanneer meerdere bestanden dependencies delen, de code als package kan worden geïmporteerd, tests projectstate nodig hebben of meerdere scripts gezamenlijk moeten worden gewijzigd.

Requirements files: compatibiliteit, geen garantie op succes

Een requirements.txt-bestand kan losse inputs, exacte pins, hashes, constraints, indexes, URLs of een door een resolver gegenereerde requirements-export bevatten. De reproduceerbaarheid hangt af van de manier waarop het bestand is geproduceerd en gebruikt. De bestandsnaam zegt op zichzelf niets.

Gebruik de pip-compatibele interface van uv zonder te migreren:

uv venv
uv pip sync requirements.txt
uv run python app.py

uv pip sync zorgt dat de environment overeenkomt met het bestand. uv pip install -r is additief.

Voor een applicatie waarvan je zelf eigenaar bent, kan een gefaseerde migratie nuttig zijn:

uv init --bare
uv add -r requirements.in
uv add --group test -r requirements-test.in
uv lock
uv sync --locked

Voer het volledige test- en deploymentpad uit voordat je oude bestanden verwijdert. Als een downstreamsysteem nog steeds pip-format verwacht, leid je dat af van de gevalideerde uv-lock met uv’s export command:

uv export --format requirements.txt \
  --output-file requirements.txt

Beheer uv.lock en een handmatig bewerkte export niet als twee concurrerende resolutions.

Cache, indexes en supply-chain-grenzen

De uv-cache maakt herhaalde installaties sneller, maar blijft disposable:

uv cache prune
uv cache clean

Gebruik voor routine cleanup bij voorkeur prune. clean verwijdert alle cache-items en dwingt latere downloads en builds af.

Een lockfile verbetert de repeatability. Deze maakt dependencies niet automatisch betrouwbaar. Controleer package-sources, indexconfiguratie, Git-revisions, build backends, licenties en credentials. Houd geauthenticeerde indexconfiguratie buiten gecommitte bestanden. Een verwijzing naar een goedgekeurd credentialmechanisme is toegestaan zolang die geen secret bevat.

Leg voor native packages de deploymentarchitectuur vast en test of wheels beschikbaar zijn. Anders kan een resolver terugvallen op een source build waarvoor compilers en system libraries nodig zijn die in CI of productie ontbreken.

Compacte operationele checklist

Voor elk project:

  1. Definieer requires-python en directe dependencies in pyproject.toml.
  2. Scheid gepubliceerde extras van lokale dependency groups.
  3. Commit uv.lock. Ignore .venv en cachestate.
  4. Voer projectgekoppelde tools uit via de projectenvironment.
  5. Gebruik uv lock --check of --locked in CI.
  6. Test elk doel-OS en elke doelarchitectuur die door de lock wordt vertegenwoordigd.
  7. Exporteer compatibiliteitsformaten alleen voor expliciet benoemde downstreamconsumers.
  8. Beoordeel source- en credentialbeleid onafhankelijk van de resolution.

uv is het nuttigst wanneer deze ownershipgrenzen zichtbaar blijven. Eén binary kan ze allemaal beheren zonder er één environment van te maken. Minder tools, terwijl de vier workflows gescheiden blijven. Daarom gebruik ik uv nog steeds als mijn standaard Python-projecttool op macOS.

Referenties