Coverage for ezdag/format.py: 90.2%

41 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-29 15:59 -0700

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 

11from __future__ import annotations 

12 

13from typing import Any, Callable, Iterable 

14 

15 

16def _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 

30def _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 

41def _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 

50def _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 

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

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

59 return f"({value})" 

60 

61 

62def _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 

73def _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 

87DICT_FORMATTERS: dict[str, Callable] = { 

88 "environment": _format_environment, 

89 "transfer_output_remaps": _semicolon_separated_pairs, 

90 "transfer_plugins": _semicolon_separated_pairs, 

91} 

92LIST_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 

102def _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 

121def 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