#!/usr/bin/env python3 """ Automatische Bereinigung von ungenutzten Imports im MYP Backend Dieser Script bereinigt sichere, ungenutzte Imports automatisch und erstellt ein Backup vor den Änderungen. """ import os import re import shutil import ast from pathlib import Path from datetime import datetime from typing import List, Dict, Set, Tuple class ImportCleaner: def __init__(self, backend_path: str): self.backend_path = Path(backend_path) self.backup_dir = self.backend_path / f"backup_imports_{datetime.now().strftime('%Y%m%d_%H%M%S')}" self.changes = [] # Sichere Imports die entfernt werden können (niedrige Fehlerwahrscheinlichkeit) self.safe_unused_imports = { # Typing imports (fast immer sicher zu entfernen) 'typing.Set', 'typing.Tuple', 'typing.List', 'typing.Dict', 'typing.Any', 'typing.Optional', 'typing.Union', 'typing.Callable', # Standard library (meist sicher wenn ungenutzt) 'pathlib.Path', 'enum.Enum', 'dataclasses.dataclass', 'dataclasses.asdict', 'collections.defaultdict', 'collections.Counter', # Entwicklungs-spezifische imports 'rich.console.Console', 'rich.table.Table', 'rich.panel.Panel', 'rich.progress.Progress', 'rich.text.Text', 'faker.Faker', 'bs4.BeautifulSoup', # Selenium (oft in Test-Dateien ungenutzt) 'selenium.common.exceptions.TimeoutException', 'selenium.common.exceptions.WebDriverException', 'selenium.common.exceptions.NoSuchElementException', 'selenium.webdriver.firefox.service.Service', 'selenium.webdriver.chrome.service.Service', 'selenium.webdriver.support.expected_conditions', 'selenium.webdriver.common.by.By', 'selenium.webdriver.support.ui.WebDriverWait', 'selenium.webdriver.chrome.options.Options', 'selenium.webdriver.firefox.options.Options', # WTForms (sicher wenn nicht in Templates verwendet) 'wtforms.validators.NumberRange', 'wtforms.validators.Optional', 'wtforms.validators.DataRequired', 'wtforms.validators.Email', 'wtforms.TextAreaField', 'wtforms.IntegerField', 'wtforms.StringField', 'wtforms.SelectField', } # Dateien die NUR automatisch bereinigt werden (niedrige Kritikalität) self.safe_files = { 'template_analysis_tool.py', 'template_validation_final.py', 'template_problem_analysis.py', 'import_analyzer.py', 'form_test_automator.py', 'simple_form_tester.py', 'test_flask_minimal.py', 'scripts/screenshot_tool.py', 'scripts/quick_unicode_fix.py', 'scripts/test_protocol_generator.py', 'ssl/ssl_fix.py', 'ssl/fix_ssl_browser.py', 'static/icons/generate_icons.py', } def create_backup(self): """Erstellt Backup aller Python-Dateien""" print(f"Erstelle Backup in: {self.backup_dir}") self.backup_dir.mkdir(exist_ok=True) for py_file in self.backend_path.rglob("*.py"): if self.should_process_file(py_file): rel_path = py_file.relative_to(self.backend_path) backup_file = self.backup_dir / rel_path backup_file.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(py_file, backup_file) print(f"✅ Backup erstellt: {len(list(self.backup_dir.rglob('*.py')))} Dateien") def should_process_file(self, file_path: Path) -> bool: """Bestimmt ob eine Datei verarbeitet werden soll""" # Überspringe bestimmte Verzeichnisse exclude_dirs = {'__pycache__', '.git', 'node_modules', 'instance'} if any(part in str(file_path) for part in exclude_dirs): return False # Überspringe Backup-Verzeichnisse if 'backup_' in str(file_path): return False return True def analyze_file_imports(self, file_path: Path) -> Tuple[List[str], Set[str]]: """Analysiert Imports und Verwendungen in einer Datei""" try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() tree = ast.parse(content) # Sammle alle Imports imports = [] for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: imports.append(alias.name) elif isinstance(node, ast.ImportFrom): module = node.module or '' for alias in node.names: if module: imports.append(f"{module}.{alias.name}") else: imports.append(alias.name) # Sammle verwendete Namen (vereinfacht) used_names = set() for node in ast.walk(tree): if isinstance(node, ast.Name): used_names.add(node.id) elif isinstance(node, ast.Attribute): if isinstance(node.value, ast.Name): used_names.add(f"{node.value.id}.{node.attr}") # Prüfe auch String-Literale for imp in imports: if imp in content: used_names.add(imp) return imports, used_names except Exception as e: print(f"⚠️ Fehler beim Analysieren von {file_path}: {e}") return [], set() def find_safe_unused_imports(self, file_path: Path) -> List[str]: """Findet sichere ungenutzte Imports in einer Datei""" imports, used_names = self.analyze_file_imports(file_path) unused_safe = [] for imp in imports: # Nur sichere Imports berücksichtigen if imp in self.safe_unused_imports: # Prüfe verschiedene Nutzungsformen base_name = imp.split('.')[0] is_used = ( base_name in used_names or imp in used_names or any(imp in name for name in used_names) ) if not is_used: unused_safe.append(imp) return unused_safe def remove_unused_imports(self, file_path: Path, unused_imports: List[str]) -> bool: """Entfernt ungenutzte Imports aus einer Datei""" if not unused_imports: return False try: with open(file_path, 'r', encoding='utf-8') as f: lines = f.readlines() modified = False new_lines = [] for line in lines: should_remove = False # Prüfe ob die Zeile einen zu entfernenden Import enthält for unused_imp in unused_imports: # Verschiedene Import-Patterns prüfen patterns = [ f"from {unused_imp.split('.')[0]} import {unused_imp.split('.')[-1]}", f"import {unused_imp}", f"from {'.'.join(unused_imp.split('.')[:-1])} import {unused_imp.split('.')[-1]}", ] for pattern in patterns: if pattern in line and line.strip().startswith(('from ', 'import ')): # Prüfe ob es eine reine Import-Zeile ist (keine Kommentare etc.) clean_line = line.split('#')[0].strip() if clean_line.endswith(unused_imp.split('.')[-1]) or clean_line.endswith(unused_imp): should_remove = True break if should_remove: break if not should_remove: new_lines.append(line) else: modified = True print(f" Entferne: {line.strip()}") if modified: with open(file_path, 'w', encoding='utf-8') as f: f.writelines(new_lines) return True except Exception as e: print(f"❌ Fehler beim Bereinigen von {file_path}: {e}") return False return False def clean_file(self, file_path: Path) -> bool: """Bereinigt eine einzelne Datei""" rel_path = file_path.relative_to(self.backend_path) # Nur sichere Dateien automatisch bereinigen if str(rel_path) not in self.safe_files: print(f"⚠️ Überspringe {rel_path} (nicht in sicherer Liste)") return False print(f"\n🔍 Analysiere: {rel_path}") unused_imports = self.find_safe_unused_imports(file_path) if unused_imports: print(f" Gefunden: {len(unused_imports)} sichere ungenutzte Imports") modified = self.remove_unused_imports(file_path, unused_imports) if modified: self.changes.append({ 'file': str(rel_path), 'removed_imports': unused_imports, 'count': len(unused_imports) }) print(f" ✅ {len(unused_imports)} Imports entfernt") return True else: print(f" ⚠️ Imports gefunden aber nicht entfernt") else: print(f" ✅ Keine sicheren ungenutzten Imports gefunden") return False def run_cleanup(self): """Führt die komplette Bereinigung durch""" print("🧹 Starte automatische Import-Bereinigung...") print(f"📁 Backend-Pfad: {self.backend_path}") # Backup erstellen self.create_backup() # Alle Python-Dateien durchgehen modified_files = 0 total_removed = 0 for py_file in self.backend_path.rglob("*.py"): if self.should_process_file(py_file): if self.clean_file(py_file): modified_files += 1 # Statistiken total_removed = sum(change['count'] for change in self.changes) print(f"\n" + "="*60) print(f"📊 BEREINIGUNG ABGESCHLOSSEN") print(f"="*60) print(f"Bearbeitete Dateien: {modified_files}") print(f"Entfernte Imports gesamt: {total_removed}") print(f"Backup erstellt in: {self.backup_dir}") if self.changes: print(f"\n📝 GEÄNDERTE DATEIEN:") for change in self.changes: print(f" 📄 {change['file']}: {change['count']} Imports entfernt") print(f"\n💡 NÄCHSTE SCHRITTE:") print(f"1. Tests ausführen: python -m pytest") print(f"2. App starten und prüfen: python app.py --debug") print(f"3. Bei Problemen Backup wiederherstellen") print(f"4. Manuelle Bereinigung von app.py und models.py") return modified_files, total_removed def restore_backup(self): """Stellt das Backup wieder her""" if not self.backup_dir.exists(): print("❌ Kein Backup gefunden!") return False print(f"🔄 Stelle Backup wieder her aus: {self.backup_dir}") restored = 0 for backup_file in self.backup_dir.rglob("*.py"): rel_path = backup_file.relative_to(self.backup_dir) original_file = self.backend_path / rel_path if original_file.exists(): shutil.copy2(backup_file, original_file) restored += 1 print(f"✅ {restored} Dateien wiederhergestellt") return True def main(): backend_path = Path(__file__).parent cleaner = ImportCleaner(str(backend_path)) import sys if len(sys.argv) > 1 and sys.argv[1] == '--restore': # Backup wiederherstellen cleaner.restore_backup() else: # Bereinigung durchführen modified, removed = cleaner.run_cleanup() if modified > 0: print(f"\n⚠️ WICHTIG: Führe Tests aus um sicherzustellen dass alles funktioniert!") print(f"Bei Problemen: python cleanup_imports.py --restore") if __name__ == "__main__": main()