Coverage for pystratum_common/ConstantClass.py: 0%

68 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-13 08:46 +0200

1import importlib 

2import inspect 

3import re 

4from typing import Any, Dict, List, Union 

5 

6from pystratum_backend.StratumIO import StratumIO 

7 

8 

9class ConstantClass: 

10 """ 

11 Helper class for loading and modifying the class that acts like a namespace for constants. 

12 """ 

13 

14 # ------------------------------------------------------------------------------------------------------------------ 

15 def __init__(self, class_name: str, io: StratumIO): 

16 """ 

17 Object constructor. 

18 

19 :param class_name: The name of class that acts like a namespace for constants. 

20 :param io: The output decorator. 

21 """ 

22 self.__class_name: str = class_name 

23 """ 

24 The name of class that acts like a namespace for constants. 

25 """ 

26 

27 self.__module = None 

28 """ 

29 The module of which the class that acts like a namespace for constants belongs. 

30 

31 :type: module 

32 """ 

33 

34 self.__annotation: str = '# PyStratum' 

35 """ 

36 The comment after which the auto generated constants must be inserted. 

37 """ 

38 

39 self._io: StratumIO = io 

40 """ 

41 The output decorator. 

42 """ 

43 

44 self.__load() 

45 

46 # ------------------------------------------------------------------------------------------------------------------ 

47 def __load(self) -> None: 

48 """ 

49 Loads dynamically the class that acts like a namespace for constants. 

50 """ 

51 parts = self.__class_name.split('.') 

52 module_name = ".".join(parts[:-1]) 

53 module = __import__(module_name) 

54 modules = [] 

55 for comp in parts[1:]: 

56 module = getattr(module, comp) 

57 modules.append(module) 

58 

59 self.__module = modules[-2] 

60 

61 # ------------------------------------------------------------------------------------------------------------------ 

62 def file_name(self) -> str: 

63 """ 

64 Returns the filename of the module with the class that acts like a namespace for constants. 

65 """ 

66 return inspect.getfile(self.__module) 

67 

68 # ------------------------------------------------------------------------------------------------------------------ 

69 def source(self) -> str: 

70 """ 

71 Returns the source of the module with the class that acts like a namespace for constants. 

72 """ 

73 return inspect.getsource(self.__module) 

74 

75 # ------------------------------------------------------------------------------------------------------------------ 

76 def reload(self) -> None: 

77 """ 

78 Reloads the module with the class that acts like a namespace for constants. 

79 """ 

80 importlib.reload(self.__module) 

81 

82 # ------------------------------------------------------------------------------------------------------------------ 

83 def constants(self) -> Dict[str, Any]: 

84 """ 

85 Gets the constants from the class that acts like a namespace for constants. 

86 """ 

87 ret = {} 

88 

89 name = self.__class_name.split('.')[-1] 

90 constant_class = getattr(self.__module, name) 

91 for name, value in constant_class.__dict__.items(): 

92 if re.match(r'^[A-Z][A-Z0-9_]*$', name): 

93 ret[name] = value 

94 

95 return ret 

96 

97 # ------------------------------------------------------------------------------------------------------------------ 

98 def __extract_info(self, lines: List[str]) -> Dict[str, Union[str, int]]: 

99 """ 

100 Extracts the following info from the source of the module with the class that acts like a namespace for 

101 constants: 

102 * Start line with constants 

103 * Last line with constants 

104 * Indent for constants 

105 

106 :param lines: The source of the module with the class that acts like a namespace for constants. 

107 """ 

108 ret = {'start_line': 0, 

109 'last_line': 0, 

110 'indent': ''} 

111 

112 mode = 1 

113 count = 0 

114 for line in lines: 

115 if mode == 1: 

116 if line.strip() == self.__annotation: 

117 ret['start_line'] = count + 1 

118 ret['last_line'] = count + 1 

119 parts = re.match(r'^(\s+)', line) 

120 ret['indent'] = parts.group(1) 

121 mode = 2 

122 

123 elif mode == 2: 

124 if line.strip() == '' or line.strip()[0:1] == '#': 

125 mode = 3 

126 else: 

127 ret['last_line'] = count + 1 

128 

129 else: 

130 break 

131 

132 count += 1 

133 

134 if mode != 3: 

135 raise RuntimeError("Unable to find '{}' in file {}".format(self.__annotation, self.source())) 

136 

137 return ret 

138 

139 # ------------------------------------------------------------------------------------------------------------------ 

140 def source_with_constants(self, constants: Dict[str, int]) -> str: 

141 """ 

142 Returns the source of the module with the class that acts like a namespace for constants with new constants. 

143 

144 :param constants: The new constants. 

145 """ 

146 old_lines = self.source().split("\n") 

147 info = self.__extract_info(old_lines) 

148 

149 new_lines = old_lines[0:info['start_line']] 

150 

151 for constant, value in sorted(constants.items()): 

152 new_lines.append("{0}{1} = {2}".format(info['indent'], str(constant), str(value))) 

153 

154 new_lines.extend(old_lines[info['last_line']:]) 

155 

156 return "\n".join(new_lines) 

157 

158# ----------------------------------------------------------------------------------------------------------------------