3.2 uv projekt és ténylegesen rögzített függőségek

3.2.1 Cél és konkrét helyzet

Ebben az alfejezetben egy projektkörnyezet három, egymástól élesen különböző pillanatfelvételét választjuk szét: a pyproject.toml-ban megfogalmazott függőségi tartományt, az uv.lock-ban rögzített pontos feloldást, és a .venv-ben ténylegesen telepített csomagkészletet. A megfigyelési egység most nem adatsor, hanem egy projektkörnyezeti állapot.

A labor bemenete a mellékelt lab/pyproject.toml, amely a Northstar-projekt alapvető numerikus függőségeit írja le. A szállított labor szándékosan nem tartalmaz uv.lock fájlt. A feladat: saját gépen készítsük el a lockot, telepítsük a környezetet, majd bizonyítsuk, hogy a futás friss feloldás nélkül, --locked módban megismételhető.

Előzetes becslés: az első uv lock létrehoz egy, a pyproject.toml-nál nagyobb uv.lock fájlt, mert a tranzitív függőségeket is rögzíti. A uv sync --locked parancs 0 kilépési kóddal zárul, a környezet Python-verziója 3.12.x, és a --locked módú második futtatás között a lock fájl hash-e változatlan marad.

3.2.2 Három réteg, nem három szinonima

A leggyakoribb félreértés az, hogy a pyproject.toml, az uv.lock és a .venv ugyanannak a dolognak három neve. Valójában három különböző kérdésre válaszolnak:

Réteg Mit mond? Példa
pyproject.toml Mi az elfogadható? pandas>=2.2,<3
uv.lock Mit választottunk pontosan? pandas==2.2.3
.venv Mi van ténylegesen telepítve? site-packages/pandas, importálható

A függőségi tartomány (dependency range) egy verzióhalmazt enged meg. A pandas>=2.2,<3 feltétel két részből áll: a verzió legyen legalább 2.2, és legyen kisebb 3-nál. Például a 2.2.3 mindkét feltételt teljesíti: 2.2.3≥2.22.2.3 \ge 2.2 igaz, és 2.2.3<3.02.2.3 < 3.0 is igaz. A tartomány azonban önmagában nem mondja meg, hogy éppen 2.2.1-et, 2.2.3-at vagy egy jövőbeli 2.2.x-et telepítsünk.

A lockfájl (lockfile) ezt a döntést rögzíti. Az uv lock parancs feloldja (resolve) az összes tartományt, azaz megkeresi az egymással kompatibilis pontos verziókat, és a teljes függőségi fát kiírja az uv.lock fájlba. Egy szemléltető kivonat:

# uv.lock, szemléltető kivonat; a pontos szerkezet uv-verziófüggő lehet
version = 1
requires-python = ">=3.12,<3.13"

[[package]]
name = "numpy"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }

[[package]]
name = "pandas"
version = "2.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "numpy" },
    { name = "python-dateutil" },
    { name = "pytz" },
]

A .venv könyvtár a harmadik réteg: itt már nem előírás, hanem tényleges importálható anyag van. Ez a három réteg eltérhet egymástól. A lock pontos verziója nem garantálja, hogy a .venv-ben is az van, ha valaki kézzel beleír a környezetbe; a pyproject tartománya pedig akkor is érvényben marad, ha a lock egy régebbi feloldást őriz.

3.2.3 uv lock, uv sync és a zárolt/fagyasztott mód

A laborban a következő parancssort használjuk:

  1. uv lock: explicit feloldás. Beolvassa a pyproject.toml függőségeit, és létrehozza vagy frissíti az uv.lock-ot. Az első futtatás hálózatot igényel.
  2. uv sync --locked: a lock alapján telepíti a .venv-et. A --locked kapcsoló előtte ellenőrzi, hogy a lock még összhangban van-e a pyproject.toml-lal. A lock egy kivonatot tárol a projekt definíciójáról; ha a pyproject változott, a parancs hibával megáll. Így a friss feloldás nem történik meg véletlenül.
  3. uv run --locked --no-sync: a projekt környezetében futtat egy parancsot. A --locked itt is a lock aktualitását követeli meg; a --no-sync pedig megakadályozza, hogy a futás maga módosítsa a környezetet. Ez kényelmes a lock rögzítésének utólagos bizonyításához.

A --frozen kapcsoló nem erősebb a --locked-nál, hanem más célt szolgál: kihagyja az aktualitásvizsgálatot. A parancs a lockot tekinti igazságnak, és nem ellenőrzi, hogy a pyproject.toml időközben megváltozott-e. A --frozen gyorsabb, és hasznos lehet olyan automatizált környezetben, ahol a lockot már korábban érvényesítettük; a fejlesztői munkamenetben viszont a --locked a biztonságosabb választás.

Az uv lock --check read-only ellenőrzést végez: 0 kilépési kóddal tér vissza, ha a lock érvényes, és nem nulla kóddal, ha a projekt definíciója megváltozott. A pontos kilépési kód és a hibaszöveg a helyi uv verzió dokumentációjában ellenőrizhető.

3.2.4 Függőségi csoport és opcionális extra

A pyproject.toml két, egymástól eltérő szerepű kiegészítő szakaszt ismerhet. A függőségi csoport (dependency group) a projekt belső fejlesztői környezetét szervezi:

[dependency-groups]
dev = [
    "pytest>=8,<9",
    "ruff>=0.5,<1",
]

Az opcionális extra (optional extra) viszont a projekt mint közzétett csomag fogyasztói számára kínál kiegészítő képességet:

[project.optional-dependencies]
plot = [
    "matplotlib>=3.8,<4",
]

Az uv.lock a csoportokat és az extrákat is rögzíti, de a telepítéskor kiválasztjuk, melyikre van szükség. Például a uv sync --locked --group dev a törzsfüggőségek mellett a dev csoportot is telepíti, míg a uv sync --locked --extra plot a plot extrát adja hozzá. A laborban az alapút csak a törzsfüggőségeket igényli; a bővítő fejezetek eszközei külön csoportként vagy extraként állnak rendelkezésre, és az adott fejezetben kapcsoljuk be őket. A pontos kapcsolók a helyi uv sync --help kimenetében ellenőrizhetők, mert a CLI alakja verziófüggő.

3.2.5 Teljes minimális munkapélda

Készítsünk egy kis uv_demo projektet, amelyen mindhárom réteg és a --locked üzemmód kézzel ellenőrizhető.

# uv_demo/pyproject.toml
[project]
name = "lock-demo"
version = "0.1.0"
requires-python = ">=3.12,<3.13"
dependencies = [
    "numpy>=1.26,<3",
    "pandas>=2.2,<3",
]

[dependency-groups]
dev = [
    "pytest>=8,<9",
]

[project.optional-dependencies]
plot = [
    "matplotlib>=3.8,<4",
]

A környezetjelentéshez egy apró, függőség nélküli scriptet használunk. Ez az 3.1-ben megismert sys.executable és sys.prefix szerződést egészíti ki a csomagverziókkal:

# uv_demo/check_lock.py
import importlib.metadata as md
import json
import sys

packages = sys.argv[1:]
print(json.dumps({p: md.version(p) for p in packages}, indent=2))

Linux/macOS rendszeren, bash munkakörnyezetben a uv_demo munkakönyvtárból:

# munkakönyvtár: uv_demo
uv lock
uv lock --check
# szemléltető kimenet; exit code 0, ha a lock érvényes
uv sync --locked --group dev
uv run --locked --no-sync python check_lock.py numpy pandas

A példában várható, szemléltető kimenet:

{
  "numpy": "2.2.1",
  "pandas": "2.2.3"
}

A verziók az adott nap elérhető csomagjaitól és a tartományoktól függnek; a pandas>=2.2,<3 tartományban a 2.2.3 a legfrissebb 2.2-es kiadás, ezért ez a várható érték, de a lockot nem a várakozás, hanem a fájl tartalma ellenőrzi.

Az ismétléshez rögzítsük a lock hashét a futás előtt és után. Linux/macOS:

shasum -a 256 uv.lock > /tmp/lock_before.txt
uv run --locked --no-sync python check_lock.py pandas
shasum -a 256 uv.lock > /tmp/lock_after.txt
diff /tmp/lock_before.txt /tmp/lock_after.txt && echo "lock unchanged"

Windows PowerShellben ugyanez:

# munkakönyvtár: uv_demo
$before = (Get-FileHash uv.lock -Algorithm SHA256).Hash
uv run --locked --no-sync python check_lock.py pandas
$after = (Get-FileHash uv.lock -Algorithm SHA256).Hash
$before -eq $after

Az elvárt eredmény szemléltető jelleggel True, tehát a lock fájl a második futtatás során sem változott.

3.2.6 Eredmény értelmezése és ellenőrző invariant

A munkapéldában három ellenőrző invariantet rögzítünk.

  1. A lock létezik, és a bejegyzései teljesítik a tartományokat. A uv.lock pandas bejegyzésének verziója a >=2.2,<3 intervallumba esik, és a requires-python mező megfelel a projekt Python-korlátjának.
  2. A telepített környezet a lockot tükrözi. A check_lock.py kimenete és a lock megfelelő bejegyzése megegyezik. Ha nem, a uv sync --locked újratelepíti a lock szerinti verziót.
  3. A lock a második --locked futás alatt változatlan. A hash-egyenlőség azt igazolja, hogy a parancs nem végzett csendes újrafeloldást.

A uv run --locked --no-sync tehát nem a környezet tartalmát hasonlítja a lockhoz, hanem a projekt definícióját. A --no-sync arra szolgál, hogy a futás maga ne módosítsa a .venv-et. Így a munkamenet a következő sémára épül: előbb explicit uv lock, utána uv sync --locked, végül uv run --locked --no-sync a tényleges munkához.

Lockfájl és hordozhatóság. A lockfájl a függőségfeloldás eredményét rögzíti, de nem helyettesíti az interpreter-, operációsrendszer- és architektúra-követelmények leírását. A uv sync --locked sikerét a jelen dokumentációhoz ténylegesen használt uv --version mellett kell igazolni; egy generált lockfájl jelenléte nem önmagában futtatási bizonyíték.

Tisztasági kontroll: új virtuális környezetben telepítés rögzített függőségekkel, a projekthez tartozó importok füsttesztje és az eltérés dokumentálása. A rövid távú gyakorlati parancsot mindig a verzióhoz tartozó hivatalos súgóval kell egyeztetni.

3.2.7 Feladatok

1. feladat — Értelmezés. Egy projekt pyproject.toml-jában dependencies = ["pandas>=2.2,<3"] áll, az uv.lock-ban pandas==2.2.3, és a uv run --locked --no-sync python check_lock.py pandas parancs kimenete {"pandas": "2.3.1"}. Mit jelent a kimenet? Melyik parancs állítja helyre a lock és a környezet egyezését? Miért nem elegendő ehhez a pyproject.toml tartománya?

2. feladat — Implementálás a labon. A mellékelt lab mappa nem tartalmaz uv.lock fájlt. A lab/pyproject.toml alapján készítsd el a lockot, telepítsd a környezetet --locked módban, majd futtasd le a fenti check_lock.py scriptet a lab három alapvető csomagjára: numpy, pandas, scikit-learn. A scriptet a lab/scripts/check_lock.py útvonalon helyezd el, ha még nincs ott. Rögzítsd a lock hashét a futás előtt és után; az elvárt eredmény változatlan hash.

3. feladat — Hibadiagnózis. Egy friss klónban a tanuló a uv run --locked --no-sync python scripts/check_lock.py pandas parancsot adta ki, és a következő hibát kapta: error: No uv.lock found. Mi a hiba oka? Mi a helyes parancssor a hibából való kilábaláshoz? Indokold meg, miért nem a uv run --frozen a megoldás.

4. feladat — Megváltozott feltétel. A fenti uv_demo miniprojekt pyproject.toml-jában add hozzá az alábbi opcionális extrát:

[project.optional-dependencies]
plot = [
    "matplotlib>=3.8,<4",
]

Friss lock készítése nélkül add ki a uv run --locked --no-sync python check_lock.py pandas parancsot. Milyen kimenet várható, és miért? Ezután add meg a helyes parancssort, és ellenőrizd, hogy a matplotlib is importálható.

3.2.8 Megoldások és indoklás

1. feladat. A kimenet azt mutatja, hogy a .venv eltér a locktól: a lock 2.2.3-at ír elő, a telepített környezetben 2.3.1 van. A helyreállítás uv sync --locked, mert ez a locknak megfelelő verziót telepíti. A pyproject tartománya önmagában nem elegendő, mert a 2.3.1 is teljesíti a >=2.2,<3 feltételt; a lock az, amely a pontos verziót rögzíti.

2. feladat. A lab pyproject.toml-jában ellenőrizd, hogy a dependencies szakasz tartalmazza a numpy, pandas, scikit-learn csomagokat; a mellékelt lab szerződése szerint a starter_experiment.py ezeket vagy ezek egy részhalmazát használja. Ha a script még nincs ott:

# lab/scripts/check_lock.py
import importlib.metadata as md
import json
import sys

packages = sys.argv[1:]
print(json.dumps({p: md.version(p) for p in packages}, indent=2))

Futtatás a lab munkakönyvtárból, Linux/macOS bash alatt:

cd lab
uv lock
uv lock --check
uv sync --locked
shasum -a 256 uv.lock > /tmp/before.txt
uv run --locked --no-sync python scripts/check_lock.py numpy pandas scikit-learn
shasum -a 256 uv.lock > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt && echo "lock unchanged"

Windows PowerShell alatt a hash rögzítése:

cd lab
uv lock
uv sync --locked
$before = (Get-FileHash uv.lock -Algorithm SHA256).Hash
uv run --locked --no-sync python scripts/check_lock.py numpy pandas scikit-learn
$after = (Get-FileHash uv.lock -Algorithm SHA256).Hash
$before -eq $after

A lab fájl szerinti pontos kimenet a helyi gépen mért verzióktól függ, ezért azt a tanulónak kell rögzítenie. A lényegi ellenőrzés a hash-egyenlőség, nem a kézzel kitalált verziószám.

3. feladat. A hiba oka az, hogy a friss klónban nincs uv.lock; a --locked mód nem hozza létre, hanem megköveteli a lock meglétét. A helyes sorrend:

uv lock
uv sync --locked
uv run --locked --no-sync python scripts/check_lock.py pandas

A uv run --frozen nem oldja meg a hibát, mert a fagyasztott mód sem hoz létre hiányzó lockot; ráadásul kihagyja a pyproject és a lock egyezésének ellenőrzését, így csendes elavuláshoz vezethet.

4. feladat. A pyproject.toml módosítása után a uv run --locked --no-sync parancs hibával áll meg, mert a lock korábban rögzített kivonata nem egyezik az új projekt definícióval. A pontos hibaszöveg a helyi uv kiadásban ellenőrizhető; a lényeg az, hogy a --locked megakadályozza a szándéktalan újrafeloldást. A helyes sorrend:

# munkakönyvtár: uv_demo
uv lock
uv sync --locked --extra plot
uv run --locked --no-sync python check_lock.py pandas matplotlib

Az ellenőrző invariant: az új lockban a plot extra függőségei is megjelennek, a lock hash-e megváltozik, és a matplotlib verzió importálhatóvá válik.

3.2.9 Gyakori tévedés ellenpéldával

Az egyik jellegzetes hiba az, hogy a tanuló a friss laborban uv run --locked parancsot ad ki, mert úgy gondolja, a --locked ellenőrzi, sőt létrehozza a lockot. Az ellenpélda: a lab/pyproject.toml létezik, de a uv.lock még nem; a --locked parancs hibát ad, mert nincs mit ellenőriznie. A csomag tehát nem állítja, hogy egy még nem létező lockfájlt már ellenőrzött.

A másik gyakori félreértés: „a --frozen erősebb, mert teljesen fagyasztott”. Az ellenpélda ugyanez a projekt, de most a lock már létezik, és a pyproject.toml-t megváltoztatjuk. A uv run --locked hibát ad; a uv run --frozen viszont a régi lockkal tovább dolgozik, és nem jelzi, hogy a projekt definíciója már mást ír elő. A --frozen tehát nem erősebb garancia, hanem az aktualitásvizsgálat kihagyása. Reprodukálható átadásnál a --locked ellenőrzött friss lockot követel, a --frozen csak a már érvényesített lock gyors végrehajtására való.

Önellenőrző állítások: „a lock a telepített környezet pontos listája” hamis, mert a .venv ettől függetlenül eltérhet; „a tartomány a lockban van” hamis, mert a tartomány a pyproject-ben, a pontos verzió a lockban van. Az 2.2-ból ismert kilépési kód és az 3.1-ből ismert sys.prefix-ellenőrzés együtt adja a rétegek bizonyítékát: a parancsok exit code-ja a lock érvényességét, a környezetjelentés a telepített anyag valódi forrását mutatja.

3.2.10 Rövid összefoglaló

A pyproject.toml tartománya azt mondja meg, mi elfogadható; az uv.lock azt, hogy az adott feloldáskor pontosan mit választottunk; a .venv azt, hogy mi van ténylegesen telepítve. A uv lock explicit feloldást készít, a uv sync --locked a lockot és a környezetet hozza összhangba, a uv run --locked --no-sync pedig friss feloldás nélkül futtat. A --frozen kihagyja az aktualitásvizsgálatot, ezért nem erősebb, hanem más üzemmód. A következő alfejezetben a bináris csomagok, a BLAS és a conda-döntés kerül sorra: ott az eddigi lockolt függőségek mellett a natív futtatási környezet kérdéseit vizsgáljuk.