manage-your-printer/utils/update_requirements.py
2025-06-04 10:03:22 +02:00

295 lines
10 KiB
Python

#!/usr/bin/env python3
"""
MYP Platform - Requirements Update Script
Aktualisiert die Requirements basierend auf tatsächlich verwendeten Imports
"""
import os
import sys
import subprocess
import ast
import importlib.util
from pathlib import Path
from typing import Set, List, Dict
def get_imports_from_file(file_path: Path) -> Set[str]:
"""Extrahiert alle Import-Statements aus einer Python-Datei."""
imports = set()
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
imports.add(alias.name.split('.')[0])
elif isinstance(node, ast.ImportFrom):
if node.module:
imports.add(node.module.split('.')[0])
except Exception as e:
print(f"Fehler beim Parsen von {file_path}: {e}")
return imports
def get_all_imports(project_root: Path) -> Set[str]:
"""Sammelt alle Imports aus dem Projekt."""
all_imports = set()
# Wichtige Dateien analysieren
important_files = [
'app.py',
'models.py',
'utils/rate_limiter.py',
'utils/job_scheduler.py',
'utils/queue_manager.py',
'utils/ssl_manager.py',
'utils/security.py',
'utils/permissions.py',
'utils/analytics.py',
'utils/template_helpers.py',
'utils/logging_config.py'
]
for file_path in important_files:
full_path = project_root / file_path
if full_path.exists():
imports = get_imports_from_file(full_path)
all_imports.update(imports)
print(f"✓ Analysiert: {file_path} ({len(imports)} Imports)")
return all_imports
def get_package_mapping() -> Dict[str, str]:
"""Mapping von Import-Namen zu PyPI-Paketnamen."""
return {
'flask': 'Flask',
'flask_login': 'Flask-Login',
'flask_wtf': 'Flask-WTF',
'sqlalchemy': 'SQLAlchemy',
'werkzeug': 'Werkzeug',
'bcrypt': 'bcrypt',
'cryptography': 'cryptography',
'PyP100': 'PyP100',
'redis': 'redis',
'requests': 'requests',
'jinja2': 'Jinja2',
'markupsafe': 'MarkupSafe',
'itsdangerous': 'itsdangerous',
'psutil': 'psutil',
'click': 'click',
'blinker': 'blinker',
'pywin32': 'pywin32',
'pytest': 'pytest',
'gunicorn': 'gunicorn'
}
def get_current_versions() -> Dict[str, str]:
"""Holt die aktuell installierten Versionen."""
versions = {}
try:
result = subprocess.run(['pip', 'list', '--format=freeze'],
capture_output=True, text=True,
encoding='utf-8', errors='replace')
for line in result.stdout.strip().split('\n'):
if '==' in line:
package, version = line.split('==', 1)
versions[package.lower()] = version
except Exception as e:
print(f"Fehler beim Abrufen der Versionen: {e}")
return versions
def check_package_availability(package: str, version: str = None) -> bool:
"""Prüft, ob ein Paket in der angegebenen Version verfügbar ist."""
try:
if version:
cmd = ['pip', 'index', 'versions', package]
else:
cmd = ['pip', 'show', package]
result = subprocess.run(cmd, capture_output=True, text=True,
encoding='utf-8', errors='replace')
return result.returncode == 0
except Exception:
return False
def generate_requirements(imports: Set[str], versions: Dict[str, str]) -> List[str]:
"""Generiert die Requirements-Liste."""
package_mapping = get_package_mapping()
requirements = []
# Standard-Bibliotheken, die nicht installiert werden müssen
stdlib_modules = {
'os', 'sys', 'logging', 'atexit', 'datetime', 'time', 'subprocess',
'json', 'signal', 'threading', 'functools', 'typing', 'contextlib',
'secrets', 'hashlib', 'calendar', 'random', 'socket', 'ipaddress',
'enum', 'dataclasses', 'concurrent', 'collections'
}
# Lokale Module ausschließen
local_modules = {
'models', 'utils', 'config', 'blueprints'
}
# Nur externe Pakete berücksichtigen
external_imports = imports - stdlib_modules - local_modules
for import_name in sorted(external_imports):
package_name = package_mapping.get(import_name, import_name)
# Version aus installierten Paketen holen
version = versions.get(package_name.lower())
if version and check_package_availability(package_name, version):
if package_name == 'pywin32':
requirements.append(f"{package_name}=={version}; sys_platform == \"win32\"")
else:
requirements.append(f"{package_name}=={version}")
else:
print(f"⚠️ Paket {package_name} nicht gefunden oder Version unbekannt")
return requirements
def write_requirements_file(requirements: List[str], output_file: Path):
"""Schreibt die Requirements in eine Datei."""
header = """# MYP Platform - Python Dependencies
# Basierend auf tatsächlich verwendeten Imports in app.py
# Automatisch generiert am: {date}
# Installiere mit: pip install -r requirements.txt
# ===== CORE FLASK FRAMEWORK =====
# Direkt in app.py verwendet
{flask_requirements}
# ===== DATENBANK =====
# SQLAlchemy für Datenbankoperationen (models.py, app.py)
{db_requirements}
# ===== SICHERHEIT UND AUTHENTIFIZIERUNG =====
# Werkzeug für Passwort-Hashing und Utilities (app.py)
{security_requirements}
# ===== SMART PLUG STEUERUNG =====
# PyP100 für TP-Link Tapo Smart Plugs (utils/job_scheduler.py)
{smartplug_requirements}
# ===== RATE LIMITING UND CACHING =====
# Redis für Rate Limiting (utils/rate_limiter.py) - optional
{cache_requirements}
# ===== HTTP REQUESTS =====
# Requests für HTTP-Anfragen (utils/queue_manager.py, utils/debug_drucker_erkennung.py)
{http_requirements}
# ===== TEMPLATE ENGINE =====
# Jinja2 und MarkupSafe (automatisch mit Flask installiert, aber explizit für utils/template_helpers.py)
{template_requirements}
# ===== SYSTEM MONITORING =====
# psutil für System-Monitoring (utils/debug_utils.py, utils/debug_cli.py)
{monitoring_requirements}
# ===== ZUSÄTZLICHE CORE ABHÄNGIGKEITEN =====
# Click für CLI-Kommandos (automatisch mit Flask)
{core_requirements}
# ===== WINDOWS-SPEZIFISCHE ABHÄNGIGKEITEN =====
# Nur für Windows-Systeme erforderlich
{windows_requirements}
# ===== OPTIONAL: ENTWICKLUNG UND TESTING =====
# Nur für Entwicklungsumgebung
{dev_requirements}
# ===== OPTIONAL: PRODUKTIONS-SERVER =====
# Gunicorn für Produktionsumgebung
{prod_requirements}
"""
# Requirements kategorisieren
flask_reqs = [r for r in requirements if any(x in r.lower() for x in ['flask'])]
db_reqs = [r for r in requirements if 'SQLAlchemy' in r]
security_reqs = [r for r in requirements if any(x in r for x in ['Werkzeug', 'bcrypt', 'cryptography'])]
smartplug_reqs = [r for r in requirements if 'PyP100' in r]
cache_reqs = [r for r in requirements if 'redis' in r]
http_reqs = [r for r in requirements if 'requests' in r]
template_reqs = [r for r in requirements if any(x in r for x in ['Jinja2', 'MarkupSafe', 'itsdangerous'])]
monitoring_reqs = [r for r in requirements if 'psutil' in r]
core_reqs = [r for r in requirements if any(x in r for x in ['click', 'blinker'])]
windows_reqs = [r for r in requirements if 'sys_platform' in r]
from datetime import datetime
content = header.format(
date=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
flask_requirements='\n'.join(flask_reqs) or '# Keine Flask-spezifischen Requirements',
db_requirements='\n'.join(db_reqs) or '# Keine Datenbank-Requirements',
security_requirements='\n'.join(security_reqs) or '# Keine Sicherheits-Requirements',
smartplug_requirements='\n'.join(smartplug_reqs) or '# Keine Smart Plug Requirements',
cache_requirements='\n'.join(cache_reqs) or '# Keine Cache-Requirements',
http_requirements='\n'.join(http_reqs) or '# Keine HTTP-Requirements',
template_requirements='\n'.join(template_reqs) or '# Keine Template-Requirements',
monitoring_requirements='\n'.join(monitoring_reqs) or '# Keine Monitoring-Requirements',
core_requirements='\n'.join(core_reqs) or '# Keine Core-Requirements',
windows_requirements='\n'.join(windows_reqs) or '# Keine Windows-Requirements',
dev_requirements='pytest==8.3.4; extra == "dev"\npytest-cov==6.0.0; extra == "dev"',
prod_requirements='gunicorn==23.0.0; extra == "prod"'
)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(content)
def main():
"""Hauptfunktion."""
print("🔄 MYP Platform Requirements Update")
print("=" * 50)
# Projekt-Root ermitteln
project_root = Path(__file__).parent
print(f"📁 Projekt-Verzeichnis: {project_root}")
# Imports sammeln
print("\n📋 Sammle Imports aus wichtigen Dateien...")
imports = get_all_imports(project_root)
print(f"\n📦 Gefundene externe Imports: {len(imports)}")
for imp in sorted(imports):
print(f" - {imp}")
# Aktuelle Versionen abrufen
print("\n🔍 Prüfe installierte Versionen...")
versions = get_current_versions()
# Requirements generieren
print("\n⚙️ Generiere Requirements...")
requirements = generate_requirements(imports, versions)
# Requirements-Datei schreiben
output_file = project_root / 'requirements.txt'
write_requirements_file(requirements, output_file)
print(f"\n✅ Requirements aktualisiert: {output_file}")
print(f"📊 {len(requirements)} Pakete in requirements.txt")
# Zusammenfassung
print("\n📋 Generierte Requirements:")
for req in requirements:
print(f" - {req}")
print("\n🎉 Requirements-Update abgeschlossen!")
print("\nNächste Schritte:")
print("1. pip install -r requirements.txt")
print("2. Anwendung testen")
print("3. requirements-dev.txt und requirements-prod.txt bei Bedarf anpassen")
if __name__ == "__main__":
main()