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.

Einige der folgenden Befehle erfordern eine neue Version von pip, sodass ihr sicherstellen solltet, dass ihr die neueste Version installiert habt:

$ python3 -m pip install --upgrade pip
> python  -m pip install --upgrade pip

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"]
3build-backend = "hatchling.build"
build-system

definiert einen Abschnitt, der das Build-System beschreibt

requires

definiert eine Liste von Abhängigkeiten, die installiert sein müssen, damit das Build-System funktioniert, in unserem Fall hatchling.

Bemerkung

Versionsnummern von Abhängigkeiten sollten üblicherweise jedoch nicht hier festgeschrieben werden sondern in der requirements.txt-Datei.

build-backend

identifiziert den Einstiegspunkt für das Build-Backend-Objekt als gepunkteten Pfad. Das hatchling-Backend-Objekt ist unter hatchling.build verfügbar.

Bemerkung

Für Python-Pakete, die binäre Erweiterungen mit Cython, C-, C++-, Fortran- oder Rust enthalten, 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

With validate-pyproject you can check your pyproject.toml file.

Metadaten

In pyproject.toml könnt ihr auch Metadaten zu eurem Paket angeben, wie z.B.:

 5[project]
 6name = "dataprep"
 7version = "0.1.0"
 8authors = [
 9  { name="Veit Schiele", email="veit@cusy.io" },
10]
11description = "A small dataprep package"
12readme = "README.rst"
13requires-python = ">=3.7"
14classifiers = [
15    "Programming Language :: Python :: 3",
16    "License :: OSI Approved :: BSD License",
17    "Operating System :: OS Independent",
18]
19dependencies = [
20    "pandas",
21]
22
23[project.urls]
24"Homepage" = "https://github.com/veit/dataprep"
25"Bug Tracker" = "https://github.com/veit/dataprep/issues"
name

ist 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.

version

ist 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__ oder VERSION, die die Version enthält, optional mit dem vorangestellten Kleinbuchstaben v. Dabei basiert das Standardschema auf PEP 440.

Wenn dies nicht der Art entspricht, wie ihr Versionen speichern wollt, könnt ihr mit der Option pattern auch einen anderen regulären Ausdruck definieren.

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", "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>=61.0", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[project]
...
dynamic = ["version"]

[tool.setuptools.dynamic]
version = {attr = "dataprep.VERSION"}
authors

wird verwendet, um die Autoren des Pakets anahnd ihrer Namen und E-Mail-Adressen zu identifizieren.

Ihr könnt auch maintainers im selben Format auflisten.

description

ist eine kurze Zusammenfassung des Pakets, die aus einem Satz besteht.

readme

ist ein Pfad zu einer Datei, die eine detaillierte Beschreibung des Pakets enthält. Diese wird auf der Paketdetailseite auf Python Package Index (PyPI) angezeigt. In diesem Fall wird die Beschreibung aus README.rst geladen.

requires-python

gibt 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.

classifiers

gibt 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 Klassifikator "Private :: Do Not Upload". PyPI wird immer Pakete ablehnen, deren Klassifizierer mit "Private ::" beginnt.

dependencies

gibt die Abhängigkeiten für euer Paket in einem Array an.

Siehe auch

PEP 631

urls

lä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.

Optionale Abhängigkeiten

project.optional-dependencies

erlaubt euch, optionale Abhängigkeiten für euer Paket anzugeben. Dabei könnt ihr auch zwischen verschiedenen Sets unterscheiden:

34[project.optional-dependencies]
35tests = [
36    "coverage[toml]",
37    "pytest>=6.0",
38]
39docs = [
40    "furo",
41    "sphinxext-opengraph",
42    "sphinx-copybutton",
43    "sphinx_inline_tabs"
44]

Auch rekursive optionale Abhängigkeiten sind mit pip ≥ 21.2 möglich. So könnt ihr beispielsweise für dev neben pre-commit auch alle Abhängigkeiten aus docs und test übernehmen:

35dev = [
36    "dataprep[tests, docs]",
37    "pre-commit"
38]

Ihr könnt diese optionalen Abhängigkeiten installieren, z.B. mit:

$ cd /PATH/TO/YOUR/DISTRIBUTION_PACKAGE
$ python3 -m venv .
$ source bin/activate
$ python -m pip install --upgrade pip
$ python -m pip install -e '.[dev]'
> cd C:\PATH\TO\YOUR\DISTRIBUTION_PACKAGE
> python3 -m venv .
> Scripts\activate.bat
> python -m pip install --upgrade pip
> python -m pip install -e '.[dev]'

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

Bemerkung

In Python ≥ 3.11 kann mit PYTHONSAFEPATH sichergestellt werden, dass die installierten Pakete zuerst verwendet werden.

dataprep

ist 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__.py

ist erforderlich, um das Verzeichnis als Paket zu importieren. Die Datei sollte leer sein.

loaders.py

ist ein Beispiel für ein Modul innerhalb des Pakets, das die Logik (Funktionen, Klassen, Konstanten, etc.) eures Pakets enthalten könnte.

Andere Dateien

CONTRIBUTORS.rst

Siehe auch

LICENSE

Ausführliche Informationen hierzu findet ihr im Abschnitt Lizenzieren.

README.rst

Diese Datei teilt denjenigen, die sich für das Paket interessieren, in kurzer Form mit, wie sie es nutzen können.

Wenn ihr das Dokument in reStructuredText schreibt, könnt ihr die Inhalte auch als ausführliche Beschreibung in euer Paket übernehmen:

from Cython.Build import cythonize
setup(
    ext_modules=cythonize("src/dataprep/cymean.pyx"),
)

Zudem könnt ihr sie dann auch in eure Sphinx-Dokumentation mit .. include:: ../../README.rst übernehmen.

CHANGELOG.rst

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:

1setup(
2    ext_modules=cythonize("src/dataprep/cymean.pyx"),

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.

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.

Stellt sicher, dass ihr die neueste Version von build installiert habt:

Führt nun den Befehl in demselben Verzeichnis aus, in dem sich pyproject.toml befindet:

$ python -m pip install build
$ cd /PATH/TO/YOUR/DISTRIBUTION_PACKAGE
$ rm -rf build dist
$ python -m build
> python -m pip install build
> cd C:\PATH\TO\YOUR\DISTRIBUTION_PACKAGE
> rm -rf build dist
> python -m build

Die zweite Zeile stellt sicher, dass ein sauberes Build ohne Artefakte früherer Builds erstellt wird. Die dritte Zeile sollte eine Menge Text ausgeben und nach Abschluss zwei Dateien im dist-Verzeichnis erzeugen:

dist
├── dataprep-0.1.0-py3-none-any.whl
└── dataprep-0.1.0.tar.gz
dataprep-0.1.0-py3-none-any.whl

ist eine Build-Distribution. Neuere pip-Versionen installieren bevorzugt Build-Distributionen, greifen aber bei Bedarf auf Source-Distributionen zurück. 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 Beispielpaket mit Python auf jeder Plattform kompatibel, so dass nur eine Build-Distribution benötigt wird:

dataprep

ist der normalisierte Paketname

0.1.0

ist die Version des Distrubitionspakets

py3

gibt die Python-Version und ggf. die C-ABI an

none

gibt an, ob das Wheel-Paket für jedes oder nur spezifische OS geeignet ist

any

any eignet sich für jede Prozessorarchitektur, x86_64 hingegen nur für Chips mit dem x86-Befehlssatz und einer 64-Bit-Architektur

dataprep-0.1.0.tar.gz

ist eine Source Distribution.

Siehe auch

Die Referenz für die Dateinamen findet ihr in PEP 427.

Weitere Infos zu Source-Distributionen erhaltet ihr in Creating a Source Distribution. und PEP 376.

Testen

$ mkdir test_env
$ cd test_env
$ python3 -m venv .
$ . bin/activate
$ python -m pip install dist/dataprep-0.1.0-py3-none-any.whl
Processing ./dist/dataprep-0.1.0-py3-none-any.whl
Collecting pandas
  Using cached pandas-1.3.4-cp39-cp39-macosx_10_9_x86_64.whl (11.6 MB)

Successfully installed dataprep-0.1.0 numpy-1.21.4 pandas-1.3.4 python-dateutil-2.8.2 pytz-2021.3 six-1.16.0
> mkdir test_env
> cd test_env
> python -m venv .
> Scripts\activate.bat
> python -m pip install dist/dataprep-0.1.0-py3-none-any.whl
Processing ./dist/dataprep-0.1.0-py3-none-any.whl
Collecting pandas
  Using cached pandas-1.3.4-cp39-cp39-macosx_10_9_x86_64.whl (11.6 MB)

Successfully installed dataprep-0.1.0 numpy-1.21.4 pandas-1.3.4 python-dateutil-2.8.2 pytz-2021.3 six-1.16.0

Anschließend könnt ihr die Wheel-Datei überprüfen mit:

$ python -m pip install check-wheel-contents
$ check-wheel-contents dist/*.whl
dist/dataprep-0.1.0-py3-none-any.whl: OK

Alternativ könnt ihr das Paket auch installieren:

$ python -m pip install dist/dataprep-0.1.0-py3-none-any.whl
Processing ./dist/dataprep-0.1-py3-none-any.whl
Collecting pandas

Installing collected packages: numpy, pytz, six, python-dateutil, pandas, dataprep
Successfully installed dataprep-0.1 numpy-1.21.4 pandas-1.3.4 python-dateutil-2.8.2 pytz-2021.3 six-1.16.0

Anschließend könnt ihr Python aufrufen und euer loaders-Modul importieren:

from dataprep import loaders

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.