"""PySide6 worker base classes and thread helpers.""" from __future__ import annotations from typing import Any, Dict, Optional try: from PySide6.QtCore import QObject, QThread, Signal, Slot QT_IMPORT_ERROR = None except ModuleNotFoundError as exc: QObject = object QThread = None QT_IMPORT_ERROR = exc def Signal(*args, **kwargs): # type: ignore return None def Slot(*args, **kwargs): # type: ignore def decorator(func): return func return decorator class WorkerError(RuntimeError): """Raised when a worker cannot be started.""" def _require_qt() -> None: if QT_IMPORT_ERROR is not None: raise WorkerError("PySide6 未安装,无法启动后台任务") def _summary_dict(value: Any) -> Dict[str, Any]: if value is None: return {} if isinstance(value, dict): return dict(value) return {"result": value} if QT_IMPORT_ERROR is None: class BaseWorker(QObject): """Base QObject worker for long-running GUI tasks.""" progress = Signal(dict) row_updated = Signal(int, dict) log = Signal(str) failed = Signal(int, str) finished = Signal(dict) cancelled = Signal(dict) def __init__(self, parent: Optional[QObject] = None): super().__init__(parent) self._cancel_requested = False def cancel(self) -> None: self._cancel_requested = True def is_cancelled(self) -> bool: return self._cancel_requested def should_cancel(self) -> bool: return self.is_cancelled() @Slot() def run(self) -> None: if self.is_cancelled(): self.cancelled.emit({"cancelled": True}) return try: summary = self.execute() except Exception as exc: # pragma: no cover - covered through signal behavior error = str(exc) or exc.__class__.__name__ self.failed.emit(-1, error) self.finished.emit({"ok": False, "error": error}) return payload = _summary_dict(summary) if self.is_cancelled(): payload["cancelled"] = True self.cancelled.emit(payload) else: self.finished.emit(payload) def execute(self) -> Optional[Dict[str, Any]]: raise NotImplementedError else: class BaseWorker(QObject): # type: ignore def __init__(self, *args, **kwargs): _require_qt() def run_worker( worker: BaseWorker, thread_name: Optional[str] = None, start: bool = True, ) -> QThread: """Move a worker to a QThread, wire lifecycle signals, and start it.""" _require_qt() if not isinstance(worker, BaseWorker): raise TypeError("worker 必须是 BaseWorker 实例") thread = QThread() if thread_name: thread.setObjectName(thread_name) worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(thread.quit) worker.cancelled.connect(thread.quit) worker.finished.connect(worker.deleteLater) worker.cancelled.connect(worker.deleteLater) thread.finished.connect(thread.deleteLater) # Keep the Python wrapper alive until Qt owns the lifecycle. thread._cmshopee_worker = worker # type: ignore[attr-defined] if start: thread.start() return thread