Verteilungspaket erstellen¶
Verteilungspakete (engl.: Distribution Packages sind Archive, die in einen Paket-Index wie z.B. pypi.org hochgeladen und mit pip installiert werden können.
Struktur¶
Ein minimales Distribution Package kann z.B. so aussehen:
dataprep
├── pyproject.toml
└── src
└── dataprep
├── __init__.py
└── loaders.py
pyproject.toml¶
PEP 517 und PEP 518 brachten erweiterbare Build-Backends, isolierte Builds und pyproject.toml im TOML-Format.
pyproject.toml teilt u.a. pip und
build mit, welches Backend-Werkzeug verwendet werden soll, um
Distributionspakete für euer Projekt zu erstellen. Ihr könnt aus einer Reihe von
Backends wählen, wobei dieses Tutorial standardmäßig hatchling verwendet.
Eine minimale und dennoch funktionale dataprep/pyproject.toml-Datei
sieht dann z.B. so aus:
1[build-system]
2requires = ["hatchling>=1.27"]
3build-backend = "hatchling.build"
build-systemdefiniert einen Abschnitt, der das Build-System beschreibt
requiresdefiniert eine Liste von Abhängigkeiten, die installiert sein müssen, damit das Build-System funktioniert, in unserem Fall
hatchling>=1.27für die Unterstützung von PEP 639.Bemerkung
Versionsnummern von Abhängigkeiten sollten üblicherweise jedoch nicht hier festgeschrieben werden sondern in der constraints.txt-Datei.
build-backendidentifiziert den Einstiegspunkt für das Build-Backend-Objekt als gepunkteten Pfad. Das
hatchling-Backend-Objekt ist unterhatchling.buildverfügbar.Bemerkung
Für Python-Pakete, die binäre Erweiterungen mit
Cython,C-,C++-,Fortran- oderRustenthalten, ist das hatchling-Backend jedoch nicht geeignet. Hier sollte eines der folgenden Backends verwendet werden:Doch damit nicht genug – es gibt noch weitere Backends:
Siehe auch
Bemerkung
Mit check-toml,
pyproject-fmt und validate-pyproject könnt ihr die
pyproject.toml-Datei formattieren und überprüfen.
Metadaten¶
In pyproject.toml könnt ihr auch Metadaten zu eurem Paket angeben, wie
z.B.:
5[project]
6name = "dataprep"
7version = "0.1.0"
8description = "A small dataprep package"
9readme = "README.rst"
10authors = [
11 { name = "Veit Schiele", email = "veit@cusy.io" },
12]
13requires-python = ">=3.10"
14classifiers = [
15 "Operating System :: OS Independent",
16 "Programming Language :: Python :: 3 :: Only",
17 "Programming Language :: Python :: 3.10",
18 "Programming Language :: Python :: 3.11",
19 "Programming Language :: Python :: 3.12",
20 "Programming Language :: Python :: 3.13",
21]
22dependencies = [
23 "cython",
24 "sphinx-inline-tabs",
25 "sphinxext-opengraph",
26]
27tests = [
28 "coverage[toml]",
29 "pytest>=6",
30]
nameist der Distributionsname eures Pakets. Dies kann ein beliebiger Name sein, solange er nur Buchstaben, Zahlen,
.,_und-enthält. Er sollte auch nicht bereits auf dem Python Package Index (PyPI) vergeben sein.versionist die Version des Pakets.
In unserem Beispiel ist die Versionsnummer statisch gesetzt worden. Es gibt jedoch auch die Möglichkeit, die Version dynamisch anzugeben, z.B. durch eine Datei:
[project] ... dynamic = ["version"] [tool.hatch.version] path = "src/dataprep/__about__.py"
Das Standardmuster sucht nach einer Variablen namens
__version__oderVERSION, die die Version enthält, optional mit dem vorangestellten Kleinbuchstabenv. Dabei basiert das Standardschema auf PEP 440.Wenn dies nicht der Art entspricht, wie ihr Versionen speichern wollt, könnt ihr mit der Option
patternauch einen anderen regulären Ausdruck definieren.Siehe auch
Es gibt jedoch noch weitere Versionsschema-Plugins, wie z.B. hatch-semver für Semantic Versioning.
Mit dem Version-Source-Plugin hatch-vcs könnt ihr auch Git-Tags verwenden:
[build-system] requires = ["hatchling>=1.27", "hatch-vcs"] ... [tool.hatch.version] source = "vcs" raw-options = { local_scheme = "no-local-version" }
Auch das setuptools-Backend erlaubt dynamische Versionierung:
[build-system] requires = ["setuptools>=77.0", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] ... dynamic = ["version"] [tool.setuptools.dynamic] version = {attr = "dataprep.VERSION"}
Wollt ihr diese Version nun in eurem Paket zugänglich machen, könnt ihr folgenden Code verwenden:
src/dataprep/__init__.py¶import importlib.metadata try: __version__ = importlib.metadata.version(__name__) except importlib.metadata.PackageNotFoundError: __version__ = "0.1.dev0" # Fallback for development mode
Tipp
Wenn die Version in mehreren Textdateien steht, kann sich die Verwendung von Bump My Version empfehlen.
Die Konfigurationsdatei
.bumpversion.tomlkann z.B. so aussehen:[tool.bumpversion] current_version = "0.1.0" parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" replace = "{new_version}" regex = false ignore_missing_version = false tag = false sign_tags = false tag_name = "v{new_version}" tag_message = "Bump version: {current_version} → {new_version}" allow_dirty = false commit = false message = "Bump version: {current_version} → {new_version}" commit_args = "" [[tool.bumpversion.files]] filename = "src/dataprep/__init__.py" [[tool.bumpversion.files]] filename = "docs/conf.py"
authorswird verwendet, um die Autoren des Pakets anhand ihrer Namen und E-Mail-Adressen zu identifizieren.
Ihr könnt auch
maintainersim selben Format auflisten.descriptionist eine kurze Zusammenfassung des Pakets, die aus einem Satz besteht.
readmeist ein Pfad zu einer Datei, die eine detaillierte Beschreibung des Pakets enthält. Diese wird auf der Paket-Detailseite auf Python Package Index (PyPI) angezeigt. In diesem Fall wird die Beschreibung aus der
README.rst-Datei geladen.
license-expressionenthält valide SPDX license expressions.
Siehe auch
license-filesgibt eine Liste von Dateien mit Lizenzinformationen an.
Siehe auch
requires-pythongibt die Versionen von Python an, die von eurem Projekt unterstützt werden. Dabei werden Installationsprogramme wie pip ältere Versionen von Paketen durchsuchen, bis sie eines finden, das eine passende Python-Version hat.
classifiersgibt dem Python Package Index (PyPI) und pip einige zusätzliche Metadaten über euer Paket. In diesem Fall ist das Paket nur mit Python 3 kompatibel, steht unter der BSD-Lizenz und ist OS-unabhängig. Ihr solltet immer zumindest die Versionen von Python angeben, unter denen euer Paket läuft, unter welcher Lizenz euer Paket verfügbar ist und auf welchen Betriebssystemen euer Paket läuft. Eine vollständige Liste der Klassifizierer findet ihr unter https://pypi.org/classifiers/.
Außerdem haben sie eine nützliche Zusatzfunktion: Um zu verhindern, dass ein Paket zu PyPI hochgeladen wird, verwendet den speziellen Klassifizierer
"Private :: Do Not Upload". PyPI wird immer Pakete ablehnen, deren Klassifizierer mit"Private ::"beginnt.
dependenciesgibt die Abhängigkeiten für euer Paket in einem Array an.
Siehe auch
urlslässt euch eine beliebige Anzahl von zusätzlichen Links auflisten, die auf dem Python Package Index (PyPI) angezeigt werden. Im Allgemeinen könnte dies zum Quellcode, zur Dokumentation, zu Aufgabenverwaltungen usw. führen.
Fehler
Wenn ihr in uv den Fehler error: No `project` table found in:
`/PATH/TO/pyproject.toml` erhaltet, habt ihr vermutlich keinen
[project]-Abschnitt definiert. Dies kann in Repositorys auftreten, die
die pyproject.toml-Datei nur für die Konfiguration von Tools wie
Black und Ruff verwenden, das Projekt selbst jedoch nicht definieren.
Um das Problem zu beheben, könnt ihr einen [project]-Abschnitt einfügen,
der zumindest name und version enthalten muss. Alternativ könnt ihr
auch uv run mit der Option --no-project verwenden.
Siehe auch
Abhängigkeitsgruppen¶
dependency-groupserlaubt euch, Abhängigkeitsgruppen für euer Paket anzugeben. Dabei könnt ihr auch zwischen verschiedenen Sets unterscheiden:
33dev = [ 34 "furo", 35 "sphinx-copybutton", 36 "sphinx-inline-tabs", 37 "sphinxext-opengraph", 38] 39tests = [
Auch rekursive Abhängigkeitsgruppen sind möglich. So könnt ihr beispielsweise
für dev neben pre-commit auch alle Abhängigkeiten aus docs und
test übernehmen:
34 "pre-commit",
35 { include-group = "docs" },
36 { include-group = "tests" },
37]
38docs = [
Ihr könnt diese Abhängigkeitsgruppen installieren, z.B. mit:
$ cd /PATH/TO/YOUR/DISTRIBUTION_PACKAGE
$ uv sync --group dev
Siehe auch
src-Package¶
Wenn ihr ein neues Paket erstellt, solltet ihr kein flaches sondern das
src-Layout verwenden, das auch in Packaging Python Projects der
PyPA empfohlen wird. Ein wesentlicher Vorteil dieses Layouts ist, dass
Tests mit der installierten Version eures Pakets und nicht mit den Dateien in
eurem Arbeitsverzeichnis ausgeführt werden.
Siehe auch
Hynek Schlawack: Testing & Packaging
Bemerkung
In Python ≥ 3.11 kann mit PYTHONSAFEPATH sichergestellt werden,
dass die installierten Pakete zuerst verwendet werden.
dataprepist das Verzeichnis, das die Python-Dateien enthält. Der Name sollte mit dem Projektnamen übereinstimmen um die Konfiguration zu vereinfachen und für diejenigen, die das Paket installieren, besser erkennbar zu sein.
__init__.pyist erforderlich, um das Verzeichnis als Paket zu importieren. Dies erlaubt euch folgende Importe:
import dataprep.loaders
oder
from dataprep import loaders
Obwohl
__init__.py-Dateien oft leer sind, können sie auch Code enthalten.Siehe auch
loaders.pyist ein Beispiel für ein Modul innerhalb des Pakets, das die Logik (Funktionen, Klassen, Variablen, etc.) eures Pakets enthalten könnte.
Andere Dateien¶
CHANGELOG¶
In der CHANGELOG-Datei sollten alle wesentlichen Änderungen eines
Projekts dokumentiert werden. Keep a Changelog empfiehlt hierfür folgendes Format:
[Unreleased]
============
Added
-----
…
Changed
-------
…
Removed
-------
…
[x.y.z] - YYYY-MM-DD
====================
Added
-----
…
Siehe auch
Es gibt auch etliche Python-Bibliotheken, die euch beim Erstellen der
CHANGELOG-Datei unterstützen:
- Release Drafter
erstellt Entwürfe für eure nächsten Versionshinweise, sobald Pull-Requests in den Hauptzweig integriert werden. Release Drafter wurde entwickelt mit Probot, einem Framework zum Erstellen von GitHub-Apps zur Automatisierung und Verbesserung eurer Workflows.
- towncrier
ist ein Dienstprogramm zur Erstellung nützlicher, zusammengefasster Nachrichten-Dateien für euer Projekt.
- Scriv
ist ein Kommandozeilen-Tool, das Entwicklern hilft, nützliche Änderungsprotokolle zu führen. Es verwaltet ein Verzeichnis mit Fragmenten von Änderungsprotokollen. Diese werden zu Einträgen in einer
CHANGELOG.rst-Datei zusammengefasst.- Dinghy
nutzt die GitHub GraphQL API, um aktuelle Aktivitäten zu Releases, Issues und Pull Requests zu finden, und erstellt daraus einen kompakten HTML-Überblick.
- github-activity
generiert Markdown-Änderungsprotokolle für GitHub-Repositorys und bietet dabei mehr Kontrolle über die Arten von Beiträgen und Metadaten, die zur Erstellung der Änderungsprotokolle verwendet werden.
- changelog_manager
hilft euch, eine
CHANGELOG.md-Datei für euer Git-Repo zu erstellen, die dem Keep A Changelog-Standard entspricht.- blurb
ist ein Tool, um die CPython-Entwicklung von den lästigen Konflikten in cpython/Misc/NEWS.d/ zu befreien.
CODE_OF_CONDUCT¶
Wenn ihr Hinweise geben wollt, wie Fragen an euch gestellt werden können oder
wie andere zum Projekt beitragen können, kann eine CODE_OF_CONDUCT-Datei
hilfreich sein. Damit könnt ihr festlegen, welche Art der Interaktion ihr
erwartet. Er legt auch Regeln fest, um euch und andere vor unerwünschten
Verhaltensweisen zu schützen.
Siehe auch
CONTRIBUTING¶
Siehe auch
CONTRIBUTORS¶
Siehe auch
LICENSE¶
Ausführliche Informationen hierzu findet ihr im Abschnitt Lizenzieren.
README¶
Diese Datei teilt denjenigen, die sich für das Paket interessieren, in kurzer Form mit, wie sie es nutzen können.
Siehe auch
Wenn ihr das Dokument in reStructuredText schreibt, könnt ihr die Inhalte auch als ausführliche Beschreibung in euer Paket übernehmen:
5[project]
6name = "dataprep"
7version = "0.1.0"
8description = "A small dataprep package"
9readme = "README.rst"
10authors = [
11 { name = "Veit Schiele", email = "veit@cusy.io" },
12]
Zudem könnt ihr sie dann auch in eure Sphinx-Dokumentation mit .. include:: ../../README.rst übernehmen.
SECURITY¶
Diese Datei sollte Informationen enthalten,
wie eine Sicherheitslücke gemeldet werden kann ohne dass sie öffentlich sichtbar wird,
über den Ablauf und den Zeitplan für die Offenlegung der Schwachstelle,
zu Links, z. B. URLs und E-Mails, unter denen Unterstützung angefragt werden kann.
Siehe auch
Historische oder für binäre Erweiterungen benötigte Dateien¶
Bevor die mit PEP 518 eingeführte pyproject.toml-Datei zum Standard
wurde, benötigte setuptools setup.py, setup.cfg und
MANIFEST.in. Heute werden die Dateien jedoch bestenfalls noch für
binäre Erweiterungen benötigt.
Wenn ihr diese Dateien in euren Paketen ersetzen wollt, könnt ihr dies mit
hatch new --init oder ini2toml.
setup.py¶
Eine minimale und dennoch funktionale dataprep/setup.py kann
z.B. so aussehen:
1from Cython.Build import cythonize
2from setuptools import find_packages, setup
3
4setup(
5 ext_modules=cythonize("src/dataprep/cymean.pyx"),
6)
package_dir
verweist auf das Verzeichnis src, in dem sich ein oder mehrere Pakete
befinden können. Anschließend könnt ihr mit setuptools’s find_packages()
alle Pakete in diesem Verzeichnis finden.
Bemerkung
find_packages() ohne src/-Verzeichnis würde alle Verzeichnisse mit
einer __init__.py-Datei paketieren, also auch tests/-Verzeichnisse.
setup.cfg¶
Diese Datei wird nicht mehr benötigt, zumindest nicht für die Paketierung.
wheel sammelt heutzutage alle erforderlichen Lizenzdateien automatisch und
setuptools kann mit dem options-Keyword-Argument universelle
wheel-Pakete bauen, z.B.
dataprep-0.1.0-py3-none-any.whl.
MANIFEST.in¶
Die Datei enthält alle Dateien und Verzeichnisse, die nicht bereits mit
packages oder py_module erfasst werden. Sie kann z.B. so aussehen: dataprep/MANIFEST.in:
1include LICENSE *.rst *.toml *.yml *.yaml *.ini
2graft src
3recursive-exclude __pycache__ *.py[cod]
Weitere Anweisungen in Manifest.in findet ihr in MANIFEST.in commands.
Bemerkung
Häufig wird die Aktualisierung der Manifest.in-Datei vergessen. Um
dies zu vermeiden, könnt ihr check-manifest in einem Git pre-commit
Hook verwenden.
Bemerkung
Wenn Dateien und Verzeichnisse aus MANIFEST.in auch installiert
werden sollen, z.B. wenn es sich um laufzeitrelevante
Daten handelt, könnt ihr dies mit include_package_data=True in eurem
setup()-Aufruf angeben.
Paketstruktur erstellen¶
Mit uv init --package MYPACK lässt sich einfach eine initiale
Dateistruktur für Pakete erstellen:
$ uv init --package mypack
$ tree mypack -a
mypack
├── .git
│ └── ...
├── .gitignore
├── .python-version
├── README.md
├── pyproject.toml
└── src
└── mypack
└── __init__.py
.python-versiongibt an, welche Python-Version für die Entwicklung des Projekts verwendet werden soll.
Fehler
Wenn ihr die folgende Fehlermeldung erhaltet
error: The Python request from `.python-version` resolved to Python U.V.W, which is incompatible with the project's Python requirement: `>=X.Y`. Use `uv python pin` to update the `.python-version` file to a compatible version., weist dies auf einen Konflikt zwischen der Versionsangabe in der.python-version-Datei und derrequires-python-Angabe in derpyproject.toml-Datei hin. Nun habt ihr drei verschiedene Möglichkeiten:Aktualisiert eure
.python-version-Datei mituv python pin X.Y.Z.Überschreibt die Python-Version für einen einzelnen Befehl mit
uv run --python X.Y COMMAND.Aktualisiert
requires-python.
mypack/pyproject.tomlDie Datei
pyproject.tomlenthält einenscripts-Einstiegspunktmypack:main:mypack/pyproject.toml¶[build-system] build-backend = "hatchling.build" requires = [ "hatchling" ] [project] name = "mypack" version = "0.1.0" description = "Add your description here" readme = "README.md" authors = [ { name = "Veit Schiele", email = "veit@cusy.io" }, ] requires-python = ">=3.13" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] dependencies = [] scripts.mypack = "mypack:main"
mypack/src/mypack/__init__.pyDas Modul definiert eine CLI-Funktion
main():mypack/src/mypack/__init__.py¶def main() -> None: print("Hello from mypack!")
Sie kann mit
uv runaufgerufen werden:$ uv run mypack Hello from mypack!
Bemerkung
Ggf. erstellt
uv runeine virtuelle Python-Umgebung im Ordner.venvbevormain()ausgeführt wird.
Build¶
Der nächste Schritt besteht darin, Distributionspakete für das Paket zu erstellen. Dies sind Archive, die in den Python Package Index (PyPI) hochgeladen und von pip installiert werden können.
Führt nun den Befehl in demselben Verzeichnis aus, in dem sich
pyproject.toml befindet:
$ uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/mypack-0.1.0.tar.gz and dist/mypack-0.1.0-py3-none-any.whl
> uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist/mypack-0.1.0.tar.gz and dist/mypack-0.1.0-py3-none-any.whl
dist/mypack-0.1.0-py3-none-any.whlist eine Build-Distribution. pip installiert bevorzugt Build-Distributionen und greift lediglich auf die Source-Distributionen zurück, wenn keine passende Build-Distribution vorhanden ist. Ihr solltet immer eine Source-Distribution hochladen und Build-Distributionen für die Plattformen bereitstellen, mit denen euer Projekt kompatibel ist. In diesem Fall ist unser Beispiel-Paket mit Python auf jeder Plattform kompatibel, so dass nur eine Build-Distribution benötigt wird:
mypackist der normalisierte Paketname
0.1.0ist die Version des Distrubitionspakets
py3gibt die Python-Version und ggf. die C-ABI an
nonegibt an, ob das Wheel-Paket für jedes oder nur spezifische OS geeignet ist
anyanyeignet sich für jede Prozessorarchitektur,x86_64hingegen nur für Chips mit dem x86-Befehlssatz und einer 64-Bit-Architektur
Siehe auch
mypack-0.1.0.tar.gzist eine Source Distribution
Siehe auch
Testen¶
Anschließend könnt ihr die Wheel-Datei überprüfen mit:
$ uv add --dev check-wheel-contents
Resolved 17 packages in 8ms
Built mypack @ file:///Users/veit/sandbox/mypack
Prepared 1 package in 442ms
Uninstalled 1 package in 0.89ms
Installed 10 packages in 5ms
+ annotated-types==0.7.0
+ attrs==24.2.0
+ check-wheel-contents==0.6.0
+ click==8.1.7
~ mypack==0.1.0 (from file:///Users/veit/sandbox/mypack)
+ packaging==24.1
+ pydantic==2.9.2
+ pydantic-core==2.23.4
+ typing-extensions==4.12.2
+ wheel-filename==1.4.1
$ uv run check-wheel-contents dist/*.whl
dist/dataprep-0.1.0-py3-none-any.whl: OK
Alternativ könnt ihr das Paket auch in einem neuen Projekt installieren,
z.B. in myapp:
$ uv init --app myapp
$ cd myapp
$ uv add ../mypack/dist/mypack-0.1.0-py3-none-any.whl
Resolved 8 packages in 130ms
Installed 1 package in 3ms
+ mypack==0.1.0 (from file:///Users/veit/sandbox/mypack/dist/mypack-0.1.0-py3-none-any.whl)
Anschließend könnt ihr mypack mit uv run aufrufen:
$ uv run mypack
Hello from mypack!
Siehe auch
Bemerkung
Es gibt immer noch viele Anleitungen, die einen Schritt zum Aufruf der
setup.py enthalten, z.B. python
setup.py sdist. Dies wird jedoch heutzutage von Teilen der Python Packaging
Authority (PyPA) als Anti-Pattern angesehen.
Checks¶
Wenn ihr ein Paket für eine Aufgabenverwaltung erstellen wollt, das die Aufgaben in eine Datenbank schreibt und über ein Python-API und eine Befehlszeilenschnittstelle (CLI bereitstellt, wie würdet ihr die Dateien strukturieren?
Überlegt euch, wie ihr die oben genannten Aufgaben erledigen wollt. Welche Bibliotheken und Module fallen euch ein, die diese Aufgabe erfüllen könnten? Skizziert den Code für die Module der Python-API, der Befehlszeilenschnittstelle und der Datenbankanbindung.