Vor allem in Data-Science-Projekten wächst Code oft organisch: schnelle Analysen, viele Notebooks, wenig Struktur. Doch spätestens, wenn ein Modell produktiv gehen oder ein Team gemeinsam entwickeln soll, werden Codequalität und Reproduzierbarkeit entscheidend.
Automatisierte Code-Qualität ist dabei kein Luxus, sondern ein entscheidender Hebel für Nachvollziehbarkeit, Wartbarkeit und Vertrauen in Ergebnisse – besonders, wenn Machine-Learning-Pipelines oder APIs im Spiel sind.
Dieser Beitrag zeigt, wie du mit einer zentralen Konfiguration in pyproject.toml, Pre-Commit Hooks und GitHub Actions eine nachhaltige Code-Qualitätsstrategie aufbaust.
Ziel: Qualität automatisieren statt manuell kontrollieren
Manuelle Code-Reviews und Formatierungen sind zeitaufwendig und fehleranfällig. Stattdessen helfen verschiedene Stufen der Automatisierung:
- Zentrale Konfiguration – alles in
pyproject.tomlgebündelt - Pre-Commit Hooks – lokale Formatierung und Checks vor jedem Git-Commit
- Makefile – einheitliche lokale Workflows für alle Entwickler:innen
- CI/CD Pipeline – automatisierte Qualitätssicherung bei jedem Push
Das funktioniert für klassische Softwareprojekte genauso wie für Data Science Pipelines.
Linting, Formatting und Typprüfung im Überblick
| Tool | Aufgabe | Nutzen |
|---|---|---|
| Black | Formatierung nach klaren Regeln | Einheitlicher Stil, keine Diskussionen |
| Ruff | Linter + isort-Ersatz | Findet Stil-, Syntax- und Performance-Probleme |
| Mypy | Typprüfung | Erkennt Fehler frühzeitig dank Type Hints |
Zentrale Konfiguration mit pyproject.toml
Python bietet seit PEP 518 die Möglichkeit, Tool-Konfigurationen in einer zentralen Datei zu pflegen. Die Datei pyproject.toml wird im Hauptverzeichnis des Repositories abgelegt. Diese Datei dient als Meta-Konfiguration für alle relevanten Tools – Formatierung, Linting, Typprüfung, Build-System etc. In dieser Datei wird unter anderem Ruff und Black konfiguriert.
In der Section [project] werden grundsätzliche Infos wie Name oder Versionsnummer festgehalten. Dort kann man sogar die dependencies festhalten (das wird bspw. von Poetry verwendet). Wenn klassische requirements.txt mit pip verwendet wird, dann ist das nicht notwendig.
Ruff und Black dienen zur Code Formatierung und Linting.
Mypy ist der Type Checker für Python. Er prüft, ob der Code mit den Typannotationen übereinstimmt – ähnlich wie TypeScript bei JavaScript.
Hinweis: In VSCode wird die toml nicht automatisch erkannt, dafür benötigt man die Extension Suche nach TOML Language Support.
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "fizzbuzz-api"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.111",
"uvicorn[standard]>=0.29",
"pydantic>=2.7",
"httpx>=0.27,<0.29",
]
[tool.ruff]
line-length = 100
target-version = "py311"
# Wähle ein gutes Basis-Set an Regeln:
lint.select = ["E", "F", "I", "UP", "B", "C4", "SIM", "PL"]
lint.ignore = [
"E501", # Länge übernimmt black
]
# isort via Ruff:
src = ["app", "tests"]
[tool.black]
line-length = 100
target-version = ["py311"]
[tool.mypy]
python_version = "3.11"
warn_unused_configs = true
warn_return_any = true
warn_unused_ignores = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
no_implicit_optional = true
check_untyped_defs = true
strict_equality = true
pretty = true
show_error_codes = true
# FastAPI/Pydantic:
plugins = ["pydantic.mypy"]
# Wo wir streng prüfen:
mypy_path = "."
# Paket-spezifische Ausnahmen (falls nötig)
[[tool.mypy.overrides]]
module = ["uvicorn.*", "starlette.*"]
ignore_missing_imports = true
Entwicklungsabhängigkeiten: requirements-dev.txt
Damit CI, lokale Tests und Pre-Commit denselben Stand nutzen, empfiehlt sich eine dedizierte Datei für das lokale Testing und Formatier anzulegen.
-r requirements.txt
black>=24.0
ruff>=0.5.0
mypy>=1.10
pydantic>=2.7
pydantic-settings>=2.4 ; python_version >= "3.11" # optional, falls du Settings nutzt
pytest>=8.4
pytest-cov>=5.0
Pre-commit Hooks
Mit pre-commit kann man bereits vor jedem Commit lokal die Code-Formatierungen optimieren. Dazu installiert man das Paket pre-commit. Die Einstellungen werden in der Datei pre-commit-config.yaml festgelegt:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7
hooks:
- id: ruff
args: ["--fix"] # auto-fix wo möglich
- id: ruff-format # ruff-format (alternativ zu black, wenn du magst)
# Falls du black lieber separat willst, aktiviere diesen Block und entferne ruff-format oben:
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
additional_dependencies: ["pydantic>=2.7", "pydantic-core>=2.18"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
- id: trailing-whitespace
Um die pre-commit auszuführen benötigt man folgenden Befehl:
pre-commit run --all-filesTipp: In Teams lohnt es sich, pre-commit als Pflichtschritt in der Onboarding-Dokumentation oder Makefile zu hinterlegen.
Makefile: Automatisierung für Entwickler:innen
Ein Makefile ist eine einfache, aber mächtige Möglichkeit, häufige Befehle in standardisierte Kurzbefehle (Targets) zu verpacken.
Es stammt ursprünglich aus der C-Entwicklung, wird heute aber in Python-, Data-Science- und DevOps-Projekten häufig genutzt.
Komplexe Befehle werden einmal ins Makefile überführt und dann mit einem einfachen make-Befehl ausgeführt.
.PHONY: install lint format test typecheck quality
install:
pip install -r requirements-dev.txt
lint:
ruff check .
format:
black .
ruff format .
typecheck:
mypy .
test:
pytest -q --disable-warnings --maxfail=1
quality: lint format typecheck test
Erklärung:
.PHONYbedeutet, dass die Befehle keine echten Dateien sind, sondern Tasks.- Jeder Abschnitt (
install,lint,test…) ist ein Target, das mitmake <target>ausgeführt wird. - Man kann Targets kombinieren oder Abhängigkeiten definieren, z. B.
quality: lint testbedeutet, dassmake qualitybeide Schritte ausführt.
Beispiele zur Anwendung:
# Erstinstallation
make install
# Codequalität prüfen
make quality
# Nur Formatierung
make format In Data-Science-Projekten kann man Makefiles auch nutzen um Daten-Tasks zu automatisieren:
data-clean:
python scripts/clean_data.py --input data/raw --output data/clean
train-model:
python src/train_model.py --config configs/model.yamlCI Pipeline mit Github Actions
Automatisierte Qualitätssicherung in der Cloud ist der letzte Baustein: Jeder Commit oder Pull Request löst eine Continuous Integration (CI)-Pipeline aus. Um die CI Pipeline aufzusetzen erstellt man die Datei .github/workflows/ci.yaml
name: CI
on:
pull_request:
push:
branches: [ main ]
jobs:
quality-test-docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dev deps
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt
- name: Lint (ruff)
run: ruff check .
- name: Format check (black)
run: black --check .
- name: Type check (mypy)
run: mypy .
- name: Run tests
run: pytest -q --maxfail=1 --disable-warnings
- name: Build Docker image
uses: docker/setup-buildx-action@v3
- name: Docker build (no push)
run: docker build -t fizzbuzz-api:ci .
# Optional: Container kurz starten und Healthcheck testen
- name: Smoke test container
run: |
docker run -d -p 8000:8000 --name fb fizzbuzz-api:ci
sleep 2
curl -f http://127.0.0.1:8000/health
docker rm -f fb
So wird bei jedem Commit sichergestellt, dass:
- Der Code korrekt formatiert ist,
- Typfehler früh erkannt werden,
- Tests fehlerfrei laufen.
Data-Science-Tipp:
Diese CI-Struktur lässt sich leicht erweitern – z. B. für MLflow-Modelle, Docker Builds, oder automatisierte Data Validation mit Great Expectations.
Fazit
Automatisierte Codequalität macht Projekte nachhaltig und teamfähig. Gerade in Data-Science-Teams, wo Analysecode oft später zur API oder zum Produktionsskript wird, lohnt sich dieser Aufwand frühzeitig.