Source code for devctrl.gui.app.task_host

"""Shared, cross-module task system host.

There is a **single** global :class:`~devctrl.gui.task.TaskManagerWidget` owned
by the host. Every module registers its task runners into it, so one task list
can naturally mix tasks from different modules and run them sequentially (e.g.
a TAS measurement right after a PRS spectrum on shared hardware).

No scheduling engine is written: sequential execution of an ordered task list
covers "task B after task A". Safety on shared hardware is enforced by the
app-global blocking signals (§6 of the plan).
"""
import logging
import os
from typing import Optional

from devctrl.gui import task as devtask

log = logging.getLogger(__name__)


[docs] class TaskHost: """Holds the shared task manager and helper state. The widget is created lazily so it is available even when no module uses the task system heavily; the host keeps a reference for the console/plugins. """
[docs] def __init__(self): self._manager: devtask.TaskManagerWidget | None = None self._preset_manager: devtask.TaskPresetManager | None = None self.task_directory_changed = None # callable(last_dir) self.tasklist_directory_changed = None # callable(last_dir)
[docs] def get_manager(self) -> devtask.TaskManagerWidget: """Return the shared task manager, creating it lazily if needed. :raises RuntimeError: If the manager could not be created (usually only when called before :meth:`ensure_manager` provided the required dirs). """ self.ensure_manager() return self._manager
[docs] def ensure_manager(self, preset_manager: Optional[devtask.TaskPresetManager] = None, initial_task_directory: Optional[str] = None, initial_tasklist_directory: Optional[str] = None, tasklist_path: Optional[str] = None) -> devtask.TaskManagerWidget: """Create the single global :class:`TaskManagerWidget` if not present. If the manager already exists, the preset manager and directory arguments are applied to it, so a module can (re-)configure the manager even when the host or another module has already created it. A call without arguments never modifies the existing configuration. If the manager does not exist yet, missing arguments are replaced by sensible defaults below the host cache directory, so the task system works even before a module supplies its own config paths. :param preset_manager: A :class:`TaskPresetManager`, or None to create one in the default preset directory. :param initial_task_directory: Initial "load task settings" directory. :param initial_tasklist_directory: Initial "load tasklist" directory. :param tasklist_path: Path to load the task list from on creation (``None`` skips). Ignored when the manager already exists. :return: The shared :class:`TaskManagerWidget`. """ if self._manager is None: if preset_manager is None or initial_task_directory is None or initial_tasklist_directory is None: from devctrl.gui.app.config import AppConfigBase default_dir = os.path.join(AppConfigBase.cache_dir(), "tasks") if preset_manager is None: preset_manager = devtask.TaskPresetManager(os.path.join(default_dir, "presets")) if initial_task_directory is None: initial_task_directory = default_dir if initial_tasklist_directory is None: initial_tasklist_directory = default_dir self._manager = devtask.TaskManagerWidget( preset_manager=preset_manager, initial_task_directory=initial_task_directory, initial_tasklist_directory=initial_tasklist_directory, ) if tasklist_path: try: self._manager.w_tasklist.load_tasklist(tasklist_path) except Exception as e: # pragma: no cover - defensive log.warning(f"Could not load initial tasklist from '{tasklist_path}': {e}") else: # Apply the configuration to the already-created manager so a module # can replace the defaults (or another module's configuration). w_tasklist = self._manager.w_tasklist if preset_manager is not None: w_tasklist.preset_manager = preset_manager if initial_task_directory is not None: w_tasklist.initial_task_directory = initial_task_directory if initial_tasklist_directory is not None: w_tasklist.initial_tasklist_directory = initial_tasklist_directory return self._manager