Coverage for pystratum_common/ConstantClass.py : 0%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import importlib
2import inspect
3import re
4from typing import Any, Dict, List, Union
6from pystratum_backend.StratumStyle import StratumStyle
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: StratumStyle):
16 """
17 Object constructor.
19 :param str class_name: The name of class that acts like a namespace for constants.
20 :param PyStratumStyle 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: StratumStyle = 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.
66 :rtype: str
67 """
68 return inspect.getfile(self.__module)
70 # ------------------------------------------------------------------------------------------------------------------
71 def source(self) -> str:
72 """
73 Returns the source of the module with the class that acts like a namespace for constants.
75 :rtype: str
76 """
77 return inspect.getsource(self.__module)
79 # ------------------------------------------------------------------------------------------------------------------
80 def reload(self) -> None:
81 """
82 Reloads the module with the class that acts like a namespace for constants.
83 """
84 importlib.reload(self.__module)
86 # ------------------------------------------------------------------------------------------------------------------
87 def constants(self) -> Dict[str, Any]:
88 """
89 Gets the constants from the class that acts like a namespace for constants.
91 :rtype: dict<str,*>
92 """
93 ret = {}
95 name = self.__class_name.split('.')[-1]
96 constant_class = getattr(self.__module, name)
97 for name, value in constant_class.__dict__.items():
98 if re.match(r'^[A-Z][A-Z0-9_]*$', name):
99 ret[name] = value
101 return ret
103 # ------------------------------------------------------------------------------------------------------------------
104 def __extract_info(self, lines: List[str]) -> Dict[str, Union[str, int]]:
105 """
106 Extracts the following info from the source of the module with the class that acts like a namespace for
107 constants:
108 * Start line with constants
109 * Last line with constants
110 * Indent for constants
112 :param list[str] lines: The source of the module with the class that acts like a namespace for constants.
114 :rtype: dict<str,int|str>
115 """
116 ret = {'start_line': 0,
117 'last_line': 0,
118 'indent': ''}
120 mode = 1
121 count = 0
122 for line in lines:
123 if mode == 1:
124 if line.strip() == self.__annotation:
125 ret['start_line'] = count + 1
126 ret['last_line'] = count + 1
127 parts = re.match(r'^(\s+)', line)
128 ret['indent'] = parts.group(1)
129 mode = 2
131 elif mode == 2:
132 if line.strip() == '' or line.strip()[0:1] == '#':
133 mode = 3
134 else:
135 ret['last_line'] = count + 1
137 else:
138 break
140 count += 1
142 if mode != 3:
143 raise RuntimeError("Unable to find '{}' in file {}".format(self.__annotation, self.source()))
145 return ret
147 # ------------------------------------------------------------------------------------------------------------------
148 def source_with_constants(self, constants: Dict[str, int]) -> str:
149 """
150 Returns the source of the module with the class that acts like a namespace for constants with new constants.
152 :param dict[str,int] constants: The new constants.
154 :rtype: str
155 """
156 old_lines = self.source().split("\n")
157 info = self.__extract_info(old_lines)
159 new_lines = old_lines[0:info['start_line']]
161 for constant, value in sorted(constants.items()):
162 new_lines.append("{0}{1} = {2}".format(info['indent'], str(constant), str(value)))
164 new_lines.extend(old_lines[info['last_line']:])
166 return "\n".join(new_lines)
168# ----------------------------------------------------------------------------------------------------------------------