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
« 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
6from pystratum_backend.StratumIO import StratumIO
9class ConstantClass:
10 """
11 Helper class for loading and modifying the class that acts like a namespace for constants.
12 """
14 # ------------------------------------------------------------------------------------------------------------------
15 def __init__(self, class_name: str, io: StratumIO):
16 """
17 Object constructor.
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 """
27 self.__module = None
28 """
29 The module of which the class that acts like a namespace for constants belongs.
31 :type: module
32 """
34 self.__annotation: str = '# PyStratum'
35 """
36 The comment after which the auto generated constants must be inserted.
37 """
39 self._io: StratumIO = io
40 """
41 The output decorator.
42 """
44 self.__load()
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)
59 self.__module = modules[-2]
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)
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)
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)
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 = {}
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
95 return ret
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
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': ''}
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
123 elif mode == 2:
124 if line.strip() == '' or line.strip()[0:1] == '#':
125 mode = 3
126 else:
127 ret['last_line'] = count + 1
129 else:
130 break
132 count += 1
134 if mode != 3:
135 raise RuntimeError("Unable to find '{}' in file {}".format(self.__annotation, self.source()))
137 return ret
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.
144 :param constants: The new constants.
145 """
146 old_lines = self.source().split("\n")
147 info = self.__extract_info(old_lines)
149 new_lines = old_lines[0:info['start_line']]
151 for constant, value in sorted(constants.items()):
152 new_lines.append("{0}{1} = {2}".format(info['indent'], str(constant), str(value)))
154 new_lines.extend(old_lines[info['last_line']:])
156 return "\n".join(new_lines)
158# ----------------------------------------------------------------------------------------------------------------------