← Python/Modules en tooling
Les 8 van 8
MODULE 09 · 08◷ 40 min

Een eigen installeerbaar CLI-pakket

Na deze les kun je
  • Herbruikbare regels en commandoregelinvoer scheiden.
  • Een echte console-ingang voor een pakket installeren.
  • Dezelfde CLI via een pakketstartpunt gebruiken met de juiste exitstatus.

Een collega wil je programma vanaf de commandoregel gebruiken, terwijl een andere collega dezelfde berekening in Python wil aanroepen. Maak daarom één bibliotheek met een kleine ingang voor de commandoregel. De twee gebruikers delen de regels, zonder elkaars invoerafhandeling nodig te hebben.

Drie verantwoordelijkheden

De bibliotheekfunctie summary maakt tekst en controleert haar eigen invoer. De CLI leest argumenten, roept die functie aan en schrijft het resultaat. Het startpunt kiest hoe de CLI als proces wordt gestart.

In dit pakket staan die taken in report.py, cli.py en __main__.py. De projectbeschrijving noemt het consolecommando. De meegeleverde __init__.py biedt summary ook als publieke bibliotheekfunctie aan.

Het eerste voorbeeld installeert een ander klein pakket. Daarna starten zowel het geïnstalleerde consolecommando als python -m dezelfde hoofdfunctie. Beide paden zijn echte processen in de projectomgeving.

Een echte installatie biedt dezelfde CLI als consolecommando en als module
pyproject.toml
[project]
name = "note-tags"
version = "0.2.0"
requires-python = ">=3.14,<3.15"
dependencies = []

[project.scripts]
note-tags = "note_tags:main"

[build-system]
requires = ["uv_build==0.13.6"]
build-backend = "uv_build"
.python-version
3.14.2
src/note_tags/__init__.py
import argparse


def main(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("name")
    args = parser.parse_args(argv)
    print(f"NIEUW: {args.name}")
    return 0
src/note_tags/__main__.py
from . import main


if __name__ == "__main__":
    raise SystemExit(main())
example.py
import subprocess

subprocess.run(["uv", "--quiet", "sync", "--offline"], check=True)
first = subprocess.run([".venv/bin/note-tags", "boek"])
if first.returncode:
    raise SystemExit(first.returncode)
second = subprocess.run([".venv/bin/python", "-I", "-m", "note_tags", "kaart"])
raise SystemExit(second.returncode)
Verwachte uitvoer
NIEUW: boek
NIEUW: kaart

Herbruikbare regels eerst

Maak summary bruikbaar zonder console. De functie verwijdert buitenste witruimte van de naam en geeft bijvoorbeeld "3 x fiets" terug. Zij print die tekst niet zelf.

Een lege naam en een ongeldig aantal zijn invoerfouten. In deze opdracht is een aantal precies een int van nul of hoger; een bool telt niet als aantal. Geef daarvoor de gevraagde ValueError, zodat ook een Pythonaanroeper een duidelijke fout krijgt.

Van argumenten naar een bibliotheekaanroep

Je kent argparse uit de eerdere lessen. ArgumentParser beschrijft de invoer: een positionele name en de optionele --count, met type int en standaardwaarde 1. Een opgegeven nul blijft nul en mag niet door de standaardwaarde worden vervangen.

Laat main(argv=None) de argumentlijst ontvangen. Met None leest argparse de echte procesargumenten; met een lijst kun je de functie rechtstreeks uitproberen. Roep daarna de huidige report.summary aan, print het ontvangen resultaat en geef bij succes de integer nul terug.

Een verkeerd geschreven getal wordt al door argparse afgewezen. Geeft summary een ValueError, gebruik dan parser.error(str(error)). Argparse schrijft daarmee naar stderr, de aparte uitvoerstroom voor foutmeldingen, en stopt met status 2.

Onverwachte bibliotheekfouten, zoals RuntimeError, moeten zichtbaar blijven met hun eigen fouttype. Laat ze doorlopen zodat je tijdens het ontwikkelen de oorzaak kunt onderzoeken.

Een installatie een ingang geven

De declaratie onder [project.scripts] koppelt de naam walk-notes aan "walk_notes.cli:main". De functie krijgt hier geen haakjes. Bij installatie maakt uv een startbestand dat deze functie gebruikt.

De meegeleverde controle synchroniseert de omgeving opnieuw vanuit jouw projectbeschrijving. Zij controleert de geïnstalleerde metadata en start het echte consolecommando. Alleen een gelijknamig bronbestand schrijven is dus niet genoeg.

Het pakket ook als module starten

from .cli import main

if __name__ == "__main__":
    raise SystemExit(main())

Dit dunne startpunt deelt de CLI in plaats van de parser opnieuw te schrijven. De bewaking voorkomt dat een gewone import van de startmodule meteen argumenten gaat verwerken. SystemExit draagt een teruggegeven status van main over aan het proces.

Veelgemaakte fout: starten tijdens import

Het tweede voorbeeld importeert een startmodule zonder die bewaking. Daardoor probeert argparse meteen een verplicht argument te lezen en stopt het proces. Plaats zulke starthandelingen achter de bewaking en houd de herbruikbare functies apart.

Een onbewaakt startpunt verwerkt per ongeluk argumenten tijdens import
pyproject.toml
[project]
name = "note-tags"
version = "0.2.0"
requires-python = ">=3.14,<3.15"
dependencies = []

[project.scripts]
note-tags = "note_tags:main"

[build-system]
requires = ["uv_build==0.13.6"]
build-backend = "uv_build"
.python-version
3.14.2
src/note_tags/__init__.py
import argparse


def main(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("name")
    args = parser.parse_args(argv)
    print(f"NIEUW: {args.name}")
    return 0
src/note_tags/__main__.py
from . import main

raise SystemExit(main())
example.py
import subprocess

subprocess.run(["uv", "--quiet", "sync", "--offline"], check=True)
imported = subprocess.run([".venv/bin/python", "-I", "-c", "import note_tags.__main__"])
raise SystemExit(imported.returncode)
Verwachte fout
the following arguments are required: name
AAN DE SLAG 0/4
1

Een herbruikbare samenvatting maken

Maak summary(name, count) in src/walk_notes/report.py. Strip de naam en geef f"{count} x {name}" terug. Een lege gestripte naam geeft ValueError("name mag niet leeg zijn"). Een count waarvan type niet precies int is, of die negatief is, geeft ValueError("count moet een niet-negatieve int zijn"). Print niets.

2

Een testbare argparse-hoofdfunctie schrijven

Maak main(argv=None) in src/walk_notes/cli.py. Gebruik argparse met name en --count (int, default 1), parse argv, print de huidige report.summary en geef de integer 0 terug. Importeer report als module binnen het pakket. Verwerk ValueError uit summary met parser.error(str(error)); argparse-invoerfouten geven status 2.

3

Het consolecommando echt installeren

Voeg onder [project.scripts] in pyproject.toml walk-notes = "walk_notes.cli:main" toe. Na echte installatie moet walk-notes fiets --count 3 de regel 3 x fiets schrijven en normaal eindigen. De meegeleverde metadata en overige bronnen hoef je niet te veranderen.

4

Dezelfde CLI als pakketstartpunt aanbieden

Vul src/walk_notes/__main__.py in. Importeer main uit .cli en gebruik onder if __name__ == "__main__": raise SystemExit(main()). python -m walk_notes moet dezelfde CLI uitvoeren. Een gewone import van walk_notes.__main__ mag niets starten; ook een andere teruggegeven main-status moet doorlopen.

Onthoud dit

  • De bibliotheek geeft waarden of fouten terug; de CLI regelt de console.
  • Projectmetadata verbindt een commandonaam aan een functie.
  • Een dun pakketstartpunt bewaart dezelfde CLI en haar exitstatus.

In de volgende module toets je zulke onderdelen gericht met echte tests.

src/walk_notes/report.py

Deze les gebruikt Python-tools. Log in om de opdrachten uit te voeren →

UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.