Refactor (Welle C): create_app()-Factory einführen

- App-Aufbau in create_app(config) gekapselt; Blueprint-Importe und
  -Registrierung, CSRF, DB-Init, Pfade/Version dort.
- app = create_app() am Modulende -> 'from app import app, db' und die
  bestehenden 80 Tests bleiben unverändert grün.
- create_app(config) erlaubt isolierte Test-Apps (z.B. TESTING,
  In-Memory-DB) ohne den FLASK_TESTING-Env-Hack.
- DB-Check/Lektion-Laden in _load_lessons_or_exit() ausgelagert,
  weiterhin nur im Normalbetrieb.
- Obsoletes app.py-E402-Ignore aus ruff.toml entfernt (Importe jetzt
  funktionslokal).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-22 12:42:19 +02:00
commit c2663c0801
2 changed files with 59 additions and 51 deletions

108
app.py
View file

@ -17,54 +17,8 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
# Flask App erstellen
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///typewriter.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.secret_key = helpers.get_or_create_secret_key()
# CSRF-Schutz aktivieren
csrf = CSRFProtect(app)
# Datenbank initialisieren
db.init_app(app)
# Initialisiere Pfade für helpers
with app.app_context():
helpers.initialize_paths(app.root_path)
# VERSION aus helpers laden
VERSION = helpers.get_version()
# Globale Template-Variablen
@app.context_processor
def inject_globals():
"""Stellt globale Variablen für alle Templates bereit"""
return {'APP_VERSION': VERSION}
# Blueprints importieren und registrieren
from routes.main import main_bp
from routes.training import training_bp
from routes.statistics import statistics_bp
from routes.settings import settings_bp
from routes.session import session_bp
from routes.utils import utils_bp, shutdown
app.register_blueprint(main_bp)
app.register_blueprint(training_bp)
app.register_blueprint(statistics_bp)
app.register_blueprint(settings_bp)
app.register_blueprint(session_bp)
app.register_blueprint(utils_bp)
# CSRF-Ausnahme gezielt nur für die Shutdown-Route.
# /repair_json und /recreate_lessons bleiben damit CSRF-geschützt.
csrf.exempt(shutdown)
# Datenbank-Status prüfen und Lektionen laden (nur wenn nicht im Test-Modus)
if not app.config.get('TESTING', False) and os.environ.get('FLASK_TESTING') != 'true':
def _load_lessons_or_exit(app: Flask) -> None:
"""Prüft die Datenbank und lädt die Lektionen (nur im Normalbetrieb)."""
with app.app_context():
db_ready = helpers.check_database_status(db.engine)
@ -85,10 +39,66 @@ if not app.config.get('TESTING', False) and os.environ.get('FLASK_TESTING') != '
logger.info(f"{len(helpers.lessons)} Lektionen geladen")
def create_app(config: dict | None = None) -> Flask:
"""Application-Factory: erstellt und konfiguriert die Flask-App.
Args:
config: Optionale Config-Overrides (z.B. für Tests).
"""
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///typewriter.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.secret_key = helpers.get_or_create_secret_key()
if config:
app.config.update(config)
# CSRF-Schutz und Datenbank initialisieren
csrf = CSRFProtect(app)
db.init_app(app)
# Pfade und Version
helpers.initialize_paths(app.root_path)
app.config['APP_VERSION'] = helpers.get_version()
@app.context_processor
def inject_globals():
"""Stellt globale Variablen für alle Templates bereit"""
return {'APP_VERSION': app.config['APP_VERSION']}
# Blueprints importieren und registrieren
from routes.main import main_bp
from routes.training import training_bp
from routes.statistics import statistics_bp
from routes.settings import settings_bp
from routes.session import session_bp
from routes.utils import utils_bp, shutdown
app.register_blueprint(main_bp)
app.register_blueprint(training_bp)
app.register_blueprint(statistics_bp)
app.register_blueprint(settings_bp)
app.register_blueprint(session_bp)
app.register_blueprint(utils_bp)
# CSRF-Ausnahme gezielt nur für die Shutdown-Route.
# /repair_json und /recreate_lessons bleiben damit CSRF-geschützt.
csrf.exempt(shutdown)
# Datenbank-Status prüfen und Lektionen laden (nur im Normalbetrieb)
if not app.config.get('TESTING', False) and os.environ.get('FLASK_TESTING') != 'true':
_load_lessons_or_exit(app)
return app
app = create_app()
if __name__ == '__main__':
logger.info("=" * 50)
logger.info("Tipptrainer gestartet")
logger.info(f"Version: {VERSION}")
logger.info(f"Version: {app.config['APP_VERSION']}")
logger.info(f"Lektionen geladen: {len(helpers.lessons)}")
logger.info("Debug-Info verfügbar unter: http://127.0.0.1:5000/debug")
logger.info("=" * 50)

View file

@ -15,5 +15,3 @@ 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"]