qq_lib.properties.states
State models for qq jobs across different layers of the system.
This module defines three related enums - NaiveState, BatchState, and
RealState - used to represent a job's status as recorded in qq's metadata,
reported by the batch system, and interpreted by qq's higher-level logic.
qq often receives partial or inconsistent information (e.g., a job marked as
finished locally while still running in the batch system). RealState
normalizes these signals into a single coherent state used by qq operators.
1# Released under MIT License. 2# Copyright (c) 2025-2026 Ladislav Bartos and Robert Vacha Lab 3 4""" 5State models for qq jobs across different layers of the system. 6 7This module defines three related enums - `NaiveState`, `BatchState`, and 8`RealState` - used to represent a job's status as recorded in qq's metadata, 9reported by the batch system, and interpreted by qq's higher-level logic. 10 11qq often receives partial or inconsistent information (e.g., a job marked as 12finished locally while still running in the batch system). `RealState` 13normalizes these signals into a single coherent state used by qq operators. 14""" 15 16from enum import Enum 17 18from qq_lib.core.config import CFG 19from qq_lib.core.logger import get_logger 20 21logger = get_logger(__name__) 22 23 24class NaiveState(Enum): 25 """ 26 Naive state of the job written into qqinfo files. 27 """ 28 29 QUEUED = 1 30 RUNNING = 2 31 FAILED = 3 32 FINISHED = 4 33 KILLED = 5 34 UNKNOWN = 6 35 36 def __str__(self) -> str: 37 """ 38 Return the lowercase string representation of the enum variant. 39 40 Returns: 41 str: The name of the state in lowercase. 42 """ 43 return self.name.lower() 44 45 @classmethod 46 def from_str(cls, s: str) -> "NaiveState": 47 """ 48 Convert a string to the corresponding NaiveState enum variant. 49 50 Args: 51 s (str): String representation of the state (case-insensitive). 52 53 Returns: 54 NaiveState: Corresponding enum variant. Returns UNKNOWN if no match is found. 55 """ 56 try: 57 return cls[s.upper()] 58 except KeyError: 59 return cls.UNKNOWN 60 61 62class BatchState(Enum): 63 """ 64 State of the job according to the underlying batch system. 65 """ 66 67 RUNNING = 1 68 QUEUED = 2 69 FINISHED = 3 70 FAILED = 4 71 HELD = 5 72 EXITING = 6 73 WAITING = 7 74 MOVING = 8 75 SUSPENDED = 9 76 UNKNOWN = 10 77 78 def __str__(self) -> str: 79 """ 80 Return the lowercase string representation of the enum variant. 81 82 Returns: 83 str: The name of the batch state in lowercase. 84 """ 85 return self.name.lower() 86 87 @classmethod 88 def _code_to_state(cls) -> dict[str, str]: 89 """ 90 Internal mapping from one-letter codes to batch state names. 91 92 Returns: 93 dict[str, str]: Mapping of codes to corresponding batch state names. 94 """ 95 return { 96 "E": "exiting", 97 "H": "held", 98 "Q": "queued", 99 "R": "running", 100 "T": "moving", 101 "W": "waiting", 102 "S": "suspended", 103 "F": "finished", 104 "X": "failed", 105 } 106 107 @classmethod 108 def from_code(cls, code: str) -> "BatchState": 109 """ 110 Convert a one-letter batch system code to a BatchState enum variant. 111 112 Args: 113 code (str): One-letter code representing the batch system state. 114 115 Returns: 116 BatchState: Corresponding enum variant, or UNKNOWN if the code is invalid. 117 """ 118 code = code.upper() 119 if code not in cls._code_to_state(): 120 return cls.UNKNOWN 121 122 name = cls._code_to_state()[code].upper() 123 return cls[name] 124 125 def to_code(self) -> str: 126 """ 127 Return the one-letter code corresponding to this BatchState. 128 129 Returns: 130 str: One-letter code representing the batch state. Returns '?' if unknown. 131 """ 132 for k, v in self._code_to_state().items(): 133 if v.upper() == self.name: 134 return k 135 136 return "?" 137 138 @property 139 def color(self) -> str: 140 """ 141 Return the display color associated with this BatchState. 142 143 Returns: 144 str: A string representing the color for presentation purposes. 145 """ 146 if self is BatchState.MOVING: 147 return RealState.QUEUED.color 148 return RealState[self.name].color 149 150 151class RealState(Enum): 152 """ 153 Precise state of the job obtained by combining information from NaiveState and BatchState. 154 """ 155 156 QUEUED = 1 157 HELD = 2 158 SUSPENDED = 3 159 WAITING = 4 160 RUNNING = 5 161 BOOTING = 6 162 KILLED = 7 163 FAILED = 8 164 FINISHED = 9 165 EXITING = 10 166 IN_AN_INCONSISTENT_STATE = 11 167 UNKNOWN = 12 168 169 def __str__(self) -> str: 170 """ 171 Return the human-readable string representation of the state. 172 173 Returns: 174 str: Lowercase string with underscores replaced by spaces. 175 """ 176 return self.name.lower().replace("_", " ") 177 178 @classmethod 179 def from_states( 180 cls, naive_state: NaiveState, batch_state: BatchState 181 ) -> "RealState": 182 """ 183 Determine the RealState of a job based on its NaiveState and BatchState. 184 185 Args: 186 naive_state (NaiveState): The naive state of the job from qqinfo. 187 batch_state (BatchState): The state of the job according to the batch system. 188 189 Returns: 190 RealState: The corresponding RealState. 191 """ 192 logger.debug( 193 f"Converting to RealState from '{naive_state}' and '{batch_state}'." 194 ) 195 match (naive_state, batch_state): 196 case (NaiveState.UNKNOWN, _): 197 return cls.UNKNOWN 198 199 case (NaiveState.QUEUED, BatchState.QUEUED | BatchState.MOVING): 200 return cls.QUEUED 201 case (NaiveState.QUEUED, BatchState.HELD): 202 return cls.HELD 203 case (NaiveState.QUEUED, BatchState.SUSPENDED): 204 return cls.SUSPENDED 205 case (NaiveState.QUEUED, BatchState.WAITING): 206 return cls.WAITING 207 case (NaiveState.QUEUED, BatchState.RUNNING): 208 return cls.BOOTING 209 case (NaiveState.QUEUED, _): 210 return cls.IN_AN_INCONSISTENT_STATE 211 212 case (NaiveState.RUNNING, BatchState.RUNNING): 213 return cls.RUNNING 214 case (NaiveState.RUNNING, BatchState.SUSPENDED): 215 return cls.SUSPENDED 216 case (NaiveState.RUNNING, _): 217 return cls.IN_AN_INCONSISTENT_STATE 218 219 case (NaiveState.KILLED, BatchState.RUNNING): 220 return cls.EXITING 221 case (NaiveState.KILLED, _): 222 return cls.KILLED 223 224 case (NaiveState.FINISHED, BatchState.RUNNING): 225 return cls.EXITING 226 case ( 227 NaiveState.FINISHED, 228 BatchState.QUEUED 229 | BatchState.WAITING 230 | BatchState.HELD 231 | BatchState.FAILED, 232 ): 233 return cls.IN_AN_INCONSISTENT_STATE 234 case (NaiveState.FINISHED, _): 235 return cls.FINISHED 236 237 case (NaiveState.FAILED, BatchState.RUNNING): 238 return cls.EXITING 239 case ( 240 NaiveState.FAILED, 241 BatchState.QUEUED 242 | BatchState.WAITING 243 | BatchState.HELD 244 | BatchState.FINISHED, 245 ): 246 return cls.IN_AN_INCONSISTENT_STATE 247 case (NaiveState.FAILED, _): 248 return cls.FAILED 249 250 return cls.UNKNOWN 251 252 @property 253 def color(self) -> str: 254 """ 255 Return the display color associated with this RealState. 256 257 Returns: 258 str: A string representing the color for presentation purposes. 259 """ 260 return getattr(CFG.state_colors, self.name.lower())
25class NaiveState(Enum): 26 """ 27 Naive state of the job written into qqinfo files. 28 """ 29 30 QUEUED = 1 31 RUNNING = 2 32 FAILED = 3 33 FINISHED = 4 34 KILLED = 5 35 UNKNOWN = 6 36 37 def __str__(self) -> str: 38 """ 39 Return the lowercase string representation of the enum variant. 40 41 Returns: 42 str: The name of the state in lowercase. 43 """ 44 return self.name.lower() 45 46 @classmethod 47 def from_str(cls, s: str) -> "NaiveState": 48 """ 49 Convert a string to the corresponding NaiveState enum variant. 50 51 Args: 52 s (str): String representation of the state (case-insensitive). 53 54 Returns: 55 NaiveState: Corresponding enum variant. Returns UNKNOWN if no match is found. 56 """ 57 try: 58 return cls[s.upper()] 59 except KeyError: 60 return cls.UNKNOWN
Naive state of the job written into qqinfo files.
46 @classmethod 47 def from_str(cls, s: str) -> "NaiveState": 48 """ 49 Convert a string to the corresponding NaiveState enum variant. 50 51 Args: 52 s (str): String representation of the state (case-insensitive). 53 54 Returns: 55 NaiveState: Corresponding enum variant. Returns UNKNOWN if no match is found. 56 """ 57 try: 58 return cls[s.upper()] 59 except KeyError: 60 return cls.UNKNOWN
Convert a string to the corresponding NaiveState enum variant.
Arguments:
- s (str): String representation of the state (case-insensitive).
Returns:
NaiveState: Corresponding enum variant. Returns UNKNOWN if no match is found.
63class BatchState(Enum): 64 """ 65 State of the job according to the underlying batch system. 66 """ 67 68 RUNNING = 1 69 QUEUED = 2 70 FINISHED = 3 71 FAILED = 4 72 HELD = 5 73 EXITING = 6 74 WAITING = 7 75 MOVING = 8 76 SUSPENDED = 9 77 UNKNOWN = 10 78 79 def __str__(self) -> str: 80 """ 81 Return the lowercase string representation of the enum variant. 82 83 Returns: 84 str: The name of the batch state in lowercase. 85 """ 86 return self.name.lower() 87 88 @classmethod 89 def _code_to_state(cls) -> dict[str, str]: 90 """ 91 Internal mapping from one-letter codes to batch state names. 92 93 Returns: 94 dict[str, str]: Mapping of codes to corresponding batch state names. 95 """ 96 return { 97 "E": "exiting", 98 "H": "held", 99 "Q": "queued", 100 "R": "running", 101 "T": "moving", 102 "W": "waiting", 103 "S": "suspended", 104 "F": "finished", 105 "X": "failed", 106 } 107 108 @classmethod 109 def from_code(cls, code: str) -> "BatchState": 110 """ 111 Convert a one-letter batch system code to a BatchState enum variant. 112 113 Args: 114 code (str): One-letter code representing the batch system state. 115 116 Returns: 117 BatchState: Corresponding enum variant, or UNKNOWN if the code is invalid. 118 """ 119 code = code.upper() 120 if code not in cls._code_to_state(): 121 return cls.UNKNOWN 122 123 name = cls._code_to_state()[code].upper() 124 return cls[name] 125 126 def to_code(self) -> str: 127 """ 128 Return the one-letter code corresponding to this BatchState. 129 130 Returns: 131 str: One-letter code representing the batch state. Returns '?' if unknown. 132 """ 133 for k, v in self._code_to_state().items(): 134 if v.upper() == self.name: 135 return k 136 137 return "?" 138 139 @property 140 def color(self) -> str: 141 """ 142 Return the display color associated with this BatchState. 143 144 Returns: 145 str: A string representing the color for presentation purposes. 146 """ 147 if self is BatchState.MOVING: 148 return RealState.QUEUED.color 149 return RealState[self.name].color
State of the job according to the underlying batch system.
108 @classmethod 109 def from_code(cls, code: str) -> "BatchState": 110 """ 111 Convert a one-letter batch system code to a BatchState enum variant. 112 113 Args: 114 code (str): One-letter code representing the batch system state. 115 116 Returns: 117 BatchState: Corresponding enum variant, or UNKNOWN if the code is invalid. 118 """ 119 code = code.upper() 120 if code not in cls._code_to_state(): 121 return cls.UNKNOWN 122 123 name = cls._code_to_state()[code].upper() 124 return cls[name]
Convert a one-letter batch system code to a BatchState enum variant.
Arguments:
- code (str): One-letter code representing the batch system state.
Returns:
BatchState: Corresponding enum variant, or UNKNOWN if the code is invalid.
126 def to_code(self) -> str: 127 """ 128 Return the one-letter code corresponding to this BatchState. 129 130 Returns: 131 str: One-letter code representing the batch state. Returns '?' if unknown. 132 """ 133 for k, v in self._code_to_state().items(): 134 if v.upper() == self.name: 135 return k 136 137 return "?"
Return the one-letter code corresponding to this BatchState.
Returns:
str: One-letter code representing the batch state. Returns '?' if unknown.
139 @property 140 def color(self) -> str: 141 """ 142 Return the display color associated with this BatchState. 143 144 Returns: 145 str: A string representing the color for presentation purposes. 146 """ 147 if self is BatchState.MOVING: 148 return RealState.QUEUED.color 149 return RealState[self.name].color
Return the display color associated with this BatchState.
Returns:
str: A string representing the color for presentation purposes.
152class RealState(Enum): 153 """ 154 Precise state of the job obtained by combining information from NaiveState and BatchState. 155 """ 156 157 QUEUED = 1 158 HELD = 2 159 SUSPENDED = 3 160 WAITING = 4 161 RUNNING = 5 162 BOOTING = 6 163 KILLED = 7 164 FAILED = 8 165 FINISHED = 9 166 EXITING = 10 167 IN_AN_INCONSISTENT_STATE = 11 168 UNKNOWN = 12 169 170 def __str__(self) -> str: 171 """ 172 Return the human-readable string representation of the state. 173 174 Returns: 175 str: Lowercase string with underscores replaced by spaces. 176 """ 177 return self.name.lower().replace("_", " ") 178 179 @classmethod 180 def from_states( 181 cls, naive_state: NaiveState, batch_state: BatchState 182 ) -> "RealState": 183 """ 184 Determine the RealState of a job based on its NaiveState and BatchState. 185 186 Args: 187 naive_state (NaiveState): The naive state of the job from qqinfo. 188 batch_state (BatchState): The state of the job according to the batch system. 189 190 Returns: 191 RealState: The corresponding RealState. 192 """ 193 logger.debug( 194 f"Converting to RealState from '{naive_state}' and '{batch_state}'." 195 ) 196 match (naive_state, batch_state): 197 case (NaiveState.UNKNOWN, _): 198 return cls.UNKNOWN 199 200 case (NaiveState.QUEUED, BatchState.QUEUED | BatchState.MOVING): 201 return cls.QUEUED 202 case (NaiveState.QUEUED, BatchState.HELD): 203 return cls.HELD 204 case (NaiveState.QUEUED, BatchState.SUSPENDED): 205 return cls.SUSPENDED 206 case (NaiveState.QUEUED, BatchState.WAITING): 207 return cls.WAITING 208 case (NaiveState.QUEUED, BatchState.RUNNING): 209 return cls.BOOTING 210 case (NaiveState.QUEUED, _): 211 return cls.IN_AN_INCONSISTENT_STATE 212 213 case (NaiveState.RUNNING, BatchState.RUNNING): 214 return cls.RUNNING 215 case (NaiveState.RUNNING, BatchState.SUSPENDED): 216 return cls.SUSPENDED 217 case (NaiveState.RUNNING, _): 218 return cls.IN_AN_INCONSISTENT_STATE 219 220 case (NaiveState.KILLED, BatchState.RUNNING): 221 return cls.EXITING 222 case (NaiveState.KILLED, _): 223 return cls.KILLED 224 225 case (NaiveState.FINISHED, BatchState.RUNNING): 226 return cls.EXITING 227 case ( 228 NaiveState.FINISHED, 229 BatchState.QUEUED 230 | BatchState.WAITING 231 | BatchState.HELD 232 | BatchState.FAILED, 233 ): 234 return cls.IN_AN_INCONSISTENT_STATE 235 case (NaiveState.FINISHED, _): 236 return cls.FINISHED 237 238 case (NaiveState.FAILED, BatchState.RUNNING): 239 return cls.EXITING 240 case ( 241 NaiveState.FAILED, 242 BatchState.QUEUED 243 | BatchState.WAITING 244 | BatchState.HELD 245 | BatchState.FINISHED, 246 ): 247 return cls.IN_AN_INCONSISTENT_STATE 248 case (NaiveState.FAILED, _): 249 return cls.FAILED 250 251 return cls.UNKNOWN 252 253 @property 254 def color(self) -> str: 255 """ 256 Return the display color associated with this RealState. 257 258 Returns: 259 str: A string representing the color for presentation purposes. 260 """ 261 return getattr(CFG.state_colors, self.name.lower())
Precise state of the job obtained by combining information from NaiveState and BatchState.
179 @classmethod 180 def from_states( 181 cls, naive_state: NaiveState, batch_state: BatchState 182 ) -> "RealState": 183 """ 184 Determine the RealState of a job based on its NaiveState and BatchState. 185 186 Args: 187 naive_state (NaiveState): The naive state of the job from qqinfo. 188 batch_state (BatchState): The state of the job according to the batch system. 189 190 Returns: 191 RealState: The corresponding RealState. 192 """ 193 logger.debug( 194 f"Converting to RealState from '{naive_state}' and '{batch_state}'." 195 ) 196 match (naive_state, batch_state): 197 case (NaiveState.UNKNOWN, _): 198 return cls.UNKNOWN 199 200 case (NaiveState.QUEUED, BatchState.QUEUED | BatchState.MOVING): 201 return cls.QUEUED 202 case (NaiveState.QUEUED, BatchState.HELD): 203 return cls.HELD 204 case (NaiveState.QUEUED, BatchState.SUSPENDED): 205 return cls.SUSPENDED 206 case (NaiveState.QUEUED, BatchState.WAITING): 207 return cls.WAITING 208 case (NaiveState.QUEUED, BatchState.RUNNING): 209 return cls.BOOTING 210 case (NaiveState.QUEUED, _): 211 return cls.IN_AN_INCONSISTENT_STATE 212 213 case (NaiveState.RUNNING, BatchState.RUNNING): 214 return cls.RUNNING 215 case (NaiveState.RUNNING, BatchState.SUSPENDED): 216 return cls.SUSPENDED 217 case (NaiveState.RUNNING, _): 218 return cls.IN_AN_INCONSISTENT_STATE 219 220 case (NaiveState.KILLED, BatchState.RUNNING): 221 return cls.EXITING 222 case (NaiveState.KILLED, _): 223 return cls.KILLED 224 225 case (NaiveState.FINISHED, BatchState.RUNNING): 226 return cls.EXITING 227 case ( 228 NaiveState.FINISHED, 229 BatchState.QUEUED 230 | BatchState.WAITING 231 | BatchState.HELD 232 | BatchState.FAILED, 233 ): 234 return cls.IN_AN_INCONSISTENT_STATE 235 case (NaiveState.FINISHED, _): 236 return cls.FINISHED 237 238 case (NaiveState.FAILED, BatchState.RUNNING): 239 return cls.EXITING 240 case ( 241 NaiveState.FAILED, 242 BatchState.QUEUED 243 | BatchState.WAITING 244 | BatchState.HELD 245 | BatchState.FINISHED, 246 ): 247 return cls.IN_AN_INCONSISTENT_STATE 248 case (NaiveState.FAILED, _): 249 return cls.FAILED 250 251 return cls.UNKNOWN
Determine the RealState of a job based on its NaiveState and BatchState.
Arguments:
- naive_state (NaiveState): The naive state of the job from qqinfo.
- batch_state (BatchState): The state of the job according to the batch system.
Returns:
RealState: The corresponding RealState.
253 @property 254 def color(self) -> str: 255 """ 256 Return the display color associated with this RealState. 257 258 Returns: 259 str: A string representing the color for presentation purposes. 260 """ 261 return getattr(CFG.state_colors, self.name.lower())
Return the display color associated with this RealState.
Returns:
str: A string representing the color for presentation purposes.