qq_lib.go

Navigation utilities for entering a qq job's working directory.

This module defines the Goer class, which extends Navigator to ensure a job is in a suitable state for directory access and to open an interactive shell on the job's main execution node. It handles queued jobs, missing destinations, and state-based safety checks.

 1# Released under MIT License.
 2# Copyright (c) 2025-2026 Ladislav Bartos and Robert Vacha Lab
 3
 4"""
 5Navigation utilities for entering a qq job's working directory.
 6
 7This module defines the `Goer` class, which extends `Navigator` to ensure a job
 8is in a suitable state for directory access and to open an interactive shell on
 9the job's main execution node. It handles queued jobs, missing destinations,
10and state-based safety checks.
11"""
12
13from .goer import Goer
14
15__all__ = [
16    "Goer",
17]
class Goer(qq_lib.core.navigator.Navigator):
 15class Goer(Navigator):
 16    """
 17    Handles opening a new shell in the job's working directory on the job's main execution node.
 18    """
 19
 20    def ensure_suitable(self) -> None:
 21        """
 22        Verify that the job is in a state where its working directory can be visited.
 23
 24        Raises:
 25            QQNotSuitableError: If the working directory is not expected to exist.
 26        """
 27        if self._is_synchronized() and not self._work_dir_is_input_dir():
 28            raise QQNotSuitableError(
 29                "Job has been completed and was synchronized: working directory no longer exists."
 30            )
 31
 32        if self._is_killed() and not self.has_destination():
 33            raise QQNotSuitableError(
 34                "Job has been killed and no working directory has been created."
 35            )
 36
 37    def go(self) -> None:
 38        """
 39        Open a shell in the job's working directory on the main execution node (if the node is available).
 40
 41        Raises:
 42            QQError: If the working directory or main node is not set and navigation
 43                    cannot proceed.
 44
 45        Notes:
 46            - This method may block while waiting for a queued job to start.
 47        """
 48        if self._is_in_work_dir():
 49            logger.info("You are already in the working directory.")
 50            return
 51
 52        if self._is_killed() and not self._work_dir_is_input_dir():
 53            logger.warning(
 54                "Job has been killed: working directory may no longer exist."
 55            )
 56
 57        elif (
 58            self._is_failed() or self._is_finished()
 59        ) and not self._work_dir_is_input_dir():
 60            logger.warning(
 61                "Job has been completed: working directory may no longer exist."
 62            )
 63
 64        elif self._is_unknown_inconsistent():
 65            logger.warning("Job is in an unknown, unrecognized, or inconsistent state.")
 66
 67        elif self._is_queued():
 68            logger.warning(
 69                f"Job is {str(self._state)}: cannot visit the working directory. Will retry every {CFG.goer.wait_time} seconds."
 70            )
 71
 72            # keep retrying until the job stops being queued
 73            self._wait_queued()
 74            if self._is_in_work_dir():
 75                logger.info("You are already in the working directory.")
 76                return
 77
 78        if not self.has_destination():
 79            raise QQError(
 80                "Host ('main_node') or working directory ('work_dir') are not defined."
 81            )
 82
 83        # hint for type checker
 84        # work_dir and main_node must be set - we check that in self.hasDestination
 85        assert self._work_dir and self._main_node
 86        logger.info(f"Navigating to '{str(self._work_dir)}' on '{self._main_node}'.")
 87        self._batch_system.navigate_to_destination(self._main_node, self._work_dir)
 88
 89    def _wait_queued(self):
 90        """
 91        Wait until the job is no longer in queued/booting/waiting state.
 92
 93        Raises:
 94            QQNotSuitableError: If at any point the job is found to be finished
 95                                or killed without a working directory.
 96
 97        Note:
 98            This is a blocking method and will continue looping until the job
 99            leaves the queued/booting/waiting state or an exception is raised.
100        """
101        while self._is_queued():
102            sleep(CFG.goer.wait_time)
103            self.update()
104            self.ensure_suitable()

Handles opening a new shell in the job's working directory on the job's main execution node.

def ensure_suitable(self) -> None:
20    def ensure_suitable(self) -> None:
21        """
22        Verify that the job is in a state where its working directory can be visited.
23
24        Raises:
25            QQNotSuitableError: If the working directory is not expected to exist.
26        """
27        if self._is_synchronized() and not self._work_dir_is_input_dir():
28            raise QQNotSuitableError(
29                "Job has been completed and was synchronized: working directory no longer exists."
30            )
31
32        if self._is_killed() and not self.has_destination():
33            raise QQNotSuitableError(
34                "Job has been killed and no working directory has been created."
35            )

Verify that the job is in a state where its working directory can be visited.

Raises:
  • QQNotSuitableError: If the working directory is not expected to exist.
def go(self) -> None:
37    def go(self) -> None:
38        """
39        Open a shell in the job's working directory on the main execution node (if the node is available).
40
41        Raises:
42            QQError: If the working directory or main node is not set and navigation
43                    cannot proceed.
44
45        Notes:
46            - This method may block while waiting for a queued job to start.
47        """
48        if self._is_in_work_dir():
49            logger.info("You are already in the working directory.")
50            return
51
52        if self._is_killed() and not self._work_dir_is_input_dir():
53            logger.warning(
54                "Job has been killed: working directory may no longer exist."
55            )
56
57        elif (
58            self._is_failed() or self._is_finished()
59        ) and not self._work_dir_is_input_dir():
60            logger.warning(
61                "Job has been completed: working directory may no longer exist."
62            )
63
64        elif self._is_unknown_inconsistent():
65            logger.warning("Job is in an unknown, unrecognized, or inconsistent state.")
66
67        elif self._is_queued():
68            logger.warning(
69                f"Job is {str(self._state)}: cannot visit the working directory. Will retry every {CFG.goer.wait_time} seconds."
70            )
71
72            # keep retrying until the job stops being queued
73            self._wait_queued()
74            if self._is_in_work_dir():
75                logger.info("You are already in the working directory.")
76                return
77
78        if not self.has_destination():
79            raise QQError(
80                "Host ('main_node') or working directory ('work_dir') are not defined."
81            )
82
83        # hint for type checker
84        # work_dir and main_node must be set - we check that in self.hasDestination
85        assert self._work_dir and self._main_node
86        logger.info(f"Navigating to '{str(self._work_dir)}' on '{self._main_node}'.")
87        self._batch_system.navigate_to_destination(self._main_node, self._work_dir)

Open a shell in the job's working directory on the main execution node (if the node is available).

Raises:
  • QQError: If the working directory or main node is not set and navigation cannot proceed.
Notes:
  • This method may block while waiting for a queued job to start.