qq_lib.properties.transfer_mode

  1# Released under MIT License.
  2# Copyright (c) 2025-2026 Ladislav Bartos and Robert Vacha Lab
  3
  4import re
  5from abc import ABC, abstractmethod
  6from dataclasses import dataclass
  7
  8from qq_lib.core.error import QQError
  9
 10
 11@dataclass(frozen=True)
 12class TransferMode(ABC):
 13    """
 14    Specifies when job data should be transferred from the working directory to
 15    the input directory or archived from the working directory.
 16    """
 17
 18    @classmethod
 19    def from_str(cls, s: str) -> "TransferMode":
 20        """
 21        Convert a string to the corresponding TransferMode.
 22
 23        Args:
 24            s (str): String representation of the transfer mode.
 25
 26        Returns:
 27            TransferMode variant.
 28
 29        Raises:
 30            QQError if the string corresponds to no transfer mode.
 31        """
 32        match s.lower().strip():
 33            case "always":
 34                return Always()
 35            case "never":
 36                return Never()
 37            case "success":
 38                return Success()
 39            case "failure":
 40                return Failure()
 41            case _:
 42                # if the string is a number (positive or negative)
 43                if bool(re.match(r"^-?\d+$", s.strip())):
 44                    return ExitCode(int(s))
 45
 46                raise QQError(f"Could not recognize a transfer mode variant '{s}'.")
 47
 48    @classmethod
 49    def multi_from_str(cls, raw: str) -> list["TransferMode"]:
 50        """
 51        Parse a string containing multiple transfer modes.
 52
 53        Args:
 54            s: String containing transfer mode variants separated by a colon, comma, or space.
 55                Example: "success:42", "1,2,3", or "failure success".
 56
 57        Returns:
 58            list[TransferMode]: A list of parsed transfer modes.
 59
 60        Raises:
 61            QQError: If any of the individual mode strings cannot be recognized.
 62        """
 63        mode_strings = re.split(r"[:,\s]+", raw.strip())
 64        mode_strings = [ms for ms in mode_strings if ms]
 65
 66        return [TransferMode.from_str(mode_str) for mode_str in mode_strings]
 67
 68    @abstractmethod
 69    def should_transfer(self, exit_code: int) -> bool:
 70        """
 71        Determine whether data should be transferred/archived based on the exit code.
 72
 73        Args:
 74            exit_code: The exit code of the completed job.
 75
 76        Returns:
 77            True if data should be transferred/archived, False otherwise.
 78        """
 79
 80    @abstractmethod
 81    def to_str(self) -> str:
 82        """
 83        Convert the TransferMode variant to its string representation.
 84
 85        Returns:
 86            A string representation of the transfer mode variant.
 87        """
 88
 89
 90@dataclass(frozen=True)
 91class Always(TransferMode):
 92    """
 93    Data are always transferred/archived regardless the job's exit code.
 94    """
 95
 96    def should_transfer(self, exit_code: int) -> bool:
 97        _ = exit_code
 98        return True
 99
100    def to_str(self) -> str:
101        return "always"
102
103
104@dataclass(frozen=True)
105class Never(TransferMode):
106    """
107    Data are never transferred/archived.
108    """
109
110    def should_transfer(self, exit_code: int) -> bool:
111        _ = exit_code
112        return False
113
114    def to_str(self) -> str:
115        return "never"
116
117
118@dataclass(frozen=True)
119class Success(TransferMode):
120    """
121    Data are transferred/archived only if the job completes successfully.
122    """
123
124    def should_transfer(self, exit_code: int) -> bool:
125        return exit_code == 0
126
127    def to_str(self) -> str:
128        return "success"
129
130
131@dataclass(frozen=True)
132class Failure(TransferMode):
133    """
134    Data are transferred/archived only if the job fails.
135    """
136
137    def should_transfer(self, exit_code: int) -> bool:
138        return exit_code != 0
139
140    def to_str(self) -> str:
141        return "failure"
142
143
144@dataclass(frozen=True)
145class ExitCode(TransferMode):
146    """
147    Data are transferred/archived only if the job exits with a specific code.
148
149    Attributes:
150        code: The exit code that triggers data transfer.
151    """
152
153    code: int
154
155    def should_transfer(self, exit_code: int) -> bool:
156        return exit_code == self.code
157
158    def to_str(self) -> str:
159        return f"{self.code}"
@dataclass(frozen=True)
class TransferMode(abc.ABC):
12@dataclass(frozen=True)
13class TransferMode(ABC):
14    """
15    Specifies when job data should be transferred from the working directory to
16    the input directory or archived from the working directory.
17    """
18
19    @classmethod
20    def from_str(cls, s: str) -> "TransferMode":
21        """
22        Convert a string to the corresponding TransferMode.
23
24        Args:
25            s (str): String representation of the transfer mode.
26
27        Returns:
28            TransferMode variant.
29
30        Raises:
31            QQError if the string corresponds to no transfer mode.
32        """
33        match s.lower().strip():
34            case "always":
35                return Always()
36            case "never":
37                return Never()
38            case "success":
39                return Success()
40            case "failure":
41                return Failure()
42            case _:
43                # if the string is a number (positive or negative)
44                if bool(re.match(r"^-?\d+$", s.strip())):
45                    return ExitCode(int(s))
46
47                raise QQError(f"Could not recognize a transfer mode variant '{s}'.")
48
49    @classmethod
50    def multi_from_str(cls, raw: str) -> list["TransferMode"]:
51        """
52        Parse a string containing multiple transfer modes.
53
54        Args:
55            s: String containing transfer mode variants separated by a colon, comma, or space.
56                Example: "success:42", "1,2,3", or "failure success".
57
58        Returns:
59            list[TransferMode]: A list of parsed transfer modes.
60
61        Raises:
62            QQError: If any of the individual mode strings cannot be recognized.
63        """
64        mode_strings = re.split(r"[:,\s]+", raw.strip())
65        mode_strings = [ms for ms in mode_strings if ms]
66
67        return [TransferMode.from_str(mode_str) for mode_str in mode_strings]
68
69    @abstractmethod
70    def should_transfer(self, exit_code: int) -> bool:
71        """
72        Determine whether data should be transferred/archived based on the exit code.
73
74        Args:
75            exit_code: The exit code of the completed job.
76
77        Returns:
78            True if data should be transferred/archived, False otherwise.
79        """
80
81    @abstractmethod
82    def to_str(self) -> str:
83        """
84        Convert the TransferMode variant to its string representation.
85
86        Returns:
87            A string representation of the transfer mode variant.
88        """

Specifies when job data should be transferred from the working directory to the input directory or archived from the working directory.

@classmethod
def from_str(cls, s: str) -> TransferMode:
19    @classmethod
20    def from_str(cls, s: str) -> "TransferMode":
21        """
22        Convert a string to the corresponding TransferMode.
23
24        Args:
25            s (str): String representation of the transfer mode.
26
27        Returns:
28            TransferMode variant.
29
30        Raises:
31            QQError if the string corresponds to no transfer mode.
32        """
33        match s.lower().strip():
34            case "always":
35                return Always()
36            case "never":
37                return Never()
38            case "success":
39                return Success()
40            case "failure":
41                return Failure()
42            case _:
43                # if the string is a number (positive or negative)
44                if bool(re.match(r"^-?\d+$", s.strip())):
45                    return ExitCode(int(s))
46
47                raise QQError(f"Could not recognize a transfer mode variant '{s}'.")

Convert a string to the corresponding TransferMode.

Arguments:
  • s (str): String representation of the transfer mode.
Returns:

TransferMode variant.

Raises:
  • QQError if the string corresponds to no transfer mode.
@classmethod
def multi_from_str(cls, raw: str) -> list[TransferMode]:
49    @classmethod
50    def multi_from_str(cls, raw: str) -> list["TransferMode"]:
51        """
52        Parse a string containing multiple transfer modes.
53
54        Args:
55            s: String containing transfer mode variants separated by a colon, comma, or space.
56                Example: "success:42", "1,2,3", or "failure success".
57
58        Returns:
59            list[TransferMode]: A list of parsed transfer modes.
60
61        Raises:
62            QQError: If any of the individual mode strings cannot be recognized.
63        """
64        mode_strings = re.split(r"[:,\s]+", raw.strip())
65        mode_strings = [ms for ms in mode_strings if ms]
66
67        return [TransferMode.from_str(mode_str) for mode_str in mode_strings]

Parse a string containing multiple transfer modes.

Arguments:
  • s: String containing transfer mode variants separated by a colon, comma, or space. Example: "success:42", "1,2,3", or "failure success".
Returns:

list[TransferMode]: A list of parsed transfer modes.

Raises:
  • QQError: If any of the individual mode strings cannot be recognized.
@abstractmethod
def should_transfer(self, exit_code: int) -> bool:
69    @abstractmethod
70    def should_transfer(self, exit_code: int) -> bool:
71        """
72        Determine whether data should be transferred/archived based on the exit code.
73
74        Args:
75            exit_code: The exit code of the completed job.
76
77        Returns:
78            True if data should be transferred/archived, False otherwise.
79        """

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

@abstractmethod
def to_str(self) -> str:
81    @abstractmethod
82    def to_str(self) -> str:
83        """
84        Convert the TransferMode variant to its string representation.
85
86        Returns:
87            A string representation of the transfer mode variant.
88        """

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.

@dataclass(frozen=True)
class Always(TransferMode):
 91@dataclass(frozen=True)
 92class Always(TransferMode):
 93    """
 94    Data are always transferred/archived regardless the job's exit code.
 95    """
 96
 97    def should_transfer(self, exit_code: int) -> bool:
 98        _ = exit_code
 99        return True
100
101    def to_str(self) -> str:
102        return "always"

Data are always transferred/archived regardless the job's exit code.

def should_transfer(self, exit_code: int) -> bool:
97    def should_transfer(self, exit_code: int) -> bool:
98        _ = exit_code
99        return True

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

def to_str(self) -> str:
101    def to_str(self) -> str:
102        return "always"

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.

@dataclass(frozen=True)
class Never(TransferMode):
105@dataclass(frozen=True)
106class Never(TransferMode):
107    """
108    Data are never transferred/archived.
109    """
110
111    def should_transfer(self, exit_code: int) -> bool:
112        _ = exit_code
113        return False
114
115    def to_str(self) -> str:
116        return "never"

Data are never transferred/archived.

def should_transfer(self, exit_code: int) -> bool:
111    def should_transfer(self, exit_code: int) -> bool:
112        _ = exit_code
113        return False

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

def to_str(self) -> str:
115    def to_str(self) -> str:
116        return "never"

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.

@dataclass(frozen=True)
class Success(TransferMode):
119@dataclass(frozen=True)
120class Success(TransferMode):
121    """
122    Data are transferred/archived only if the job completes successfully.
123    """
124
125    def should_transfer(self, exit_code: int) -> bool:
126        return exit_code == 0
127
128    def to_str(self) -> str:
129        return "success"

Data are transferred/archived only if the job completes successfully.

def should_transfer(self, exit_code: int) -> bool:
125    def should_transfer(self, exit_code: int) -> bool:
126        return exit_code == 0

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

def to_str(self) -> str:
128    def to_str(self) -> str:
129        return "success"

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.

@dataclass(frozen=True)
class Failure(TransferMode):
132@dataclass(frozen=True)
133class Failure(TransferMode):
134    """
135    Data are transferred/archived only if the job fails.
136    """
137
138    def should_transfer(self, exit_code: int) -> bool:
139        return exit_code != 0
140
141    def to_str(self) -> str:
142        return "failure"

Data are transferred/archived only if the job fails.

def should_transfer(self, exit_code: int) -> bool:
138    def should_transfer(self, exit_code: int) -> bool:
139        return exit_code != 0

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

def to_str(self) -> str:
141    def to_str(self) -> str:
142        return "failure"

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.

@dataclass(frozen=True)
class ExitCode(TransferMode):
145@dataclass(frozen=True)
146class ExitCode(TransferMode):
147    """
148    Data are transferred/archived only if the job exits with a specific code.
149
150    Attributes:
151        code: The exit code that triggers data transfer.
152    """
153
154    code: int
155
156    def should_transfer(self, exit_code: int) -> bool:
157        return exit_code == self.code
158
159    def to_str(self) -> str:
160        return f"{self.code}"

Data are transferred/archived only if the job exits with a specific code.

Attributes:
  • code: The exit code that triggers data transfer.
ExitCode(code: int)
code: int
def should_transfer(self, exit_code: int) -> bool:
156    def should_transfer(self, exit_code: int) -> bool:
157        return exit_code == self.code

Determine whether data should be transferred/archived based on the exit code.

Arguments:
  • exit_code: The exit code of the completed job.
Returns:

True if data should be transferred/archived, False otherwise.

def to_str(self) -> str:
159    def to_str(self) -> str:
160        return f"{self.code}"

Convert the TransferMode variant to its string representation.

Returns:

A string representation of the transfer mode variant.