Blog
Eine aktualisierte Anleitung zu Setuptools und Pyproject.toml

In der Vergangenheit habe ich mehrere Blog-Beiträge darüber geschrieben, wie man ein Python-Paket mit Setuptools erstellt. Seitdem hat sich die empfohlene Art und Weise, Setuptools zu verwenden, in Richtung der Verwendung der
Startpunkt: Projektverzeichnis
Um ein Paket zu erstellen, benötigen wir den Quellcode. In dieser Anleitung ist unser Ausgangspunkt die folgende Verzeichnisstruktur:
project_directory/
├── src/
│ └── example/ Python package with source code.
│ ├── __init__.py Makes the folder a package.
│ └── source.py An example module containing source code.
├── tests/
│ └── test_source.py A file containing tests for the code in source.py.
└── README.md README with information about the project.
Innerhalb des Projektverzeichnisses haben wir ein Verzeichnis src/, das die Python-Pakete enthält. In unserem Fall ist das Paketverzeichnis init.py Datei) geben.
Das Projektverzeichnis enthält auch eine Datei namens README.md. Diese Datei ist in der Regel in Software-Repositories zu finden und enthält wichtige Informationen über das Projekt oder das Repository. Obwohl sie nicht unbedingt erforderlich ist, gehen wir für diesen Leitfaden davon aus, dass sie vorhanden ist.
Und schließlich gibt es ein tests/ Verzeichnis. Dies ist zwar nicht zwingend erforderlich, aber ich denke, dass jedes Paket, das es wert ist, weitergegeben zu werden, auch getestet werden sollte. Beachten Sie, dass sich die Tests außerhalb des Quellverzeichnisses befinden und nicht in das Paket aufgenommen werden.
Normalerweise haben Sie mehr Dateien und Ordner im Projektverzeichnis, wie z.B. ein .gitignore oder eine LICENSE. Diese sind jedoch nicht erforderlich.
Ein Paket erstellen
Um das Projekt zu paketieren, müssen wir eine einzige Datei zum Projektverzeichnis hinzufügen: die pyproject.toml. Diese Konfigurationsdatei wurde mit PEP 517 und PEP 518 eingeführt und wird die Metadaten enthalten, die das Paket definieren.
In unserem Beispiel könnte es so aussehen:
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "example"
description = "An Example Project"
version = "0.1.0"
readme = "README.md"
dependencies = ["PyYAML==6.0.1"]
Die Tabelle [build-system] legt fest, dass unser Paket von Setuptools erstellt wird und dass dafür das Paket setuptools mindestens in der Version 61 erforderlich ist.
Die Tabelle [project] legt die Metadaten des Pakets fest: den Namen, die Beschreibung und die Version. Sie verlinkt die Datei README.md als Readme und definiert, dass PyYAML==6.0.1 eine Abhängigkeit ist.
Beachten Sie, dass der Name mit dem Namen des Quellpaketverzeichnisses übereinstimmt: "Beispiel". Dies ist keine Voraussetzung, aber es kann verwirrend sein, wenn es nicht der Fall ist. Ein Beispiel für ein Paket, bei dem der Paketname und das Verzeichnis nicht übereinstimmen, ist Scikit-Learn: Sie installieren es mit pip install scikit-learn, während Sie es durch Importieren von sklearn verwenden.
Beachten Sie auch, dass es Anforderungen an die zulässigen Versionsstrings gibt. Eine vollständige Spezifikation finden Sie in den Dokumenten zu Versionsangaben.
Ein Paket erstellen
Das obige Beispiel ist alles, was Sie brauchen, um ein Paket zu bauen. Die Angabe der erforderlichen Build-Tools in einer pyproject.toml-Datei bietet Vorteile wie die Isolierung des Builds und die Möglichkeit, die Version von setuptools anzugeben
Führen Sie dazu aus:
python3 -m pip install build
python3 -m build
Dadurch wird das Build-Paket installiert und zur Erstellung Ihres Pakets verwendet. Durch die Angabe des Build-Backend-Schlüssels in der Datei
Sie können diese Dateien zum Beispiel verteilen und Ihr Paket daraus installieren:
pip install dist/example-0.0.1-py3-none-any.whl
Dadurch wird das Paket aus dem Rad in die Python-Umgebung installiert.
Wenn Sie ein Paket erstellt haben, das Sie mit der Welt teilen möchten, können Sie die Distributionen auf PyPI hochladen. Eine Anleitung finden Sie hier. Sie müssen sich allerdings einen originelleren Namen als "Beispiel" einfallen lassen!
Bearbeitbare Installation
Neben der Erstellung eines Pakets und der anschließenden Installation können Sie auch editierbare Installationen durchführen, indem Sie das Paket mit pyproject.toml aus dem Quellcode in Ihrer Python-Umgebung installieren.
Laufen
pip install -e .
macht ihn für Python verfügbar. Das macht es einfach, mit dem Code zu entwickeln.
Erweiterte Konfigurationsoptionen
Das obige Beispiel pyproject.toml ist ungefähr das Minimum, das zur Erstellung eines Pakets erforderlich ist. Es gibt jedoch noch viel mehr, was Sie konfigurieren können.
Metadaten
Zunächst möchten Sie vielleicht weitere Metadaten zum Projekt angeben, z. B. die Autoren und Betreuer des Projekts:
[project]
authors = [
{name = "Your Name", email = "your@email.address"},
{name = "Your Co-Author", email = "their@email.address"},
]
maintainers = [
{name = "Your Name", email = "your@email.address"}
]
Um Ihr Paket auf PyPI leichter zu finden, können Sie Schlüsselwörter hinzufügen:
[project]
keywords = ["some", "words", "that", "are", "applicable"]
Um die Suche weiter zu erleichtern, fügen Sie außerdem Klassifikatoren hinzu, um Ihre Veröffentlichung zu kategorisieren:
[project]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]
Eine vollständige Liste der Klassifikatoren finden Sie hier.
Wenn Sie hingegen sicherstellen möchten, dass Ihr Paket niemals zu PyPI hochgeladen wird, fügen Sie den Private :: Do Not Upload classifier hinzu. PyPI wird Pakete mit diesem Klassifikator immer ablehnen.
Wenn Sie eine Nicht-Standardlizenz haben, können Sie die Datei auf diese Weise verlinken, aber bei Standardlizenzen reicht es aus, den entsprechenden Klassifikator anzugeben:
[project]
license = {file = "LICENSE"}
Schließlich können Sie im Abschnitt project.urls eine Liste von URLs angeben:
[project.urls]
Homepage = "https://your.project"
Documentation = "https://docs.your.project"
Repository = "http://github.com/you/your.project"
Abhängigkeiten und Anforderungen
Der Parameter dependencies ist nicht erforderlich, aber so üblich, dass ich ihn in das Minimalbeispiel aufgenommen habe. Er nimmt eine Liste von Zeichenketten entgegen, die jeweils eine Abhängigkeit darstellen:
Das Dateiformat, das für die Angabe des Inhalts verschiedener Felder wie readme, dependencies und optional-dependencies verwendet wird, ähnelt einem requirements.txt-Dateiformat und entspricht dem PEP 508:
[project]
dependencies = [
"numpy",
"pandas>=2.0",
"PyYAML==6.0.1",
]
Dies bedeutet, dass wir eine beliebige Version von numpy, Version 2.0 oder höher von pandas und genau Version 6.0.1 von PyYAML benötigen.
Abgesehen vom Schlüssel dependencies können Sie noch weitere Einstellungen zu den Anforderungen und Abhängigkeiten Ihres Projekts vornehmen. Vielleicht am wichtigsten: Sie können die erforderliche Python-Version angeben. Zum Beispiel:
[project]
requires-python = ">=3.8"
Alle Abhängigkeiten, die nicht unbedingt erforderlich sind oder nur in bestimmten Situationen verwendet werden, können als optionale Anforderungen hinzugefügt werden:
[project.optional-dependencies]
dev = ["pytest>=7.4.3", "ruff==0.1.8"]
Dies wird üblicherweise verwendet, um Abhängigkeiten anzugeben, die für die Entwicklung erforderlich sind, wie im obigen Beispiel. Die Installation des Pakets wie pip install "example[dev]" (oder pip install -e ".[dev]", wenn Sie eine editierbare Installation durchführen) löst pip aus, um auch die optionalen Abhängigkeiten zu installieren.
Andere häufige Anwendungsfälle sind das Hinzufügen optionaler Abhängigkeiten für eine grafische Benutzeroberfläche oder eine Datenquelle. Natürlich sollten Sie sicherstellen, dass Ihr Paket auch ohne diese Abhängigkeiten funktioniert - und nicht mit einer ImportError fehlschlägt, wenn sie nicht vorhanden sind.
Skripte
Um einen Teil Ihres Codes als CLI-Befehl ausführbar zu machen, können Sie ein Skript hinzufügen. Wenn es zum Beispiel eine Funktion namens main in example/source.py gibt, würde sie dadurch als your-cli-command verfügbar gemacht:
[project.scripts]
your-cli-command = "example.source:main"
Wenn Sie
typer
als Ihr CLI-Framework verwenden und Ihre Datei source.py den folgenden Code enthält:
from typer import Typer
app = Typer()
@app.command()
def hello():
print("Hello.")
@app.command()
def bye(name: str):
print(f"Bye {name}")
Sie können das Skript wie folgt konfigurieren, um die Ausführung von Befehlen wie your-cli-command bye rogier zu ermöglichen:
[project.scripts]
your-cli-command = "example.source:app"
Dynamische Versionen
Die Version Ihres Pakets in pyproject.toml fest zu kodieren, ist möglicherweise nicht ideal, da Sie sie dann manuell aktualisieren müssen. Jedes Mal die Version in der Datei zu aktualisieren, kann fehleranfällig und zeitaufwändig sein, insbesondere bei größeren Projekten mit häufigen Aktualisierungen.
Wenn Sie außerdem möchten, dass Ihr Paket Zugriff auf seine eigene Version hat, müssen Sie eine globale Variable mit der Version zu einem Quellpaket hinzufügen. Das bedeutet, dass Sie diese Versionen manuell synchron halten müssen, was den Prozess noch aufwändiger macht.
Zum Glück bietet setuptools Optionen, um die Version dynamisch zu füllen. Wenn Sie git verwenden (natürlich tun Sie das!), ist es am einfachsten, wenn Sie die Option setuptools_scm. Sie müssen es als Anforderung zum Build-System hinzufügen und angeben, dass die Version dynamisch sein soll:
[build-system]
requires = ["setuptools", "setuptools-scm"]
[project]
# version = "0.0.1" # make sure there is no version parameter
dynamic = ["version"]
[tool.setuptools_scm]
version_file = "src/example/_version.py"
Der Abschnitt [tool.setuptools_scm] muss existieren, kann aber leer sein, um setuptools_scm zu aktivieren. Der Parameter version_file sorgt dafür, dass setuptools_scm eine version und eine __version_tuple__ in eine Datei innerhalb Ihres Pakets schreibt, so dass das Paket Zugriff auf seine eigene Version hat.
Die Version wird aus dem letzten Tag und der Anzahl der Commits seit dem letzten Tag ermittelt. Ein Datum wird hinzugefügt, wenn es in Ihrem Baum noch nicht übertragene Änderungen gibt. Weitere Einzelheiten finden Sie hier.
Paketdaten
In einem typischen Python-Paket finden Sie normalerweise Code, entweder als (Python-)Quellcode oder als Binärdatei. Es gibt jedoch Fälle, in denen Sie Datendateien in Ihr Paket aufnehmen müssen, die vom Paket selbst verwendet werden. Die Angabe von Konfigurationsdaten in einer pyproject.toml Datei statt in einer separaten Datei ermöglicht es Projekten, ihre Konfigurationen zu konsolidieren und so die Gesamtzahl der Konfigurationsdateien im Projekt zu reduzieren. Um dies zu erreichen, bietet setuptools mehrere Optionen.
Am einfachsten ist es, dafür zu sorgen, dass diese Dateien von git verfolgt werden und setuptools_scm als Build-Systemanforderung hinzuzufügen (siehe oben).
Wenn Sie eine feinere Kontrolle darüber wünschen, welche Dateien eingeschlossen werden sollen, können Sie diese explizit auflisten. Wenn Sie zum Beispiel alle .txt Dateien einschließen möchten:
[tool.setuptools.package-data]
example = ["*.txt"]
Dies weist setuptools an, alle Dateien mit der Erweiterung .txt in Ihr Beispielpaket aufzunehmen. Sie können dann mit importlib.resources auf diese Dateien zugreifen.
Nachbereitung
Der folgende Inhalt ist ein guter Ausgangspunkt für die Erstellung eines Python-Projekts mit Setuptools. Weitere Informationen finden Sie im Python Packaging User Guide - Schreiben Ihrer pyproject.toml.
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "example"
dependencies = [
"PyYAML==6.0.1"
]
requires-python = ">=3.8"
authors = [
{name = "Your Name", email = "your@email.address"},
{name = "Your Co-Author", email = "their@email.address"},
]
maintainers = [
{name = "Your Name", email = "your@email.address"}
]
description = "An Example Project"
readme = "README.md"
license = {file = "LICENSE"}
keywords = ["some", "words", "that", "are", "applicable"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]
dynamic = ["version"]
[project.optional-dependencies]
dev = ["pytest>=7.4.3", "ruff==0.1.8"]
[project.scripts]
your-cli-command = "example.source:main"
[project.urls]
Homepage = "https://your.project"
Documentation = "https://docs.your.project"
Repository = "http://github.com/you/your.project"
[tool.setuptools_scm]
version_file = "src/example/_version.py"
Nachdem Sie dies kopiert haben, überprüfen Sie bitte jede Zeile auf notwendige Änderungen.
Foto von Brandable Box auf Unsplash
Verfasst von
Rogier van der Geer
Unsere Ideen
Weitere Blogs
Contact



