Coverage for ezdag/format.py: 90.2%

1# Copyright (C) 2023 Cardiff University

2#

3# This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.

4# If a copy of the MPL was not distributed with this file, You can obtain one at

5# <https://mozilla.org/MPL/2.0/>.

6#

7# SPDX-License-Identifier: MPL-2.0

8

9"""HTCondor submit command formatting utilities."""

10

11 from __future__ import annotations

12

13 from typing import Any, Callable, Iterable

14

15

16 def _separated_factory(delimiter: str) -> Callable:

17 """Factory function to define a delimiter-joiner function."""

18

19 def _joiner(value: Iterable[Any]) -> str:

20 return delimiter.join(map(str, value))

21

22 return _joiner

23

24

25 _comma_separated = _separated_factory(", ")

26 _space_separated = _separated_factory(" ")

27 _semicolon_separated = _separated_factory("; ")

28

29

30 def _format_argument(arg: str) -> str:

31 """Format a single command argument for inclusion in the condor_submit

32 ``arguments`` command.

33

34 According to the condor_submit _New Syntax_ description.

35 """

36 return "'{}'".format(

37 arg.replace('"', '""').replace("'", "''"),

38 )

39

40

41 def _format_arguments(value: Iterable[str]) -> str:

42 """Format an iterable of command arguments for inclusion in the condor_submit

43 ``arguments`` command.

44

45 According to the condor_submit _New Syntax_ description.

46 """

47 return '"{}"'.format(" ".join(map(_format_argument, value)))

48

49

50 def _format_environment(value: dict[str, Any]) -> str:

51 """Format a `dict` of environment settings for the condor_submit

52 ``environment`` command.

53 """

54 return '"{}"'.format(" ".join(f"{key}='{value}'" for key, value in value.items()))

55

56

57 def _format_classad_item(value: Any) -> str:

58 """Format a single classad expression item."""

59 return f"({value})"

60

61

62 def _format_classad_boolean_expression(

63 value: Iterable[Any],

64 operator: str = "&&",

65) -> str:

66 """Format an iterable of requirements expressions as a ClassAd Boolean

67 Expression for the condor_submit ``requirements`` command, or similar,

68 using the relevant operator.

69 """

70 return " {operator} ".join(map(_format_classad_item, value))

71

72

73 def _semicolon_separated_pairs(value: dict[str, Any]) -> str:

74 """Format a dict as a semicolon-separated list of key, value pairs

75 for the condor_submit ``transfer_output_remaps`` command, or similar.

76 """

77

78 def _format_item(key):

79 val = value[key]

80 if val is None:

81 return key

82 return f"{key}={val}"

83

84 return '"{}"'.format("; ".join(map(_format_item, value)))

85

86

87 DICT_FORMATTERS: dict[str, Callable] = {

88 "environment": _format_environment,

89 "transfer_output_remaps": _semicolon_separated_pairs,

90 "transfer_plugins": _semicolon_separated_pairs,

91}

92 LIST_FORMATTERS: dict[str, Callable] = {

93 "arguments": _format_arguments,

94 "getenv": _comma_separated,

95 "requirements": _format_classad_boolean_expression,

96 "transfer_checkpoint_files": _comma_separated,

97 "transfer_input_files": _comma_separated,

98 "transfer_output_files": _comma_separated,

99}

100

101

102 def _format_condor_command_value(

103 command: str,

104 value: Any,

105) -> str:

106 """Format a condor submit command value."""

107 if isinstance(value, dict) and command.lower() in DICT_FORMATTERS:

108 return DICT_FORMATTERS[command.lower()](value)

109 if isinstance(value, (list, tuple)) and command.lower() in LIST_FORMATTERS:

110 return LIST_FORMATTERS[command.lower()](value)

111

112 # special cases for dynamically-named commands

113 if isinstance(value, (list, tuple)) and (

114 "_oauth_permissions" in command or "_oauth_resource" in command

115 ):

116 return _space_separated(value)

117

118 return str(value)

119

120

121 def format_submit_description(desc: dict[str, Any]) -> dict[str, str]:

122 """Format a dictionary of submit description options.

123

124 This method converts 'arbitrary' Pythonic input types for the values of

125 condor submit description options into a string for inclusion in a

126 condor submit file.

127 """

128 out = {}

129 for key, value in desc.items():

130 out[key] = _format_condor_command_value(key, value)

131 return out