From 814b3f45cb333de2bb122240065027b70461dd4f Mon Sep 17 00:00:00 2001 From: dschlueter Date: Mon, 22 Jun 2026 12:39:31 +0200 Subject: [PATCH] =?UTF-8?q?Tooling=20(Welle=20B):=20ruff-Linting=20einf?= =?UTF-8?q?=C3=BChren,=20Pre-commit-Hook=20h=C3=A4rten?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ruff.toml: Config (E/F/W, E501 bewusst ignoriert wegen langer deutscher Daten-/Log-Strings; alembic/versions ausgeschlossen) - requirements-dev.txt mit ruff==0.15.18 - .githooks/pre-commit: ruff-Linting vor den Tests; fragiles 'grep -q OK' durch echte Exit-Code-Prüfung ersetzt; Testaufruf auf 'unittest discover' vereinheitlicht - Lints behoben: ungenutzte Imports (datetime, Optional, request u.a.), f-strings ohne Platzhalter, ungenutzte Test-Variable; bewusster alembic-Verfügbarkeits-Check mit noqa markiert Co-Authored-By: Claude Sonnet 4.6 --- .githooks/pre-commit | 47 ++++++++++++++-------------- app.py | 2 +- models.py | 1 - requirements-dev.txt | 4 +++ routes/utils.py | 2 +- ruff.toml | 19 +++++++++++ services/helpers.py | 2 +- services/progress_service.py | 2 +- setup_database.py | 4 +-- tests/test_integration_statistics.py | 4 +-- 10 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 requirements-dev.txt create mode 100644 ruff.toml diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 99e0c9d..61862dc 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,46 +1,47 @@ #!/bin/bash # Pre-commit hook für Typewriter Trainer -# Führt automatisch Tests aus vor jedem Commit +# Führt Linting (ruff) und Tests vor jedem Commit aus. # # Installation: # cp .githooks/pre-commit .git/hooks/pre-commit # chmod +x .git/hooks/pre-commit -echo "🧪 Running tests before commit..." +set -u + +echo "🔎 Running pre-commit checks..." echo "" -# Farben für Output -RED='\033[0;31m' GREEN='\033[0;32m' +RED='\033[0;31m' YELLOW='\033[1;33m' -NC='\033[0m' # No Color +NC='\033[0m' -# Unit Tests ausführen (schnell) -echo "📋 Running Unit Tests..." -if python -m unittest tests.test_lesson_service tests.test_statistics_service tests.test_settings_service -v 2>&1 | grep -q "OK"; then - echo -e "${GREEN}✅ Unit Tests passed${NC}" +# 1. Linting mit ruff (falls installiert) +if python -m ruff --version >/dev/null 2>&1; then + echo "🧹 Running ruff..." + if ! python -m ruff check .; then + echo -e "${RED}❌ Linting failed${NC}" + echo "Bitte 'python -m ruff check .' beheben (ggf. '--fix')." + exit 1 + fi + echo -e "${GREEN}✅ Linting passed${NC}" else - echo -e "${RED}❌ Unit Tests failed${NC}" - echo "" - echo "Please fix the tests before committing." - exit 1 + echo -e "${YELLOW}⚠️ ruff nicht installiert – Linting übersprungen${NC}" + echo " Installation: pip install -r requirements-dev.txt" fi echo "" -# Integration Tests ausführen -echo "🔗 Running Integration Tests..." -if python -m unittest tests.test_integration_progress tests.test_integration_statistics -v 2>&1 | grep -q "OK"; then - echo -e "${GREEN}✅ Integration Tests passed${NC}" -else - echo -e "${RED}❌ Integration Tests failed${NC}" - echo "" - echo "Please fix the tests before committing." +# 2. Tests (Exit-Code-basiert, kein grep) +echo "🧪 Running tests..." +if ! python -m unittest discover -s tests -p "test_*.py"; then + echo -e "${RED}❌ Tests failed${NC}" + echo "Bitte die Tests vor dem Commit beheben." exit 1 fi +echo -e "${GREEN}✅ Tests passed${NC}" echo "" -echo -e "${GREEN}✅ All tests passed! Proceeding with commit...${NC}" +echo -e "${GREEN}✅ All checks passed! Proceeding with commit...${NC}" echo "" - exit 0 diff --git a/app.py b/app.py index 0974584..1b380a1 100644 --- a/app.py +++ b/app.py @@ -90,7 +90,7 @@ if __name__ == '__main__': logger.info("Tipptrainer gestartet") logger.info(f"Version: {VERSION}") logger.info(f"Lektionen geladen: {len(helpers.lessons)}") - logger.info(f"Debug-Info verfügbar unter: http://127.0.0.1:5000/debug") + logger.info("Debug-Info verfügbar unter: http://127.0.0.1:5000/debug") logger.info("=" * 50) app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/models.py b/models.py index c8818af..9fa82f3 100644 --- a/models.py +++ b/models.py @@ -1,5 +1,4 @@ from flask_sqlalchemy import SQLAlchemy -from datetime import datetime db = SQLAlchemy() diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..d5689d2 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +# Entwicklungs-Abhängigkeiten (nicht für den Produktivbetrieb nötig) +-r requirements.txt + +ruff==0.15.18 diff --git a/routes/utils.py b/routes/utils.py index 5df1f97..16647f2 100644 --- a/routes/utils.py +++ b/routes/utils.py @@ -8,7 +8,7 @@ import json import logging import threading import time -from flask import Blueprint, request, jsonify, current_app +from flask import Blueprint, jsonify, current_app from services import helpers from services.helpers import ( repair_json, diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..d2a0937 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,19 @@ +# Ruff-Konfiguration – https://docs.astral.sh/ruff/ +target-version = "py311" +line-length = 120 + +# Generierte / externe Verzeichnisse nicht prüfen +extend-exclude = ["venv", "alembic/versions"] + +[lint] +select = ["E", "F", "W"] +# Zeilenlänge nicht erzwingen: das Projekt enthält lange deutsche +# Daten-Strings (Lektionstexte) und Log-Meldungen, bei denen E501 nur +# Rauschen erzeugt. Logikfehler (E/F/W) bleiben aktiv. +ignore = ["E501"] + +[lint.per-file-ignores] +# Tests setzen FLASK_TESTING bewusst VOR dem App-Import (E402) +"tests/*" = ["E402"] +# app.py registriert Blueprints bewusst nach App-/CSRF-Setup (E402) +"app.py" = ["E402"] diff --git a/services/helpers.py b/services/helpers.py index 437297e..3ca6e79 100644 --- a/services/helpers.py +++ b/services/helpers.py @@ -74,7 +74,7 @@ def get_or_create_secret_key() -> str: # Setze Dateiberechtigungen auf 0o600 (nur User kann lesen/schreiben) os.chmod(env_path, 0o600) - logger.info(f"Neuer Secret Key generiert und in .env gespeichert (Berechtigungen: 0o600)") + logger.info("Neuer Secret Key generiert und in .env gespeichert (Berechtigungen: 0o600)") except Exception as e: logger.error(f"Fehler beim Speichern der .env Datei: {e}") logger.warning("Verwende temporären Secret Key für diese Session") diff --git a/services/progress_service.py b/services/progress_service.py index 48a5f73..2bd879c 100644 --- a/services/progress_service.py +++ b/services/progress_service.py @@ -4,7 +4,7 @@ Handles all progress and lesson progress related business logic """ import logging -from typing import Dict, Any, Optional +from typing import Dict, Any from models import db, Progress, LessonProgress logger = logging.getLogger(__name__) diff --git a/setup_database.py b/setup_database.py index 0972c86..feb341d 100644 --- a/setup_database.py +++ b/setup_database.py @@ -26,7 +26,7 @@ def main(): # Prüfe ob Alembic installiert ist try: - import alembic + import alembic # noqa: F401 # nur Verfügbarkeits-Check logger.info("✓ Alembic ist installiert") except ImportError: logger.error("✗ Alembic nicht gefunden!") @@ -36,7 +36,7 @@ def main(): # Prüfe ob instance/ Verzeichnis existiert instance_dir = os.path.join(os.path.dirname(__file__), 'instance') if not os.path.exists(instance_dir): - logger.info(f"Erstelle instance/ Verzeichnis...") + logger.info("Erstelle instance/ Verzeichnis...") os.makedirs(instance_dir) logger.info("✓ Verzeichnis erstellt") diff --git a/tests/test_integration_statistics.py b/tests/test_integration_statistics.py index 41408a8..591ab2b 100644 --- a/tests/test_integration_statistics.py +++ b/tests/test_integration_statistics.py @@ -5,7 +5,7 @@ import unittest import os os.environ['FLASK_TESTING'] = 'true' # Set before importing app -from datetime import datetime, timedelta, date +from datetime import timedelta, date from app import app, db from models import LessonStatistic, DailyPractice from services.statistics_service import StatisticsService @@ -281,7 +281,7 @@ class TestStatisticsServiceIntegration(unittest.TestCase): ) # Speichere in DB - saved_stat = StatisticsService.save_lesson_statistics( + StatisticsService.save_lesson_statistics( lesson_index=5, chars_per_minute=stats['typing_speed'], error_rate=stats['error_rate'],