Skip to content

Pythonprojecten met uv

In de vorige hoofdstukken heb je gewerkt met een eigen virtual environment per project, 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 kunt delen. In deze package 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 de packages als dependencies opgeven is 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. Als je dat netjes doet, dan hoeven jouw gebruikers alleen maar jouw package te installeren — de rest gaat vanzelf.

En… hoe test je je package, zodat je zeker weet dat je package 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 dat 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 uv pip install my_new_cool_app; dat zou wel mooi zijn.

uv helpt je ook hier om al deze problemen op te lossen.

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). De Terminal Adventure Game helpt je om met meer gemak te navigeren in een terminal.

Projecttraject

  • Werken in een terminal

Je gaat uv bedienen door commando's te geven in de terminal van Visual Studio Code. Je laat de terminal weten welk programma je wilt gaan besturen door uv in te typen. En daarachter wat je wilt dat uv gaat doen. Je kunt 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 

Info

Zoals je ziet heeft uv heel veel verschillende commando's. uv is een Zwitsers zakmes: het bevat heel veel tools voor wie dat nodig heeft. Jij hebt voor nu lang niet alles nodig, dus laat je niet uit het veld slaan door deze lange lijst. In de rest van dit hoofdstuk vertellen we je precies wat je wel nodig hebt. Als je meer wilt weten kun je het beste de documentatie lezen.

Nieuw uv project

Info

Je gaat 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 package 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

  1. Open Github Desktop en ga naar Menu > File. Kies hier voor New repository .... Geef de repository de naam easystat en zet de repository in de map ECPC. Vink Initialize this repository with a README aan en kies bij Git ignore voor Python.
  2. Open de repository easystat in Visual Studio Code (Menu > Repository > Open in Visual Studio Code).
  3. Open een terminal in je Visual Studio Code-omgeving (Menu > Terminal > New Terminal). Maak het uv project aan met:
    Terminal
    uv init --package
    
  4. 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
    └── •••

  5. 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 map 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 

Checkpunten

  • De projectmap easystat staat in de map ECPC.
  • In de projectmap easystat staat een map src.
  • In de map src staat een package map easystat

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Laten we één voor één kijken welke mappen en bestanden uv heeft aangemaakt. Je had al een README.md in de projectmap staan. Hierin komt een algemene beschrijving van het project.3

Dan komt de src-map. Daarin komt het nieuwe package easystat4 te staan. Er is alvast 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.toml5 waarin alle informatie over je project wordt bijgehouden. Ook staat er in dit bestand informatie over 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 het project. Je kunt daar bijvoorbeeld een beschrijving toevoegen of het versienummer aanpassen. Ook bevat die sectie de dependencies. Dit zijn alle Pythonpackages die het 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 de 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 — deze versie is inmiddels bijna twee jaar oud.

Pythonversie in het 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 of je code ook echt werkt met 3.12 (omdat jijzelf dan bijvoorbeeld met 3.13 werkt en je de code dus nooit getest hebt met 3.12).

De sectie [project.scripts] zorgt ervoor dat je het script kunt aanroepen door easystat in de terminal in te typen. De sectie [build-system] zorgt ervoor dat je een package kunt maken en uploaden naar de Python Package Index (PyPI). De sectie [build-system] is voor nu nog niet belangrijk.

Easystat: virtual environment synchroniseren

  1. Hoewel we hierboven beweerden dat je easystat kunt intypen in de terminal en dat er dan een script draait, werkt dat hier (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.
  2. Open het bestand src/eaystat/__init__.py. Rechtsonderin zie je inderdaad Select Interpreter. Als je daar op klikt, zie je alleen niet Python 3.x.x (easystat) in het rijtje staan... Druk op Esc om het menu te verlaten.
  3. Type in de terminal van Visual Studio Code:
    > uv sync 
    
    Wat dit gedaan heeft is het automatisch aanmaken van het virtual environment op basis van je projectinstellingen. Dus de Pythonversie die in .python-version staat en eventuele dependencies die gedefinieerd zijn in je pyproject.toml.
  4. Kies de nieuwe virtual environment.
  5. Open een nieuwe terminal en type easystat. Als het goed is werkt het nu wél!
  6. Commit in GitHub Desktop de wijzigingen die uv sync heeft gedaan (een uv.lock file, zie later).

Checkpunten

  • In Visual Studio Code staat rechtsonderin 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 synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Maken van de package

Nu ga je starten met de package. Je gaat een aantal keer de foutmelding ModuleNotFoundError tegenkomen, maar dit los je stap voor stap ook weer op. Stel, je berekent vaak de standaarddeviatie van het gemiddelde en je maakt daarvoor een handige shortcut in shortcuts.py. Nu wil je 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 kun je doen door de oorspronkelijke module te importeren in het nieuwe script, zodat je de functie stdev_of_mean ook daar kunt gebruiken. Je maakt een script try_measurements.py om dit allemaal te testen, en dit script zet je expres niet in het package, maar in een nieuwe map tests. Het testscript hoort immers niet bij de code van de easystat package.

Easystat: benodigde scripts aanmaken

Maak de bestanden shortcuts.py, measurements.py en try_measurements.py aan. Kopieer onderstaande code in de betreffende bestanden. In welke map moet elk bestand staan? Je moet in ieder geval nog één map zelf aanmaken.

shortcuts.py
"""Easy shortcut functions for statistical calculations.

Currently only provides the `stdev_of_mean()` function which calculates the
standard deviation of the mean.
"""

import numpy as np 


def stdev_of_mean(values):
    """Calculate the standard deviation of the mean"""
    return np.std(values) / np.sqrt(len(values))    
measurements.py
"""Statistical functions related to measurements."""

import numpy as np
from shortcuts import stdev_of_mean


def result_with_uncertainty(values):
    """Return result with uncertainty from list of measurements."""
    return np.mean(values), stdev_of_mean(values)
try_measurements.py
"""Test the easystat package."""

from measurements import result_with_uncertainty


measurements = [1, 2, 2, 2, 3]
result, uncertainty = result_with_uncertainty(measurements)

print(f"{measurements=}")
print(f"Result of measurements is: {result:.2f} +- {uncertainty:.2f}.")
easystat
├── src
           ├── easystat
                      ├── __init__.py
                      ├── measurements.py
                      └── shortcuts.py
├── tests
           └── try_measurements.py
├── pyproject.toml
└── readme.md

Import numpy could not be resolved

Misschien valt het je op dat Visual Studio Code een oranje kringeltje onder numpy zet in de eerste regels van twee scripts. En dit gebeurt ook onder shortcuts en measurements. Als je hier je muiscursor op plaatst, krijg je een popup met de melding Import numpy could not be resolved. Daar moet je misschien wat mee... In de volgende opdrachten ga je deze problemen één voor één oplossen.

Checkpunten

  • De map easystat/src/easystat bevat het bestand measurements.py.
  • De map easystat/src/easystat bevat het bestand shortcuts.py.
  • In de projectmap easystat staat een map tests.
  • De map easystat/tests bevat het bestand try_measurements.py.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

In de eerste regel van try_measurements.py importeer je de functie uit het nieuwe package om deze uit te proberen. In de eerste print-regel gebruik je een handige functie van f-strings.6

Easystat: testen

Je bent heel benieuwd of je package al werkt. Je runt als eerste het bestand shortcuts.py en... je krijgt een foutmelding.

Testcode

shortcuts.py
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

Checkpunten

  • Je hebt de juiste virtual environment geactiveerd.
  • Je krijgt een foutmelding ModuleNotFoundError: No module named 'numpy' als je het bestand shortcuts.py runt.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

De beloofde ModuleNotFoundError! Het package heeft numpy nodig en dat heb je nog niet geïnstalleerd in de virtual environment. Dat is ook de reden voor de kringeltjes onder numpy in het bestand shortcuts.py. Het installeren van numpy kun je handmatig doen, maar dan hebben straks andere gebruikers een probleem. Veel beter is het om netjes aan te geven dat het package numpy nodig heeft — als dependency.

Dependencies toevoegen

Om een dependency aan te geven vertel je uv dat hij deze moet toevoegen met:

(easystat) > uv add numpy 

Easystat: dependency 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 meer als je het bestand shortcuts.py runt.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Fijn! uv heeft numpy nu toegevoegd aan de virtual 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 project 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 een dependency gaat met uv remove PACKAGE.

Info

Als je de code in het package aanpast, dan hoef je het virtual environment niet opnieuw te synchroniseren met uv sync. Maar als je met de hand iets wijzigt 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. In dit bestand 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.lock

  1. Clone de repository NatuurkundePracticumAmsterdam/upgrade-uv-lock.
  2. Open de repository in Visual Studio Code en open een nieuwe terminal.
  3. Maak in één keer een virtual environment aan en installeer de dependencies met uv sync.
  4. Waarvoor gebruikt uv de lockfile (uv.lock)? Welke versies van NumPy en matplotlib worden geïnstalleerd?
  5. 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 in pyproject.toml.
  6. Nu wil je toch 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 dat uv.lock gewijzigd is.
  7. 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 uitgevoerd worden met uv sync --upgrade. Grote kans dat je die vaker zult gebruiken.

Absolute imports

Je hebt nu een uv project en dependencies toegevoegd, maar je hebt nog niet alle code getest. Dat ga je nu doen!

Easystat: opnieuw testen

Je probeert nog een keer het bestand shortcuts.py te runnen en ziet dat dat gewoon werkt. Daarna probeer je het bestand measurements.py te runnen. Dat werkt ook. Maar wel gek dat er kringeltjes onder from shortcuts import stdev_of_mean staan, hij doet het toch gewoon? Je kijkt even welke waarschuwing gegeven wordt door je muiscursor op de kringeltjes te plaatsen. Daarna probeer je het bestand try_measurements.py. Hier gaan dingen mis: daarom zette Visual Studio Code kringeltjes onder measurements (en onder shortcuts vanwege een vergelijkbare reden).

Testcode

(easystat) > python tests/try_measurements.py 

Checkpunten

  • Je kunt zonder problemen het bestand shortcuts.py runnen.
  • Je kunt het bestand measurements.py runnen.
  • Je krijgt de foutmelding ModuleNotFoundError als je het bestand try_measurements.py runt.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Je wilt dus de module measurements importeren, maar Python kan deze module niet vinden. Dat is ook wel een klein beetje logisch, want try_measurements.py staat in de map tests terwijl measurements.py in de map src/easystat staat. Je moet Python daarom vertellen wáár hij die module kan vinden, namelijk in jouw nieuwe package easystat. Doordat je een package gemaakt hebt, hoef je niet precies te vertellen in welke map alles te vinden is, je gebruikt alleen de naam van het package. Dus niet map.op.computer.easystat.src.easystat, maar gewoon easystat. Wel zo makkelijk.

Easystat: imports aanpassen

Je past from measurements ... aan naar from easystat.measurements .... Je test de code opnieuw. Verdorie, weer een error. Overleg met je buurmens wat deze error betekent. Waarom kreeg je die error niet toen je het bestand measurements.py testte?

Testcode

(easystat) > python tests/try_measurements.py 

Checkpunten

  • Je kunt nog steeds zonder problemen het bestand shortcuts.py runnen.
  • Je kunt ook nog steeds het bestand measurements.py runnen.
  • Bij het runnen van het bestand try_measurements.py krijg je geen foutmelding meer voor de module measurements.
  • Bij het runnen van het bestand try_measurements.py krijg je wel een nieuwe foutmelding.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

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 (in dit geval tests) en daarna pas zoekt in de lijst met geïnstalleerde packages. De module shortcuts staat niet in tests. Toen je measurements.py draaide kon Python de module shortcuts wél vinden, want measurements.py en shortcuts.py staan in dezelfde map. Dus afhankelijk van welk script je draait kan Python de modules soms wel vinden en soms niet. Dat is natuurlijk niet zo handig. De oplossing: absolute imports. Geef bij alle imports van je eigen package altijd de naam van je package op.

Easystat: 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 kringeltjes ook verdwijnen. Je test de code in try_measurements.py opnieuw. Gelukt!

Testcode

(easystat) > python tests/try_measurements.py 

Checkpunten

  • Je hebt de import in het bestand try_measurements.py aangepast.
  • Je hebt de import in het bestand measurements.py aangepast.
  • Je krijgt geen foutmelding meer als je het bestand try_measurements.py runt.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal
Wheels

Wheels

Wanneer je klaar bent om jouw package te delen met andere gebruikers gebruik je het commando build om zogeheten wheels te bouwen. Wheels zijn de bestanden die uv en pip downloaden en installeren wanneer je zegt pip install PACKAGE. Het is een soort ingepakte installer met alles wat er nodig is om het package te gebruiken.

Easystat: wheel

  1. Bouw het wheel van easystat met uv build.
  2. Bekijk de namen van de bestanden in de nieuwe map easystat/dist. Welke extensie hebben ze?

(easystat) > uv build 

Een .tar.gz-bestand is een soort zipbestand met daarin de broncode van het package (een source distribution). De tests worden daar niet in meegenomen. Zogenaamde pure-python wheels 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. None voor niet-OS-specifiek en any voor draait op elk hardwareplatform. Je kunt dit bestand als download neerzetten op een website of aan anderen mailen. Zij kunnen het dan installeren met pip install.

Easystat: test wheel

Tijd om het wheel uit te proberen. Je gaat straks een nieuw virtual environment aanmaken, installeert het wheel en probeert het testscript te runnen — één keer vóór het installeren van het wheel en één keer ná het installeren. Volg hiervoor de volgende stappen:

  1. Maak een nieuw — leeg — virtual environment aan.
  2. Run het bestand test/try_measurements.py en bekijk de foutmelding.
  3. Installeer het wheel met uv pip install .\dist\easystat-0.1.0-py3-none-any.whl.
  4. Run het bestand test/try_measurements.py opnieuw en bekijk de uitkomst.

Het werkt! Je ziet dat pip install niet alleen jouw package easystat installeert, maar ook de dependency numpy. Dat is precies wat je wilt.

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

Natuurlijk willen we uv ook gaan gebruiken bij pythondaq. Je maakt nu alleen geen nieuw project, maar je gaat uv toevoegen aan een bestaand project. Daarvoor moet je twee dingen doen: als eerste ga je uv initialiseren in de repository pythondaq en daarna moet je de code in de src-structuur plaatsen.

Pythondaq: uv project

  1. 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 daarna in Visual Studio Code. Open nu een nieuwe terminal. Je test voor de zekerheid run_experiment.py nog even uit, zodat je zeker weet dat alles nu nog werkt. Vervolgens maak je een uv project aan . Dan geef je op twee plaatsen aan dat je Python 3.12 wilt gebruiken (in welke bestanden moet je dit aangeven?). Daarna synchroniseer je je virtual environment en commit je je wijzigingen.
  2. Test nu run_experiment.py opnieuw. Voeg alle benodigde dependencies toe , net zolang totdat alles werkt en je opnieuw de LED ziet branden en de resultaten van je experiment krijgt. Commit je wijzigingen.

Checkpunten

  • Je hebt uv geïnitialiseerd in de projectmap pythondaq.
  • Na het initialiseren van uv zijn de bestanden pyproject.toml en .python-version in de projectmap aangemaakt.
  • Wanneer je met uv sync een nieuw virtual environment aanmaakt, staat hier Python 3.12 in.
  • Je hebt alle dependencies toegevoegd, zodat run_experiment.py in de nieuwe environment naar behoren werkt.

Projecttraject

  • Pythondaq: docstrings
  • Pythondaq: uv project
  • Pythondaq: src-layout
  • Pythondaq: test imports
  • Pythondaq: applicatie

Pythondaq: src-layout

Nu de code weer werkt, ga je de bestanden in een src-layout zetten, zie de weergegeven structuur voor het voorbeeld. Je test run_experiment.py opnieuw en je zorgt er voor dat je ook nu de LED ziet branden en de resultaten van je experiment krijgt.
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

  • Al je 'oude' code staat nu in src/pythondaq.
  • run_experiment.py runt zonder problemen.

Projecttraject

  • Pythondaq: docstrings
  • Pythondaq: uv project
  • Pythondaq: src-layout
  • Pythondaq: test imports
  • Pythondaq: applicatie
Model, view en controller packages

In grotere projecten is het gebruikelijk om model, view en controller niet alleen uit te splitsen in verschillende scripts, maar ook in aparte packages te zetten.

  1. Maak drie extra packages in de package pythondaq aan: models, views en controllers.
  2. Zet de modules in de juiste packages.
  3. Test je code zodat alle imports weer werken.

Van script naar applicatie

Om 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:

Terminal
python script.py
Je moet dan wel in Visual Studio Code de juiste map geopend hebben zodat Python het bestand kan vinden. En als je de run-knop gebruikt, moet wel het bestand dat je wilt runnen openstaan. Kortom, best een beetje gedoe. Maar als je programma's zoals uv, Conda of Python wilt gebruiken hoef je helemaal niet het juiste bestand op te zoeken en te runnen. Je hoeft alleen maar een commando in de terminal te geven — bijvoorbeeld python of uv — en de computer start automatisch het juiste programma op.

Dat willen wij ook voor onze programma's! En met uv kun je dat heel eenvoudig doen. Voordat je doorgaat met jouw package pythondaq ga je dit eerst een keer testen. Je gaat daarvoor terug naar je easystat repository.

Je kunt 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.

Easystat: voorbeeldscript bekijken

Je opent je easystat repository in Visual Studio Code. Je opent een terminal en voert de opdracht easystat uit. De code die nu gerund wordt staat in het bestand src/easystat/__init__.py. Dit is overigens niet de beste plek, maar werkt prima als eenvoudig voorbeeld en is daarom automatisch aangemaakt door uv. Je bekijkt de code en ziet dat de bewuste code in een functie main() staat.

Testcode

(easystat) > easystat

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Als je wilt dat er andere code gerund wordt als de opdracht easystat gegeven wordt, dan moet je er voor zorgen dat je naar een functie in het betreffende bestand verwijst. Dit doe je net zoals dat gedaan is in het bestand src/easystat/__init__.py.

Easystat: functie main() toevoegen

Voor het vervolg heb je wat voorbeeldcode nodig. Dat kan van alles zijn dus je kopieert de code in het bestand tests/try_measurements.py naar src/easystat/app.py en zet de body van de module in een functie main(). Ook voeg je aan het eind toe:

if __name__ == '__main__':
    main()
Dit zorgt er voor dat als je het script direct runt de functie main() wordt aangeroepen, terwijl als je code importeert dat niet direct gebeurt. Zie de sectie Modules voor meer informatie. Als je beide regels weglaat gebeurt er helemaal niets als je het script runt. Er wordt dan wel een functie gedefinieerd maar hij wordt niet uitgevoerd.

Testcode

src/easystat/app.py
(easystat) > python src/easystat/app.py

Checkpunten

  • Er staat een functie main() in het bestand app.py.
  • In het bestand app.py is een statement if __name__ == '__main__' toegevoegd.
  • Het runnen van het bestand app.py geeft als laatste regel Result of measurements is: 2.00 +- 0.28.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

In pyproject.toml kun je nu het commando toevoegen om een specifieke functie te runnen. In de sectie [project.scripts] kun je aangeven met welk commando een functie uit een module wordt uitgevoerd. In pyproject.toml staat deze sectie al:

[project.scripts]
easystat = "easystat:main"
De vorm van die laatste regel is als volgt:
naam_commando = "package.module:naam_functie"
Hier is naam_commando het commando dat je in moet typen in de terminal. package is de naam van het Python package waar de code staat en module is de naam van de module waar de code staat. naam_functie is de naam van de functie waarin de code staat. Als je module weglaat, dan kijkt uv in __init__.py. Vandaar dat uv met het commando easystat op het moment de functie main() in __init__.py draait. Maar dit kun je natuurlijk aanpassen!

Om de wijzigingen aan pyproject.toml door te voeren, moet je je virtual environment wel opnieuw synchroniseren. uv installeert dan jouw package ook opnieuw.

Easystat: commando toevoegen

Je past in de pyproject.toml in de sectie [project.scripts] het commando easystat aan. Deze verwijst naar de functie main() welke in de module app.py staat, die ondergebracht is in de package easystat. Omdat je handmatig de pyproject.toml hebt aangepast, synchroniseer je je virtual environment opnieuw .

Checkpunten

  • De naam van het commando is easystat.
  • De verwijzing na het = teken begint met twee aanhalingstekens gevolgd door het package easystat gevolgd door een punt.
  • Na de punt staat de naam van de module app.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 de virtual environment gesynchroniseerd.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Easystat: commando testen

Nu je het commando easystat hebt aangemaakt ga je deze testen in een terminal. Je ziet de verwachte uitvoer verschijnen, met als laatste regel Result of measurements is: 2.00 +- 0.28.

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 easystat en ziet weer de tekst Result of measurements is: 2.00 +- 0.28. 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 easystat nogmaal te proberen. Je krijgt een error en hebt daarmee je vermoeden bewezen. Tevreden ga je door naar de volgende opdracht.

Pseudo-code

(easystat) > easystat 

Checkpunten

  • Het commando easystat werkt als het juiste virtual environment is geactiveerd.
  • Het commando easystat werkt nog steeds nadat je met het commando cd.. naar een bovenliggende map bent gegaan.
  • Het commando easystat werkt niet als het virtual environment is gedeactiveerd.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal
Error analysis

Maak deze opdracht alleen als je extra wilt oefenen. Je gaat 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 met een commando.

  1. Ga naar GitHub en clone NatuurkundepracticumAmsterdam/erroranalysis in GitHub Desktop en open de repository daarna in Visual Studio Code.
  2. Natuurlijk synchroniseer je meteen een virtual environment , voordat je dit package gaat testen.
  3. Snuffel door de bestanden en mappen. Open src/erroranalysis/data_analysis.py. Dit is het script wat moet kunnen runnen.
  4. Run het script data_analysis.py en los de errors één voor één op.
  5. Om erachter te komen of de problemen die je eerder had écht zijn opgelost, maak je een nieuw — leeg(!) — virtual environment aan en test je dat het script niet werkt. Daarna synchroniseer je het environment en run je het script opnieuw. Werkt alles? Mooi!
  6. Dan ga je nu een commando aanmaken om de functie table() aan te roepen. Open pyproject.toml en zoek de sectie scripts. Maak een commando die de functie table() aanroept in src/erroranalysis/data_analysis.py. Je mag de naam van het commando zelf kiezen. Het formaat voor een commando is:
    [project.scripts]
    naam_commando = "package.module:naam_functie"
    
  7. Ga naar de terminal en kijk of het commando werkt. Als je onderstaande output krijgt, dan is het gelukt!
    (erroranalysis) > naam_commando 
    
    

Pythondaq: test imports

Bij het uitbouwen van de applicatie ga je mogelijk onderdelen uit de Python-package importeren. Daarom is het verstandig om, net als in de opdracht Easystat: opnieuw testen, het importeren uit de package te testen. Maak daarvoor een tests-map met test_imports.py in de repository pythondaq. Voeg aan test_imports.py onderstaande code toe:
test_imports.py
import pythondaq.run_experiment
Run het bestand test_imports.py en los de errors op. Nu werkt je package ook als je het aanroept van buiten de map met broncode. Je repository pythondaq 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

run_experiment.py
# define from which package the module diode_experiment should be imported
...
Testcode
test_imports.py
import pythondaq.run_experiment
(ECPC) > python test_imports.py

Checkpunten

  • Er staat een map tests in de repository pythondaq.
  • De import-statements in de modules in het package pythondaq zijn aangepast, zodat het bestand test_imports.py runt zonder problemen.

Projecttraject

  • Pythondaq: docstrings
  • Pythondaq: uv project
  • Pythondaq: src-layout
  • Pythondaq: test imports
  • Pythondaq: applicatie

Easystat: applicatie runnen in terminal

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 Visual Studio Code? Omdat uv virtual environments in je projectmap neerzet — de map .venv — 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 van het virtual environment niet nodig, of zonder uv, dan is activeren van het virtual environment wel nodig. Voer de volgende opdrachten uit om dit te proberen.

  1. Open in GitHub Desktop nogmaals je repository easystat.
  2. Klik op Menu > Repository > Open in Command Prompt om een nieuwe terminal te openen, in je projectmap.
  3. Run het commando easystat. Dit werkt niet.
  4. Run het commando uv run easystat. Dit werkt wel.
  5. Activeer het environment door het volgende commando te runnen: .venv\Scripts\activate.
  6. Controleer dat (easystat) aan het begin van de opdrachtprompt staat.
  7. Run het commando easystat. Dit werkt nu wel.

Je mag zelf kiezen welke methode je fijn vindt.

Projecttraject

  • Easystat: uv project aanmaken
  • Easystat: virtual environment synchroniseren
  • Easystat: benodigde scripts aanmaken
  • Easystat: testen
  • Easystat: dependency toevoegen
  • Easystat: opnieuw testen
  • Easystat: imports aanpassen
  • Easystat: absolute imports
  • Easystat: voorbeeldscript bekijken
  • Easystat: functie main() toevoegen
  • Easystat: commando toevoegen
  • Easystat: commando testen
  • Easystat: applicatie runnen in terminal

Pythondaq: applicatie

Je maakt een commando om het script run_experiment.py uit de repository pythondaq te starten . Wanneer je het commando aanroept gaat de LED 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 een nieuwe terminal te openen (Menu > Repository > Open in Command Prompt). Je test het commando met uv run COMMAND en ook door het juiste virtual environment te activeren . Je ziet dat in beide gevallen het commando werkt. Wat een feest! Je hebt nu een applicatie geschreven die een Arduino aanstuurt om een LED te laten branden. En je kunt je applicatie gewoon vanuit de terminal aanroepen!

Pseudo-code

run_experiment.py
# import statements

# def function
    # code to start a measurement
pyproject.toml
[project.scripts]
naam_commando = "package.module:naam_functie"

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 in run_experiment.py.
  • Het aanroepen van het commando zorgt ervoor dat een meting gestart wordt.
  • Het commando werkt ook in een terminal buiten Visual Studio Code, zolang het juiste virtual environment actief is óf uv run wordt gebruikt.

Projecttraject

  • Pythondaq: docstrings
  • Pythondaq: uv project
  • 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 cijfer 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 bijvoorbeeld bestanden die je met de oude versie hebt gemaakt niet met de nieuwe versie geopend kunnen worden, 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 het gebruik, dan kies je er vaak voor om versie 1.0.0 te releasen.


  1. Echt gebeurd: meerdere studenten leverden hun grafische applicatie in voor een beoordeling. We konden het niet draaien, want er misten bestanden. Bij de studenten werkte het wel, maar bij ons echt niet. 

  2. 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. Dus uv help | more stuurt de output van uv help door naar het programma more

  3. Wanneer de repository op GitHub wordt geplaatst, wordt deze README automatisch op de hoofdpagina van de repository getoond, onder de code. 

  4. Ja, er is een map easystat met daarin een map src met daarin weer een map easystat — dat kan nog wel eens verwarrend zijn. Het is conventie om de projectmap dezelfde naam te geven als je package. Het pad is dus eigenlijk project/src/package en dat wordt dan, in dit geval, easystat/src/easystat

  5. Vroeger was er een setup.py, maar Python schakelt nu langzaam over naar dit nieuwe bestand. 

  6. 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 definieert name = "Alice", dan geeft print(f"{name}") als uitkomst Alice. Maar voeg je het =-teken toe, zoals in print(f"{name=")}, dan wordt de uitvoer name='Alice'. Je ziet dan dus ook meteen de naam van de variabele en dat kan handig zijn. 

  7. Tom Preston-Werner, Pradyun Gedam, and others. Tom's obvious, minimal language. URL: https://github.com/toml-lang/toml