Zamknout závislosti

Bez zamčených verzí si každá stavba stáhne to, co je zrovna nejnovější:
binárka z dneška a binárka z příštího jara obsahují něco jiného a není jak
zjistit co. Zároveň může vydání kterékoli knihovny rozbít build, aniž by se
sáhlo na řádku kódu — a nejspíš zrovna ve chvíli, kdy se tagne release.

Volné seznamy jsou nově v `.in`, zamčené v `.txt`. Rozděleno na tři: běh,
testy a stavba. Pytest se tak přestane instalovat do prostředí, kde se
staví binárka, a PyInstaller má konečně taky zamčenou verzi — ta na podobu
výsledku sahá ze všech nejvíc.

Zůstává, že se verze píšou i v hlavičce PEP 723, jinak by přestalo fungovat
`uv run web.py`. Aby se ta dvě místa nerozešla, hlídá je test.
This commit is contained in:
gitea-actions committed 2026-08-22 18:22:35 +02:00
1 parent e0413b6adf
commit 3af8fe22ef
14 files changed
+202 -11

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
uv --version
- name: Testy
run: uv run --with-requirements requirements.txt --no-project pytest -q
run: uv run --with-requirements requirements-dev.txt --no-project pytest -q
- name: Binárka
run: ./build.sh
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
- name: Testy
run: |
uv run --managed-python --python "${{ matrix.python }}" \
--with-requirements requirements.txt --no-project \
--with-requirements requirements-dev.txt --no-project \
pytest -q
- name: Terminálová verze
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
- uses: astral-sh/setup-uv@v10.0.1
- name: Testy
run: uv run --with-requirements requirements.txt --no-project pytest -q
run: uv run --with-requirements requirements-dev.txt --no-project pytest -q
- name: Binárka
run: ./build.sh
+25 -1
View File
@@ -155,10 +155,34 @@ python -m venv .venv
Na Fedoře stačí i systémové balíčky, pak se nemusí řešit vůbec nic:
```bash
sudo dnf install python3-flask python3-qrcode python3-tabulate
sudo dnf install python3-flask python3-qrcode python3-tabulate python3-waitress
python3 web.py
```
## Závislosti
Verze jsou zamčené, aby binárka postavená dnes a za půl roku obsahovala
totéž. Volné seznamy jsou v `.in`, zamčené v `.txt`:
| Soubor | K čemu |
|---|---|
| `requirements.in` → `.txt` | běh aplikace |
| `requirements-dev.in` → `.txt` | + pytest, pro testy |
| `requirements-build.in` → `.txt` | + PyInstaller, pro stavbu binárky |
Aktualizace je vědomý krok, ne vedlejší efekt toho, že něco vyšlo na PyPI:
```bash
for f in requirements requirements-dev requirements-build; do
uv pip compile "$f.in" -o "$f.txt"
done
```
Verze se píšou ještě jednou v hlavičce PEP 723 na začátku `web.py` a
`dice.py`, aby fungovalo `uv run web.py`. Že se ty dva zápisy shodují, hlídá
`tests/test_zavislosti.py` — po každém přegenerování je potřeba hlavičky
srovnat.
## Vlastní binárka
```bash
+1 -1
View File
@@ -26,7 +26,7 @@ if [ -d "$BUILD/Scripts" ]; then
pyinstaller="$BUILD/Scripts/pyinstaller.exe"
fi
uv pip install --quiet --python "$python" -r requirements.txt pyinstaller
uv pip install --quiet --python "$python" -r requirements-build.txt
"$pyinstaller" --clean --noconfirm \
--distpath dist --workpath "$BUILD/work" kostky.spec
+1 -1
View File
@@ -1,6 +1,6 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["tabulate"]
# dependencies = ["tabulate==0.10.0"]
# ///
"""Počitadlo skóre v terminálu.
+4
View File
@@ -0,0 +1,4 @@
# Co je potřeba ke stavbě binárky. Zamyká se do requirements-build.txt:
# uv pip compile requirements-build.in -o requirements-build.txt
-r requirements.in
pyinstaller
+37
View File
@@ -0,0 +1,37 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements-build.in -o requirements-build.txt --python-version 3.11
altgraph==0.17.5
# via pyinstaller
blinker==1.9.0
# via flask
click==8.4.2
# via flask
flask==3.1.3
# via -r requirements.in
itsdangerous==2.2.0
# via flask
jinja2==3.1.6
# via flask
markupsafe==3.0.3
# via
# flask
# jinja2
# werkzeug
packaging==26.3
# via
# pyinstaller
# pyinstaller-hooks-contrib
pyinstaller==6.22.2
# via -r requirements-build.in
pyinstaller-hooks-contrib==2026.6
# via pyinstaller
qrcode==8.2
# via -r requirements.in
setuptools==84.0.0
# via pyinstaller
tabulate==0.10.0
# via -r requirements.in
waitress==3.0.2
# via -r requirements.in
werkzeug==3.1.8
# via flask
+4
View File
@@ -0,0 +1,4 @@
# Co je potřeba k vývoji a testům. Zamyká se do requirements-dev.txt:
# uv pip compile requirements-dev.in -o requirements-dev.txt
-r requirements.in
pytest
+35
View File
@@ -0,0 +1,35 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements-dev.in -o requirements-dev.txt --python-version 3.11
blinker==1.9.0
# via flask
click==8.4.2
# via flask
flask==3.1.3
# via -r requirements.in
iniconfig==2.3.0
# via pytest
itsdangerous==2.2.0
# via flask
jinja2==3.1.6
# via flask
markupsafe==3.0.3
# via
# flask
# jinja2
# werkzeug
packaging==26.3
# via pytest
pluggy==1.6.0
# via pytest
pygments==2.21.0
# via pytest
pytest==9.1.1
# via -r requirements-dev.in
qrcode==8.2
# via -r requirements.in
tabulate==0.10.0
# via -r requirements.in
waitress==3.0.2
# via -r requirements.in
werkzeug==3.1.8
# via flask
+6
View File
@@ -0,0 +1,6 @@
# Běhové závislosti. Verze se zamykají do requirements.txt přes:
# uv pip compile requirements.in -o requirements.txt
flask
qrcode
tabulate
waitress
+25 -4
View File
@@ -1,4 +1,25 @@
flask
qrcode
tabulate
pytest
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in -o requirements.txt --python-version 3.11
blinker==1.9.0
# via flask
click==8.4.2
# via flask
flask==3.1.3
# via -r requirements.in
itsdangerous==2.2.0
# via flask
jinja2==3.1.6
# via flask
markupsafe==3.0.3
# via
# flask
# jinja2
# werkzeug
qrcode==8.2
# via -r requirements.in
tabulate==0.10.0
# via -r requirements.in
waitress==3.0.2
# via -r requirements.in
werkzeug==3.1.8
# via flask
+60
View File
@@ -0,0 +1,60 @@
"""Závislosti jsou zamčené a všechna místa se shodnou.
Verze se píšou na dvou místech: v `requirements.txt` pro stavbu binárky
a v hlavičce PEP 723 pro `uv run web.py`. Ta dvě místa se dřív nebo později
rozejdou — leda že by to někdo hlídal. Proto tenhle soubor.
"""
import re
from pathlib import Path
import pytest
ROOT = Path(__file__).parent.parent
# Skript a to, co si podle hlavičky nese s sebou.
SCRIPTS = {"web.py": {"flask", "qrcode", "waitress"},
"dice.py": {"tabulate"}}
def pins(name: str) -> dict[str, str]:
"""Zamčené verze z compilovaného requirements souboru."""
text = (ROOT / name).read_text(encoding="utf-8")
return {m.group(1).lower(): m.group(2)
for m in re.finditer(r"^([A-Za-z0-9_.\-]+)==(\S+)", text, re.M)}
def header(script: str) -> dict[str, str]:
"""Závislosti z hlavičky PEP 723 na začátku skriptu."""
text = (ROOT / script).read_text(encoding="utf-8")
line = re.search(r"^# dependencies = \[(.*)\]$", text, re.M)
assert line, f"{script} nemá hlavičku PEP 723"
return {m.group(1).lower(): m.group(2)
for m in re.finditer(r'"([A-Za-z0-9_.\-]+)==(\S+?)"', line.group(1))}
@pytest.mark.parametrize("name", ["requirements.txt", "requirements-dev.txt",
"requirements-build.txt"])
def test_zavislosti_jsou_zamcene(name):
"""Bez zamčených verzí se binárka z března liší od binárky z dubna."""
lines = [line.strip() for line in (ROOT / name).read_text().splitlines()]
volne = [line for line in lines
if line and not line.startswith("#") and "==" not in line]
assert not volne, f"{name} má nezamčené závislosti: {volne}"
@pytest.mark.parametrize("script", sorted(SCRIPTS))
def test_hlavicka_sedi_s_requirements(script):
"""`uv run web.py` musí dostat totéž, co se zabalí do binárky."""
zamcene = pins("requirements.txt")
v_hlavicce = header(script)
assert set(v_hlavicce) == SCRIPTS[script]
for balicek, verze in v_hlavicce.items():
assert balicek in zamcene, f"{balicek} chybí v requirements.txt"
assert verze == zamcene[balicek], (
f"{script}: {balicek}=={verze}, ale zamčeno je "
f"{zamcene[balicek]} — přepiš hlavičku")
+1 -1
View File
@@ -1,6 +1,6 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["flask", "qrcode"]
# dependencies = ["flask==3.1.3", "qrcode==8.2", "waitress==3.0.2"]
# ///
"""Webové počitadlo pro domácí síť.