fix: QC-Ratings aus 7 älteren Artikeln entfernt (CHECK 10)
This commit is contained in:
@@ -0,0 +1,800 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BSN Coding Standards — Verbindliches Regelwerk v1.0 (Review-fixed)</title>
|
||||
<style>
|
||||
:root {
|
||||
--bsn-blue: #20228a;
|
||||
--bsn-blue-light: #2d30b5;
|
||||
--bg: #ffffff; --bg-card: #f8f9ff; --bg-code: #1a1a2e;
|
||||
--text: #1a1a2e; --text-secondary: #555; --border: #e0e0e8;
|
||||
--success: #28a745; --warning: #ffc107; --danger: #dc3545;
|
||||
--radius: 8px;
|
||||
}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;line-height:1.6;color:var(--text);background:#f5f5fa}
|
||||
.container{max-width:1100px;margin:0 auto;padding:0 20px}
|
||||
.header{background:linear-gradient(135deg,var(--bsn-blue),#181a6e);color:#fff;padding:60px 20px 50px;text-align:center;position:relative;overflow:hidden}
|
||||
.header::after{content:'';position:absolute;bottom:0;left:0;right:0;height:4px;background:linear-gradient(90deg,#ff6b6b,#ffd93d,#6bcb77,#4d96ff)}
|
||||
.header h1{font-size:clamp(1.8rem,4vw,2.8rem);font-weight:800;letter-spacing:-0.5px;margin-bottom:10px}
|
||||
.header .subtitle{font-size:1.1rem;opacity:.85;max-width:650px;margin:0 auto;line-height:1.5}
|
||||
.header .meta{margin-top:20px;font-size:.85rem;opacity:.7;display:flex;gap:20px;justify-content:center;flex-wrap:wrap}
|
||||
.status-badge{display:inline-block;background:rgba(255,255,255,.15);padding:4px 14px;border-radius:20px;font-size:.8rem;font-weight:600;letter-spacing:.5px;text-transform:uppercase;margin-bottom:15px}
|
||||
.review-badge{display:inline-block;background:#d4edda;color:#155724;padding:4px 14px;border-radius:20px;font-size:.8rem;font-weight:600;margin-bottom:15px;margin-left:8px}
|
||||
.toc{background:white;border:2px solid var(--border);border-radius:var(--radius);padding:30px;margin:-30px auto 40px;position:relative;box-shadow:0 4px 20px rgba(0,0,0,.06)}
|
||||
.toc h2{color:var(--bsn-blue);font-size:1.3rem;margin-bottom:15px;padding-bottom:10px;border-bottom:2px solid var(--bsn-blue)}
|
||||
.toc ol{columns:2;column-gap:40px;list-style-position:inside}
|
||||
@media(max-width:600px){.toc ol{columns:1}}
|
||||
.toc li{margin-bottom:6px}
|
||||
.toc a{color:var(--bsn-blue);text-decoration:none;font-weight:500}
|
||||
.toc a:hover{color:var(--bsn-blue-light);text-decoration:underline}
|
||||
section{background:white;border:1px solid var(--border);border-radius:var(--radius);padding:35px 30px;margin-bottom:25px;box-shadow:0 2px 8px rgba(0,0,0,.03)}
|
||||
h2{font-size:1.5rem;color:var(--bsn-blue);margin-bottom:20px;padding-bottom:8px;border-bottom:2px solid var(--bsn-blue);display:flex;align-items:center;gap:10px}
|
||||
h2 .num{background:var(--bsn-blue);color:white;width:32px;height:32px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;font-size:.9rem;font-weight:700;flex-shrink:0}
|
||||
h3{font-size:1.15rem;color:#181a6e;margin:25px 0 12px}
|
||||
h4{font-size:1rem;color:#333;margin:16px 0 8px}
|
||||
p{margin-bottom:12px}p:last-child{margin-bottom:0}
|
||||
code{background:#eef0ff;color:#181a6e;padding:2px 6px;border-radius:3px;font-family:'JetBrains Mono','Fira Code','Cascadia Code','Consolas',monospace;font-size:.9em}
|
||||
pre{background:var(--bg-code);color:#e0e0e0;padding:20px;border-radius:var(--radius);overflow-x:auto;margin:15px 0;font-size:.88rem;line-height:1.5;position:relative}
|
||||
pre code{background:none;color:inherit;padding:0;font-size:inherit}
|
||||
.kw{color:#c792ea}.str{color:#c3e88d}.cm{color:#676e95;font-style:italic}.fn{color:#82aaff}.num{color:#f78c6c}.op{color:#89ddff}
|
||||
.badge-correct{display:inline-block;background:#d4edda;color:#155724;padding:2px 8px;border-radius:3px;font-size:.75rem;font-weight:600;margin-right:5px}
|
||||
.badge-wrong{display:inline-block;background:#f8d7da;color:#721c24;padding:2px 8px;border-radius:3px;font-size:.75rem;font-weight:600;margin-right:5px}
|
||||
.badge-manual{display:inline-block;background:#fff3cd;color:#856404;padding:2px 8px;border-radius:3px;font-size:.75rem;font-weight:600;margin-right:5px}
|
||||
.badge-auto{display:inline-block;background:#d4edda;color:#155724;padding:2px 8px;border-radius:3px;font-size:.75rem;font-weight:600;margin-right:5px}
|
||||
.table-wrap{overflow-x:auto;margin:15px 0}
|
||||
table{width:100%;border-collapse:collapse;font-size:.92rem}
|
||||
th{background:var(--bsn-blue);color:white;padding:10px 14px;text-align:left;font-weight:600}
|
||||
td{padding:10px 14px;border-bottom:1px solid var(--border)}
|
||||
tr:nth-child(even) td{background:var(--bg-card)}
|
||||
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin:15px 0}
|
||||
.card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);padding:20px}
|
||||
.card h4{color:var(--bsn-blue);margin-top:0;margin-bottom:8px}
|
||||
.principle-box{background:linear-gradient(135deg,#f0f0ff,#e8e8ff);border-left:4px solid var(--bsn-blue);padding:20px 24px;border-radius:0 var(--radius) var(--radius) 0;margin:20px 0}
|
||||
.principle-box h4{color:var(--bsn-blue);margin-top:0}
|
||||
.warning-box{background:#fff8e1;border-left:4px solid var(--warning);padding:16px 20px;border-radius:0 var(--radius) var(--radius) 0;margin:15px 0}
|
||||
.danger-box{background:#fff0f0;border-left:4px solid var(--danger);padding:16px 20px;border-radius:0 var(--radius) var(--radius) 0;margin:15px 0}
|
||||
.info-box{background:#e8f4fd;border-left:4px solid var(--bsn-blue-light);padding:16px 20px;border-radius:0 var(--radius) var(--radius) 0;margin:15px 0}
|
||||
.fixed-box{background:#f0fff4;border-left:4px solid var(--success);padding:16px 20px;border-radius:0 var(--radius) var(--radius) 0;margin:15px 0}
|
||||
.checklist{list-style:none;padding:0}
|
||||
.checklist li{padding:6px 0 6px 28px;position:relative}
|
||||
.checklist li::before{content:'☐';position:absolute;left:0;color:var(--bsn-blue);font-size:1.1rem}
|
||||
.footer{text-align:center;padding:40px 20px;color:var(--text-secondary);font-size:.9rem}
|
||||
.footer a{color:var(--bsn-blue)}
|
||||
.tool-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:15px;margin:20px 0}
|
||||
.tool-card{border:2px solid var(--border);border-radius:var(--radius);padding:20px;text-align:center;transition:border-color .2s,transform .2s}
|
||||
.tool-card:hover{border-color:var(--bsn-blue);transform:translateY(-2px)}
|
||||
.tool-card .tool-icon{font-size:2rem;margin-bottom:10px}
|
||||
.tool-card h4{color:var(--bsn-blue);margin:5px 0}
|
||||
.tool-card p{font-size:.85rem;color:var(--text-secondary)}
|
||||
.changelog{font-size:.85rem;color:var(--text-secondary);margin-top:5px}
|
||||
.changelog li{margin-bottom:3px}
|
||||
.changelog .fix{color:#155724;font-weight:600}
|
||||
@media print{body{background:white}.header{background:var(--bsn-blue)!important;-webkit-print-color-adjust:exact}section{break-inside:avoid;box-shadow:none}pre{background:#f5f5f5;color:#333}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p class="meta">21. Juni 2026</p>
|
||||
|
||||
<div class="header">
|
||||
<span class="status-badge">🔒 VERBINDLICH · v1.0</span><span class="review-badge">✅ EXTERN GEPRÜFT</span>
|
||||
<h1>BSN Coding Standards</h1>
|
||||
<p class="subtitle">
|
||||
Das verbindliche Regelwerk für sämtlichen Python-Code im
|
||||
Brettspiel-News-Community-Projekt. Basierend auf PEP 8, Google Python Style Guide
|
||||
und dem modernen Tooling-Konsens 2026. Extern reviewed und korrigiert.
|
||||
</p>
|
||||
<div class="meta">
|
||||
<span>📅 21. Juni 2026</span>
|
||||
<span>🐍 Python 3.13+</span>
|
||||
<span>🔧 Ruff 0.15 + mypy 2.1</span>
|
||||
<span>🇩🇪 Deutsche Docstrings</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<nav class="toc">
|
||||
<h2>📑 Inhaltsverzeichnis</h2>
|
||||
<ol>
|
||||
<li><a href="#s1">Allgemeine Prinzipien</a></li>
|
||||
<li><a href="#s2">Code-Stil (PEP 8+)</a></li>
|
||||
<li><a href="#s3">Typ-Annotationen</a></li>
|
||||
<li><a href="#s4">Namenskonventionen</a></li>
|
||||
<li><a href="#s5">Kommentare & Docstrings</a></li>
|
||||
<li><a href="#s6">Projektstruktur & Architektur</a></li>
|
||||
<li><a href="#s7">Sicherheit</a></li>
|
||||
<li><a href="#s8">Testing</a></li>
|
||||
<li><a href="#s9">Flask-spezifische Regeln</a></li>
|
||||
<li><a href="#s10">Git & Commits</a></li>
|
||||
<li><a href="#s11">Toolchain & Automatische Durchsetzung</a></li>
|
||||
<li><a href="#s12">Review-Checkliste</a></li>
|
||||
<li><a href="#s13">Konfigurationsdateien (Referenz)</a></li>
|
||||
<li><a href="#s14">Änderungshistorie (Review-Fixes)</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 1. ALLGEMEINE PRINZIPIEN -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s1">
|
||||
<h2><span class="num">1</span> Allgemeine Prinzipien</h2>
|
||||
<div class="principle-box">
|
||||
<h4>„Readability counts." — Tim Peters, Zen of Python</h4>
|
||||
<p>Code wird 10× öfter gelesen als geschrieben. Jede Zeile muss
|
||||
für einen anderen Entwickler (oder dein zukünftiges Ich) sofort
|
||||
verständlich sein.</p>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<div class="card"><h4>📖 Lesbarkeit</h4><p>Code ist Kommunikation. Unklares wird umgeschrieben — nicht mit Kommentar „erklärt".</p></div>
|
||||
<div class="card"><h4>🎯 Explizit</h4><p>Kein Magic. Typen, Parameter, Rückgabewerte — alles sichtbar.</p></div>
|
||||
<div class="card"><h4>🧩 Einfach</h4><p>YAGNI — keine Abstraktionen für hypothetische Zukunft.</p></div>
|
||||
<div class="card"><h4>🛡️ Sichtbare Fehler</h4><p>Exceptions nie still schlucken. Spezifisch fangen + loggen.</p></div>
|
||||
</div>
|
||||
<div class="warning-box">
|
||||
<strong>⚠️ Konsistenz-Regel (aus PEP 8):</strong><br>
|
||||
Konsistenz mit diesem Styleguide ist wichtig. Konsistenz <em>innerhalb des Projekts</em> ist wichtiger.
|
||||
Konsistenz <em>innerhalb eines Moduls</em> ist am wichtigsten.<br><br>
|
||||
Regelbruch? Dokumentiere WARUM im Kommentar.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 2. CODE-STIL -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s2">
|
||||
<h2><span class="num">2</span> Code-Stil (PEP 8+)</h2>
|
||||
<p><strong>Durchsetzung:</strong> <code>ruff check</code> + <code>ruff format</code> <span class="badge-auto">AUTO</span></p>
|
||||
|
||||
<h3>2.1 Zeilenlänge</h3>
|
||||
<p><strong>Maximal 100 Zeichen.</strong> 88 (Black-Default) ist zu knapp für Flask-Routen und SQLAlchemy-Queries.</p>
|
||||
|
||||
<h3>2.2 Einrückung</h3>
|
||||
<ul>
|
||||
<li><strong>4 Leerzeichen</strong> pro Ebene — <span class="badge-wrong">NIE Tabs</span></li>
|
||||
<li>Fortsetzungszeilen: doppelte Einrückung (8) oder vertikale Ausrichtung</li>
|
||||
</ul>
|
||||
<pre><code><span class="cm"># ✅ Richtig — vertikale Ausrichtung</span>
|
||||
result = some_function(
|
||||
argument_one,
|
||||
argument_two,
|
||||
argument_three,
|
||||
)
|
||||
|
||||
<span class="cm"># ✅ Richtig — doppelte Einrückung bei Bedingungen</span>
|
||||
<span class="kw">if</span> (some_very_long_condition
|
||||
<span class="kw">and</span> another_long_condition):
|
||||
do_something()</code></pre>
|
||||
|
||||
<h3>2.3 Quotes</h3>
|
||||
<ul>
|
||||
<li><strong>Doppelte Quotes</strong> (<code>"</code>) für Strings</li>
|
||||
<li>Einfache nur, wenn der String <code>"</code> enthält</li>
|
||||
<li>Docstrings: <strong>immer</strong> <code>"""triple double"""</code></li>
|
||||
</ul>
|
||||
<pre><code><span class="kw">def</span> <span class="fn">get_games</span>() -> <span class="kw">list</span>[<span class="kw">dict</span>[<span class="kw">str</span>, <span class="kw">object</span>]]:
|
||||
<span class="str">"""Holt alle Spiele aus der Datenbank."""</span>
|
||||
...</code></pre>
|
||||
|
||||
<h3>2.4 Imports</h3>
|
||||
<p><strong>Reihenfolge</strong> (automatisch): 1. Standard-Library 2. Drittanbieter 3. Projekt-intern</p>
|
||||
<div class="danger-box"><strong>🚫 Verboten:</strong> <code>from module import *</code> · Zirkuläre Imports</div>
|
||||
|
||||
<h3>2.5 Trailing Commas</h3>
|
||||
<p><strong>Immer</strong> bei mehrzeiligen Collections.</p>
|
||||
<pre><code>items = [<span class="str">"Brettspiel"</span>, <span class="str">"Kartenspiel"</span>, <span class="str">"Würfelspiel"</span>,]</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 3. TYP-ANNOTATIONEN (FIXED) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s3">
|
||||
<h2><span class="num">3</span> Typ-Annotationen</h2>
|
||||
<p><strong>Durchsetzung:</strong> <code>mypy --strict</code> <span class="badge-auto">AUTO</span></p>
|
||||
|
||||
<div class="principle-box">
|
||||
<h4>Grundregel</h4>
|
||||
<p><strong>Jede Funktion und Methode MUSS vollständige Typ-Annotationen haben.</strong>
|
||||
Keine Ausnahmen.</p>
|
||||
</div>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix #1:</strong> Die ursprüngliche <code>Optional[str]</code>-Regel wurde umgedreht.
|
||||
Bei <code>requires-python = ">=3.13"</code> ist <strong><code>X | None</code></strong> (PEP 604)
|
||||
die idiomatische, empfohlene Form — und Ruff (UP045) schreibt <code>Optional[X]</code>
|
||||
automatisch zu <code>X | None</code> um. Regel und Linter sind jetzt im Einklang.
|
||||
</div>
|
||||
|
||||
<pre><code><span class="cm"># ✅ Richtig — X | None (PEP 604, idiomatisch für 3.13+)</span>
|
||||
<span class="kw">def</span> <span class="fn">get_game_by_id</span>(game_id: <span class="kw">int</span>) -> <span class="kw">dict</span>[<span class="kw">str</span>, <span class="kw">str</span> | <span class="kw">int</span>] | <span class="kw">None</span>:
|
||||
<span class="str">"""Holt ein Spiel anhand seiner ID."""</span>
|
||||
...
|
||||
|
||||
<span class="cm"># ❌ Falsch — keine Typen</span>
|
||||
<span class="kw">def</span> <span class="fn">get_game_by_id</span>(game_id):
|
||||
...</code></pre>
|
||||
|
||||
<h3>3.1 Spezifische Typ-Regeln</h3>
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Regel</th><th>✅ Richtig</th><th>❌ Falsch</th></tr>
|
||||
<tr><td>Nullable mit <code>X | None</code></td><td><code>str | None</code></td><td><code>Optional[str]</code></td></tr>
|
||||
<tr><td>Built-in Generics (3.9+)</td><td><code>list[dict[str, object]]</code></td><td><code>List[Dict]</code></td></tr>
|
||||
<tr><td>Generics parametrisieren</td><td><code>dict[str, Any]</code></td><td><code>dict</code> (implizites <code>dict[Any, Any]</code>)</td></tr>
|
||||
<tr><td>Explizites <code>-> None</code></td><td><code>def log(msg: str) -> None:</code></td><td><code>def log(msg: str):</code></td></tr>
|
||||
</table></div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>💡 Warum <code>X | None</code> und nicht <code>Optional[X]</code>?</strong><br>
|
||||
Seit <strong>Python 3.10 / PEP 604</strong> ist <code>X | None</code> der idiomatische Weg.
|
||||
<code>from typing import Optional</code> ist ein Relikt aus Python 3.9 und früher.
|
||||
Ruff (unter <code>UP045</code>) wandelt <code>Optional[X]</code> automatisch in <code>X | None</code> um —
|
||||
diesen Auto-Fix nutzen wir aktiv (<code>fixable = ["ALL"]</code>).
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 4. NAMENSKONVENTIONEN -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s4">
|
||||
<h2><span class="num">4</span> Namenskonventionen</h2>
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Element</th><th>Konvention</th><th>Beispiel</th></tr>
|
||||
<tr><td>Module / Packages</td><td><code>snake_case</code></td><td><code>game_service.py</code></td></tr>
|
||||
<tr><td>Klassen</td><td><code>PascalCase</code></td><td><code>GameRepository</code></td></tr>
|
||||
<tr><td>Funktionen / Methoden</td><td><code>snake_case</code></td><td><code>get_game_by_id()</code></td></tr>
|
||||
<tr><td>Variablen</td><td><code>snake_case</code></td><td><code>game_count</code></td></tr>
|
||||
<tr><td>Konstanten</td><td><code>UPPER_SNAKE_CASE</code></td><td><code>MAX_RESULTS = 50</code></td></tr>
|
||||
<tr><td>Private (intern)</td><td><code>_leading_underscore</code></td><td><code>def _parse_response():</code></td></tr>
|
||||
</table></div>
|
||||
<h3>Zusätzlich</h3>
|
||||
<ul>
|
||||
<li>Boolesche: <code>is_active</code>, <code>has_permission</code>, <code>should_retry</code></li>
|
||||
<li>Funktionen als Verben: <code>get_</code>, <code>create_</code>, <code>validate_</code></li>
|
||||
<li><span class="badge-wrong">Keine</span> Ein-Buchstaben-Variablen (außer Comprehensions)</li>
|
||||
<li><span class="badge-wrong">Keine</span> kryptischen Abkürzungen</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 5. KOMMENTARE -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s5">
|
||||
<h2><span class="num">5</span> Kommentare & Docstrings</h2>
|
||||
<div class="principle-box">
|
||||
<h4>🇩🇪 Alle Kommentare und Docstrings auf DEUTSCH</h4>
|
||||
<p>Code-Schlüsselwörter sind Englisch. Alle menschenlesbaren Erklärungen: Deutsch.</p>
|
||||
</div>
|
||||
<h3>Google-Style Docstrings</h3>
|
||||
<pre><code><span class="kw">def</span> <span class="fn">berechne_durchschnitt</span>(werte: <span class="kw">list</span>[<span class="kw">float</span>]) -> <span class="kw">float</span>:
|
||||
<span class="str">"""Berechnet den arithmetischen Durchschnitt.
|
||||
|
||||
Args:
|
||||
werte: Liste von Gleitkommazahlen.
|
||||
|
||||
Returns:
|
||||
Der Durchschnittswert.
|
||||
|
||||
Raises:
|
||||
ValueError: Wenn die Liste leer ist.
|
||||
"""</span>
|
||||
<span class="kw">if not</span> werte:
|
||||
<span class="kw">raise</span> <span class="kw">ValueError</span>(<span class="str">"Die Werteliste darf nicht leer sein."</span>)
|
||||
<span class="kw">return</span> sum(werte) / len(werte)</code></pre>
|
||||
<div class="danger-box"><strong>🚫 Auskommentierter Code:</strong> Nie! Dafür gibt's Git History.</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 6. PROJEKTSTRUKTUR (FIXED — Automatik-Status) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s6">
|
||||
<h2><span class="num">6</span> Projektstruktur & Architektur</h2>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix #4:</strong> Die „max 500 Zeilen / 50 Zeilen"-Grenzen sind
|
||||
jetzt <strong>teil-automatisiert</strong>: <code>C901</code> (McCabe-Komplexität) und
|
||||
<code>PLR0915</code> (too-many-statements, Default 50) wurden in Ruff aktiviert.
|
||||
Dateigrößen-Limits bleiben manuell — Ruff hat keinen „lines per file"-Checker.
|
||||
Die Tabelle unten zeigt klar, was automatisch und was manuell geprüft wird.
|
||||
</div>
|
||||
|
||||
<h3>6.1 Maximalgrößen</h3>
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Einheit</th><th>Maximum</th><th>Prüfung</th></tr>
|
||||
<tr><td>Modul (.py-Datei)</td><td><strong>500 Zeilen</strong></td><td><span class="badge-manual">MANUELL</span> (Review)</td></tr>
|
||||
<tr><td>Funktion / Methode</td><td><strong>50 Statements</strong></td><td><span class="badge-auto">AUTO</span> (Ruff PLR0915)</td></tr>
|
||||
<tr><td>Komplexität (McCabe)</td><td><strong>max 10</strong></td><td><span class="badge-auto">AUTO</span> (Ruff C901)</td></tr>
|
||||
<tr><td>Klasse</td><td><strong>300 Zeilen</strong></td><td><span class="badge-manual">MANUELL</span> (Review)</td></tr>
|
||||
<tr><td>Zeile</td><td><strong>100 Zeichen</strong></td><td><span class="badge-auto">AUTO</span> (Ruff E501)</td></tr>
|
||||
</table></div>
|
||||
|
||||
<h3>6.2 Verzeichnisstruktur</h3>
|
||||
<pre><code>project/
|
||||
├── app.py <span class="cm"># Flask-App-Factory + Config</span>
|
||||
├── routes/ <span class="cm"># NUR Request-Handling — keine Business-Logik</span>
|
||||
├── services/ <span class="cm"># Business-Logik (Validierung, Berechnung, DB)</span>
|
||||
├── models/ <span class="cm"># DB-Modelle — nur Schema, keine Logik</span>
|
||||
├── utils/ <span class="cm"># Pure Functions — kein Zustand, keine DB</span>
|
||||
├── templates/ <span class="cm"># Jinja2 — CSS IMMER inline</span>
|
||||
├── static/ <span class="cm"># JS, Bilder</span>
|
||||
├── tests/ <span class="cm"># Spiegelt src/-Struktur</span>
|
||||
├── migrations/ <span class="cm"># Alembic</span>
|
||||
├── pyproject.toml <span class="cm"># ALLE Konfiguration an einem Ort</span>
|
||||
├── .pre-commit-config.yaml
|
||||
└── CODING_STANDARDS.md</code></pre>
|
||||
|
||||
<h3>6.3 Architekturprinzipien</h3>
|
||||
<div class="card-grid">
|
||||
<div class="card"><h4>🚫 Keine God-Files</h4><p><code>app.py</code> ≠ 6000 Zeilen. Auslagern!</p></div>
|
||||
<div class="card"><h4>🧠 Routes sind dumm</h4><p>Request → Service → Response. Nichts weiter.</p></div>
|
||||
<div class="card"><h4>⚙️ Services = Logik</h4><p>Validierung, Berechnung, DB-Zugriffe.</p></div>
|
||||
<div class="card"><h4>📦 Models = Schema</h4><p>Tabellen-Definition. Keine Business-Logik.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 7. SICHERHEIT (FIXED — B101) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s7">
|
||||
<h2><span class="num">7</span> Sicherheit</h2>
|
||||
<p><strong>Durchsetzung:</strong> <code>ruff</code> (S-Regeln = flake8-bandit) <span class="badge-auto">AUTO</span></p>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix #2+3:</strong> Bandit als separater pre-commit-Hook wurde
|
||||
<strong>entfernt</strong> — Ruffs <code>S</code>-Regeln decken dieselben Checks ab, nur schneller,
|
||||
ohne separates Tool. Der <strong>B101-Skip wurde entfernt</strong>: asserts werden jetzt
|
||||
global geprüft, nur in <code>tests/**/*.py</code> per <code>per-file-ignores</code> erlaubt.
|
||||
Die assert-Regel aus 7.3 wird damit tatsächlich durchgesetzt.
|
||||
</div>
|
||||
|
||||
<h3>7.1 Secrets</h3>
|
||||
<div class="danger-box">
|
||||
<strong>🚫 NIEMALS Secrets im Code!</strong> API-Keys, Passwörter, Tokens → immer
|
||||
Environment Variables. <code>.env</code> + <code>.db</code> in <code>.gitignore</code>.
|
||||
</div>
|
||||
|
||||
<h3>7.2 Input-Validierung</h3>
|
||||
<ul>
|
||||
<li><strong>Alle User-Inputs validieren</strong> vor Verarbeitung</li>
|
||||
<li>SQL-Injection: Parameterisierte Queries (SQLAlchemy Default)</li>
|
||||
<li>XSS: User-Input escapen (Jinja2 Default)</li>
|
||||
</ul>
|
||||
|
||||
<h3>7.3 Weitere Regeln</h3>
|
||||
<ul>
|
||||
<li><span class="badge-wrong">Keine</span> <code>assert</code>-Statements in Produktionscode <span class="badge-auto">AUTO</span></li>
|
||||
<li><span class="badge-wrong">Kein</span> <code>pickle</code> für ungetrustete Daten</li>
|
||||
<li><code>subprocess</code> mit <code>shell=False</code> (Default)</li>
|
||||
<li>Passwörter: <code>bcrypt</code> oder <code>argon2</code> — nie <code>md5</code>/<code>sha1</code></li>
|
||||
<li>HTTPS überall — HSTS-Header setzen</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 8. TESTING (FIXED — TDD→Empfehlung) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s8">
|
||||
<h2><span class="num">8</span> Testing</h2>
|
||||
<p><strong>Durchsetzung:</strong> <code>pytest --cov --cov-fail-under=80</code> <span class="badge-manual">CI</span> (nicht in pre-commit)</p>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix (#6):</strong> TDD („erst Test, dann Code") ist jetzt eine
|
||||
<strong>Empfehlung</strong>, keine verbindliche Regel. Echte TDD-Pflicht wäre
|
||||
inkonsequent bei 80% Coverage-Floor. Der Floor bleibt hart bei 80%.
|
||||
</div>
|
||||
|
||||
<h3>8.1 Grundregeln</h3>
|
||||
<ol>
|
||||
<li><strong>80% Code Coverage</strong> als harter Floor — unterschreiten blockiert den Merge in CI</li>
|
||||
<li><strong>Empfohlen:</strong> TDD (Test-Driven Development) — erst Test, dann Code</li>
|
||||
<li><code>pytest</code> als einziges Test-Framework</li>
|
||||
<li>Tests in <code>tests/</code>, Struktur spiegelt <code>src/</code></li>
|
||||
</ol>
|
||||
|
||||
<h3>8.2 Test-Beispiel</h3>
|
||||
<pre><code><span class="kw">class</span> <span class="fn">TestGameService</span>:
|
||||
<span class="kw">def</span> <span class="fn">test_get_by_id_returns_game_when_found</span>(<span class="kw">self</span>, db_session):
|
||||
<span class="cm"># Arrange</span>
|
||||
service = GameService(db_session)
|
||||
<span class="cm"># Act</span>
|
||||
result = service.get_by_id(<span class="num">1</span>)
|
||||
<span class="cm"># Assert</span>
|
||||
<span class="kw">assert</span> result <span class="kw">is not None</span>
|
||||
<span class="kw">assert</span> result.name == <span class="str">"Catan"</span></code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 9. FLASK (FIXED — dict-Typ) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s9">
|
||||
<h2><span class="num">9</span> Flask-spezifische Regeln</h2>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix (#6):</strong> <code>def search_games() -> dict:</code> wurde
|
||||
zu <code>-> dict[str, object]</code> korrigiert. <code>dict</code> ohne Parameter
|
||||
ist <code>dict[Any, Any]</code> — das verstößt gegen mypy strict.
|
||||
</div>
|
||||
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Regel</th><th>✅ Richtig</th><th>❌ Falsch</th></tr>
|
||||
<tr><td><code>methods</code> explizit</td><td><code>@app.route("/api/games", methods=["GET"])</code></td><td>ohne methods</td></tr>
|
||||
<tr><td>POST = <code>get_json()</code></td><td><code>data = request.get_json(silent=True)</code> ← None bei Parse-Fehler!</td><td><code>request.args</code></td></tr>
|
||||
<tr><td>Response + Statuscode</td><td><code>return data, 201</code></td><td><code>return data</code></td></tr>
|
||||
<tr><td>Fehler einheitlich</td><td><code>{"error": "msg"}, 400</code></td><td>plain string</td></tr>
|
||||
<tr><td>Generics immer parametrisiert</td><td><code>-> tuple[dict[str, object], int]</code></td><td><code>-> tuple[dict, int]</code></td></tr>
|
||||
</table></div>
|
||||
|
||||
<pre><code><span class="cm"># ✅ Route — korrekte Typen, methods explizit</span>
|
||||
<span class="kw">@app</span>.route(<span class="str">"/api/search"</span>, methods=[<span class="str">"GET"</span>])
|
||||
<span class="kw">def</span> <span class="fn">search_games</span>() -> <span class="kw">tuple</span>[<span class="kw">dict</span>[<span class="kw">str</span>, <span class="kw">object</span>], <span class="kw">int</span>]:
|
||||
query = request.args.get(<span class="str">"q"</span>, <span class="str">""</span>).strip()
|
||||
<span class="kw">if not</span> query <span class="kw">or</span> len(query) > <span class="num">100</span>:
|
||||
<span class="kw">return</span> {<span class="str">"error"</span>: <span class="str">"Ungültiger Suchbegriff"</span>}, <span class="num">400</span>
|
||||
<span class="kw">return</span> game_service.search(query), <span class="num">200</span></code></pre>
|
||||
|
||||
<h3>Config & UI</h3>
|
||||
<ul>
|
||||
<li><code>app.config.from_prefixed_env()</code> — Secrets nie hartkodieren</li>
|
||||
<li>CSS <strong>immer inline</strong> in Templates</li>
|
||||
<li>Mobile-first, Primärfarbe <strong style="color:#20228a;">BSN-Blau #20228a</strong></li>
|
||||
<li>Touch-Buttons min 64px, Overlays min 85vh</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 10. GIT -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s10">
|
||||
<h2><span class="num">10</span> Git & Commits</h2>
|
||||
<h3>Conventional Commits</h3>
|
||||
<pre><code><type>(<scope>): <description></code></pre>
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Typ</th><th>Verwendung</th></tr>
|
||||
<tr><td><code>feat</code></td><td>Neues Feature</td></tr>
|
||||
<tr><td><code>fix</code></td><td>Bugfix</td></tr>
|
||||
<tr><td><code>docs</code></td><td>Dokumentation</td></tr>
|
||||
<tr><td><code>style</code></td><td>Formatierung (kein Code-Change)</td></tr>
|
||||
<tr><td><code>refactor</code></td><td>Code-Umbau</td></tr>
|
||||
<tr><td><code>test</code></td><td>Tests</td></tr>
|
||||
<tr><td><code>chore</code></td><td>Build, Deps, CI</td></tr>
|
||||
<tr><td><code>security</code></td><td>Sicherheitsrelevante Änderung</td></tr>
|
||||
</table></div>
|
||||
<h3>Commit-Hygiene</h3>
|
||||
<ul>
|
||||
<li>Nach <strong>jeder abgeschlossenen Aufgabe</strong> committen</li>
|
||||
<li>Ein Commit = eine logische Änderung</li>
|
||||
<li><strong>Vor jedem Commit:</strong> <code>pre-commit run</code> + <code>pytest</code></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 11. TOOLCHAIN (FIXED — mypy raus, Bandit raus) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s11">
|
||||
<h2><span class="num">11</span> Toolchain & Automatische Durchsetzung</h2>
|
||||
|
||||
<div class="fixed-box">
|
||||
<strong>🔧 Review-Fix #5:</strong> <strong>mypy wurde aus pre-commit entfernt.</strong>
|
||||
mypy im isolierten pre-commit-venv driftet garantiert vom echten Projekt-Environment weg
|
||||
(Stubs, Abhängigkeiten) und produziert andere Ergebnisse als <code>mypy .</code> im
|
||||
Projekt-venv. Stattdessen: <strong>mypy läuft lokal</strong> (Schritt 4 im Workflow)
|
||||
und <strong>in CI</strong> gegen das echte venv.<br><br>
|
||||
<strong>🔧 Review-Fix #3:</strong> <strong>Bandit wurde als separater Hook gestrichen.</strong>
|
||||
Ruffs <code>S</code>-Regeln (flake8-bandit) decken dieselben Sicherheits-Checks ab
|
||||
— ein Tool weniger, keine widersprüchlichen Findings.
|
||||
</div>
|
||||
|
||||
<div class="tool-grid">
|
||||
<div class="tool-card"><div class="tool-icon">🦀</div><h4>Ruff 0.15</h4><p>Linting + Formatierung<br>10-100× schneller als Flake8+Black</p></div>
|
||||
<div class="tool-card"><div class="tool-icon">🔍</div><h4>mypy 2.1</h4><p>Statische Typ-Prüfung<br>⚠️ Major v2 (Mai 2026) — bei Problemen 1.20</p></div>
|
||||
<div class="tool-card"><div class="tool-icon">🪝</div><h4>pre-commit</h4><p>Git-Hooks<br>Blockiert Commits bei Verstößen</p></div>
|
||||
<div class="tool-card"><div class="tool-icon">🧪</div><h4>pytest</h4><p>Testing + Coverage<br>80% Coverage minimum</p></div>
|
||||
</div>
|
||||
|
||||
<div class="warning-box">
|
||||
<strong>⚠️ mypy 2.1 — Breaking Changes (Mai 2026):</strong> Version 2.x ist sechs Wochen alt.
|
||||
Mit <code>strict = true</code> kann Code, der unter 1.x sauber lief, neue Fehler werfen
|
||||
(<code>--allow-redefinition</code>-Semantik, PEP 688 bytearray/memoryview).
|
||||
Bei Projektstart bewusst entscheiden: Entweder 2.1 nehmen (stricter) oder auf
|
||||
<strong>1.20</strong> bleiben (stabil) und nach dem ersten Milestone upgraden.
|
||||
</div>
|
||||
|
||||
<h3>11.1 Täglicher Workflow</h3>
|
||||
<pre><code><span class="cm"># 1. Einmalig: Tools installieren</span>
|
||||
pip install ruff mypy pre-commit pytest
|
||||
|
||||
<span class="cm"># 2. Einmalig: Hooks aktivieren</span>
|
||||
pre-commit install
|
||||
|
||||
<span class="cm"># 3. Beim Coden: Formatieren + Linten (auto-fix!)</span>
|
||||
ruff check . --fix
|
||||
ruff format .
|
||||
|
||||
<span class="cm"># 4. Typ-Prüfung (im Projekt-venv, NICHT in pre-commit)</span>
|
||||
mypy .
|
||||
|
||||
<span class="cm"># 5. Tests ausführen</span>
|
||||
pytest --cov --cov-fail-under=80
|
||||
|
||||
<span class="cm"># 6. Commit — pre-commit prüft AUTOMATISCH</span>
|
||||
git commit -m <span class="str">"feat: neue Spiele-Suche"</span>
|
||||
<span class="cm"># → Ruff Lint + Format laufen automatisch</span>
|
||||
<span class="cm"># → Basic Checks (Merge-Konflikte, Whitespace, Debug-Statements)</span></code></pre>
|
||||
|
||||
<h3>11.2 Was wird automatisch geprüft?</h3>
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>Hook</th><th>Prüft</th><th>Blockiert bei</th></tr>
|
||||
<tr><td>Ruff (Lint)</td><td>Code-Stil, Fehler, Komplexität, Sicherheit</td><td>PEP-8-Verstößen, ungenutzten Imports, S-Regeln</td></tr>
|
||||
<tr><td>Ruff (Format)</td><td>Einrückung, Quotes, Zeilenlänge</td><td>Abweichender Formatierung</td></tr>
|
||||
<tr><td>Check-Merge-Conflict</td><td>Merge-Konflikte</td><td><code><<<<<<<</code> im Code</td></tr>
|
||||
<tr><td>Trailing-Whitespace</td><td>Whitespace am Zeilenende</td><td>Leerzeichen/Tabs am Zeilenende</td></tr>
|
||||
<tr><td>End-of-File-Fixer</td><td>Fehlende Newline am Dateiende</td><td>Dateien ohne abschließende Newline</td></tr>
|
||||
<tr><td>Debug-Statements</td><td><code>pdb</code>, <code>breakpoint()</code></td><td>Debug-Code im Commit</td></tr>
|
||||
<tr><td>Large Files</td><td>Dateigröße > 5 MB</td><td>Versehentliche Binaries</td></tr>
|
||||
</table></div>
|
||||
|
||||
<h3>11.3 Was wird NICHT automatisch geprüft?</h3>
|
||||
<p>Diese Checks sind <strong>manuell im Review</strong> durchzuführen oder laufen in CI:</p>
|
||||
<ul>
|
||||
<li><strong>mypy</strong> — läuft lokal + in CI gegen das echte venv (siehe Fix #5)</li>
|
||||
<li><strong>Dateigrößen-Limits</strong> (500 Zeilen/Modul, 300 Zeilen/Klasse) — manuelles Review</li>
|
||||
<li><strong>Coverage 80%</strong> — <code>pytest --cov</code> muss manuell ausgeführt werden (oder in CI)</li>
|
||||
</ul>
|
||||
|
||||
<h3>11.4 CI-Setup (später)</h3>
|
||||
<pre><code><span class="cm"># .gitea/workflows/quality.yml (Gitea Actions — GitHub-kompatibel)</span>
|
||||
- <span class="kw">name</span>: Lint + Security
|
||||
run: ruff check .
|
||||
- <span class="kw">name</span>: Type Check (im echten venv!)
|
||||
run: mypy .
|
||||
- <span class="kw">name</span>: Tests + Coverage
|
||||
run: pytest --cov --cov-fail-under=80</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 12. CHECKLISTE (FIXED — Auto/Manuell markiert) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s12">
|
||||
<h2><span class="num">12</span> Code-Review-Checkliste</h2>
|
||||
<p>Bei <strong>jedem Code-Review</strong> durchgehen. <span class="badge-auto">AUTO</span> = wird von pre-commit erzwungen.</p>
|
||||
|
||||
<ul class="checklist">
|
||||
<li><span class="badge-auto">AUTO</span> PEP 8 eingehalten? (Ruff-Check bestanden)</li>
|
||||
<li><span class="badge-auto">AUTO</span> Keine <code>from module import *</code>?</li>
|
||||
<li><span class="badge-auto">AUTO</span> Keine Debug-Statements (<code>pdb</code>, <code>breakpoint</code>)?</li>
|
||||
<li><span class="badge-auto">AUTO</span> Keine Merge-Konflikte im Code?</li>
|
||||
<li>Alle Funktionen haben vollständige Typ-Annotationen? (mypy bestanden)</li>
|
||||
<li>Docstrings auf Deutsch für alle öffentlichen Funktionen?</li>
|
||||
<li>Keine Secrets im Code?</li>
|
||||
<li>User-Input wird validiert?</li>
|
||||
<li>Tests geschrieben und grün? Coverage ≥ 80%?</li>
|
||||
<li>Keine God-Files (>500 Zeilen)? <span class="badge-manual">MANUELL</span></li>
|
||||
<li>Funktionen max 50 Statements, McCabe ≤ 10? <span class="badge-auto">AUTO</span></li>
|
||||
<li>Route enthält keine Business-Logik?</li>
|
||||
<li>Commit folgt Conventional Commits?</li>
|
||||
<li>Keine zirkulären Imports?</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 13. KONFIGURATIONSDATEIEN (FIXED) -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s13">
|
||||
<h2><span class="num">13</span> Konfigurationsdateien (Referenz)</h2>
|
||||
<p>Diese Dateien werden im Projekt-Root abgelegt. Alle Review-Fixes sind eingearbeitet.</p>
|
||||
|
||||
<h3>13.1 pyproject.toml</h3>
|
||||
<pre><code>[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bsn-community"
|
||||
version = "0.1.0"
|
||||
description = "Brettspiel-News Community-Projekt"
|
||||
requires-python = ">=3.13"
|
||||
readme = "README.md"
|
||||
license = {text = "Proprietary"}
|
||||
authors = [{name = "Daniel Krause", email = "info@brettspiel-news.de"}]
|
||||
|
||||
# ── Ruff: Linting + Formatierung + Sicherheit ──
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
indent-width = 4
|
||||
target-version = "py313"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"F", # Pyflakes
|
||||
"W", # pycodestyle warnings
|
||||
"I", # isort (Import-Ordnung)
|
||||
"N", # pep8-naming (Namenskonventionen)
|
||||
"UP", # pyupgrade (auto-upgrade auf 3.13-Syntax)
|
||||
"B", # flake8-bugbear (Common Bugs)
|
||||
"C4", # flake8-comprehensions
|
||||
"SIM", # flake8-simplify
|
||||
"TCH", # flake8-type-checking
|
||||
"RUF", # Ruff-spezifische Regeln
|
||||
"S", # flake8-bandit — Sicherheit (ersetzt separaten Bandit-Hook!)
|
||||
"D", # pydocstyle (Docstrings)
|
||||
"C90", # McCabe-Komplexität
|
||||
"PL", # Pylint-Regeln (too-many-*)
|
||||
]
|
||||
ignore = [
|
||||
"D100", # Modul-Docstring optional
|
||||
"D104", # Package-Docstring optional
|
||||
"D107", # __init__ Docstring optional
|
||||
"D203", # Konflikt mit D211
|
||||
"D213", # Konflikt mit D212
|
||||
]
|
||||
fixable = ["ALL"]
|
||||
|
||||
[tool.ruff.lint.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.ruff.lint.pylint]
|
||||
max-statements = 50 # PLR0915 — entspricht „max 50 Statements/Funktion"
|
||||
max-args = 7 # PLR0913
|
||||
max-branches = 15 # PLR0912
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**/*.py" = ["S101", "D103"] # asserts + fehlende Docstrings in Tests erlaubt
|
||||
"migrations/**/*.py" = ["D"] # Keine Docstring-Pflicht in Migrationen
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
|
||||
# ── mypy: Statische Typ-Prüfung ──
|
||||
[tool.mypy]
|
||||
strict = true
|
||||
python_version = "3.13"
|
||||
exclude = ["migrations/", "tests/"]
|
||||
warn_unreachable = true
|
||||
warn_unused_ignores = true
|
||||
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
|
||||
|
||||
# ── pytest: Testing ──
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = "test_*.py"
|
||||
python_classes = "Test*"
|
||||
python_functions = "test_*"
|
||||
addopts = [
|
||||
"--strict-markers",
|
||||
"--tb=short",
|
||||
"--cov=src",
|
||||
"--cov-report=term-missing",
|
||||
]
|
||||
markers = [
|
||||
"slow: Langsame Tests (Datenbank, API-Calls)",
|
||||
"integration: Integrationstests",
|
||||
]</code></pre>
|
||||
|
||||
<h3>13.2 .pre-commit-config.yaml</h3>
|
||||
<pre><code>repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.18
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: "🔍 Ruff — Linting + Sicherheit"
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
name: "🎨 Ruff — Formatierung"
|
||||
|
||||
# mypy läuft NICHT in pre-commit (isoliertes venv-Drift-Problem).
|
||||
# Stattdessen: manuell mit `mypy .` oder in CI gegen das echte venv.
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
name: "📦 Keine großen Dateien (>5 MB)"
|
||||
args: [--maxkb=5000]
|
||||
- id: check-merge-conflict
|
||||
name: "⚠️ Keine Merge-Konflikte"
|
||||
- id: trailing-whitespace
|
||||
name: "🧹 Kein Whitespace am Zeilenende"
|
||||
- id: end-of-file-fixer
|
||||
name: "📄 Dateien enden mit Newline"
|
||||
- id: check-yaml
|
||||
name: "📋 YAML-Syntax prüfen"
|
||||
- id: check-toml
|
||||
name: "📋 TOML-Syntax prüfen"
|
||||
- id: debug-statements
|
||||
name: "🐛 Keine Debug-Statements (pdb, breakpoint)"</code></pre>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<!-- 14. ÄNDERUNGSHISTORIE -->
|
||||
<!-- ═══════════════════════════════════════════ -->
|
||||
<section id="s14">
|
||||
<h2><span class="num">14</span> Änderungshistorie (Review-Fixes)</h2>
|
||||
|
||||
<p>Diese Version enthält alle Korrekturen aus dem externen Code-Review vom 21. Juni 2026.</p>
|
||||
|
||||
<div class="table-wrap"><table>
|
||||
<tr><th>#</th><th>Problem</th><th>Fix</th><th>Betroffene Sektionen</th></tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><strong>Optional[X] vs X | None</strong> — Regel widersprach sich selbst und kämpfte gegen RUFF (UP045)</td>
|
||||
<td><code>X | None</code> als Standard (PEP 604, idiomatisch für 3.13). <code>Optional</code> aus allen Beispielen entfernt. Warnkasten durch Info-Box mit Begründung ersetzt.</td>
|
||||
<td>§3, alle Code-Beispiele</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><strong>B101 global deaktiviert</strong> — „kein assert in Produktion" wurde nie durchgesetzt</td>
|
||||
<td>Globalen B101-Skip entfernt. Asserts nur in <code>tests/**/*.py</code> per <code>per-file-ignores</code> erlaubt.</td>
|
||||
<td>§7, §13 (pyproject.toml)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><strong>Bandit doppelt</strong> — separater Hook + Ruffs S-Regeln = Redundanz</td>
|
||||
<td>Bandit-Hook komplett gestrichen. Ruffs <code>S</code>-Regeln decken alles ab.</td>
|
||||
<td>§11, §13 (.pre-commit-config.yaml)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><strong>God-File/Function</strong> als „automatisch" behauptet, aber keine Regeln konfiguriert</td>
|
||||
<td><code>C90</code> + <code>PL</code> zu Ruff-Select hinzugefügt. McCabe max 10, Statements max 50. Dateigrößen-Limits klar als <span class="badge-manual">MANUELL</span> markiert.</td>
|
||||
<td>§6, §12, §13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><strong>mypy in pre-commit</strong> — isoliertes venv driftet vom Projekt-Environment</td>
|
||||
<td>mypy aus .pre-commit-config.yaml entfernt. Läuft stattdessen lokal (<code>mypy .</code>) und in CI.</td>
|
||||
<td>§11, §13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Kleinkram:</strong> TDD-Pflicht inkonsequent, <code>-> dict:</code> verstößt gegen mypy strict, Versions-Pins veraltet</td>
|
||||
<td>TDD → Empfehlung. <code>dict</code> → <code>dict[str, object]</code>. Ruff 0.11.3 → 0.15.18, mypy 1.15.0 → 2.1.0.</td>
|
||||
<td>§8, §9, §13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" style="background:var(--bsn-blue);color:white;text-align:center;font-weight:600">Zweite Review-Runde — 21. Juni 2026</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><strong>UP007 vs UP045</strong> — falsche Regel-Nummer in der Info-Box</td>
|
||||
<td><code>UP007</code> → <code>UP045</code> korrigiert. Seit Ruff 0.12: UP007 = typing.Union, UP045 = typing.Optional.</td>
|
||||
<td>§3, §14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td><strong>Coverage als AUTO gelabelt</strong> — pre-commit führt kein pytest aus</td>
|
||||
<td>„blockiert den Commit" → „blockiert den Merge in CI". Coverage-Label ehrlich als CI-Check (nicht pre-commit).</td>
|
||||
<td>§8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td><strong>mypy 2.1 Bleeding-Edge</strong> — Major v2 (Mai 2026) mit Breaking Changes</td>
|
||||
<td>Warnbox in §11 ergänzt. Empfehlung: Entweder 2.1 bewusst nehmen, oder auf 1.20 bleiben.</td>
|
||||
<td>§11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td><strong>CI-Pfad GitHub-flavored</strong> und <code>silent=True</code> None-Falle</td>
|
||||
<td><code>.github/</code> → <code>.gitea/</code>. <code>get_json(silent=True)</code> mit None-Hinweis versehen.</td>
|
||||
<td>§9, §11</td>
|
||||
</tr>
|
||||
</table></div>
|
||||
</section>
|
||||
|
||||
<div class="footer">
|
||||
<p>
|
||||
<strong>BSN Coding Standards v1.0</strong> — Extern reviewed ✅<br>
|
||||
Erstellt von Cody, korrigiert nach externem Review, für Daniel Krause ·
|
||||
<a href="https://brettspiel-news.de">brettspiel-news.de</a><br>
|
||||
Nächste Überprüfung: Bei Projektstart oder Dezember 2026
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user