295 lines
10 KiB
Python
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() |