Pythonprojecten met uv¶
In de vorige hoofdstukken heb je gewerkt met een eigen virtual environment zodat je jouw pythonomgeving mooi gescheiden kan houden van andere projecten waar je aan werkt. Dit is echt de oplossing voor alle problemen waarbij volledige Pythoninstallaties onbruikbaar kunnen worden — waarna je alles opnieuw moet installeren. Dit kan gebeuren als je — vanwege al je verschillende projecten — zoveel packages installeert dat die met elkaar in conflict komen.
Voor ieder project nieuwe environments aanmaken heeft wel een nadeel: je moet alle packages die je nodig hebt opnieuw installeren. Welke waren dat ook alweer? Vast numpy
, en matplotlib
, en…? Niet handig. Als je code gaat delen met elkaar krijg je regelmatig te maken met een ImportError
of ModuleNotFoundError
omdat je niet precies weet wat er nodig is, waarna je weer één of ander package moet installeren.
Nu pythondaq netjes is uitgesplitst in een MVC-structuur en de wijzigingen met Git worden bijgehouden, ga je er een package van maken zodat je het ook met anderen kan delen. Daarin staan alle benodigdheden duidelijk omschreven zodat gebruikers daar verder niet over hoeven na te denken.
Packages op PyPI (de standaardplek waar Python packages gepubliceerd worden) geven altijd hun dependencies op. Dat zijn de packages die verder nog nodig zijn om alles te laten werken. Installeer je matplotlib
, dan krijg je er six, python-dateutil, pyparsing, pillow, numpy, kiwisolver, cycler
automatisch bij. Maar alleen de namen van packages zijn niet genoeg. Welke versies van numpy
werken met de huidige versie van matplotlib
? Allemaal zaken die je — als je een package schrijft — zelf moet bijhouden. Het voordeel is dat jouw gebruikers alleen maar jouw package hoeven te installeren — de rest gaat vanzelf.
En… hoe test je je package zodat je zeker weet dat hij het bij een ander ook doet? Heel vaak werkt het bij jou wel, maar vergeet je een bestand mee te sturen dat wel echt nodig is.1 Of: bij jou werkt import my_new_cool_app.gui
wel, maar bij een ander geeft hij een ImportError
of ModuleNotFoundError
. De bestanden zijn er wel, maar worden verkeerd geïmporteerd.
Hoe krijg je eigenlijk je code bij iemand anders? Liefst als één bestand, of zelfs met pip install my_new_cool_app
; dat zou wel mooi zijn.
Ook daarvoor gebruiken we uv.
Info
Voorgaande jaren leerden we studenten om Poetry te gebruiken. Heel populair, maar uv is de afgelopen anderhalf jaar nog veel populairder geworden. En terecht.
Er zijn meerdere tools ontwikkeld om dezelfde problemen op te lossen. uv is in korte tijd heel populair geworden. Het richt zich op het officiële ecosysteem: standaard Python packages, ofwel PyPI en pip
; niet conda
(zie meer hierover in paragraaf pip vs conda). Dit zorgt er voor dat iedereen mét of zónder Anaconda je package kan installeren. Omdat uv ook in staat is zelf verschillende versies van Python te installeren hebben we Anaconda niet meer nodig. De installer van Anaconda is bijna 1 Gb groot en bevat heel veel Python packages die je nooit gebruikt. De installer van uv is nog geen 20 Mb en kun je gebruiken om precies te installeren wat je nodig hebt.
Werken in een terminal
uv is een tool die je enkel en alleen in de terminal kunt gebruiken. Het heeft alleen een command-line interface (CLI). Ben je nog niet zo bekend met het navigeren in een terminal dan kun je als oefening de Terminal Adventure Game spelen.
We gaan uv bedienen door commando's te geven in de terminal van Visual Studio Code. We laten de terminal weten welk programma wij willen gaan besturen, door uv
in te typen. En daarachter wat we willen dat uv gaat doen. We kunnen bijvoorbeeld kijken welke commando's allemaal beschikbaar zijn met uv help
. Dat geeft een vrij lange lijst die je terug kunt scrollen in de terminal, maar je kunt ook uv help | more
intypen om de tekst per pagina weer te geven.2
> uv help | more
An extremely fast Python package manager.
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
remove Remove dependencies from the project
version Read or update the project's version
sync Update the project's environment
lock Update the project's lockfile
export Export the project's lockfile to an alternate format
tree Display the project's dependency tree
format Format Python code in the project
tool Run and install commands provided by Python packages
python Manage Python versions and installations
pip Manage Python packages with a pip-compatible interface
venv Create a virtual environment
build Build Python packages into source distributions and wheels
publish Upload distributions to an index
cache Manage uv's cache
self Manage the uv executable
generate-shell-completion Generate shell completion
help Display documentation for a command
Cache options:
-n, --no-cache Avoid reading from or writing to the cache, instead using a temporary directory for the
-- More --
Info
Zoals je ziet heeft uv
dus heel veel verschillende commando's. uv is een Zwitsers zakmes: het bevat heel veel tools voor wie dat nodig heeft. Wij hebben lang niet alles nodig dus laat je daardoor niet uit het veld slaan. In de rest van dit hoofdstuk vertellen we precies wat je wel nodig hebt. Als je meer wilt weten kun je het beste de documentatie lezen.
Nieuw uv project¶
Info
We gaan werken met modules en packages. Ben je daar nog niet zo bekend mee, zorg dan dat je paragraaf Modules en paragraaf packages gemaakt hebt.
Stel je wilt een package schrijven met wat handige functies om veelgebruikte statistische berekeningen makkelijk uit te voeren. Je noemt het easystat
. Het doel is eerst om het in al je eigen analyses makkelijk te kunnen gebruiken (import easystat
) maar je wilt het ook op GitHub zetten en wie weet vinden anderen het ook handig! Je wilt het dus ook netjes doen. En niet later van anderen horen: leuk, maar bij mij werkt het niet!
Easystat uv project aanmaken
- Open Github Desktop en ga naar het dropdownmenu File. Kies hier voor
New repository ...
. Geef de repository de naameasystat
en zet de repository in de mapECPC
. VinkInitialize this repository with a README
aan en kies bijGit ignore
voorPython
. - Open de repository
easystat
in Visual Studio Code. - Open een Terminal in je Visual Studio Code-omgeving (Menu > Terminal > New Terminal). Maak het uv project aan met:
-
Je bekijkt de nieuw gemaakte mappenstructuur en ziet dat het overeenkomt met de mappenstructuur zoals hieronder weergegeven:
ECPC
├──oefenopdrachten
├──pythondaq
├──easystat
├──src
└──easystat
└──__init__.py
├──.gitattributes
├──.gitignore
├──.python-version
├──pyproject.toml
└──README.md
└── ••• -
Commit in GitHub Desktop de wijzigingen die
uv init
heeft gedaan.
src-layout
Door het project in een source layout (src-layout) te bouwen (easystat
zit in een mapje src
) staat al je Pythoncode netjes bij elkaar weggestopt. Dit maakt het makkelijker om te testen of het installeren goed werkt zodat je zeker weet dat andere mensen met jouw code aan de slag kunnen.
Testcode
(ECPC) > uv init --package
Initialized project `easystat`
Checkpunten:
- De projectmap
easystat
staat in de mapECPC
. - In de projectmap
easystat
staat een mapsrc
. - In de map
src
staat een package mapeasystat
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
Laten we één voor één kijken welke mappen en bestanden uv heeft aangemaakt. We hadden al een README.md
in de projectmap staan. Hierin komt een algemene beschrijving van ons project.3
Dan komt de src
-map. Daarin komt ons nieuwe package easystat
4 te staan. Er is alvast voor ons een __init__.py
aangemaakt. Handig! De bestanden .gitattributes
en .gitignore
bewaren wat instellingen voor git, en .python-version
bewaart het versienummer van Python dat uv gebruikt. Vul je daar 3.12 in? Dan installeert uv Python 3.12 in je virtual environment.
En als laatste… een pyproject.toml
5 waarin alle informatie over je project wordt bijgehouden. Ook staat er in dit bestand informatie voor de verschillende tools die je kunt gebruiken. De inhoud van het bestand ziet er ongeveer zo uit:
[project]
name = "easystat"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "David Fokkema", email = "davidfokkema@icloud.com" }
]
requires-python = ">=3.13"
dependencies = []
[project.scripts]
easystat = "easystat:main"
[build-system]
requires = ["uv_build>=0.8.4,<0.9.0"]
build-backend = "uv_build"
Het bestand is in het TOML-formaat.7 Tussen de vierkante haken staan de koppen van de verschillende secties in dit configuratiebestand. In de eerste sectie staat informatie over ons project. Je kunt daar bijvoorbeeld een beschrijving toevoegen of het versienummer aanpassen. Ook bevat die sectie de dependencies. Dit zijn alle Pythonpackages die ons project nodig heeft. Op dit moment is dat nog niets. Ook het versienummer van Python is belangrijk. Hier is dat groter of gelijk aan 3.13. Dit kan belangrijk zijn. Gebruikers met een iets oudere versie van Python — bijvoorbeeld versie 3.11 — kunnen nu het package niet installeren. Als je niet per se de nieuwste snufjes van Python 3.13 nodig hebt kun je aangeven dat een iets oudere versie van Python ook prima is. Op moment van schrijven — zomer 2025 — is Python 3.13 de nieuwste versie. Het is dus prima om minimaal 3.12 te vragen — die versie is inmiddels bijna twee jaar oud.
Python versie in je project
Het is heel handig om als je requires-python = ">=3.12"
invult, ofwel 'minstens 3.12', dat je dan in .python-version
ook 3.12 invult omdat je anders niet zeker weet dat je code ook echt werkt met 3.12 (omdat jij zelf dan bijvoorbeeld met 3.13 werkt en het dus nooit getest hebt met 3.12).
De sectie [project.scripts]
zorgt ervoor dat we ons script kunnen aanroepen door easystat
in de terminal in te typen en de sectie [build-system]
zorgt ervoor dat we een package kunnen maken en uploaden naar de Python Package Index (PyPI). De [build-system]
sectie is nu nog niet belangrijk.
Synchroniseren van virtual environments
- Hoewel we hierboven beweerden dat je
easystat
kunt intypen in de terminal en dat er dan een scriptje draait, werkt dat (nog) niet. Probeer maar eens! Het werkt ook niet als je een nieuwe terminal opent. En... er staat niets tussen haakjes aan het begin van de opdrachtprompt. Blijkbaar is er nog geen virtual environment actief. - Open
src/eaystat/__init__.py
. Rechtsonderin zie je inderdaadSelect Interpreter
. Als je daarop klikt zie je alleen nietPython 3.x.x (easystat)
in het rijtje staan... Druk op Esc om het menu te verlaten. - In een terminal in VS Code, type in:
Wat dit gedaan heeft is het automatisch aanmaken van het virtual environment op basis van je projectinstellingen. Dus de Pythonversie die in> uv sync Using CPython 3.13.5 Creating virtual environment at: .venv Resolved 1 package in 5ms Installed 1 package in 47ms + easystat==0.1.0 (from file:///C:/Users/David/Documents/ECPC/easystat)
.python-version
staat en eventuele dependencies die gedefinieerd zijn in jepyproject.toml
. - Kies het nieuwe virtual environment.
- Open een nieuwe terminal en type
easystat
. Als het goed is werkt het nu wél! - Commit in GitHub Desktop de wijzigingen die
uv sync
heeft gedaan (eenuv.lock
file, zie later).
Checkpunten:
- Rechtsonderin staat je Python environment geselecteerd (3.12.x (easystat)).
- In de terminal staat (easystat) vooraan de opdrachtprompt.
- Het commando
easystat
runt zonder problemen.
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
Maken van de easystat-package¶
We starten met ons package. We gaan een aantal ModuleNotFoundError
s tegenkomen, maar dat lossen we ook weer op. Stel, we berekenen vaak de standaarddeviatie van het gemiddelde en maken daarvoor een handige shortcut
in shortcuts.py
. Nu willen we deze shortcut ook in een ander script measurements.py
gebruiken, die op basis van een aantal metingen het gemiddelde mét een onzekerheid geeft. Dit kunnen we doen door de module te importeren in het nieuwe script zodat we de functie stdev_of_mean
daar ook kunnen gebruiken. We maken uiteindelijk een script try_measurements.py
om dit allemaal te testen, en die zetten we expres niet in het package, maar in een nieuwe map tests
. Het testscript hoort immers niet bij de code van het easystat
package.
Easystat shortcuts.py en try_shortcuts.py aanmaken
Maak zoals hieronder aangegeven de bestanden shortcuts.py
, measurements.py
en try_measurements.py
aan, waarbij je let op in welke map de bestanden moeten staan (je moet nog een map zelf aanmaken):
import numpy as np
def stdev_of_mean(values):
"""Calculate the standard deviation of the mean"""
return np.std(values) / np.sqrt(len(values))
easystat
├──
src
├──
easystat
├──
__init__.py
├──
measurements.py
└──
shortcuts.py
├──
tests
└──
try_measurements.py
├──
pyproject.toml
└──
readme.md
Import numpy could not be resolved
Misschien is het je al opgevallen dat VS Code een oranje kringeltje onder numpy
zet in de eerste regels van twee scripts, en ook onder shortcuts
en measurements
. Als je daar je muiscursor op plaatst krijg je een popup met de melding Import numpy could not be resolved
. Daar moeten we misschien wat mee... In de volgende opdrachten gaan we die problemen één voor één oplossen.
Checkpunten:
- De map
easystat/src/easystat
bevat het bestandmeasurements.py
. - De map
easystat/src/easystat
bevat het bestandshortcuts.py
. - In de projectmap
easystat
staat een maptests
. - De map
easystat/tests
bevat het bestandtry_measurements.py
.
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
In de eerste regel van try_measurements.py
importeren we de functie uit het nieuwe package om uit te proberen. In de eerste print
-regel gebruiken we een handige functie van f-strings.6
Easystat shortcuts.py testen
Je bent heel benieuwd of je package al werkt. Je runt als eerste het bestand shortcuts.py
en krijgt een foutmelding...
Testcode
import numpy as np
def stdev_of_mean(values):
"""Calculate the standard deviation of the mean"""
return np.std(values) / np.sqrt(len(values))
(easystat) > python .\src\easystat\shortcuts.py
Traceback (most recent call last):
File "C:\Users\David\Documents\ECPC\easystat\src\easystat\shortcuts.py", line 1, in
import numpy as np
ModuleNotFoundError: No module named 'numpy'
Checkpunten:
- Je hebt de juiste virtual environment geactiveerd.
- Je runt het bestand
shortcuts.py
. - Je krijgt een foutmelding
ModuleNotFoundError: No module named 'NumPy'
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
De beloofde ModuleNotFoundError
! Ons package heeft NumPy nodig en dat hebben we nog niet geïnstalleerd. Dat was de reden voor de kriebeltjes onder numpy
. Het installeren kunnen we handmatig doen maar dan hebben andere gebruikers een probleem. Veel beter is het om netjes aan te geven dat ons package NumPy nodig heeft — als dependency.
Dependencies toevoegen¶
Om een dependency aan te geven vertellen we uv dat hij deze moet toevoegen met:
(easystat) > uv add numpy
Resolved 2 packages in 453ms
Built easystat @ file:///C:/Users/David/Documents/ECPC/easystat
Prepared 1 package in 82ms
Uninstalled 1 package in 9ms
Installed 2 packages in 798ms
~ easystat==0.1.0 (from file:///C:/Users/David/Documents/ECPC/easystat)
+ numpy==2.3.2
Easystat dependencies toevoegen
Je voegt numpy
als dependency toe aan het project easystat
met het commando uv add numpy
. Je kijkt in de pyproject.toml
en warempel daar staat numpy
nu bij de dependencies! Je vraagt je af of numpy
nu ook in het virtual environment easystat
is geïnstalleerd en controleert dit met uv pip list
en waarachtig numpy
staat in de lijst . Weer ga je shortcuts.py
draaien en ditmaal krijg geen foutmelding! Commit de wijzigingen.
Checkpunten:
- Je hebt de juiste virtual environment geacitveerd.
- Je hebt
numpy
als dependency toegevoegd. - Je krijgt geen foutmelding als je het bestand
shortcuts.py
runt.
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
Fijn! uv heeft NumPy nu toegevoegd aan de environment easystat
. Gewone package managers als Pip en Conda zullen geen packages toevoegen aan je uv project als je pip/conda install package
aanroept. Gebruik daarom altijd uv add package
als je met uv aan een package werkt. Sterker nog, als je met Pip handmatig packages extra installeert zal uv sync
deze packages als overbodig herkennen en ze prompt weer verwijderen. Heb je iets verkeerds toegevoegd? Het verwijderen van dependency PACKAGE
gaat met uv remove PACKAGE
.
Info
Als we de code in ons package aanpassen dan hoeven we het environment niet opnieuw te synchroniseren met uv sync
, maar als we met de hand iets wijzigen in de pyproject.toml
dan moet dat wel. Als je een ImportError
of ModuleNotFoundError
krijgt voor je eigen package — bijvoorbeeld als je nieuwe mappen of bestanden hebt aangemaakt — probeer dan eerst voor de zekerheid uv sync
.
uv.lock
uv.lock¶
Na het toevoegen van NumPy is er ook een grote wijziging in het bestand uv.lock
bijgekomen. Hierin staan de exacte versies van alle geïnstalleerde packages. Vaak wordt dit bestand gecommit zodat collega-ontwikkelaars van hetzelfde project exact dezelfde versies installeren zodra ze uv sync
aanroepen. Ook als er nieuwere versies van NumPy bijkomen blijven alle ontwikkelaars precies dezelfde NumPy-versie gebruiken totdat uv.lock
geüpdatet wordt. Niets is zo vervelend als "oh, bij mij werkt het wel" dus hoe meer dingen precies hetzelfde zijn, hoe minder problemen je tegenkomt. Updaten naar nieuwere versies kan natuurlijk wel, maar alleen op het moment dat je er klaar voor bent om te testen of dan alles nog netjes werkt.
Upgrade uv lockfile
- Clone de repository
NatuurkundePracticumAmsterdam/upgrade-uv-lock
. - Open de repository in Visual Studio Code en open een nieuwe terminal.
- Installeer de dependencies in één keer met
uv sync
. - Waarvoor gebruikt uv de lockfile (
uv.lock)
? Welke versies van NumPy en matplotlib worden geïnstalleerd? - Sinds het maken van de repository zijn er nieuwere versies van NumPy en matplotlib uitgekomen. Die worden nu nog niet geïnstalleerd, hoewel er in
pyproject.toml
staat:dependencies = ["matplotlib>=3.10.3", "numpy>=2.2.6"]
(het mag dus wel!). Controleer dat inpyproject.toml
. - Nu willen we de nieuwe versies hebben. Je kunt de lockfile updaten met
uv lock --upgrade
. De nieuwere versies worden genoemd en verwerkt in de lockfile. Controleer in GitHub Desktop datuv.lock
gewijzigd is. - Update je virtual environment met
uv sync
en controleer dat de nieuwere versies inderdaad geïnstalleerd worden.
Upgrade dependencies
De stappen uv lock --upgrade
en uv sync
kunnen in één keer met uv sync --upgrade
. Die zul je dus vaker gebruiken.
Absolute imports¶
We hebben een uv project, dependencies toegevoegd maar nog niet alle code getest. Dat gaan we nu doen!
Easystat package testen
Je probeert nog een keer shortcuts.py
te runnen en ziet dat dat gewoon werkt. Daarna probeer je measurements.py
. Werkt ook, maar wel gek dat er golfjes onder from shortcuts import stdev_of_mean
staan, hij doet het toch gewoon? Je kijkt even welke waarschuwing daarbij gegeven wordt door je muiscursor op de golfjes te schuiven. Daarna probeer je try_measurements.py
. Hier gaan dingen mis: daarom zette VS Code kriebeltjes onder measurements
(en onder shortcuts
vanwege een vergelijkbare reden).
Testcode
(easystat) > python tests/try_measurements.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\easystat\tests\try_measurements.py", line 1, in
from measurements import result_with_uncertainty
ModuleNotFoundError: No module named 'measurements'
We willen dus de module measurements
importeren, maar Python kan hem niet vinden. Dat is ook wel een klein beetje logisch, want try_measurements.py
staat in de map tests
maar measurements.py
staat in de map src/easystat
. Dus we moeten Python vertellen wáár hij die module kan vinden, namelijk in ons nieuwe package easystat
. Doordat we een package gemaakt hebben hoeven we dus niet precies te vertellen in welke map alles te vinden is, maar hoeven we alleen de naam van het package te gebruiken. Dus niet map.op.computer.easystat.src.easystat
maar gewoon easystat
. Wel zo makkelijk.
Import aanpassen: easystat package gebruiken
Je past from measurements ...
aan naar from easystat.measurements ...
. Je test de code opnieuw. Verdorie, weer een error. Overleg met elkaar wat deze error betekent. Waarom kregen we die error niet toen we measurements.py
testten?
Testcode
(easystat) > python tests/try_measurements.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\easystat\tests\try_measurements.py", line 1, in
from easystat.measurements import result_with_uncertainty
File "C:\Users\David\Documents\ECPC\easystat\src\easystat\measurements.py", line 2, in
from shortcuts import stdev_of_mean
ModuleNotFoundError: No module named 'shortcuts'
Het probleem is dat wanneer je met Python een script runt en je importeert iets, dat Python eerst in de map kijkt waar het script staat (hier tests
) en daarna zoekt in de lijst met geïnstalleerde packages. De module shortcuts
staat niet in tests
. Toen we measurements.py
draaiden kon hij die wél vinden want measurements.py
en shortcuts.py
staan in dezelfde map. Dus afhankelijk van welk script we draaien kan hij de modules soms wel vinden, soms niet. Dat is natuurlijk niet zo handig. De oplossing: absolute imports: geef bij alle imports altijd de naam van je package op.
Import aanpassen: absolute imports
Je past in het bestand measurements.py
de regel from shortcuts ...
aan door de naam van het package toe te voegen en ziet dat de oranje kriebeltjes ook verdwijnen. Je test de code in try_measurements.py
opnieuw. Gelukt!
Testcode
(easystat) > python tests/try_measurements.py
measurements=[1, 2, 2, 2, 3]
Result of measurements is: 2.00 +- 0.28.
Checkpunten:
- Je hebt de import in
try_measurements.py
aangepast. - Je hebt de import in
measurements.py
aangepast. - Je krijgt geen foutmelding als je het bestand
try_measurements.py
runt.
Projecttraject
- Easystat uv project aanmaken
- Easystat virtual environment aanmaken
- Easystat
shortcuts.py
,measurements.py
entry_measurements.py
aanmaken - Easystat
shortcuts.py
testen - Easystat dependencies toevoegen
- Easystat package imports fixen
Wheels
Wheels¶
Wanneer we klaar zijn om ons package te delen met andere gebruikers gebruiken we het commando build
om zogeheten wheels te bouwen. Wheels zijn de bestanden die uv en pip downloaden en installeren wanneer je zegt pip install numpy
. Het is een soort ingepakte installer met alles wat er nodig is om het package te gebruiken.
Bouw een wheel
- Bouw het wheel van easystat met
uv build
. - Bekijk de namen van de bestanden in de nieuwe map
easystat/dist
, welke extensie hebben ze?
(easystat) > uv build
Building source distribution (uv build backend)...
Building wheel from source distribution (uv build backend)...
Successfully built dist\easystat-0.1.0.tar.gz
Successfully built dist\easystat-0.1.0-py3-none-any.whl
Een .tar.gz
-bestand is een soort zipbestand met daarin de broncode van ons package (een source distribution). De tests worden daar niet in meegenomen. Een wheelis een soort bestand dat direct geïnstalleerd kan worden met
pip
. Zogenaamde pure-python packages bevatten alleen Pythoncode — en geen C-code die gecompileerd moet worden voor verschillende besturingssystemen of hardwareplatforms. Je herkent ze aan none-any
in de bestandsnaam. Nonevoor
niet-OS-specifieken
anyvoor
draait op elk hardwareplatform. We kunnen dit bestand als download neerzetten op een website of aan anderen mailen. Zij kunnen het dan installeren met
pip install
.
Test wheel
Laten we het wheel uitproberen. We gaan straks een nieuw virtual environment aanmaken, installeren het wheel en proberen het testscript te runnen — één keer vóór het installeren van het wheel en één keer ná het installeren, als volgt:
- Maak een nieuw, leeg, virtual environment.
- Draai
tests/try_measurements.py
en bekijk de foutmelding. - Installeer het wheel met
uv pip install .\dist\easystat-0.1.0-py3-none-any.whl
. - Draai
tests/try_measurements.py
en bekijk de uitkomst.
Het werkt! Je ziet dat pip install
niet alleen ons package easystat
installeert, maar ook de dependency numpy
. Dat is precies wat we willen.
Het is belangrijk om de wheels niet in je GitHub repository te committen. Je repository is voor broncode, waarmee wheels gebouwd kunnen worden. Als je de stappen voor het aanmaken van de repository netjes gevolgd hebt dan heb je een .gitignore
toegevoegd met Python-specifieke bestandsnamen en directories die genegeerd worden door Git en GitHub.
uv gebruiken voor een bestaand project: pythondaq¶
Natuurlijk willen we uv ook gaan gebruiken bij pythondaq
. We maken nu alleen geen nieuw project, maar gaan uv toevoegen aan een bestaand project. Daarvoor moeten we twee dingen doen. Als eerste gaan we uv initialiseren in de pythondaq
repository en dan moeten we de code in de src
-structuur plaatsen.
Pythondaq: uv
- Je project
pythondaq
is zo tof aan het worden dat je het met uv gaat beheren zodat jij en anderen het gemakkelijk kunnen installeren en gebruiken. Als eerste open je de repository in GitHub Desktop en Visual Studio Code en open je een nieuwe terminal. Je test voor de zekerheidrun_experiment.py
nog even uit zodat je zeker weet dat alles nu nog werkt. Vervolgens maak je een uv project . Dan kies je op twee plaatsen dat je Python 3.12 wilt gebruiken. Dan synchroniseer je je virtual environment en commit je je wijzigingen. - Test
run_experiment.py
en voeg alle benodigde dependencies toe totdat alles werkt en je opnieuw het lampje ziet gaan branden en de resultaten van je experiment krijgt. Commit je wijzigingen.
Checkpunten:
- Je hebt uv geïnitialiseerd in de Pythondaq projectmap.
- Na het initialiseren van uv is er een
pyproject.toml
en een.python-version
in de projectmap aangemaakt. - Wanneer met
uv sync
een nieuwe virtual environment met Python 3.12 wordt aangemaakt werktrun_experiment.py
daarna in die nieuwe omgeving naar behoren.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Pythondaq: src-layout
run_experiment.py
en die moet werken.
pythondaq
├──
src
└──
pythondaq
├──
__init__.py
├──
arduino_device.py
├──
diode_experiment.py
└──
run_experiment.py
├──
.gitattributes
├──
.gitignore
├──
.python-version
├──
pyproject.toml
├──
README.md
└──
uv.lock
Checkpunten:
- Je 'oude' code staat nu allemaan in
src/pythondaq
. -
run_experiment.py
draait zonder problemen.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Model, view, controller packages
In grotere projecten is het gebruikelijk om model, view, controller niet alleen uit te splitsen in verschillende scripts, maar ook in aparte packages te zetten.
- Maak 3 extra packages in de
pythondaq
package.models
,views
encontrollers
. - Zet de modules in de juiste packages.
- Test je code zodat alle imports weer werken.
Van script naar applicatie¶
Om onze python code te testen heb je tot nu toe waarschijnlijk op de run
-knop in Visual Studio Code gedrukt. Of je hebt in de terminal aan python gevraagd om het script.py
te runnen:
run
-knop gebruikt moet wel het bestandje open staan dat je wilt runnen. Kortom, best een beetje gedoe. Maar als we programma's zoals uv, Conda of Python willen gebruiken hoeven we helemaal niet het juiste bestandje op te zoeken en te runnen. We hoeven alleen maar een commando in de terminal te geven — bijvoorbeeld python
of conda
— en de computer start automatisch het juiste programma op.
Dat willen wij ook voor onze programma's! En omdat we uv gebruiken kunnen we dat heel eenvoudig doen. We gaan even in een andere test-repository een commando toevoegen om de module uit te voeren waarvan je de code in paragraaf Modules kunt vinden. De twee bestanden square.py
en count_count.py
hebben we voor jullie netjes in een package geplaats in de repository AnneliesVlaar/just_count
met de volgende structuur:
just_count/
src/
just_count/
__init__.py
square.py
count_count.py
.python-version
pyproject.toml
README.md
uv.lock
De bestanden square.py
en count_count.py
zien er hetzelfde uit als in paragraaf Modules:
We kunnen uv niet vragen om een script te runnen, maar wel om een functie in een module uit te voeren. Een nieuw uv project krijgt automatisch al een voorbeeldscript. Daar gaan we even naar kijken en daarna passen we het aan voor eigen gebruik.
Voorbeeldscript
Je cloned de repository just_count in GitHub desktop en opent het daarna vanuit GitHub Desktop in Visual Studio Code. Je ziet een pyproject.toml
en een uv.lock
in de repository staan, dus je maakt meteen een virtual environment aan . Je opent een terminal en voert de opdracht just-count
uit. De code hiervoor staat in src/just_count/__init__.py
. Dit is overigens niet de beste plek, maar werkt prima als eenvoudig voorbeeld. Je bekijkt de code en ziet dat de bewuste code in een functie main()
staat.
Als wij willen dat onze eigen code draait als we just-count
intypen, dan moeten we zorgen dat onze code ook in een functie gezet wordt.
Main functie toevoegen
Je opent het hoofdbestand count_count.py
en zet de body
van de module in een functie main()
. Daarna pas je het bestand aan zodat de functie nog steeds wordt uitgevoerd wanneer je het bestand count_count.py
runt.
Testcode
Checkpunten:
- Er is een functie
main()
in het bestandcount_count.py
- Het runnen van het bestand
count_count.py
geeft de outputThe square of 5 is 25
Projecttraject
- main functie toevoegen
- commando toevoegen
- commando testen
In pyproject.toml
kunnen we nu het commando toe gaan voegen. In de scripts
-sectie kunnen we aangeven met welk commando een functie uit een module wordt uitgevoerd. In pyproject.toml
staat al zo'n kopje:
naam_commando
het commando dat je in moet typen in de terminal, package
is de naam van het Python package waar de code staat, module
is de naam van de module waar de code staat, en naam_functie
is de naam van de functie waarin de code staat. Als je module
weglaat, dan kijkt uv in __init__.py
.
Om de wijzigingen aan pyproject.toml
door te voeren moet je je virtual environment wel opnieuw synchroniseren. uv installeert dan jouw package ook opnieuw.
commando toevoegen
Je voegt in de pyproject.toml
onder het kopje [project.scripts]
een nieuw commando square
toe. Deze verwijst naar de functie main()
welke in de module count_count.py
staat die ondergebracht is in de package just_count
. Omdat je handmatig het toml-bestand hebt aangepast synchroniseer je je virtual environment opnieuw .
Checkpunten:
- De naam van het commando is
square
. - De verwijzing na het = teken begint met twee aanhalingstekens gevolgd door het package
just_count
gevolgt door een punt. - Na de punt staat de naam van de module
count_count.py
zonder de extensie.py
gevolgd door een dubbele punt. - Na de dubbele punt staat de naam van de functie
main()
zonder haakjes()
. - Achter de functie staan weer dubble aanhalingstekens om de verwijzing te sluiten.
- Na het opslaan van de
pyproject.toml
is het package opnieuw geïnstalleerd.
Projecttraject
- main functie toevoegen
- commando toevoegen
- commando testen
Commando testen
Nu je het commando square
hebt aangemaakt ga je deze testen in een terminal. Er verschijnt een error ModuleNotFoundError: No module named 'square'
. Je leest het info-blokje hieronder.
Je runt het commando square
opnieuw en je ziet de tekst The square of 5 is 25
verschijnen. Je vraagt je af of het commando ook werkt als de terminal in een andere map zit. Met het commando cd..
ga je naar een bovenliggende map. Je test het commando square
en ziet weer de tekst The square of 5 is 25
verschijnen. Je concludeert dat het commando nu overal werkt zolang het juiste virtual environment is geactiveerd. Dat test je uit door het virtual environment te deactiveren en het commando square
nogmaal te proberen. Je krijgt een error en hebt daarmee je vermoeden bewezen. Tevreden ga je door naar de volgende opdracht.
ModuleNotFoundError: No module named 'square'
Als je de Traceback leest zie je dat het probleem ontstaat in de module count_count.py
. Zoiets hebben we al eerder gezien toen we werkten met het easystat package... Pas het import statement aan naar from just_count import square
.
Pseudo-code
(just-count) > square
Traceback (most recent call last):
File "C:\Users\David\AppData\Roaming\uv\python\cpython-3.10.18-windows-x86_64-none\lib\runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Users\David\AppData\Roaming\uv\python\cpython-3.10.18-windows-x86_64-none\lib\runpy.py", line 86, in _run_code
exec(code, run_globals)
File "C:\Users\David\Documents\ECPC\just_count.venv\Scripts\square.exe__main__.py", line 4, in
File "C:\Users\David\Documents\ECPC\just_count\src\just_count\count_count.py", line 1, in
import square
ModuleNotFoundError: No module named 'square'
Checkpunten:
- Het import statement in
count_count.py
is genoteerd beginnend vanuit de mapsrc
. - Het commando
square
werkt als het juiste virtual environment is geactiveerd. - Het commando
square
werkt nog steeds nadat je met het commandocd..
naar een bovenliggende map bent gegaan. - Het commando
square
werkt niet als het virtual environment is gedeactiveerd.
Projecttraject
- main functie toevoegen
- commando toevoegen
- commando testen
Error analysis
Als extra oefening gaan we met uv een commando maken om een ander script uit te laten voeren. De package is al aangemaakt, maar werkt nog niet naar behoren. Los in de volgende opdrachten de errors op om het script data_analysis.py
te laten runnen.
- Ga naar GitHub en clone
AnneliesVlaar/erroranalysis
in GitHub Desktop en open de repository daarna in Visual Studio Code. - Natuurlijk maak je gelijk een nieuw virtual environment aan , voordat we dit package gaan testen.
- Snuffel door de bestanden en mappen, en open
src/erroranalysis/data_analysis.py
. Dit is het script wat moet kunnen runnen. - Run het script
data_analysis.py
en los de errors één voor één op.
Om erachter te komen of de problemen die we hierboven hadden écht zijn opgelost maak je een nieuw leeg virtual environment aan en test je dat het script niet werkt. Dan installeer je het package en run je het script opnieuw. Werkt alles? Mooi! Dan gaan we nu een commando aanmaken om de functie table()
aan te roepen.
- Open
pyproject.toml
en zoek het kopje voor scripts. Het formaat was: pas de regel aan zodat jouw commando de functietable()
aanroept insrc/erroranalysis/data_analysis.py
. Je mag de naam van het commando zelf kiezen. - Ga naar de terminal en kijk of het werkt!
(erroranalysis) > naam_commando Area of the kitchen table is: 1.8386 ± 0.0049 m
Pythondaq: test imports
tests
-map met test_imports.py
in de repository pythondaq
.
Je runt het bestand test_imports.py
en lost de errors op. Daarna werkt je package ook als je het aanroept van buiten de map met broncode. Je pythondaq
-repository is nu een volledig project dat je met andere gebruikers van Python kunt delen, bijvoorbeeld via een wheel.
pythondaq
├──
src
└──
pythondaq
├──
__init__.py
├──
arduino_device.py
├──
diode_experiment.py
└──
run_experiment.py
├──
tests
└──
test_imports.py
├──
.python-version
├──
pyproject.toml
├──
README.md
└──
uv.lock
Pseudocode
Testcode
(ECPC) > python test_imports.py
Traceback (most recent call last):
File "c:\Users\David\Documents\ECPC\pythondaq\tests\test_imports.py", line 1, in
import pythondaq.run_experiment
File "C:\Users\David\Documents\ECPC\pythondaq\src\pythondaq\run_experiment.py", line 5, in
from diode_experiment import DiodeExperiment, list_resources
ModuleNotFoundError: No module named 'diode_experiment'
Checkpunten:
- Er is een map
tests
in de repositorypythondaq
. - De import statements in de modules in het package
pythondaq
zijn aangepast zodat het bestandtest_imports
runt zonder problemen.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Applicaties runnen in virtual environments
Inmiddels ben je misschien gewend dat je virtual environments altijd moet activeren voordat je applicaties kunt runnen die daarin geïnstalleerd zijn. In Visual Studio Code gaat dat automatisch als je het environment goed geselecteerd hebt. Maar... hoe moet dat zonder VS Code? Omdat uv virtual environments in je projectmap neerzet (de .venv
-map) moet je met je terminal wel in de goede map zitten. Daarna kun je ervoor kiezen om het commando te runnen met uv (dan is activeren niet nodig) of zonder uv (dan is activeren wel nodig). Voer de volgende opdrachten uit om dat te proberen.
- Open in GitHub Desktop nog een keer je
easystat
repository. - Klik op Menu > Repository > Open in Command Prompt om een nieuwe terminal te openen, in je projectmap.
- Run het commando
easystat
. Dit werkt niet. - Run het commando
uv run easystat
. Dit werkt wel. - Activeer het environment door het volgende commando te runnen:
.venv\Scripts\activate
. - Controleer dat
(easystat)
aan het begin van de opdrachtprompt staat. - Run het commando
easystat
. Dit werkt nu wel.
Je mag zelf kiezen welke methode je fijn vindt.
Pythondaq: applicatie
Je maakt een commando om het script run_experiment.py
uit de repository pythondaq
te starten . Wanneer je het commando aanroept gaat het LED-lampje branden, en verschijnt er even later een IU-plot op het scherm. Je test of het commando ook buiten Visual Studio Code werkt door vanuit GitHub Menu > Repository > Open in Command Prompt een nieuwe terminal te openen. Je test het commando met uv run
en ook door het juiste virtual environment te activeren . Je ziet dat ook dan het commando werkt, zonder uv run
. Wat een feest! Je hebt nu een applicatie geschreven die een Arduino aanstuurt om een ledje te laten branden. En je kunt je applicatie gewoon vanuit de terminal aanroepen!
Pseudo-code
Checkpunten:
- De functie in
run_experiment.py
bevat alle code die uitgevoerd moet worden om een meting te starten. - Het commando in de
pyproject.toml
verwijst op de correcte manier naar de functie inrun_experiment.py
. - Het aanroepen van het commando zorgt ervoor dat een meting gestart wordt.
- Het commando werkt ook in een
losse
zolang het juiste virtual environment actief is, ófuv run
wordt gebruikt.
Projecttraject
- Pythondaq: docstrings
- Pythondaq: uv
- Pythondaq: src-layout
- Pythondaq: test imports
- Pythondaq: applicatie
Versie 2.0.0
In de pyproject.toml
kan je ook de versie aangeven van je package. Maar wanneer hoog je nu welk cijfertje op? Wanneer wordt iets versie 2.0.0? Daar zijn conventies voor. Bug fixes gaan op het laatste cijfer, wijzigingen en nieuwe features gaan op het middelste cijfer. Wanneer de applicatie dusdanig verandert dat je bijvoorbeeld bestanden die je met oude versie hebt gemaakt niet met de nieuwe versie kunt openen, dan verander je het eerste cijfer. Je start vaak met versie 0.1.0 en blijft tijdens het bouwen van je project ophogen naar 0.2.0 en soms zelfs 0.83.0. Wanneer je project min of meer klaar is voor eerste gebruik, dan kies je er vaak voor om versie 1.0.0 te releasen.
-
Echt gebeurd: meerdere studenten leverden hun grafische applicatie in voor een beoordeling. We konden het niet draaien, want er misten bestanden. Bij de student werkte het wel, maar bij ons echt niet. ↩
-
more
is een programma die aangeleverde tekst per pagina laat zien, waar je met Space een volgende pagina te zien krijgt. Met Enter krijg je maar één regel extra en met Q sluit je het programma meteen af. Het|
karakter stuurt output door. Dusuv help | more
stuurt de output vanuv help
door naar het programmamore
. ↩ -
Wanneer de repository op GitHub wordt geplaatst wordt deze README automatisch op de hoofdpagina van de repository getoond, onder de code. ↩
-
Ja er is een map
easystat
met daarin een mapsrc
met daarin weer een mapeasystat
— dat kan nog wel eens verwarrend zijn. Het is conventie om de projectmap dezelfde naam te geven als je package. Het pad is dus eigenlijkproject/src/package
en dat wordt dan, in ons geval,easystat/src/easystat
. ↩ -
Vroeger was er een
setup.py
maar Python schakelt nu langzaam over naar dit nieuwe bestand. ↩ -
In f-strings kunnen tussen de accolades variabelen of functieaanroepen staan. Voeg daar het
=
-teken aan toe en je krijgt niet alleen de waarde, maar ook de variabele of aanroep zelf te zien. Bijvoorbeeld: als je definieertname = "Alice"
, dan geeftprint(f"{name}")
als uitkomstAlice
. Maar voeg je het=
-teken toe zoals inprint(f"{name=")}
wordt de uitvoername='Alice'
. Je ziet dan dus ook meteen de naam van de variabele en dat kan handig zijn. ↩ -
Tom Preston-Werner, Pradyun Gedam, and others. Tom's obvious, minimal language. URL: https://github.com/toml-lang/toml. ↩