diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md new file mode 100644 index 0000000..acdac1b --- /dev/null +++ b/CODING_STANDARDS.md @@ -0,0 +1,687 @@ +# BSN Coding Standards — Das verbindliche Regelwerk + +> **Version:** 1.0 — 21. Juni 2026 +> **Autor:** Cody (Coding-Agent für Daniel Krause, brettspiel-news.de) +> **Verbindlichkeit:** Diese Regeln werden durch automatisierte Tools (pre-commit, Ruff, mypy) durchgesetzt. Kein Merge ohne bestandene Checks. + +--- + +## Inhaltsverzeichnis + +1. [Allgemeine Prinzipien](#1-allgemeine-prinzipien) +2. [Code-Stil](#2-code-stil) +3. [Typ-Annotationen](#3-typ-annotationen) +4. [Namenskonventionen](#4-namenskonventionen) +5. [Kommentare & Docstrings](#5-kommentare--docstrings) +6. [Projektstruktur & Architektur](#6-projektstruktur--architektur) +7. [Sicherheit](#7-sicherheit) +8. [Testing](#8-testing) +9. [Flask-spezifische Regeln](#9-flask-spezifische-regeln) +10. [Git & Commits](#10-git--commits) +11. [Toolchain & Durchsetzung](#11-toolchain--durchsetzung) + +--- + +## 1. Allgemeine Prinzipien + +### 1.1 Das Zen of Python (PEP 20) + +```python +import this +``` + +Die wichtigsten Prinzipien für dieses Projekt: + +| Prinzip | Bedeutung | +|---|---| +| **Readability counts.** | Code wird 10× öfter gelesen als geschrieben. Schreibe für den Leser. | +| **Explicit is better than implicit.** | Kein Magic. Kein `**kwargs`-Missbrauch. Typen immer sichtbar. | +| **Simple is better than complex.** | YAGNI — You Ain't Gonna Need It. Keine überflüssigen Abstraktionen. | +| **Errors should never pass silently.** | Immer `try/except` mit spezifischen Exceptions + Logging. | +| **There should be one obvious way to do it.** | Einheitlicher Stil im gesamten Projekt. | + +### 1.2 Konsistenz-Regel (PEP 8) + +> **Konsistenz mit diesem Styleguide ist wichtig. Konsistenz innerhalb des Projekts ist wichtiger. Konsistenz innerhalb eines Moduls ist am wichtigsten.** + +Wenn du eine Regel brechen musst, dokumentiere WARUM in einem Kommentar. + +--- + +## 2. Code-Stil + +**Durchsetzung:** Ruff (Linter + Formatter) in `pyproject.toml` +**Basis:** PEP 8 mit projektspezifischen Anpassungen + +### 2.1 Zeilenlänge + +``` +Maximal 100 Zeichen pro Zeile. +``` + +**Begründung:** 88 (Black-Default) ist zu knapp für Flask-Routen und SQLAlchemy-Queries. 100 Zeichen erlauben lesbare Code-Blöcke ohne horizontales Scrollen. + +### 2.2 Einrückung + +- **4 Leerzeichen** pro Ebene — **NIE Tabs** +- Fortsetzungszeilen: 8 Leerzeichen (doppelte Einrückung) oder vertikale Ausrichtung + +```python +# ✅ Richtig — vertikale Ausrichtung +result = some_function( + argument_one, + argument_two, + argument_three, +) + +# ✅ Richtig — doppelte Einrückung bei langen Bedingungen +if (some_very_long_condition + and another_long_condition): + do_something() + +# ❌ Falsch — nur 4 Leerzeichen (nicht unterscheidbar) +if (some_very_long_condition + and another_long_condition): + do_something() +``` + +### 2.3 Quotes + +- **Doppelte Anführungszeichen** (`"`) für Strings — Black/Ruff-Standard +- Einfache Anführungszeichen nur, wenn der String selbst `"` enthält +- Docstrings: **immer** `"""triple double quotes"""` (PEP 257) + +```python +# ✅ Richtig +name = "Brettspiel-News" +message = f"Willkommen bei {name}!" + +# ✅ Richtig — enthält doppelte Quotes +quote = 'Er sagte: "Das ist ein gutes Spiel!"' + +# ✅ Richtig +def get_games() -> list[dict]: + """Holt alle Spiele aus der Datenbank.""" + ... +``` + +### 2.4 Imports + +**Reihenfolge** (automatisch durch Ruff/isort): + +1. Standard-Library (`os`, `json`, `datetime`) +2. Drittanbieter (`flask`, `sqlalchemy`, `requests`) +3. Projekt-interne (`from .models import ...`) + +```python +# ✅ Richtig +import os +import json +from datetime import datetime, timedelta + +from flask import Flask, request, jsonify +from sqlalchemy import create_engine + +from .models import Game, User +from .utils import validate_email +``` + +**Verboten:** +- `from module import *` — verschmutzt den Namespace +- Zirkuläre Imports — deuten auf Architekturprobleme hin + +### 2.5 Leerzeichen + +| Regel | Beispiel | +|---|---| +| **Nach Komma** ein Leerzeichen | `[1, 2, 3]` nicht `[1,2,3]` | +| **Um Operatoren** ein Leerzeichen | `x = 1 + 2` nicht `x=1+2` | +| **Kein Leerzeichen** vor `(` bei Funktion | `def foo():` nicht `def foo ():` | +| **Kein Leerzeichen** vor `:` bei Slice | `list[1:3]` nicht `list[1 : 3]` | +| **Kein Leerzeichen** vor `=` bei Keyword-Arg | `foo(x=1)` nicht `foo(x = 1)` | + +### 2.6 Leerzeilen + +- 2 Leerzeilen vor Top-Level-Funktionen und -Klassen +- 1 Leerzeile vor Methoden innerhalb einer Klasse +- 1 Leerzeile zwischen logischen Blöcken innerhalb einer Funktion + +### 2.7 Trailing Commas + +- **Immer** trailing comma bei mehrzeiligen Listen/Dicts/Tupeln +- Erleichtert Diffs und verhindert Fehler beim Hinzufügen von Elementen + +```python +# ✅ Richtig +items = [ + "Brettspiel", + "Kartenspiel", + "Würfelspiel", +] + +config = { + "host": "localhost", + "port": 5432, + "debug": False, +} +``` + +--- + +## 3. Typ-Annotationen + +**Durchsetzung:** mypy in `strict`-Mode + +### 3.1 Grundregel + +**Jede Funktion und Methode MUSS vollständige Typ-Annotationen haben.** + +```python +# ✅ Richtig +def get_game_by_id(game_id: int) -> dict[str, str | int] | None: + """Holt ein Spiel anhand seiner ID.""" + ... + +# ❌ Falsch — keine Typen +def get_game_by_id(game_id): + ... +``` + +### 3.2 Spezifische Regeln + +| Regel | Richtig | Falsch | +|---|---|---| +| `Optional` statt `X \| None` (Konsistenz) | `Optional[str]` | `str \| None` | +| `list`/`dict` statt `List`/`Dict` (Python 3.9+) | `list[str]` | `List[str]` | +| `Any` nur in Ausnahmefällen | `dict[str, Any]` (API-Response) | `Any` als Rückgabetyp | +| `-> None` bei Funktionen ohne Return | `def log(msg: str) -> None:` | `def log(msg: str):` | + +```python +from typing import Optional, Any + +# ✅ Richtig +def find_user(email: str) -> Optional[dict[str, Any]]: + """Sucht einen User per E-Mail. Gibt None zurück, wenn nicht gefunden.""" + ... + +# ✅ Richtig — Flask-Route +@app.route("/api/games/") +def get_game(game_id: int) -> tuple[dict, int]: + """Gibt ein einzelnes Spiel zurück.""" + ... +``` + +--- + +## 4. Namenskonventionen + +### 4.1 Übersicht + +| Element | Konvention | Beispiel | +|---|---|---| +| **Module/Packages** | `snake_case` | `game_service.py`, `user_models.py` | +| **Klassen** | `PascalCase` | `GameRepository`, `UserService` | +| **Funktionen/Methoden** | `snake_case` | `get_game_by_id()`, `calculate_score()` | +| **Variablen** | `snake_case` | `game_count`, `user_email` | +| **Konstanten** | `UPPER_SNAKE_CASE` | `MAX_RESULTS = 50`, `DEFAULT_TIMEOUT = 30` | +| **Private (intern)** | `_leading_underscore` | `def _parse_response():`, `self._cache` | +| **"Private" (Name Mangling)** | `__double_underscore` | `self.__db_connection` (nur bei Vererbungskonflikten) | + +### 4.2 Spezifische Regeln + +- **Boolesche Variablen** mit `is_`, `has_`, `should_`-Präfix: `is_active`, `has_permission`, `should_retry` +- **Funktionen** als Verben: `get_`, `create_`, `update_`, `delete_`, `calculate_`, `validate_` +- **Keine Ein-Buchstaben-Variablen** außer in trivialen Comprehensions (`[x for x in items]`) +- **Keine kryptischen Abkürzungen**: `user_count` statt `uc`, `response` statt `resp` + +--- + +## 5. Kommentare & Docstrings + +### 5.1 Sprache + +**Alle Kommentare und Docstrings werden auf DEUTSCH geschrieben.** + +```python +# ✅ Richtig +def berechne_durchschnitt(werteliste: list[float]) -> float: + """Berechnet den arithmetischen Durchschnitt einer Werteliste. + + Args: + werteliste: Liste von Gleitkommazahlen. + + Returns: + Der Durchschnittswert. + + Raises: + ValueError: Wenn die Liste leer ist. + """ + if not werteliste: + raise ValueError("Die Werteliste darf nicht leer sein.") + return sum(werteliste) / len(werteliste) +``` + +### 5.2 Docstring-Format + +**Google-Style Docstrings** (maschinenlesbar, von den meisten Tools unterstützt): + +```python +def komplexe_funktion( + name: str, + werte: list[int], + optional: Optional[str] = None, +) -> dict[str, Any]: + """Kurzbeschreibung (eine Zeile). + + Ausführliche Beschreibung. Kann mehrere Sätze umfassen. + Beschreibt das WARUM, nicht das WAS. + + Args: + name: Beschreibung des Parameters. + werte: Liste von Werten, die verarbeitet werden. + optional: Optionaler Zusatzparameter. + + Returns: + Ein Dictionary mit den verarbeiteten Ergebnissen. + + Raises: + ValueError: Wenn name leer ist. + TypeError: Wenn werte keine Liste von ints ist. + + Example: + >>> komplexe_funktion("Test", [1, 2, 3]) + {"name": "Test", "summe": 6} + """ +``` + +### 5.3 Wann kommentieren? + +- **Immer:** Docstrings für alle öffentlichen Funktionen/Klassen +- **Oft:** `# TODO:`, `# FIXME:`, `# HACK:` mit Erklärung +- **Manchmal:** Erklärung von nicht-offensichtlichem Code (Algorithmus, Workaround) +- **Nie:** Code auskommentieren — bei Bedarf durch Git-History wiederherstellen + +```python +# ✅ Richtig — erklärt WARUM der Workaround +# Workaround: SQLAlchemy 2.0 deprecated `query` — bis Migration nutzen wir `session.execute()` +result = session.execute(select(Game).where(Game.id == game_id)) + +# ❌ Falsch — erklärt WAS (offensichtlich) +# Holt das Spiel mit der ID +result = session.execute(select(Game).where(Game.id == game_id)) +``` + +--- + +## 6. Projektstruktur & Architektur + +### 6.1 Maximalgrößen + +| Einheit | Maximal | +|---|---| +| **Modul (.py-Datei)** | 500 Zeilen | +| **Funktion/Methode** | 50 Zeilen | +| **Klasse** | 300 Zeilen | +| **Zeile** | 100 Zeichen | + +**Begründung:** Kleine, fokussierte Einheiten sind leichter zu testen, zu verstehen und zu warten. + +### 6.2 Separation of Concerns + +``` +project/ +├── app.py # Flask-App-Factory + Config +├── routes/ # Nur Request-Handling, keine Business-Logik +│ ├── __init__.py +│ ├── games.py # /api/games/* +│ └── users.py # /api/users/* +├── services/ # Business-Logik +│ ├── __init__.py +│ └── game_service.py +├── models/ # Datenbank-Modelle +│ ├── __init__.py +│ └── game.py +├── utils/ # Hilfsfunktionen +│ ├── __init__.py +│ └── validators.py +├── templates/ # Jinja2-Templates +├── static/ # CSS, JS, Bilder +├── tests/ # Tests (spiegelt src/-Struktur) +│ ├── test_routes/ +│ ├── test_services/ +│ └── conftest.py +├── migrations/ # DB-Migrationen (Alembic) +├── pyproject.toml # ALLE Projekt-Konfiguration +├── .pre-commit-config.yaml +├── README.md +└── CODING_STANDARDS.md +``` + +### 6.3 Prinzipien + +- **Keine God-Files:** `app.py` darf nicht 6000 Zeilen haben. Auslagern! +- **Routes sind dumm:** Nur Request-Parsing → Service aufrufen → Response formatieren +- **Services enthalten Logik:** Validierung, Berechnung, DB-Zugriffe +- **Models nur Schema:** Tabellen-Definition, keine Business-Logik +- **Utils sind pure functions:** Kein Zustand, keine Seiteneffekte, keine DB-Zugriffe + +```python +# ✅ Richtig — Route delegiert an Service +@app.route("/api/games/") +def get_game(game_id: int) -> tuple[dict, int]: + """Gibt ein einzelnes Spiel zurück.""" + game = game_service.get_by_id(game_id) + if not game: + return {"error": "Spiel nicht gefunden"}, 404 + return game.to_dict(), 200 + +# ❌ Falsch — Route enthält Business-Logik +@app.route("/api/games/") +def get_game(game_id): + game = db.session.execute( + select(Game).where(Game.id == game_id) + ).scalar_one_or_none() + if not game: + return {"error": "Not found"}, 404 + # 50 Zeilen Berechnungen hier... +``` + +--- + +## 7. Sicherheit + +**Durchsetzung:** Ruff (Sicherheits-Regeln) + Bandit + +### 7.1 Secrets + +**NIEMALS Secrets im Code!** Immer über Environment Variables oder `.env`-Datei. + +```python +# ✅ Richtig +import os +API_KEY = os.environ["BSN_API_KEY"] + +# ❌ Falsch +API_KEY = "sk-abc123def456" # SOFORT LÖSCHEN + Key rotieren! +``` + +**`.env` und `.db`-Dateien MÜSSEN in `.gitignore` stehen.** + +### 7.2 Input-Validierung + +- **Alle User-Inputs validieren**, bevor sie verarbeitet werden +- Flask-Werkzeuge nutzen: `request.args.get()`, `request.json.get()` mit Defaults +- SQL-Injection verhindern: Parameterisierte Queries (SQLAlchemy macht das automatisch) + +```python +# ✅ Richtig — validiert und sanitized +from flask import request, abort + +@app.route("/api/search") +def search_games() -> dict: + query = request.args.get("q", "").strip() + if not query or len(query) > 100: + abort(400, description="Ungültiger Suchbegriff") + return game_service.search(query) + +# ❌ Falsch — keine Validierung, roher Input in Query +@app.route("/api/search") +def search_games(): + query = request.args.get("q") + db.session.execute(f"SELECT * FROM games WHERE name LIKE '%{query}%'") +``` + +### 7.3 Weitere Regeln + +- **Keine assert-Statements** in Produktionscode (werden mit `-O` deaktiviert) +- **Keine `pickle`** für ungetrustete Daten +- **`subprocess` mit `shell=False`** (Default) +- **Hash-Funktionen für Passwörter:** `bcrypt` oder `argon2`, nie `md5`/`sha1` + +--- + +## 8. Testing + +**Durchsetzung:** pytest mit Coverage-Check + +### 8.1 Grundregeln + +1. **Jede neue Funktion → Test** (TDD: erst Test, dann Code) +2. **80% Code Coverage** als Minimum +3. **pytest** als Test-Framework +4. **Tests in `tests/`**, spiegelt die Projektstruktur + +### 8.2 Test-Struktur + +```python +# tests/test_services/test_game_service.py +import pytest +from services.game_service import GameService + + +class TestGameService: + """Tests für den GameService.""" + + def test_get_by_id_returns_game_when_found(self, db_session): + """Gibt ein Spiel zurück, wenn die ID existiert.""" + # Arrange + service = GameService(db_session) + + # Act + result = service.get_by_id(1) + + # Assert + assert result is not None + assert result.name == "Catan" + + def test_get_by_id_returns_none_when_missing(self, db_session): + """Gibt None zurück, wenn die ID nicht existiert.""" + service = GameService(db_session) + result = service.get_by_id(999999) + assert result is None + + def test_get_by_id_raises_on_negative_id(self, db_session): + """Wirft ValueError bei negativer ID.""" + service = GameService(db_session) + with pytest.raises(ValueError, match="ID darf nicht negativ sein"): + service.get_by_id(-1) +``` + +### 8.3 Test-Namen + +- **`test__`** +- Deutsch oder Englisch — aber **einheitlich** im gesamten Projekt + +--- + +## 9. Flask-spezifische Regeln + +### 9.1 Routes + +```python +# ✅ Immer `methods` explizit angeben +@app.route("/api/games", methods=["GET"]) +def list_games() -> tuple[dict, int]: + ... + +# ✅ POST-Daten aus request.get_json(), nicht request.args +@app.route("/api/games", methods=["POST"]) +def create_game() -> tuple[dict, int]: + data = request.get_json(silent=True) + if not data: + return {"error": "Kein JSON-Body"}, 400 + ... + +# ❌ Falsch — GET-Parameter in POST lesen +data = request.args.get("name") # NIEMALS für POST-Daten +``` + +### 9.2 Response-Format + +- Immer **JSON** zurückgeben: `flask.jsonify()` oder `dict` mit Statuscode +- Statuscodes **explizit** setzen +- Fehler-Responses einheitlich: `{"error": "Nachricht"}` mit passendem Statuscode + +```python +# ✅ Richtig +@app.route("/api/games/") +def get_game(game_id: int) -> tuple[dict, int]: + game = game_service.get_by_id(game_id) + if not game: + return {"error": "Spiel nicht gefunden"}, 404 + return game.to_dict(), 200 +``` + +### 9.3 Config + +- Flask-Config über `app.config.from_prefixed_env()` (Flask 3.1+) +- Secrets nie in `app.config` hartkodieren + +```python +# ✅ Richtig — Config aus Environment +app = Flask(__name__) +app.config.from_prefixed_env() +# Zugriff: os.environ["FLASK_SECRET_KEY"], os.environ["FLASK_DATABASE_URL"] +``` + +### 9.4 Templates + +- CSS **immer inline** in JCE-Templates (self-contained, keine separaten CSS-Dateien) +- Mobile-first Design +- BSN-Blau `#20228a` als Primärfarbe +- Touch-Buttons min 64px, Overlays min 85vh + +--- + +## 10. Git & Commits + +### 10.1 Conventional Commits + +Jeder Commit folgt dem **Conventional Commits**-Standard: + +``` +(): + +[optional body] + +[optional footer] +``` + +| Typ | Verwendung | +|---|---| +| `feat` | Neues Feature | +| `fix` | Bugfix | +| `docs` | Dokumentation | +| `style` | Formatierung (kein Code-Change) | +| `refactor` | Code-Umbau (kein Feature, kein Fix) | +| `test` | Tests hinzugefügt/geändert | +| `chore` | Build, Dependencies, CI | +| `perf` | Performance-Verbesserung | +| `security` | Sicherheitsrelevante Änderung | + +**Beispiele:** +``` +feat(games): Spiele-Suche mit Volltext-Filter +fix(auth): Login bricht bei leeren Feldern nicht mehr ab +docs: CODING_STANDARDS.md hinzugefügt +chore(deps): Ruff auf 0.11.3 aktualisiert +``` + +### 10.2 Commit-Frequenz + +- **Nach jeder abgeschlossenen Aufgabe** committen +- Keine Mega-Commits mit 50 geänderten Dateien +- Ein Commit = eine logische Änderung + +### 10.3 Vor jedem Commit + +```bash +pre-commit run # Linting + Formatierung + Typ-Check +pytest # Tests müssen grün sein +``` + +--- + +## 11. Toolchain & Durchsetzung + +### 11.1 Übersicht + +| Tool | Zweck | Konfiguration | +|---|---|---| +| **[Ruff](https://docs.astral.sh/ruff/)** | Linting + Formatierung | `pyproject.toml` → `[tool.ruff]` | +| **[mypy](https://mypy-lang.org/)** | Statische Typ-Prüfung | `pyproject.toml` → `[tool.mypy]` | +| **[pre-commit](https://pre-commit.com/)** | Git-Hooks zur Durchsetzung | `.pre-commit-config.yaml` | +| **[pytest](https://pytest.org/)** | Testing | `pyproject.toml` → `[tool.pytest.ini_options]` | +| **[Bandit](https://bandit.readthedocs.io/)** | Sicherheits-Scans | `pyproject.toml` → `[tool.bandit]` | + +### 11.2 Quick-Start (neues Projekt) + +```bash +# 1. Tools installieren +pip install ruff mypy pre-commit pytest bandit + +# 2. Pre-commit-Hooks aktivieren +pre-commit install + +# 3. Erstmalig alles prüfen +ruff check . --fix +ruff format . +mypy . +pytest + +# 4. Vor jedem Commit automatisch +git commit -m "feat: ..." +# → Ruff prüft + formatiert automatisch +# → mypy prüft die Typen +# → Bandit scannt auf Sicherheitslücken +``` + +### 11.3 CI-Setup (später) + +```yaml +# .github/workflows/quality.yml +- name: Lint + run: ruff check . +- name: Type Check + run: mypy . +- name: Security Scan + run: bandit -r src/ -c pyproject.toml +- name: Tests + run: pytest --cov --cov-fail-under=80 +``` + +--- + +## Anhang A: Checkliste für Code Reviews + +- [ ] PEP 8 eingehalten? (Ruff-Check bestanden) +- [ ] Alle Funktionen haben Typ-Annotationen? (mypy bestanden) +- [ ] Docstrings auf Deutsch für alle öffentlichen Funktionen? +- [ ] Keine Secrets im Code? +- [ ] User-Input validiert? +- [ ] Tests geschrieben und grün? +- [ ] Keine God-Files (>500 Zeilen)? +- [ ] Keine God-Funktionen (>50 Zeilen)? +- [ ] Route enthält keine Business-Logik? +- [ ] Commit folgt Conventional Commits? + +--- + +## Anhang B: Warum diese Regeln? + +> *"Die Basis muss stimmen — wie bei einem Haus, das Fundament ist das wichtigste."* — Daniel Krause + +Diese Regeln sind nicht willkürlich. Sie basieren auf: + +1. **PEP 8** — dem offiziellen Python-Styleguide (seit 2001) +2. **Google Python Style Guide** — bewährt in tausenden Projekten +3. **The Zen of Python** — Guido van Rossums Design-Prinzipien +4. **Erfahrungen aus dem BSN-Chatbot-Projekt** — was funktioniert hat, was nicht +5. **Moderner Tooling-Konsens 2026** — Ruff + mypy + pre-commit sind Industriestandard + +Jede Regel ist **automatisiert durchsetzbar**. Das bedeutet: Du musst sie nicht im Kopf behalten — die Tools sagen dir, wenn etwas falsch ist. Und sie verhindern, dass fehlerhafter Code überhaupt ins Repository kommt. + +--- + +> **Letzte Aktualisierung:** 21. Juni 2026 +> **Nächste Überprüfung:** Bei Projektstart oder nach 6 Monaten diff --git a/plans/2026-06-21-coding-standards.md b/plans/2026-06-21-coding-standards.md new file mode 100644 index 0000000..34655ff --- /dev/null +++ b/plans/2026-06-21-coding-standards.md @@ -0,0 +1,80 @@ +# BSN Coding Standards — Fundament-Plan + +> **Für Hermes:** Direkt umsetzen, keine Subagents nötig — kompaktes Setup. + +**Ziel:** Verbindliche Coding-Standards für das BSN-Community-Projekt, automatisch durchgesetzt durch Tooling. + +**Architektur:** Drei-Schichten-Modell: +1. **Regelwerk** (`CODING_STANDARDS.md`) — Deutsche Dokumentation, menschenlesbar +2. **Konfiguration** (`pyproject.toml`) — Ruff (Linter+Formatter), mypy, pytest +3. **Durchsetzung** (`.pre-commit-config.yaml`) — Automatische Prüfung vor jedem Commit + +**Tech Stack:** Ruff 0.11+, mypy 1.15+, pytest 8+, pre-commit 4+ + +--- + +## Recherche-Ergebnisse (Stand Juni 2026) + +### Konsens der Python-Community +- **Ruff** hat Black + isort + Flake8 + pyupgrade + Bandit nahezu vollständig abgelöst +- **mypy** bleibt der Standard für statische Typ-Prüfung +- **pyproject.toml** ist der zentrale Konfigurationspunkt (PEP 621) +- **pre-commit** ist der De-facto-Standard für Git-Hooks +- Line-Length: 88 (Black-Default) oder 100 (moderner Konsens für Web-Apps) + +### Für Flask-Webprojekte spezifisch +- Routes schlank halten (nur Request-Handling, keine Business-Logik) +- `@app.route()` immer mit expliziten `methods=[...]` +- Niemals `request.args` für POST-Daten verwenden +- Immer `send_file()` mit korrektem mimetype +- Secrets nie im Code — immer über Environment Variables + +--- + +## Aufgaben + +### Task 1: CODING_STANDARDS.md schreiben +**Datei:** `CODING_STANDARDS.md` (Projekt-Root) + +Deutsches Regelwerk mit folgenden Abschnitten: +1. Allgemeine Prinzipien (Lesbarkeit, Konsistenz, Zen of Python) +2. Code-Stil (PEP 8, Zeilenlänge 100, Quotes, Imports) +3. Typ-Annotationen (immer, strict, `Optional` statt `| None` für Python <3.10) +4. Namenskonventionen (snake_case, PascalCase, UPPER_CASE) +5. Kommentare & Docstrings (Deutsch, Google-Style) +6. Projektstruktur (keine God-Files, max 500 Zeilen/Modul) +7. Sicherheit (keine Secrets, Input-Validierung, SQL-Injection) +8. Testing (pytest, 80% Coverage, TDD) +9. Flask-spezifische Regeln +10. Git-Commit-Regeln (Conventional Commits) + +### Task 2: pyproject.toml erstellen +**Datei:** `pyproject.toml` (Projekt-Root) + +```toml +[build-system] +[tool.ruff] — line-length=100, target-version=py313 +[tool.ruff.lint] — Alle kritischen Rules aktiv +[tool.ruff.format] — quote-style="double" +[tool.mypy] — strict=true +[tool.pytest.ini_options] — testpaths, coverage +[tool.bandit] — Sicherheits-Scans +``` + +### Task 3: .pre-commit-config.yaml erstellen +**Datei:** `.pre-commit-config.yaml` (Projekt-Root) + +Hooks: ruff (lint+format), mypy, Bandit, check-added-large-files, check-merge-conflict, trailing-whitespace + +### Task 4: Committen +```bash +git add CODING_STANDARDS.md pyproject.toml .pre-commit-config.yaml +git commit -m "feat: coding standards fundament — Ruff + mypy + pre-commit" +``` + +--- + +## Verifikation +- `ruff check .` findet keine unerwünschten Issues +- `mypy .` läuft ohne Fehler (auf neuem Code) +- `pre-commit run --all-files` läuft durch