Feature: Timeout-Einstellung und asynchrone DB-Abfrage mit Abbrechen-Dialog
DB-Abfragen laufen nun in einem Hintergrund-Thread mit QProgressDialog, sodass die UI nicht mehr einfriert. connect_timeout wird als konfigurierbarer Parameter (1-300s, Standard: 10) im Connection-String übergeben. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+86
-29
@@ -10,13 +10,35 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
import polars as pl
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from PySide6.QtCore import QThread, Signal, Qt
|
||||
from PySide6.QtWidgets import QMessageBox, QProgressDialog
|
||||
|
||||
from conf import app_settings, TreeNode, XslFile
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabaseQueryThread(QThread):
|
||||
"""Thread für asynchrone Datenbankabfragen."""
|
||||
|
||||
query_completed = Signal(object) # pl.DataFrame
|
||||
query_failed = Signal(str) # Fehlermeldung
|
||||
|
||||
def __init__(self, sql_query, connection_string):
|
||||
super().__init__()
|
||||
self.sql_query = sql_query
|
||||
self.connection_string = connection_string
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
df = pl.read_database_uri(self.sql_query, self.connection_string, engine="connectorx").sort(
|
||||
["reporttyp_bez", "report_bez", "repfile_bez"]
|
||||
)
|
||||
self.query_completed.emit(df)
|
||||
except Exception as e:
|
||||
self.query_failed.emit(str(e))
|
||||
|
||||
|
||||
class DatabaseMixin:
|
||||
"""
|
||||
Mixin für Datenbank-Operationen.
|
||||
@@ -31,7 +53,7 @@ class DatabaseMixin:
|
||||
def on_load_from_fn2_clicked(self):
|
||||
"""
|
||||
Wird ausgeführt, wenn der Button "lade aus FN2" geklickt wird.
|
||||
Führt SQL-Abfrage aus und aktualisiert die Projekt-Nodes.
|
||||
Startet SQL-Abfrage asynchron mit Fortschrittsdialog.
|
||||
"""
|
||||
logger.debug("Button 'lade aus FN2' wurde geklickt!")
|
||||
|
||||
@@ -52,29 +74,68 @@ class DatabaseMixin:
|
||||
QMessageBox.warning(self, "Warnung", "PostgreSQL-Datenbank-Konfiguration nicht gefunden.")
|
||||
return
|
||||
|
||||
# Führe SQL-Abfrage aus
|
||||
df = self._execute_sql_query(db_config)
|
||||
if df is None:
|
||||
# SQL-Abfrage und Connection-String vorbereiten
|
||||
sql_query, connection_string = self._prepare_sql_query(db_config)
|
||||
if sql_query is None:
|
||||
return # Fehler bereits angezeigt
|
||||
|
||||
# Verarbeite die Daten wie in readCsv.py
|
||||
new_nodes = self._process_sql_data(df)
|
||||
# Fortschrittsdialog erstellen
|
||||
self._db_progress = QProgressDialog("Verbinde mit Datenbank...", "Abbrechen", 0, 0, self)
|
||||
self._db_progress.setWindowTitle("Datenbank-Abfrage")
|
||||
self._db_progress.setWindowModality(Qt.WindowModal)
|
||||
self._db_progress.setMinimumDuration(0)
|
||||
|
||||
# Merge mit vorhandenen Nodes
|
||||
self._merge_nodes_with_existing(new_nodes)
|
||||
|
||||
# Speichere die aktualisierten Projekt-Einstellungen
|
||||
self._save_project_settings()
|
||||
|
||||
# Lade das Projekt neu
|
||||
self._load_nodes_to_tree()
|
||||
|
||||
# QMessageBox.information(self, "Erfolg", "Daten erfolgreich aus FN2 geladen und Projekt aktualisiert!")
|
||||
# Query-Thread erstellen und starten
|
||||
self._db_query_thread = DatabaseQueryThread(sql_query, connection_string)
|
||||
self._db_query_thread.query_completed.connect(self._on_db_query_completed)
|
||||
self._db_query_thread.query_failed.connect(self._on_db_query_failed)
|
||||
self._db_query_thread.finished.connect(self._cleanup_db_query)
|
||||
self._db_progress.canceled.connect(self._on_db_query_canceled)
|
||||
self._db_query_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden aus FN2: {e}")
|
||||
QMessageBox.critical(self, "Fehler", f"Fehler beim Laden aus FN2:\n{str(e)}")
|
||||
|
||||
def _on_db_query_completed(self, df):
|
||||
"""Wird aufgerufen, wenn die Datenbankabfrage erfolgreich war."""
|
||||
# Ignoriere Ergebnis, falls der Benutzer abgebrochen hat
|
||||
if hasattr(self, "_db_progress") and self._db_progress.wasCanceled():
|
||||
return
|
||||
|
||||
try:
|
||||
new_nodes = self._process_sql_data(df)
|
||||
self._merge_nodes_with_existing(new_nodes)
|
||||
self._save_project_settings()
|
||||
self._load_nodes_to_tree()
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Verarbeiten der DB-Daten: {e}")
|
||||
QMessageBox.critical(self, "Fehler", f"Fehler beim Verarbeiten der Daten:\n{str(e)}")
|
||||
|
||||
def _on_db_query_failed(self, error_msg):
|
||||
"""Wird aufgerufen, wenn die Datenbankabfrage fehlgeschlagen ist."""
|
||||
if hasattr(self, "_db_progress") and self._db_progress.wasCanceled():
|
||||
return
|
||||
|
||||
error = f"Fehler beim Ausführen der SQL-Abfrage: {error_msg}"
|
||||
logger.error(error)
|
||||
QMessageBox.critical(self, "Fehler", error)
|
||||
|
||||
def _on_db_query_canceled(self):
|
||||
"""Wird aufgerufen, wenn der Benutzer die Abfrage abbricht."""
|
||||
logger.info("Datenbankabfrage vom Benutzer abgebrochen")
|
||||
|
||||
def _cleanup_db_query(self):
|
||||
"""Räumt nach der Datenbankabfrage auf."""
|
||||
if hasattr(self, "_db_progress"):
|
||||
self._db_progress.canceled.disconnect(self._on_db_query_canceled)
|
||||
self._db_progress.close()
|
||||
self._db_progress.deleteLater()
|
||||
del self._db_progress
|
||||
if hasattr(self, "_db_query_thread"):
|
||||
self._db_query_thread.deleteLater()
|
||||
del self._db_query_thread
|
||||
|
||||
def _get_database_config(self, db_id):
|
||||
"""
|
||||
Holt die Datenbank-Konfiguration anhand der ID.
|
||||
@@ -90,29 +151,27 @@ class DatabaseMixin:
|
||||
return db
|
||||
return None
|
||||
|
||||
def _execute_sql_query(self, db_config):
|
||||
def _prepare_sql_query(self, db_config):
|
||||
"""
|
||||
Führt die SQL-Abfrage aus der data.sql Datei aus.
|
||||
Bereitet SQL-Abfrage und Connection-String vor.
|
||||
|
||||
Args:
|
||||
db_config: PostgreSQL-Datenbank-Konfiguration
|
||||
|
||||
Returns:
|
||||
pl.DataFrame|None: Die Abfrageergebnisse oder None bei Fehler
|
||||
tuple[str, str]|tuple[None, None]: (sql_query, connection_string) oder (None, None) bei Fehler
|
||||
"""
|
||||
try:
|
||||
# Lade SQL-Abfrage aus Datei
|
||||
sql_file_path = Path("src/res/data.sql")
|
||||
if not sql_file_path.exists():
|
||||
QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}")
|
||||
return None
|
||||
return None, None
|
||||
|
||||
with open(sql_file_path, "r", encoding="utf-8") as f:
|
||||
sql_query = f.read()
|
||||
|
||||
logger.debug(f"SQL-Abfrage geladen: {len(sql_query)} Zeichen")
|
||||
|
||||
# Verbindung zur PostgreSQL-Datenbank herstellen
|
||||
connection_string = (
|
||||
"postgresql://"
|
||||
f"{db_config.username}:"
|
||||
@@ -121,19 +180,17 @@ class DatabaseMixin:
|
||||
f"{db_config.port}/"
|
||||
f"{db_config.database}?"
|
||||
f"sslmode={db_config.ssl_mode.value}"
|
||||
f"&connect_timeout={db_config.timeout}"
|
||||
)
|
||||
|
||||
logger.info(f"Verbinde zu PostgreSQL: {db_config.host}:{db_config.port}/{db_config.database}")
|
||||
|
||||
df = pl.read_database_uri(sql_query, connection_string, engine="connectorx").sort(
|
||||
["reporttyp_bez", "report_bez", "repfile_bez"]
|
||||
)
|
||||
return df
|
||||
return sql_query, connection_string
|
||||
except Exception as e:
|
||||
error_msg = f"Fehler beim Ausführen der SQL-Abfrage: {str(e)}"
|
||||
error_msg = f"Fehler beim Vorbereiten der SQL-Abfrage: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
QMessageBox.critical(self, "Fehler", error_msg)
|
||||
return None
|
||||
return None, None
|
||||
|
||||
def _process_sql_data(self, df):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user