Python-Kurs / Ein Paket mit pyproject.toml bauen und als Wheel installieren

Ein Python-Projekt wird mit einer pyproject.toml und einem Build-Werkzeug zu installierbaren Distributionsdateien. Hier bauen Sie expensekit-demo als Wheel und Quellarchiv, installieren das Wheel in einer neuen virtuellen Umgebung und prüfen den Import sowie die Kommandozeile außerhalb des Quellverzeichnisses.

Paketverzeichnis und vollständige Quelldateien anlegen

Verwenden Sie Python 3.12 oder neuer und einen eigenen Arbeitsordner. Führen Sie die Build-Befehle aus diesem Projektordner aus. Für das Build-Werkzeug können Sie eine bereits eingerichtete virtuelle Entwicklungsumgebung verwenden; installieren Sie die Werkzeuge mit deren Python-Interpreter. Die spätere Prüfumgebung bleibt davon getrennt.

Erstellen Sie src/expensekit/__init__.py mit der Summenfunktion:

def total(amounts):
    if any(type(x) is not int or x < 0 for x in amounts):
        raise ValueError("invalid amount")
    return sum(amounts)

Die Funktion erwartet eine wiederholt durchlaufbare Sammlung, etwa eine Liste oder ein Tupel, mit nicht negativen Ganzzahlen. type(x) is int schließt auch boolesche Werte aus. Ein einmaliger Iterator ist hier ungeeignet: any() verbraucht ihn bei der Prüfung, sodass sum() anschließend keine Werte mehr erhält. Eine leere Liste ergibt dagegen korrekt 0.

Speichern Sie die folgende Datei unter src/expensekit/cli.py. Der relative Import greift auf die Funktion im selben Paket zu. Der Hauptprogramm-Guard erlaubt den Import ohne sofortige Argumentauswertung.

import argparse
from . import total

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("amounts", type=int, nargs="+")
    args = parser.parse_args()
    print(total(args.amounts))

if __name__ == "__main__":
    main()

nargs="+" verlangt mindestens einen Betrag. Nicht ganzzahlige Eingaben weist argparse bereits beim Einlesen zurück. Negative Ganzzahlen erreichen die Funktion und lösen dort ValueError aus; dieses Minimalbeispiel fängt den Fehler nicht ab und zeigt daher einen Traceback. Die Beträge werden als ganze Einheiten addiert, ohne Währungsumrechnung oder Rundung.

Build-System, Metadaten und Startbefehl festlegen

Legen Sie im Projektordner neben src die Datei pyproject.toml an:

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "expensekit-demo"
version = "0.1.0"
description = "Local expense sum example"
requires-python = ">=3.12"

[project.scripts]
expense-total = "expensekit.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

build-system benennt die Build-Abhängigkeit setuptools und dessen Backend. setuptools>=68 ist eine Mindestanforderung, keine feste Versionsbindung; unterschiedliche spätere Backend-Versionen können daher verschiedene Build-Metadaten erzeugen. requires-python erklärt die unterstützte Mindestversion für die Installation.

Der Distributionsname für pip lautet expensekit-demo, der Importname im Python-Code dagegen expensekit. Der Dateiname der Distribution normalisiert den Bindestrich zum Unterstrich. Unter project.scripts verbindet expense-total den installierten Konsolenbefehl mit expensekit.cli:main. Die Paketsuche unter src bestimmt, welche Python-Pakete aufgenommen werden.

Der Packaging-Leitfaden der Python Packaging Authority erläutert die Trennung von Projektdateien und Distributionsdateien. Die setuptools-Dokumentation zum Build-System beschreibt den Build über ein Frontend wie build.

Wheel und Quellarchiv bauen

Führen Sie im Projektordner diese Befehle mit dem Interpreter Ihrer Entwicklungsumgebung aus. Für die erstmalige Installation von build und für isolierte Build-Abhängigkeiten kann eine Internetverbindung erforderlich sein.

python -m pip install build
python -m build

Ohne weitere Optionen erzeugt python -m build ein Quellarchiv und anschließend ein Wheel aus diesem Archiv. Das Quellarchiv enthält die zum erneuten Bauen nötigen Projektquellen. Das Wheel enthält die installierbaren Paketdateien und Metadaten. Bei diesem reinen Python-Beispiel erwarten Sie unter dist die Dateien expensekit_demo-0.1.0.tar.gz und expensekit_demo-0.1.0-py3-none-any.whl.

Kontrollieren Sie den Rückgabestatus und die erzeugten Dateien. Die abschließende Ordnerstruktur sieht so aus:

project/
  pyproject.toml
  src/
    expensekit/
      __init__.py
      cli.py
  dist/
    expensekit_demo-0.1.0-py3-none-any.whl
    expensekit_demo-0.1.0.tar.gz

Eine Warnung über eine fehlende README kann bei diesem Minimalprojekt auftreten. Für ein tatsächlich weitergegebenes Projekt gehören eine aussagekräftige README und geklärte Lizenzinformationen dazu. Virtuelle Umgebungen, Kennwörter und lokale Hilfsdateien dürfen nicht unbeabsichtigt im Archiv landen. Öffnen Sie das Wheel als ZIP und das Quellarchiv als tar.gz und prüfen Sie insbesondere beide Python-Dateien, pyproject.toml im Quellarchiv sowie die Entry-Point-Metadaten im Wheel.

In einer neuen Umgebung installieren und unabhängig prüfen

Erstellen Sie für die Installation eine frische Prüfumgebung im Projektordner:

python -m venv .venv-check

Installieren Sie unter Windows in PowerShell mit dem vollständigen Interpreterpfad. Eine Aktivierung der Umgebung ist dafür nicht erforderlich:

.\.venv-check\Scripts\python.exe -m pip install dist/expensekit_demo-0.1.0-py3-none-any.whl

Unter Linux und macOS lautet der entsprechende Pfad:

.venv-check/bin/python -m pip install dist/expensekit_demo-0.1.0-py3-none-any.whl

Wählen Sie den Befehl für Ihr Betriebssystem. Bei No such file oder einer nicht gefundenen Wheel-Datei kontrollieren Sie zuerst Arbeitsordner und tatsächlichen Dateinamen. Bei einer inkompatiblen Python-Version erstellen Sie die Prüfumgebung mit einem geeigneten Interpreter neu. Verwenden Sie für pip und die anschließende Ausführung immer denselben Interpreter.

Wechseln Sie nun in einen anderen, leeren Ordner außerhalb des Projekts. Verwenden Sie in den folgenden beiden Aufrufen anstelle von python den absoluten Pfad zur Python-Datei in .venv-check. So prüfen Sie das installierte Paket und greifen nicht versehentlich auf die Quelldateien zu:

python -c "from expensekit import total; print(total([3500,8000,2500]))"
python -m expensekit.cli 3500 8000 2500

Beide Aufrufe müssen jeweils 14000 ausgeben. Auch der installierte Befehl expense-total 3500 8000 2500 muss dasselbe Ergebnis liefern; ohne Aktivierung erreichen Sie ihn unter Windows über .venv-check\Scripts\expense-total.exe, unter Linux und macOS über .venv-check/bin/expense-total, jeweils mit absolutem Pfad vom externen Testordner aus.

Prüfen Sie mit expensekit.__file__, dass der Import aus dem Verzeichnis site-packages der Prüfumgebung stammt. Ohne Argumente endet die Kommandozeile mit einem argparse-Fehler, 0 ergibt 0, und ein negativer Betrag wird abgewiesen. Ändern Sie zum Vergleich den zweiten Betrag auf 9000: Das Ergebnis wird 15000. Stellen Sie anschließend 8000 wieder her und kontrollieren Sie erneut 14000.

Nach Quellcodeänderungen muss die Distribution erneut gebaut und die aktualisierte Version in der Prüfumgebung installiert werden; eine bereits installierte Wheel-Kopie ändert sich nicht automatisch mit dem Quellordner. Für eine spätere echte Weitergabe erhöhen Sie die Version nachvollziehbar. Diese Übung endet bei lokalem Build und lokaler Installation und benötigt weder ein PyPI-Konto noch einen Upload oder Zugangsschlüssel.