qq_lib.properties.interpreter

 1# Released under MIT License.
 2# Copyright (c) 2025-2026 Ladislav Bartos and Robert Vacha Lab
 3
 4import shutil
 5import socket
 6from dataclasses import dataclass, field
 7from typing import Any
 8
 9from qq_lib.core.config import CFG
10from qq_lib.core.error import QQError
11
12
13@dataclass(frozen=True)
14class Interpreter:
15    """
16    Configuration for the interpreter used to execute a job script.
17
18    Attributes:
19        executable: Name or path of the interpreter executable.
20            Defaults to the executable from `CFG.runner.default_interpreter`.
21        arguments: Additional command-line arguments passed to the interpreter.
22            Defaults to arguments parsed from `CFG.runner.default_interpreter`,
23            or an empty list if `executable` is provided explicitly.
24    """
25
26    executable: str | None = None
27    arguments: list[str] = field(default_factory=list)
28
29    def __post_init__(self):
30        """Parse executable and arguments from `CFG.runner.default_interpreter` if no executable was provided."""
31        if self.executable is None:
32            binary, *arguments = CFG.runner.default_interpreter.split()
33            object.__setattr__(self, "executable", binary)
34            object.__setattr__(self, "arguments", arguments)
35
36    @classmethod
37    def from_str(cls, s: str) -> "Interpreter":
38        """
39        Create an Interpreter from a string containing the executable and optional arguments.
40
41        The string is split on whitespace. The first token is used as the executable
42        and any remaining tokens become arguments.
43
44        Args:
45            s (str): Space-separated string of the interpreter executable followed by
46                optional arguments. For example: "python3 -u" or "/usr/bin/bash".
47
48        Returns:
49            Interpreter:An Interpreter instance with the parsed executable and arguments.
50        """
51        executable, *arguments = s.split()
52        return cls(executable=executable, arguments=arguments)
53
54    @classmethod
55    def from_dict(cls, d: dict[str, Any]) -> "Interpreter":
56        """
57        Create an Interpreter from a dictionary.
58
59        Args:
60            d (dict[str, Any]): Dictionary with keys matching the dataclass fields
61                (`executable` and `arguments`).
62
63        Returns:
64            Interpreter: An Interpreter instance constructed from the dictionary values.
65        """
66        return cls(**d)
67
68    def to_dict(self) -> dict[str, Any]:
69        """
70        Serialize the Interpreter to a dictionary.
71
72        Returns:
73            A dictionary with `executable` and `arguments` keys.
74        """
75        return {"executable": self.executable, "arguments": self.arguments}
76
77    def to_command_list(self) -> list[str]:
78        """
79        Resolve the executable to its full path and build the command list.
80
81        Uses `shutil.which` to locate the interpreter on the current node.
82        The returned list is suitable for use with `subprocess` calls.
83
84        Returns:
85            list[str]: A list containing the resolved absolute path of the executable
86            followed by any configured arguments.
87
88        Raises:
89            QQError: If the interpreter executable cannot be found on the current node.
90        """
91        # enforced in __post_init__
92        assert self.executable is not None
93
94        if not (full := shutil.which(self.executable)):
95            raise QQError(
96                f"Interpreter '{self.executable}' is not available on node '{socket.getfqdn()}'."
97            )
98
99        return [full] + self.arguments
@dataclass(frozen=True)
class Interpreter:
 14@dataclass(frozen=True)
 15class Interpreter:
 16    """
 17    Configuration for the interpreter used to execute a job script.
 18
 19    Attributes:
 20        executable: Name or path of the interpreter executable.
 21            Defaults to the executable from `CFG.runner.default_interpreter`.
 22        arguments: Additional command-line arguments passed to the interpreter.
 23            Defaults to arguments parsed from `CFG.runner.default_interpreter`,
 24            or an empty list if `executable` is provided explicitly.
 25    """
 26
 27    executable: str | None = None
 28    arguments: list[str] = field(default_factory=list)
 29
 30    def __post_init__(self):
 31        """Parse executable and arguments from `CFG.runner.default_interpreter` if no executable was provided."""
 32        if self.executable is None:
 33            binary, *arguments = CFG.runner.default_interpreter.split()
 34            object.__setattr__(self, "executable", binary)
 35            object.__setattr__(self, "arguments", arguments)
 36
 37    @classmethod
 38    def from_str(cls, s: str) -> "Interpreter":
 39        """
 40        Create an Interpreter from a string containing the executable and optional arguments.
 41
 42        The string is split on whitespace. The first token is used as the executable
 43        and any remaining tokens become arguments.
 44
 45        Args:
 46            s (str): Space-separated string of the interpreter executable followed by
 47                optional arguments. For example: "python3 -u" or "/usr/bin/bash".
 48
 49        Returns:
 50            Interpreter:An Interpreter instance with the parsed executable and arguments.
 51        """
 52        executable, *arguments = s.split()
 53        return cls(executable=executable, arguments=arguments)
 54
 55    @classmethod
 56    def from_dict(cls, d: dict[str, Any]) -> "Interpreter":
 57        """
 58        Create an Interpreter from a dictionary.
 59
 60        Args:
 61            d (dict[str, Any]): Dictionary with keys matching the dataclass fields
 62                (`executable` and `arguments`).
 63
 64        Returns:
 65            Interpreter: An Interpreter instance constructed from the dictionary values.
 66        """
 67        return cls(**d)
 68
 69    def to_dict(self) -> dict[str, Any]:
 70        """
 71        Serialize the Interpreter to a dictionary.
 72
 73        Returns:
 74            A dictionary with `executable` and `arguments` keys.
 75        """
 76        return {"executable": self.executable, "arguments": self.arguments}
 77
 78    def to_command_list(self) -> list[str]:
 79        """
 80        Resolve the executable to its full path and build the command list.
 81
 82        Uses `shutil.which` to locate the interpreter on the current node.
 83        The returned list is suitable for use with `subprocess` calls.
 84
 85        Returns:
 86            list[str]: A list containing the resolved absolute path of the executable
 87            followed by any configured arguments.
 88
 89        Raises:
 90            QQError: If the interpreter executable cannot be found on the current node.
 91        """
 92        # enforced in __post_init__
 93        assert self.executable is not None
 94
 95        if not (full := shutil.which(self.executable)):
 96            raise QQError(
 97                f"Interpreter '{self.executable}' is not available on node '{socket.getfqdn()}'."
 98            )
 99
100        return [full] + self.arguments

Configuration for the interpreter used to execute a job script.

Attributes:
  • executable: Name or path of the interpreter executable. Defaults to the executable from CFG.runner.default_interpreter.
  • arguments: Additional command-line arguments passed to the interpreter. Defaults to arguments parsed from CFG.runner.default_interpreter, or an empty list if executable is provided explicitly.
Interpreter(executable: str | None = None, arguments: list[str] = <factory>)
executable: str | None = None
arguments: list[str]
@classmethod
def from_str(cls, s: str) -> Interpreter:
37    @classmethod
38    def from_str(cls, s: str) -> "Interpreter":
39        """
40        Create an Interpreter from a string containing the executable and optional arguments.
41
42        The string is split on whitespace. The first token is used as the executable
43        and any remaining tokens become arguments.
44
45        Args:
46            s (str): Space-separated string of the interpreter executable followed by
47                optional arguments. For example: "python3 -u" or "/usr/bin/bash".
48
49        Returns:
50            Interpreter:An Interpreter instance with the parsed executable and arguments.
51        """
52        executable, *arguments = s.split()
53        return cls(executable=executable, arguments=arguments)

Create an Interpreter from a string containing the executable and optional arguments.

The string is split on whitespace. The first token is used as the executable and any remaining tokens become arguments.

Arguments:
  • s (str): Space-separated string of the interpreter executable followed by optional arguments. For example: "python3 -u" or "/usr/bin/bash".
Returns:

Interpreter:An Interpreter instance with the parsed executable and arguments.

@classmethod
def from_dict( cls, d: dict[str, typing.Any]) -> Interpreter:
55    @classmethod
56    def from_dict(cls, d: dict[str, Any]) -> "Interpreter":
57        """
58        Create an Interpreter from a dictionary.
59
60        Args:
61            d (dict[str, Any]): Dictionary with keys matching the dataclass fields
62                (`executable` and `arguments`).
63
64        Returns:
65            Interpreter: An Interpreter instance constructed from the dictionary values.
66        """
67        return cls(**d)

Create an Interpreter from a dictionary.

Arguments:
  • d (dict[str, Any]): Dictionary with keys matching the dataclass fields (executable and arguments).
Returns:

Interpreter: An Interpreter instance constructed from the dictionary values.

def to_dict(self) -> dict[str, typing.Any]:
69    def to_dict(self) -> dict[str, Any]:
70        """
71        Serialize the Interpreter to a dictionary.
72
73        Returns:
74            A dictionary with `executable` and `arguments` keys.
75        """
76        return {"executable": self.executable, "arguments": self.arguments}

Serialize the Interpreter to a dictionary.

Returns:

A dictionary with executable and arguments keys.

def to_command_list(self) -> list[str]:
 78    def to_command_list(self) -> list[str]:
 79        """
 80        Resolve the executable to its full path and build the command list.
 81
 82        Uses `shutil.which` to locate the interpreter on the current node.
 83        The returned list is suitable for use with `subprocess` calls.
 84
 85        Returns:
 86            list[str]: A list containing the resolved absolute path of the executable
 87            followed by any configured arguments.
 88
 89        Raises:
 90            QQError: If the interpreter executable cannot be found on the current node.
 91        """
 92        # enforced in __post_init__
 93        assert self.executable is not None
 94
 95        if not (full := shutil.which(self.executable)):
 96            raise QQError(
 97                f"Interpreter '{self.executable}' is not available on node '{socket.getfqdn()}'."
 98            )
 99
100        return [full] + self.arguments

Resolve the executable to its full path and build the command list.

Uses shutil.which to locate the interpreter on the current node. The returned list is suitable for use with subprocess calls.

Returns:

list[str]: A list containing the resolved absolute path of the executable followed by any configured arguments.

Raises:
  • QQError: If the interpreter executable cannot be found on the current node.