Pipeline-Stufe 1 automatisiert: requirements, pre-commit-Hook, ruff
Nach Web-Recherche zum 2026er-Python-Standard umgesetzt, aber gefiltert: von
uv/Ruff/pyproject/CI passt hier nur, was zu einer Einzelmaschine mit
GUI-gebundenem MT5 und Live-Geld passt.
(1) requirements.txt (Laufzeit) + requirements-dev.txt (Werkzeuge/Messung),
Versionen gepinnt. Bis dahin gab es GAR KEINE Liste — nach einem
Maschinenausfall waere die Rekonstruktion Raten gewesen. Per AST ueber
core/, server.py, tools/ ermittelt statt geraten.
(2) tools/pre_commit.py faehrt vor jedem Commit check_nfalle + pytest (~5 s).
Einrichten: python tools/pre_commit.py --install. Notausgang
'git commit --no-verify' ist Absicht.
Bewusst OHNE das pre-commit-Framework: dessen Nutzen ist
Werkzeug-Versionierung ueber mehrere Maschinen. Hier gibt es eine, und es
wuerde eine ZWEITE ruff-Version in einer isolierten Umgebung installieren —
genau die Divergenz 'Pruefumgebung != Laufumgebung', die heute schon einmal
zugeschlagen hat.
(3) Ruff statt pyflakes (--select F821), ganzer Scan in 2,1 s. KEIN
ruff format — das wuerde 161 Dateien umformatieren, darunter alle
Backtests, und die Migrations-Regel verletzen.
⚠ VIERTER Fall der Klasse 'Erfolg melden, wo nichts geprueft wurde', und nur
durch den Test aufgefallen: --select F821,E999 laesst ruff 0.16 komplett
abbrechen (E999 wurde entfernt, Exit 2); die Meldung geht nach stderr, und weil
ich den Rueckgabecode nicht pruefte, meldete die Stufe 'keine undefinierten
Namen' — ohne irgendetwas geprueft zu haben. Ein absichtlich eingebauter
F821-Fehler kam glatt durch den Hook. Behoben: nur F821, und Exit 2 loest jetzt
den pyflakes-Rueckfall aus.
Beide Richtungen verifiziert: mit Fehler stoppt der Hook den Commit, ohne ist
er gruen. Der Test-Commit wurde zurueckgenommen (war nur lokal, ahead 1).
Nicht umgesetzt: Gitea Actions (Runner auf derselben Maschine wie der Live-Bot,
kein Mehrwert bei 5 s Pruefzeit), Container/Blue-Green (MT5 braucht eine
Desktop-Sitzung, EINE Broker-Verbindung), mypy/ty (kaum Annotationen).
Struktureller Fund: core/engine.py importiert backtest_breakout_squeeze — ein
Backtest ist Teil des LIVE-Abhaengigkeitsgraphen.
This commit is contained in:
+64
-21
@@ -19,6 +19,7 @@ Geprueft wird:
|
||||
import pathlib
|
||||
import py_compile
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
@@ -80,31 +81,73 @@ print(f" {npy} Dateien geprueft.")
|
||||
SCHWER = ("undefined name", "undefined local", "syntax error")
|
||||
print()
|
||||
print("=" * 88)
|
||||
print(" A2) PYFLAKES — undefinierte Namen (was py_compile nicht sieht)")
|
||||
print(" A2) LINTER — undefinierte Namen (was py_compile nicht sieht)")
|
||||
print("=" * 88)
|
||||
# ⚠ RUFF statt pyflakes (2026-08-07, Web-Recherche zum 2026er-Standard): gleiche
|
||||
# Befunde, deutlich schneller, ein Werkzeug statt vieler. Bewusst NUR
|
||||
# `ruff check` mit ENGER Regelauswahl — **kein `ruff format`**: das wuerde 161
|
||||
# Dateien umformatieren, darunter alle Backtests, und damit die Migrations-Regel
|
||||
# verletzen (deren Zahlen sind dokumentiert).
|
||||
# F821 = undefined name ← die Klasse, die hier real weh tut
|
||||
# E999 = syntax error
|
||||
# F811 (redefinition) bleibt DRAUSSEN, s. Begruendung bei SCHWER oben.
|
||||
# pyflakes bleibt als Rueckfall, damit die Pruefung auch ohne ruff laeuft.
|
||||
# ⚠⚠ NUR F821 — `E999` gibt es in ruff 0.16 NICHT MEHR. Wird es trotzdem
|
||||
# selektiert, bricht ruff KOMPLETT ab („Rule E999 was removed") mit Exit 2.
|
||||
# Genau das ist beim ersten Bau passiert, und schlimmer: der Rueckgabecode
|
||||
# wurde nicht geprueft, die Fehlermeldung ging nach stderr — die Stufe meldete
|
||||
# also „keine undefinierten Namen", waehrend sie GAR NICHTS geprueft hat. Ein
|
||||
# absichtlich eingebauter `F821`-Fehler kam glatt durch den Hook.
|
||||
# Syntaxfehler deckt ohnehin Stufe A (`py_compile`) ab.
|
||||
# ⚠ RUFF-EXIT-CODES: 0 = sauber · 1 = Befunde · 2 = Werkzeugfehler.
|
||||
# 2 darf NIEMALS als „sauber" durchgehen.
|
||||
_lint_ok = False
|
||||
try:
|
||||
import io
|
||||
|
||||
from pyflakes.api import checkPath
|
||||
from pyflakes.reporter import Reporter
|
||||
|
||||
aus, err = io.StringIO(), io.StringIO()
|
||||
rep = Reporter(aus, err)
|
||||
for p in dateien(".py"):
|
||||
checkPath(str(p), rep)
|
||||
zeilen = [z for z in (aus.getvalue() + err.getvalue()).splitlines() if z.strip()]
|
||||
schwer = [z for z in zeilen if any(s in z for s in SCHWER)]
|
||||
for z in schwer:
|
||||
r = subprocess.run([sys.executable, "-m", "ruff", "check",
|
||||
"--select", "F821", "--no-cache",
|
||||
"--output-format", "concise", str(WURZEL)],
|
||||
capture_output=True, text=True, timeout=180)
|
||||
if r.returncode not in (0, 1):
|
||||
print(f" ⚠ ruff meldet einen WERKZEUGFEHLER (Exit {r.returncode}) — "
|
||||
f"Rueckfall auf pyflakes:")
|
||||
for z in (r.stderr or r.stdout).strip().splitlines()[:3]:
|
||||
print(f" {z}")
|
||||
raise RuntimeError("ruff nicht nutzbar")
|
||||
_lint_ok = True
|
||||
treffer = [z for z in r.stdout.splitlines()
|
||||
if z.strip() and not z.startswith(("Found", "All checks", "warning:"))]
|
||||
for z in treffer:
|
||||
kurz = z.replace(str(WURZEL) + "\\", "").replace(str(WURZEL) + "/", "")
|
||||
befunde.append(f"PF {kurz}")
|
||||
befunde.append(f"RUFF {kurz}")
|
||||
print(f" FEHLER {kurz}")
|
||||
if not schwer:
|
||||
print(" keine undefinierten Namen.")
|
||||
print(f" ({len(zeilen) - len(schwer)} kosmetische Hinweise unterdrueckt — "
|
||||
f"ungenutzte Importe, f-Strings ohne Platzhalter)")
|
||||
except ImportError:
|
||||
print(" ⚠ pyflakes nicht installiert — Stufe uebersprungen.")
|
||||
print(" Installation: python -m pip install pyflakes")
|
||||
if not treffer:
|
||||
print(" keine undefinierten Namen (ruff, F821/E999).")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not _lint_ok:
|
||||
try:
|
||||
import io
|
||||
|
||||
from pyflakes.api import checkPath
|
||||
from pyflakes.reporter import Reporter
|
||||
|
||||
aus, err = io.StringIO(), io.StringIO()
|
||||
rep = Reporter(aus, err)
|
||||
for p in dateien(".py"):
|
||||
checkPath(str(p), rep)
|
||||
zeilen = [z for z in (aus.getvalue() + err.getvalue()).splitlines()
|
||||
if z.strip()]
|
||||
schwer = [z for z in zeilen if any(s in z for s in SCHWER)]
|
||||
for z in schwer:
|
||||
kurz = z.replace(str(WURZEL) + "\\", "").replace(str(WURZEL) + "/", "")
|
||||
befunde.append(f"PF {kurz}")
|
||||
print(f" FEHLER {kurz}")
|
||||
if not schwer:
|
||||
print(" keine undefinierten Namen (pyflakes-Rueckfall).")
|
||||
except ImportError:
|
||||
print(" ⚠ weder ruff noch pyflakes installiert — Stufe uebersprungen.")
|
||||
print(" Installation: python -m pip install -r requirements-dev.txt")
|
||||
|
||||
|
||||
# ── B) JavaScript ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user