Hvorfor jeg næsten er holdt op med at bruge requirements.txt
De fleste Python-tutorials lærer dig at lave en requirements.txt. Det er ikke hele historien.
Jeg fandt først ud af det, da et studieprojekt nægtede at køre på min makkers laptop, selv om vi “havde delt dependencies”. Vi havde en fin requirements.txt. Den hjalp bare ikke særlig meget.
Fejlen der fik mig til at tvivle på requirements.txt
Situation fra min egen mappe, cirka kl. 22.37 en tirsdag:
pip install requests flask
pip freeze > requirements.txt
Alt virker på min maskine. Selvfølgelig gør det det.
Ugen efter skriver min makker:
pip install -r requirements.txt
# ... og så denne
ImportError: cannot import name 'X' from 'something'
Vi sad med samme kode, samme requirements.txt, men forskellige fejl. Jeg anede ikke hvorfor. Jeg havde gjort “det rigtige”: brugt pip freeze. Eller troede jeg.
Det her blev starten på min egen lille rejse ind i requirements.txt vs pyproject.toml, og hvordan man faktisk holder styr på dependencies i Python uden at drukne i versioner og mystiske fejl.
Problemet: samme projekt, forskellige maskiner
I små personlige scripts lægger man sjældent mærke til det. Men lige så snart du:
- deler projektet med andre
- arbejder på tværs af flere computere
- eller vil deploye noget til en server / CI
så dukker spørgsmålet op: Hvordan sikrer vi, at alle installerer det samme?
Der er to niveauer af kontrol her:
- Hvad du ønsker: “Jeg vil gerne bruge Flask, Requests og SQLAlchemy, nogenlunde nyeste version.”
- Hvad du faktisk ender med: “Flask 3.0.2, Requests 2.31.0, Jinja2 3.1.3, og 10 andre indirekte dependencies”.
Mange blander de to sammen i én fil. Det er der, problemerne starter.
requirements.txt: simpelt, men med skjulte faldgruber
Den klassiske opskrift:
pip install requests flask
pip freeze > requirements.txt
Så får du noget i stil med:
blinker==1.7.0
click==8.1.7
Flask==3.0.2
itsdangerous==2.2.0
Jinja2==3.1.3
MarkupSafe==2.1.5
requests==2.31.0
urllib3==2.2.0
Werkzeug==3.0.1
Det virker på overfladen fint:
- Fordele: nemt at forstå, alle kan køre
pip install -r requirements.txt, understøttet overalt. - Ulemper: blander dine “ønsker” og de låste versioner sammen, bliver hurtigt støjende og svær at vedligeholde.
Problemet med pip freeze er, at den tager alt i dit virtuelle miljø. Også ting du har testet og smidt væk. Også tools du kun bruger lokalt. Dit projekt ender med at se mere kompliceret ud end det er.
Mit første forsøg så fx sådan her ud (ægte eksempel, desværre):
autopep8==2.0.4
black==24.2.0
click==8.1.7
Flask==3.0.2
isort==5.13.2
mypy==1.8.0
requests==2.31.0
Halvdelen af det var kun ting, jeg brugte i min editor. De røg med ind i requirements.txt, og mine medstuderende skulle nu installere hele pakken, bare for at køre en simpel app.
Hvornår giver requirements.txt stadig mening?
Jeg synes generelt requirements.txt er ok til:
- små scripts, der kun lige har 1-3 dependencies
- engangsprojekter, hvor du bare skal dele noget hurtigt
- simple deploys til billige hosting-løsninger, der eksplicit forventer en
requirements.txt
Men så snart projektet begynder at leve længere end et par uger, eller der kommer flere mennesker på, vil jeg personligt have noget mere struktureret.
pyproject.toml: hvad er det egentlig?
pyproject.toml er ikke bare “Poetry-filen”. Det er en standard for, hvordan man beskriver et Python-projekt og dets behov. Tanken er, at man har ét centralt sted, hvor man beskriver:
- projektets metadata (navn, version, forfatter osv.)
- dependencies og evt. dev-dependencies
- hvilket build-system der bruges (Poetry, Hatch, Setuptools osv.)
Et meget simpelt eksempel med Poetry kunne se sådan her ud:
[tool.poetry]
name = "min-app"
version = "0.1.0"
description = "Lille webapp"
[tool.poetry.dependencies]
python = "^3.11"
flask = "^3.0.2"
requests = "^2.31.0"
[tool.poetry.group.dev.dependencies]
black = "^24.2.0"
pytest = "^8.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Læg mærke til forskellen:
- Du fortæller ønsket versions-interval (fx
^3.0.2), ikke nødvendigvis en fastlåst version. - Du adskiller “rigtige” dependencies fra dev-tools.
- Du har ét sted, hvor dit projekt beskrives, i stedet for løse filer hist og her.
Så hvor kommer låsningen så ind i billedet? Det gør den via en lockfil.
Lockfiler: pyproject.toml alene er ikke nok
Poetry laver automatisk en poetry.lock, som indeholder de helt konkrete versioner, der er valgt:
flask 3.0.2
- itsdangerous 2.2.0
- Jinja2 3.1.3
- ...
requests 2.31.0
- urllib3 2.2.0
- ...
Du committer både pyproject.toml og poetry.lock til git. Så kan alle køre:
poetry install
og få samme dependency-træ. Ikke bare samme hovedpakker, men også de indirekte dependencies.
Det er her, jeg synes, mange nyere Python-værktøjer gør en reel forskel i forhold til ren requirements.txt-baseret workflow.
Tre scenarier hvor valget faktisk betyder noget
Lille script eller engangsprojekt
Situation: Du har et lille script, der måske bruger requests og python-dotenv. Det skal måske kun køre på din egen maskine eller deles med én ven.
Min proces i dag:
python -m venv .venv
source .venv/bin/activate # på Windows: .venvScriptsactivate
pip install requests python-dotenv
pip freeze > requirements.txt
Og så skriver jeg i README:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Her ville jeg ikke begynde at introducere Poetry eller avancerede tools. Det er overkill. Brug requirements.txt, accepter at den ikke er perfekt, og kom videre.
Mindre webapp med flere dependencies
Situation: En Flask- eller FastAPI-app med lidt flere dependencies, måske også noget test-setup. Projektet lever i flere måneder. Du vil gerne kunne opgradere pakker kontrolleret.
Her begik jeg længe den fejl bare at blive ved med:
pip install ny-pakke
pip freeze > requirements.txt
så hele filen blev skrevet om hver gang. Det gør det umuligt at se, hvad der egentlig har ændret sig.
I stedet er der to rimeligt gode veje:
- bliv i pip-land, men brug et ekstra tool til at lave dine låste filer
- gå all-in på Poetry og
pyproject.toml
Gruppeprojekt med CI eller deployment
Situation: I er 3-5 personer på et projekt. Det skal køre på alles maskiner, og måske i GitHub Actions eller på en server. I vil undgå:
- “Den kører hos mig”
- “Jeg tror, du har en anden version af X”
- uventede opdateringer midt i en eksamensperiode
Her vil jeg altid stræbe efter:
- en deklarativ fil: hvad vi kræver (fx
pyproject.tomleller en “input” requirements-fil) - en låst fil: hvilke versioner vi rent faktisk har valgt (lockfil eller genereret
requirements.txt)
Og så skal CI bruge den låste variant.
Konkrete workflows: hvad jeg selv bruger nu
1) pip + requirements med pip-tools
Hvis du gerne vil blive i “ren pip”, men stadig have styr på tingene, så kig på pip-tools. Det giver dig mulighed for at adskille “ønsker” og “låste versioner”.
Struktur:
requirements.in– dine direkte dependencies, gerne med version-intervalrequirements.txt– genereret, låst fil, der bruges tilpip install -r requirements.txt
Eksempel requirements.in:
flask>=3.0,<4.0
requests>=2.31,<3.0
Generer din låste fil:
pip install pip-tools
pip-compile requirements.in
Nu får du en requirements.txt med faste versioner, men du behøver ikke selv rode med pip freeze. Når du vil opdatere dependencies:
pip-compile --upgrade requirements.in
Herfra kan du lave et flow i README, der er ret begynder-venligt:
# for udviklere
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Hvis du underviser eller laver materiale til andre, kan du fint beskrive det i en kort intro. Det er mere arbejde til dig, men meget mindre kaos for alle andre.
På Coding Class kunne sådan et setup fx være oplagt i et forløb om Python, hvor man gerne vil vise lidt mere professionelle vaner uden at miste begyndere undervejs.
2) Poetry + pyproject.toml
Poetry flytter dig helt over i pyproject.toml-land og giver dig både dependency management og virtualenv-styring i én pakke.
Typisk flow:
pip install poetry
poetry new min-app
cd min-app
poetry add flask requests
Poetry opretter:
pyproject.toml(dine krav)poetry.lock(låste versioner)- et virtuelt miljø (styret af Poetry selv)
På en ny maskine:
poetry install
poetry run python app.py
Det fungerer virkelig godt til projekter, der skal leve lidt længere, eller hvor du planlægger at bygge pakken og dele den. Jeg synes Poetry giver mening, når:
- du arbejder i en gruppe
- du har CI/deployment
- du ikke er skræmt af at lære et ekstra værktøj
Poetry og pyproject.toml er også oplagt, hvis du senere vil udgive dine egne pakker. Den verden kan du læse mere om på fx Poetrys dokumentation eller den officielle gennemgang af pyproject.toml.
3) Minimumssetup til studie- eller hobby-gruppeprojekter
Hvis jeg i dag skulle sætte et hold begyndere i gang med et Python-webprojekt, ville jeg vælge én af to retninger:
- pip + pip-tools
Enkelt at forstå, stadig pip, men med låsning. - Poetry
Lidt mere at lære, men meget pænt og samlet.
Et simpelt “minimum viable” setup med pip-tools kunne se sådan her ud:
# requirements.in
flask>=3.0,<4.0
requests>=2.31,<3.0
pytest>=8.0,<9.0
Og i README skrive:
python -m venv .venv
source .venv/bin/activate
pip install pip-tools
pip-compile requirements.in
pip install -r requirements.txt
Senere, når nogen vil tilføje noget nyt:
# tilføj i requirements.in
httpx>=0.27,<1.0
# generer ny låst fil
pip-compile requirements.in
Alle kan se ændringen i git diff. Ingen gætterier.
Tjekliste: sådan undgår du dependency-kaos
Hvis jeg skal koge mine egne erfaringer ned til noget, der faktisk kan bruges, ville jeg sige:
- Brug altid et virtuelt miljø (
venveller Poetrys indbyggede). Aldrig globalt pip til projekter. - Adskil “ønsker” og “låste versioner”, især i gruppeprojekter.
- Commit lockfiler (
poetry.lock, genereretrequirements.txt) sammen med koden. - Undgå blind
pip freeze > requirements.txti større projekter. Brug det kun til små scripts. - Dokumenter din proces i README: præcis hvilke kommandoer skal en ny udvikler køre?
- Opgrader kontrolleret: brug
pip-compile --upgradeellerpoetry update, og check hvad der ændrer sig.
Hvis du er på vej fra begynder til let øvet, er dependency management faktisk et af de steder, hvor du hurtigt kan skille dig positivt ud. Du behøver ikke være ekspert i packaging for at gøre det bedre end “pip install alt muligt og se hvad der sker”.
Hvad jeg selv vælger næste gang
Min egen tommelfingerregel er efterhånden:
- lille solo-script:
venv+ simpelrequirements.txt - lille webapp:
pip-toolsmedrequirements.in+ genereretrequirements.txt - større eller længerevarende projekt: Poetry med
pyproject.toml+poetry.lock
Hvis du vil lege videre med de forskellige workflows, er det faktisk en fin øvelse selv at bygge det samme lille projekt to gange: én gang med pip/requirements og én gang med Poetry. Skriv ned, hvad der føles mest naturligt for dig. Det er sådan, jeg selv har fundet ud af, hvad jeg gider arbejde med kl. 22.37, når resten af huset sover.
Og ja, jeg bruger stadig requirements.txt. Jeg er bare holdt op med at lade den bestemme, hvilken Python-verden jeg skal leve i.









1 kommentar