🎉 Feat: Import & Function Analysis Tool Enhancements 🎉
This commit introduces a suite of tools for analyzing and optimizing imports and functions within the backend codebase. The following files have been updated: - backend/FRONTEND_ASSETS_ANALYSE.md - backend/REDUNDANZ_ANALYSE_FINAL.md - backend/SOFORT_L\303\226SCHBARE_FUN
This commit is contained in:
325
backend/cleanup_imports.py
Normal file
325
backend/cleanup_imports.py
Normal file
@@ -0,0 +1,325 @@
|
||||
#!/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()
|
Reference in New Issue
Block a user