Issues (10)

pystratum_common/ConstantClass.py (1 issue)

1
import importlib
2
import inspect
3
import re
4
from typing import Any, Dict, List, Union
5
6
from pystratum_backend.StratumStyle import StratumStyle
7
8
9 View Code Duplication
class ConstantClass:
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
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: StratumStyle):
16
        """
17
        Object constructor.
18
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
        """
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: StratumStyle = 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
        :rtype: str
67
        """
68
        return inspect.getfile(self.__module)
69
70
    # ------------------------------------------------------------------------------------------------------------------
71
    def source(self) -> str:
72
        """
73
        Returns the source of the module with the class that acts like a namespace for constants.
74
75
        :rtype: str
76
        """
77
        return inspect.getsource(self.__module)
78
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)
85
86
    # ------------------------------------------------------------------------------------------------------------------
87
    def constants(self) -> Dict[str, Any]:
88
        """
89
        Gets the constants from the class that acts like a namespace for constants.
90
91
        :rtype: dict<str,*>
92
        """
93
        ret = {}
94
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
100
101
        return ret
102
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
111
112
        :param list[str] lines: The source of the module with the class that acts like a namespace for constants.
113
114
        :rtype: dict<str,int|str>
115
        """
116
        ret = {'start_line': 0,
117
               'last_line':  0,
118
               'indent':     ''}
119
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
130
131
            elif mode == 2:
132
                if line.strip() == '' or line.strip()[0:1] == '#':
133
                    mode = 3
134
                else:
135
                    ret['last_line'] = count + 1
136
137
            else:
138
                break
139
140
            count += 1
141
142
        if mode != 3:
143
            raise RuntimeError("Unable to find '{}' in file {}".format(self.__annotation, self.source()))
144
145
        return ret
146
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.
151
152
        :param dict[str,int] constants: The new constants.
153
154
        :rtype: str
155
        """
156
        old_lines = self.source().split("\n")
157
        info = self.__extract_info(old_lines)
158
159
        new_lines = old_lines[0:info['start_line']]
160
161
        for constant, value in sorted(constants.items()):
162
            new_lines.append("{0}{1} = {2}".format(info['indent'], str(constant), str(value)))
163
164
        new_lines.extend(old_lines[info['last_line']:])
165
166
        return "\n".join(new_lines)
167
168
# ----------------------------------------------------------------------------------------------------------------------
169