Skip to content

Command-line interface

Gebruikersomgevingen

Vanaf de jaren '60 van de vorige eeuw werden computers interactief. Het was mogelijk om via een terminal commando's aan de computer te geven en te wachten op een antwoord. In tegenstelling tot moderne gebruikersomgevingen waren deze volledig op tekst gebaseerd. Hoewel moderne besturingssystemen — of het nu computers, tablets of mobiele telefoons betreft — volledig grafisch zijn ingericht, is de tekstuele interface nooit verdwenen. Opdrachten geven door te typen is gewoon best wel handig en snel. Ook is het veel eenvoudiger om applicaties te ontwikkelen zonder grafische interface.

Op ieder besturingssysteem — Linux, MacOS, Windows — is een shell, terminal of command prompt te vinden. Als je die opstart kun je op de zogeheten command line opdrachten intypen. Veelal zijn dit commando's om door het bestandssysteem te navigeren en programma's op te starten.

Wanneer je in Visual Studio Code een Pythonscript start dan opent het een terminal onderin het scherm.

Commando's

Je hebt tot nu toe al heel wat commando's in de terminal getypt. Laten we een paar voorbeelden bestuderen:

Terminal
PS> python script.py
Als eerste vertel je welke applicatie je wilt gaan starten; in dit geval: python. Daarna geef je met het argument script.py aan welk Pythonscript je wilt uitvoeren. Vaak kun je ook opties1 meegeven zoals in:

PS> python -V 

Hiermee vraag je Python om het versienummer weer te geven. Soms kunnen opties zelf weer een argument meekrijgen. Bijvoorbeeld:

Terminal
PS> python -m antigravity
Met deze regel geef je Python de optie -m mee en die importeert een module (hier antigravity) en voert die uit. Probeer maar eens zelf wat er gebeurt als je dat commando uitvoert.

Als applicaties veel verschillende functionaliteit hebben dan krijg je regelmatig te maken met een lange regel met een combinatie van argumenten en opties:

Terminal
PS> uv venv --python 3.13
Uitgesplitst in argumenten en opties, met vierkante haken [] om aan te geven welke onderdelen bij elkaar horen, is dat:

uv venv [--python 3.13]

uv argumenten

  1. Naast uv venv heb je ook met andere argumenten gewerkt zoals sync en add. Welke argumenten ken je al van de applicatie uv?
  2. Vraag de lijst met argumenten (commando's) op van uv met uv help (scroll terug naar boven om alles te zien). Hoeveel kende je nog niet?

Projecttraject

  • uv argumenten
  • uv opties en argumenten

uv opties en argumenten

Het gaat er bij deze opdracht om dat je een beetje bekend raakt met helpteksten, het verschil tussen argumenten en opties en waar je informatie kunt vinden. Je hoeft daarvoor niet alle helpteksten volledig door te lezen of alle opties te bekijken. Dat is namelijk heel veel.

  1. Open een terminal en bekijk de helptekst van uv (uv help).
  2. Zoek de optie op om de uv versie weer te geven. Wat is deze versie?
  3. Maak gebruik van het argument help om de helpfunctie van het commando pip op te vragen (uv help pip)
  4. Welke argumenten moet je meegeven (positional arguments?) en welke opties mag je meegeven (optional arguments?) (argument: COMMAND, opties o.a. : --no-cache, --managed-python)?

Projecttraject

  • uv argumenten
  • uv opties en argumenten

Click

Als je gebruik wilt maken van commando's in jouw eigen applicatie dan moet je weten wat de gebruiker in de terminal typt. Dit is mogelijk met sys.argv.2 Hiermee wordt alles wat in de terminal getypt wordt aan input meegegeven:

cli.py
import sys

print(sys.argv)
PS> python cli.py test 123

Met if-statements kun je acties verbinden aan bepaalde argumenten:

cli.py
import sys

args = sys.argv
print(args)

if args[1] == "test":
    print(f"This is a test: {args[2]}")
else:
    print(f"CommandNotFoundError: No command '{args[1]}'.")

Als je meerdere opties en argumenten meegeeft dan wordt het veel werk om die in je script uit elkaar te plukken en ze goed te interpreteren. Om dat makkelijker te maken zijn er verschillende bibliotheken beschikbaar — waaronder een paar in de standard library van Python. Een hele handige — die níet in de standard library van Python zit, maar wél heel populair is — is Click.6

Info

Click maakt gebruik van decorators (@decorator). Om decorators te gebruiken, hoef je niet per se te weten hoe ze werken. Als je meer wilt weten over de werking ervan kijk dan de calmcode tutorial over decorators. Of lees het meer-leren blok over decorators of de Primer on Python Decorators.

Als kort voorbeeld — geïnspireerd op de documentatie van Click — neem je het volgende script:

hello.py
def hello():
    print("Hello physicist!")

if __name__ == "__main__":
    hello()

Dit script print de uitdrukking "Hello physicist!". Je gaat dit script aanpassen en maakt het daarmee mogelijk om de naam en het aantal begroetingen te kiezen. Hiervoor gebruik je Click. Allereerst moet je click importeren en aangeven dat je de hello()-functie wilt gebruiken als commando:

hello.py
import click

@click.command()
def hello():
    print("Hello physicist!")

if __name__ == "__main__":
    hello()

Dit levert je nog niet zoveel op, maar op de achtergrond is Click wel degelijk aan het werk. De @click.command() houdt in de gaten wat er in de command-line wordt ingetypt. Zo kun je de helpfunctie aanroepen door --help achter de naam van het script te zetten:

Terminal
python hello.py --help

Om de code te runnen moet je wel een virtual environment aanmaken, activeren en click installeren.

Helpfunctie

Je neemt het script hello.py over. Je vraagt de helpfunctie van het script op. Je ziet een helptekst verschijnen. Je vraagt je af wat er gebeurt als je @click.command() weghaalt en daarna de helpfunctie opvraagt. Je krijgt gewoon de output van de functie hello() en geen helptekst.
ECPC
├── oefenopdrachten
           ├── hello.py
           └── •••
├── pythondaq
└── •••

Pseudo-code

hello.py
import click

# make function Click command
# function
    # print hello physicist!

# when run this script:
    # run function
Testcode
(oefenopdrachten) > python hello.py --help 

Checkpunten

  • Je vraagt de helpfunctie op door --help achter de bestandsnaam te zetten in de terminal.
  • Er verschijnt een standaard helptekst.
  • Zonder @click.command() verschijnt er geen helptekst, maar de output van de functie.

Projecttraject

  • Helpfunctie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

In de code hieronder wordt met de regel @click.argument("name") aangegeven dat je van de gebruiker een argument verwacht. Zorg dat het argument ook gebruikt wordt in de functie hello:

hello.py
import click

@click.command()
@click.argument("name")
def hello(name):
    print(f"Hello {name}!")

if __name__ == "__main__":
    hello()

Argument toevoegen

Je runt het bestand hello.py en geeft achter de bestandsnaam de naam Alice mee. Er verschijnt Hello Alice! als output in de terminal.

Pseudo-code

hello.py
import click

# make function Click command
# make argument name
# function, parameter name
    # print hello <name>!

# when run this script:
    # run function
Testcode
(oefenopdrachten) > python hello.py 

Checkpunten

  • Het draaien van hello.py zonder een argument (python hello.py) geeft een foutmelding.
  • Het draaien van hello.py met een argument (python hello.py Alice) werkt zoals verwacht.

Projecttraject

  • Helpfunctie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

Warning

Let er op dat je bij @click.argument de naam meegeeft die overeenkomt met de namen van de parameters van je functie. In dit geval heb je een argument "name", dit moet overeenkomen met de functiedefinitie def hello(name):.

Argumenten zijn altijd verplicht en moeten in een vaste volgorde staan. Bij opties is dat anders. Je geeft met mintekens aan dat je een optie meegeeft. Veel opties hebben een lange naam en een afkorting (bijvoorbeeld --count en -c). Opties kunnen zelf weer een argument hebben (bijvoorbeeld --count 3). Het is handig om een standaardwaarde te definiëren. In dat geval mag de gebruiker de optie weglaten. Je voegt een for-loop toe om de begroeting te herhalen.3

hello.py
import click

@click.command()
@click.argument("name")
@click.option(
    "-c",
    "--count",
    default=1,
)
def hello(name, count):
    for _ in range(count):
        print(f"Hello {name}!")

if __name__ == "__main__":
    hello()

Optie toevoegen

Je runt het bestand hello.py en geeft achter de bestandsnaam de naam van je assistent mee. Tevens geef je aan dat je de begroeting vijf keer wilt herhalen. Er verschijnt vijf keer Hello <assistent>! als output in de terminal.

Pseudo-code

hello.py
import click

# make function Click command
# make argument name
# make option count with default value 1
# function, parameter name and count
    # repeat count times
        # print hello <name>!

# when run this script:
    # run function
Testcode
(oefenopdrachten) > python hello.py David -c 5 

Checkpunten

  • Je kan de naam van je assistent 5 keer printen met één commando.
  • Je kan het aantal keer printen opgeven met -c.
  • Je kan het aantal keer printen ook opgeven met --count.
  • Wanneer de optie count wordt weggelaten wordt de naam één keer geprint.
  • Wanneer er geen argument wordt meegegeven met de optie count volgt een foutmelding:

    (oefenopdrachten) > python hello.py David -c 
    
    

Projecttraject

  • Helpfunctie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

Warning

Let er op dat je bij @click.option de afkorting met één minteken meegeeft en de lange naam met twee mintekens. De lange naam moet overeenkomen met de paramater van je functie. In ons geval hebben we een optie "--count" — de lange naam telt. Dit moet overeenkomen met de functiedefinitie def hello(name, count):.

Het is handig om een korte helptekst toe te voegen. Dit gaat als volgt:

hello.py
import click

@click.command()
@click.argument("name")
@click.option(
    "-c",
    "--count",
    default=1,
    help="Number of times to print greeting.",
    show_default=True,  # show default in help
)
def hello(name,count):
    for _ in range(count):
        print(f"Hello {name}!")

if __name__ == "__main__":
    hello()  

Helptekst toevoegen

Voeg de helptekst toe. Vraag daarna de helptekst op zoals in de opdracht Helpfunctie.

Pseudo-code

hello.py
import click

# make function Click command
# make argument name
# make option count with default value 1 and help
# function, parameter name and count
    # repeat count times
        # print hello <name>!

# when run this script:
    # run function

Projecttraject

  • Helpfunctie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

Als je bovenstaand script gebruikt ziet dat er zo uit:

(oefenopdrachten) > python hello.py --help 


(oefenopdrachten) > python hello.py Alice 


(oefenopdrachten) > python hello.py Alice -c 2 


(oefenopdrachten) > python hello.py Alice --count 3 

Extra optie toevoegen

Je runt het bestand hello.py en geeft achter de bestandsnaam de naam van je assistent mee. Tevens geef je aan dat je de begroeting vijf keer wilt herhalen met een pauze van twee seconde ertussen. Het duurt dus acht seconden voordat er vijf keer Hello <assistent>! als output in de terminal staat. Als je de optie voor een pauze niet meegeeft, wordt er ook geen pauze gehouden.

Info

Je kan gebruikmaken van de module time die standaard met Python meekomt.4 Met de functie sleep() kun je de executie van de volgende regel in het script met een aantal seconden uitstellen.

import time

# wait 28 seconds
time.sleep(28)

Pseudo-code

hello.py
import click

# make function Click command
# make argument name
# make option count with default value 1 and help
# make option pause
# function, parameter name, count and pause
    # repeat count times
        # print hello <name>!
        # pause

# when run this script:
    # run function
Testcode
(oefenopdrachten) > python hello.py David -c 5 

Checkpunten

  • Als de optie voor pauze niet wordt meegegeven, dan wordt er géén pauze ingelast.
  • Bij het meegeven van de optie voor pauze, wacht het programma zo lang als verwacht.

Projecttraject

  • Help functie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

Opties zonder argument werken als vlag — een soort aan-/uitknop.5

Vlag toevoegen

Gebruik een optie als vlag om de gebruiker te laten kiezen tussen het wel (tea) of niet (no tea) aanbieden van een kopje thee. Zorg dat er standaard tea wordt aangeboden.

Boolean flags

Lees meer over boolean flags in de Click documentatie.

Pseudo-code

hello.py
import click

# make function Click command
# make argument name
# make option count with default value 1 and help
# make option pause
# make boolean flag tea/no-tea
# function, parameter name, count, pause and tea
    # repeat count times
        # print hello <name>!
        # if wished offer tea
        # pause

# when run this script:
    # run function
Testcode
(oefenopdrachten) > python hello.py David -c 5 

Checkpunten

  • Als de vlag voor een kopje thee niet wordt meegegeven, dan wordt er een kopje thee aangeboden.
  • Als de vlag tea wordt meegegeven, dan wordt er ook een kopje thee aangeboden.
  • Als de vlag no-tea wordt meegegeven, dan wordt er geen kopje thee aangeboden.

Projecttraject

  • Help functie
  • Argument toevoegen
  • Optie toevoegen
  • Helptekst toevoegen
  • Extra optie toevoegen
  • Vlag toevoegen

Easystat: click

Je opent met Github Desktop de repository easystat in Visual Studio Code. Je hebt eerder een virtual environment voor deze repository aangemaakt, maar je hebt geen idee of die in de tussentijd per ongeluk stuk is gegaan. Daarom synchroniseer je de virtual environment . En je test of je de applicatie nog kunt aanroepen met het commando easystat.

Je past de code in het bestand app.py zo aan dat met het commando easystat 4 5 6 het resultaat van de serie metingen met waardes 4, 5 en 6 in de terminal wordt geprint.

Info

Click maakt van alle argumenten een string, tenzij je een default waarde of een type definieert. Gebruik type=int, type=float enzovoorts om aan te geven wat voor type object het argument moet worden. Gebruik daarnaast nargs=-1 om aan te geven dat je argument meerdere waardes accepteert, en zelfs oneindig veel (-1).

Pseudo-code

app.py
from easystat.measurements import result_with_uncertainty

# add click-related code to pass measurements as an argument to easystat

def main(measurements):
    result, uncertainty = result_with_uncertainty(measurements)

    print(f"{measurements=}")
    print(f"Result of measurements is: {result:.2f} +- {uncertainty:.2f}.")


if __name__ == "__main__":
    main()
Testcode
(oefenopdrachten) > easystat 4 5 6 

Checkpunten

  • Je hebt de virtual environment gesynchroniseerd met uv sync.
  • Je hebt de juiste virtual environment geactiveerd.
  • Je hebt click als dependency toegevoegd aan pyproject.toml.
  • Je hebt in het bestand app.py code toegevoegd om meerdere metingen als argument mee te kunnen geven.
  • Na het commando easystat kun je getallen meegeven en krijg je het verwachte antwoord terug.

Projecttraject

  • Easystat: click
Easystat: verplicht argument

Pas de applicatie zo aan dat je zónder argument — dus zonder metingen — geen gekke uitkomst krijgt met nan +- nan, maar de uitleg dat je een argument moet meegeven. Met andere woorden: je argument is required.

Click subcommando's

Tot nu toe kon je maar één functie uitvoeren in jouw applicatie. Maar het is ook mogelijk om subcommando's aan te maken, zodat je met één programma meerdere taken kunt uitvoeren. Denk bijvoorbeeld aan uv: je voegt packages toe aan je project met uv add, verwijdert ze met uv remove, maakt een virtual environment met uv venv en synchroniseert het met uv sync.

Pythondaq: subcommando's bedenken

Je gaat de applicatie pythondaq straks verder uitbreiden, zodat er veel meer mogelijk is dan nu. Wat zou je willen dat de applicatie allemaal kan? Welke subcommando's wil je gaan aanmaken? Overleg met elkaar om goede ideeën uit te wisselen.

Projecttraject

  • Pythondaq: subcommando's bedenken
  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: grafiek
  • Pythondaq: --help
  • Pythondaq: alle subcommando's geïmplementeerd?

Een eenvoudig voorbeeldscript waarin de uv commando's add en remove worden nagebootst leggen we hieronder uit. Eerst de code:

fake_uv.py
import click


@click.group()
def cmd_group():
    pass


@cmd_group.command()
@click.argument("package")
def add(package):
    print(f"Adding and installing {package}...")


@cmd_group.command()
@click.argument("package")
def remove(package):
    print(f"Removing {package}...")


if __name__ == "__main__":
    cmd_group()
In regel 22 roep je de hoofdfunctie aan die enigszins willekeurig cmd_group() genoemd is en die redelijk bovenaan gedefinieerd wordt. In tegenstelling tot het hello.py-script doet deze functie helemaal niets (pass). Je vertelt aan click dat je een groep van commando's aan gaat maken met de @click.group()-decorator in regel 4. Vervolgens ga je commando's binnen deze groep hangen door niet de decorator @click.command() te gebruiken, maar @cmd_group.command() — zie regels 9 en 15. De namen van de commando's die worden aangemaakt zijn de namen van de functies. Dus regel 9 en 11 maken samen het commando add. Verder werkt alles hetzelfde. Dus een argument toevoegen — zoals in regel 10 — is gewoon met @click.argument(). Hier hoef je geen cmd_group te gebruiken.

Fake uv

Nu je hebt geleerd om met Click subcommando's te maken wil je deze uittesten. Je maakt in de map ECPC een nieuw uv project aan voor fake_uv . In deze map zet je de code uit het bestand fake_uv.py. Je maakt een nieuw virtual environment aan met daarin de benodigde packages . Je past de pyproject.toml aan zodat je met het commando fake_uv add scipy zogenaamd scipy kunt installeren .
ECPC
├── fake_uv
           ├── src/fake_uv
                      ├── __init__.py
                      └── fake_uv.py
           └── pyproject.toml
           └── •••
├── oefenopdrachten
├── pythondaq
└── •••

Commando

Als je een commando met uv toevoegt dan heeft dat de opbouw naam_commando = "package.module:naam_functie". Welke functie moet uitgevoerd worden als je het commando aanroept?

Pseudo-code

pyproject.toml
[project.scripts]
naam_commando = "package.module:naam_functie"
Testcode
(oefenopdrachten) > fake_uv add scipy 

Checkpunten

  • Je hebt een uv project met een actief virtual environment.
  • Na het wijzigen van de pyproject.toml is het virtual environment opnieuw gesynchroniseerd.
  • In de pyproject.toml verwijst [project.scripts] naar een functie zodat add en remove subcommando's zijn.
  • Het commando fake_uv add scipy print de tekst Adding and installing scipy... als output in de terminal.

Projecttraject

  • Fake uv

Smallangle: project aanmaken

Met deze opdracht kun je testen hoe goed je het Python-jargon onder de knie hebt. Je zult het woord smallangle zó vaak tegenkomen dat het je duizelt — maar jij weet precies over welk onderdeel het gaat.

  1. Maak een nieuw uv project aan met de naam smallangle . Let op de Octocat voor smallangle, het moet dus een repository zijn (of worden).
    ECPC
    ├── pythondaq
    └── smallangle
               └── •••
    └── •••
  2. Zet in het package smallangle een module smallangle.py.
  3. Plak onderstaande code in smallangle.py:
    import numpy as np
    from numpy import pi
    import pandas as pd
    
    
    def sin(number):
        x = np.linspace(0, 2 * pi, number)
        df = pd.DataFrame({"x": x, "sin (x)": np.sin(x)})
        print(df)
    
    
    def tan(number):
        x = np.linspace(0, 2 * pi, number)
        df = pd.DataFrame({"x": x, "tan (x)": np.tan(x)})
        print(df)
    
    
    if __name__ == "__main__":
        sin(10)
    
  4. Run het bestand smallangle.py en los de foutmeldingen op door de juiste dependencies toe te voegen aan het bestand pyproject.toml. Als je alle foutmeldingen opgelost hebt krijg je een lijst van tien punten tussen 0 en 2$\pi$ en de sinus van deze punten.

Testcode

smallangle.py
import numpy as np
from numpy import pi
import pandas as pd


def sin(number):
    x = np.linspace(0, 2 * pi, number)
    df = pd.DataFrame({"x": x, "sin (x)": np.sin(x)})
    print(df)


def tan(number):
    x = np.linspace(0, 2 * pi, number)
    df = pd.DataFrame({"x": x, "tan (x)": np.tan(x)})
    print(df)


if __name__ == "__main__":
    sin(10)
(smallangle) > python smallangle.py

Checkpunten

  • De module smallangle.py staat in de map src/smallangle.
  • Alle benodigde dependencies zijn toegevoegd aan het bestand pyproject.toml.
  • Het runnen van het bestand smallangle.py geeft een lijst van tien punten tussen 0 en 2$\pi$ en de sinus van deze punten.

Projecttraject

  • Smallangle: project aanmaken
  • Smallangle: subcommando's en opties
  • Smallangle: docstrings

Smallangle: subcommando's en opties

Je kunt met het commando smallangle en de subcommando's sin en tan een lijst genereren van getallen tussen de 0 en 2$\pi$ en de bijbehorende sinus dan wel tangens van deze getallen. Met de optie -n kun je het aantal stappen — het aantal $x$-waardes tussen 0 en $2\pi$ — kiezen. Als je de optie -n weglaat werkt de applicatie met een standaardwaarde.

TypeError: 'int' object is not iterable

Krijg je onderstaande foutmelding tijdens het draaien van het bestand smallangle?

Terminal
Traceback (most recent call last):
File "c:\smallangle\src\smallangle\smallangle.py", line 28, in <module>
    sin(10)
File "C:\click\core.py", line 1157, in __call__     
    return self.main(*args, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\click\core.py", line 1067, in main
    args = list(args)
        ^^^^^^^^^^
TypeError: 'int' object is not iterable

Dan komt dat doordat je sin(10) probeert uit te voeren, terwijl de functie al verClickt is. De functie verwacht een argument vanuit de terminal en geen integer vanuit het pythonscript. Pas je script aan zodat if __name__ == "__main__": naar de juiste functie verwijst en Click aanroept; niet sin(10).

(smallangle) > smallangle sin -n 9 

Checkpunten:

  • De gebruiker kan met het commando smallangle het script smallangle.py starten.
  • De gebruiker kan met subcommando's kiezen tussen sin en tan. Let op: het gaat hier om subcommando's!
  • De gebruiker kan het aantal stappen kiezen met een optie.
  • De gebruiker kan de optie ook weglaten.

Projecttraject:

  • Smallangle: project aanmaken
  • Smallangle: subcommando's en opties
  • Smallangle: docstrings
Smallangle: kleine hoekbenadering

Met het commando approx en een argument $\epsilon$ moet het script de grootste hoek geven waarvoor nog geldt dat $\lvert x - \sin(x) \rvert \leq \epsilon$, ofwel de grootste hoek waarvoor de kleine hoekbenadering nog geldt met de opgegeven nauwkeurigheid. Doe dit op drie cijfers nauwkeurig (loop over .000, .001 en .002, etc. totdat de vergelijking niet meer geldt). Belangrijk: besteed geen tijd aan het analytisch oplossen van de vergelijking. Een voorbeeld van de uitvoer:

(smallangle) > smallangle approx .1 

Docstrings en Click --help

Docstrings werken ook heel handig samen met Click. Ze worden gebruikt als je de helpfunctie aanroept.

Info

We gebruiken bij Click-functies niet de standaard structuur voor docstrings. Click breekt docstrings namelijk standaard af waardoor het algauw een onmogelijke brij aan informatie wordt. We kiezen daarom voor een samenvatting in een zin met daarin de PARAMETERS (argumenten) in hoofdletters en eventueel een korte toelichting daarop.

Uitgebreide documentatie en Click

In de documentatie van Click vind je meer informatie over het afbreken van zinnen (en het voorkomen daarvan). Ook vind je daar een manier om een uitgebreide docstring te schrijven zonder dat het een bende wordt.

Tijd om docstrings toe te voegen aan fake uv:

fake_uv.py
"""A fake implementation of several uv commands."""

import click


@click.group()
def cmd_group():
    """Fake uv commands."""
    pass


@cmd_group.command()
@click.argument("package")
def add(package):
    """Add a package to a uv project.

    PACKAGE is the package to add to the project.
    """
    print(f"Adding and installing {package}...")


@cmd_group.command()
@click.argument("package")
def remove(package):
    """Remove a package from a uv project.

    PACKAGE is the package to remove.
    """
    print(f"Removing {package}...")


if __name__ == "__main__":
    cmd_group()
Als je vervolgens de helpfunctie aanroept, zie je de eerste regel van de docstrings verschijnen voor alle subcommando's:

(oefenopdrachten) > fake_uv --help 

Daarna kun je uitleg vragen over de subcommando's, waarbij je de hele docstring te zien krijgt:

(oefenopdrachten) > fake_uv add --help 

Smallangle: docstrings

Je gebruikt het commando smallangle --help en leest de helptekst van de opdracht smallangle. De helptekst bevat zinvolle informatie die je in staat stelt om te begrijpen wat je met de applicatie kan doen. Je ziet dat er twee subcommando's zijn en bekijkt de helptekst van deze subcommando's met smallangle sin --help en daarna smallangle tan --help. Beide helpteksten stellen je in staat om de applicatie te begrijpen en te bedienen.

Pseudo-code

"""Summary containing ARGUMENT(S).

ARGUMENT description of the argument.
"""
Testcode
(smallangle) > smallangle --help 

Checkpunten

  • smallangle --help geeft zinvolle informatie.
  • smallangle sin --help geeft zinvolle informatie.
  • smallangle tan --help geeft zinvolle informatie.

Projecttraject

  • Smallangle: project aanmaken
  • Smallangle: subcommando's en opties
  • Smallangle: docstrings

Command-line interface voor ons experiment

In hoofdstuk Model-View-Controller heb je pythondaq uitgesplitst in model, view en controller. Wanneer we een command-line interface gaan bouwen dan is dat de softwarelaag tussen de gebruiker en de rest van de code. De command-line interface is dus een view. Het is helemaal niet gek om meerdere views te hebben, bijvoorbeeld een eenvoudig script zoals run_experiment.py, een command-line interface en een grafische interface. Hier gaan we ons richten op een command-line interface. We gaan een nieuw bestand cli.py aanmaken en dat langzaam opbouwen.

Pythondaq: commando's

Om de command-line interface voor pythondaq te maken ga je in een nieuw bestand src/pythondaq/cli.py een opzetje maken waar je stap voor stap functionaliteit aan toevoegt. De oude run_experiment.py maakte eerst een lijst van aangesloten apparaten en daarna werd een scan uitgevoerd. Daarom zet je in cli.py de subcommando's list en scan. En zorg je dat ze voor nu alleen een stukje tekst printen.

De gebruiker test de test-subcommmando's met de volgende handelingen. De gebruiker typt in de terminal het commando diode met daarachter het subcommando list en ziet een tekst verschijnen: Work in progress, list devices. De gebruiker test vervolgens het subcommando scan en ziet de tekst Work in progress, scan LED verschijnen.
ECPC
├── pythondaq
           ├── src/pythondaq
                      ├── __init__.py
                      ├── arduino_device.py
                      ├── diode_experiment.py
                      ├── run_experiment.py
                      └── cli.py
           └── •••
└── •••

Warning

Omdat de naam van een subcommando gelijk is aan de functienaam kan dat voor problemen zorgen wanneer je gereserveerde namen van python wilt gebruiken zoals: import, return, lambda. Of wanneer je de naam van het subcommando graag hetzelfde wilt hebben als een ander pythonfunctie zoals sin of list. Een oplossing is om de functienaam aan te passen en de subcommando naam expliciet aan click mee te geven bij command:

@cmd_group.command("import")
@click.argument("package")
def import_package(package):
    print(f"import {package}...")
We hebben nu een commando import aangemaakt — niet een commando import_package.

Pseudo-code

cli.py
# subcommando list
    # print Work in progress, list devices
# subcommando scan
    # print Work in progress, scan LED
Testcode
(oefenopdrachten) > diode list 

(oefenopdrachten) > diode scan 

Checkpunten:

  • De applicatie is aan te roepen met diode.
  • Het subcommando list print een stukje tekst.
  • Het subcommando scan print een ander stukje tekst.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Het uitvoeren van een meetserie Klik hier

We gaan ons eerst richten op het uitvoeren van een volledige meetserie en het tonen van de resultaten daarvan aan de gebruiker.

Info

Bij het opgeven van argumenten en opties voor de spanning kan het belangrijk zijn om te controleren of de spanning überhaupt wel een getal is tussen 0 en 3.3 V. Je kunt dit doen door de type-parameter in @click.argument() en @click.option(). Je kunt een Pythontype opgeven (bijvoorbeeld: type=int of type=float) en Click heeft speciale types zoals type=click.FloatRange(0, 3.3) voor een kommagetal tussen 0 en 3.3. Bekijk alle speciale types in de Click documentatie. Als je hiervan gebruik maakt hoef je niet zelf te controleren of de parameters kloppen. Click doet dat voor je.

Pythondaq: scan

Pas het subcommando scan aan.

De gebruiker test het subcommando scan met de volgende handelingen. De gebruiker typt het commando diode scan in de terminal. Aan de hand van de helptekst weet de gebruiker dat het met argumenten of opties mogelijk is om het spanningsbereik (in Volt) aan te passen. Er wordt een meting gestart die binnen het spanningsbereik blijft. De gebruiker ziet dat de stroomsterkte dóór en de spanning óver de LED in de terminal worden geprint. De gebruiker start een nieuwe meting en geeft ditmaal met de optie --output FILENAME een naam voor een CSV-bestand mee. Dit keer worden de metingen ook opgeslagen als CSV-bestand onder de meegegeven bestandsnaam.

Pseudo-code

# subcommando scan with range in Volt and output CSV
    # start scan with range
    # print current and voltage
    # if output:
        # create csv

Checkpunten:

  • Het programma print een lijst van metingen van de stroomsterkte dóór en de spanning óver de LED.
  • De gebruiker moet het spanningsbereik (in volt) zelf kunnen opgeven met argumenten of opties.
  • De gebruiker kan de metingen opslaan in een CSV-bestand met een optie --output FILENAME.
  • De meting wordt alleen opgeslagen als de optie wordt meegegeven.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Pythondaq: herhaalmetingen

Pas het subcommando scan aan zodat je met een optie het aantal herhaalmetingen kan kiezen.

De gebruiker test de optie om het aantal herhaalmetingen te kiezen met de volgende handelingen. Met het subcommando scan voert de gebruiker een meting uit in het bereik 2.8V tot 3.3V. Met een optie zet de gebruiker het aantal herhaalmetingen op 5. De gebruiker ziet dat het resultaat van de metingen met onzekerheden worden geprint in de terminal. De gebruiker bekijkt de grootte van de onzekerheden en voert nogmaals een scan uit maar dan met 10 metingen en daarna met 20 metingen. De gebruiker ziet dat de onzekerheden afnemen wanneer het aantal metingen toeneemt.

Pseudo-code

# subcommando scan with range in Volt, output CSV and repeat measurements
    # start scan with range and repeat measurements
    # print current, voltage and errors
    # if output:
        # create csv

Checkpunten:

  • De gebruiker kan het aantal herhaalmetingen met een optie kiezen.
  • De herhaalmetingen worden gebruikt om de beste schatting en onzekerheid te berekenen van de stroomsterkte en de spanning.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help
Pythondaq: stapgrootte

Soms wil je snel een meting uitvoeren over het hele bereik, dan is het handig om minder punten te meten dan 1023 punten. Breid de applicatie uit zodat de gebruiker de stapgrootte kan aanpassen.

Het meetinstrument kiezen

We kunnen de Arduino benaderen als we de naam weten die de VISA driver er aan heeft toegekend. Helaas kan — ook afhankelijk van het besturingssysteem — die naam veranderen als we de Arduino in een andere poort van onze computer steken of soms zelfs als we een andere Arduino op dezelfde poort koppelen. Met het commando list laten we alle apparaten zien die gevonden worden door de VISA drivers.

Pythondaq: list

Pas het subcommando list aan.

De gebruiker test het subcommando list met de volgende handelingen. De gebruiker typt het commando diode list in de terminal. Daarna verschijnt in de terminal een lijst van aangesloten instrumenten.

(oefenopdrachten) > diode list 

Checkpunten:

  • De gebruiker kan met diode list de lijst met aangesloten devices opvragen.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Pythondaq: info

Voeg een subcommando info toe.

De gebruiker test het subcommando info met de volgende handelingen. Eerst heeft de gebruiker met het commando diode list een lijst van aangesloten devices opgevraagd. De gebruiker wil weten wat de identificatiestring is van het apparaat dat aan een bepaalde poortnaam hangt. De gebruiker geeft daarom de poortnaam mee als argument aan het subcommando info waarna de identificatiestring van het instrument in de terminal wordt geprint.

identificatiestring

De identificatiestring van onze Arduino was Arduino VISA firmware v1.1.0. Je moet natuurlijk niet letterlijk deze string copy/pasten, maar de identificatie opvragen van het instrument. Welk firmwarecommando moest je daarvoor ook alweer gebruiken?

Pseudo-code

# subcommando info with device
    # print identificationstring of device
Testcode
(oefenopdrachten) > diode info ASRL28::INSTR 

Checkpunten:

  • De identificatiestring is met diode info DEVICE op te vragen.
  • De string is niet direct gecopypaste, maar wordt daadwerkelijk opgevraagd.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Pythondaq: choose device

Pas het subcommando scan aan zodat je kan aangeven met welke Arduino je een meting wilt uitvoeren.

De gebruiker test het subcommando scan met de volgende handelingen. De gebruiker typt het commando diode scan in de terminal en vergeet daarbij een poortnaam mee te geven. De gebruiker ziet een foutmelding verschijnen want een poortnaam opgeven is verplicht.

De gebruiker vraagt met het subcommando list een lijst van aangesloten instrumenten op. Met het subcommando info is de gebruiker er achtergekomen wat de naam is van de poort waar de Arduino aanhangt. Vervolgens geeft de gebruiker deze poortnaam mee bij het subcommando scan om een meting op de (juiste) Arduino uit te laten voeren.

Tot slot leent de gebruiker een Arduino van een buurmens. De gebruiker sluit de tweede Arduino aan op de computer. Met list en info kijkt de gebruiker wat de poortnaam is van de tweede Arduino. Met het subcommando scan voert de gebruiker een meting uit en ziet dat het lampje van de tweede Arduino gaat branden en niet het lampje van de eerste Arduino.

(oefenopdrachten) > diode scan 

Checkpunten:

  • De gebruiker moet een poortnaam meegeven.
  • De gekozen device wordt ook daadwerkelijk gebruikt in het model en de controller.
  • Als géén poortnaam wordt opgegeven, krijgt de gebruiker een foutmelding.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Pythondaq: Grafiek

Pas het subcommando scan aan zodat je met een boolean flag kan aangeven of er wel of niet een grafiek wordt getoond.

De gebruiker test het subcommando scan met de volgende handelingen. De gebruiker start een meting en geeft ook de optie --graph na afloop ziet de gebruiker een grafiek met daarin de metingen. Daarna start de gebruiker opnieuwe een meting en geeft dit keer de optie --no-graph mee, na afloopt van de meting ziet de gebruiker geen grafiek verschijnen. Tot slot start de gebruiker een meting en geeft daarbij geen van beide opties (`--graph/--no-graph) wederom ziet de gebruiker na afloop van de meting geen grafiek verschijnen.

Checkpunten:

  • De grafiek wordt alleen getoond wanneer --graph wordt meegegeven.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help

Pythondaq: --help

Voeg helpteksten toe.

De gebruiker test de applicatie diode met de volgende handelingen. De gebruiker typt diode --help en bekijkt de helptekst. De gebruiker ziet dat er subcommando's zijn. Met subcommando --help test de gebruiker de helpteksten een voor een uit. Ook bekijkt de gebruiker de helpteksten over de argumenten en opties. De helpteksten stellen de gebruiker in staat om de applicatie te begrijpen en te bedienen.

Pseudo-code

"""Summary containing ARGUMENTs.

ARGUMENT description of the argument.
"""
Testcode
(oefenopdrachten) > diode --help 

Checkpunten:

  • De informatie in de helpteksten is voldoende om de applicatie te begrijpen en te bedienen.
  • diode --help vertelt duidelijk welke subcommando's aanwezig zijn en wat ze doen.
  • Bij alle subcommando's, is het duidelijk welke opties en argumenten er zijn, wat de standaardwaarden zijn en wat ze doen.

Projecttraject:

  • Pythondaq: commando's
  • Pythondaq: scan
  • Pythondaq: herhaalmetingen
  • Pythondaq: list
  • Pythondaq: info
  • Pythondaq: choose device
  • Pythondaq: Grafiek
  • Pythondaq: --help
Pythondaq: list --search

Breid het commando list uit met een optie --search waarmee je niet een lijst van alle instrumenten krijgt, maar alleen de instrumenten die de zoekterm bevatten. Dus bijvoorbeeld:

(oefenopdrachten) > diode list 

(oefenopdrachten) > diode list -s usbmodem 

De lijst met instrumenten kan er op Windows heel anders uitzien. Sterker nog, op Windows is de lijst meestal vrij saai. Maar leen eens heel even een Arduino van iemand anders en je ziet dat er dan twee poorten in de lijst verschijnen.

Pas — na het uitbreiden van list — de commando's scan en info aan zodat het niet nodig is om de volledige devicenaam mee te geven, maar alleen een zoekterm.

Op dit punt hebben we de functionaliteit van ons snelle script van het vorige hoofdstuk bereikt. Dit was veel meer werk, maar het is veel flexibeler. Als je wilt meten met een andere Arduino, een ander bereik, of een andere stapgrootte dan type je gewoon een iets ander commando in de terminal. Je hoeft geen scripts meer aan te passen. Als je na een tijdje niet meer precies weet hoe het ook alweer werkte allemaal kun je dat snel weer oppakken door --help aan te roepen.

Alle subcommando's implementeren

Kijk nog eens terug naar het lijstje subcommando's die je in opdracht Subcommando's bedenken hebt opgeschreven. Heb je alles geïmplementeerd? Wat zou je willen dat je nog meer kan instellen? Als er tijd over is, kijk dan of dit lukt.

Rich

Een interface met stijl

Ook command-line interfaces gaan met hun tijd mee. Vroeger waren ze per definitie zwart/wit en statisch, maar tegenwoordig worden interfaces vaak opgeleukt met kleur, emoji's en bewegende progressbars. Rich7 is een project dat in recordtijd heel populair is geworden. Het bestaat pas sinds november 2019 en heeft precies twee jaar later meer dan 31000 verzameld. Dat is veel — en de populariteit is sindsdien nog verder toegenomen.

Rich is ontzettend uitgebreid en heeft heel veel mogelijkheden. Voor ons project kan het handig zijn om een progressbar te gebruiken of met Rich een tabel weer te geven. De documentatie8 van Rich is best goed, maar kan lastig zijn om een mooi overzicht te krijgen. Een serie van korte video tutorials kun je vinden bij calmcode. Iedere video duurt maar één tot twee minuten en laat mooi de mogelijkheden zien. Voor de functies die je wilt gebruiken kun je dan meer informatie opzoeken in de documentatie van Rich zelf.

Rich

Verrijk je interface met Rich. Doe dit naar eigen wens en inzicht.

Data-analyse

Data-analyse

Door de $I,U$-karakteristiek van de (lichtgevende) diode te analyseren is het mogelijk om de constante van Boltzmann te bepalen. De stoomsterkte door een diode wordt gegeven door de Shockley diodevergelijking. Zie ook hoofdstuk diode.

Lukt het, om binnen de te bepalen onzekerheid, overeenkomst te vinden met de literatuurwaarde? Een LED is helaas geen ideale diode dus dit kan lastig zijn.

Model fitten

Fit het model van Shockley aan je $I,U$-karakteristiek. Welke parameters kun je bepalen? Overleg met je begeleider!


  1. Opties hebben vaak een lange naam en een afkorting. De lange naam wordt gegeven met twee mintekens en de korte naam met één minteken. PS> python -V wordt met de lange naam PS> python --version. Beide commando's geven hetzelfde antwoord. Je kunt je voorstellen dat de korte naam minder typwerk is. In de handleiding gebruiken we echter meestal de lange naam van een optie, omdat het commando dan beter leesbaar wordt. 

  2. argv staat voor: argument vector, een lijst met argumenten. 

  3. Merk op: _ is de weggooivariabele in Python. In het geval van de voorbeeldcode gaat het er om dat de for-loop een aantal keer doorlopen wordt, je hoeft verder niets te doen met de loop index. 

  4. Zie ook: The Python Standard Library 

  5. Zie voor meer informatie over flags de Click documentatie

  6. Pallets. Click. URL: https://click.palletsprojects.com/

  7. Will McGugan. Rich. URL: https://github.com/willmcgugan/rich

  8. Will McGugan. Rich documentation. URL: https://rich.readthedocs.io/en/latest/