metainfo.py 7.77 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
14

15
16
from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict
import sys
17
18


19
20
__module__ = sys.modules[__name__]
MObjectBound = TypeVar('MObjectBound', bound='MObject')
21

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""

Discussion:
-----------

- Is the reflection interface really necessary? The ideas come from a compiled static
  language (Java). In Python the non reflective interface can always be dynamically
  generated and then reflectively used through pythons build in reflection mechanisms
  (e.g. getattr, hasattr, ...)
- Subsection creation would be most fluent with: ``system = System(m_parent=run)``
- Specialized data entry methods would require refl. interface:
    - m_add_array_values
    - m_add
"""

37

38
# Reflection
39

40
class MObjectMeta(type):
41

42
43
44
45
46
47
    def __new__(self, cls_name, bases, dct):
        cls = super().__new__(self, cls_name, bases, dct)
        init = getattr(cls, '__init_section_cls__')
        if init is not None:
            init()
        return cls
48
49


50
Content = Tuple[MObjectBound, Union[List[MObjectBound], MObjectBound], str, MObjectBound]
51
52


53
class MObject(metaclass=MObjectMeta):
54

55
56
57
    def __init__(self, **kwargs):
        self.m_section: 'Section' = kwargs.pop('m_section', getattr(self.__class__, 'm_section', None))  # TODO test with failure instead of None
        self.m_data = dict(**kwargs)
58

59
60
61
62
63
    @classmethod
    def __init_section_cls__(cls):
        if not hasattr(__module__, 'Quantity') or not hasattr(__module__, 'Section'):
            # no initialization during bootstrapping, will be done maunally
            return
64

65
66
67
68
69
        m_section = getattr(cls, 'm_section', None)
        if m_section is None:
            m_section = Section()
            setattr(cls, 'm_section', m_section)
        m_section.name = cls.__name__
70

71
72
73
74
        for name, value in cls.__dict__.items():
            if isinstance(value, Quantity):
                value.name = name
                m_section.m_add('Quantity', value)
75

76
77
    def m_create(self, section: Union['Section', Type[MObjectBound]], **kwargs) -> MObjectBound:
        """Creates a subsection and adds it this this section
78

79
80
81
82
        Args:
            section: The section definition of the subsection. It is either the
                definition itself, or the python class representing the section definition.
            **kwargs: Are used to initialize the subsection.
83

84
85
        Returns:
            The created subsection
86

87
88
89
90
91
92
93
94
95
        Raises:
            ValueError: If the given section is not a subsection of this section, or
                this given definition is not a section at all.
        """
        section_def: 'Section' = None
        if isinstance(section, type) and hasattr(section, 'm_section'):
            section_def = section.m_section
        elif isinstance(section, Section):
            section_def = section
96
        else:
97
            raise ValueError('Not a section definition')
98

99
100
        if section_def.parent != self.m_section:
            raise ValueError('Not a subsection')
101

102
103
        section_cls = section_def.section_cls
        section_instance = section_cls(**kwargs)
104

105
106
        if section_def.repeats:
            self.m_data.setdefault(section_def.name, []).append(section_instance)
107
        else:
108
            self.m_data[section_def.name] = section_instance
109

110
        return section_instance
111

112
113
114
    def m_add(self, name: str, value: Any):
        values = self.m_data.setdefault(name, [])
        values.append(value)
115

116
117
    def m_add_array_values(self, definition: Union[str, 'Quantity'], values, offset: int):
        pass
118

119
120
    def m_to_dict(self):
        pass
121

122
123
    def m_to_json(self):
        pass
124

125
126
127
128
    def m_all_contents(self) -> Iterable[Content]:
        for content in self.m_contents():
            for sub_content in content[0].m_all_contents():
                yield sub_content
129

130
            yield content
131

132
133
134
135
136
137
    def m_contents(self) -> Iterable[Content]:
        for name, attr in self.m_data.items():
            if isinstance(attr, list):
                for value in attr:
                    if isinstance(value, MObject):
                        yield value, attr, name, self
138

139
140
            elif isinstance(attr, MObject):
                yield value, value, name, self
141

142
143
144
145
146
147
148
    def __repr__(self):
        m_section_name = self.m_section.name
        name = ''
        if 'name' in self.m_data:
            name = self.m_data['name']

        return '%s:%s' % (name, m_section_name)
149
150


151
152
class Enum(list):
    pass
153
154


155
# M3
156

157
158
159
160
161
class Quantity(MObject):
    m_section: 'Section' = None
    name: 'Quantity' = None
    type: 'Quantity' = None
    shape: 'Quantity' = None
162

163
    __name = property(lambda self: self.m_data['name'])
164

165
    default = property(lambda self: None)
166

167
    def __get__(self, obj, type=None):
168
        return obj.m_data[self.__name]
169

170
    def __set__(self, obj, value):
171
        obj.m_data[self.__name] = value
172

173
    def __delete__(self, obj):
174
        del obj.m_data[self.__name]
175
176


177
178
179
180
181
182
class Section(MObject):
    m_section: 'Section' = None
    name: 'Quantity' = None
    repeats: 'Quantity' = None
    parent: 'Quantity' = None
    extends: 'Quantity' = None
183

184
    __all_instances: List['Section'] = []
185

186
187
    default = property(lambda self: [] if self.repeats else None)
    section_cls = property(lambda self: self.__class__)
188

189
190
191
192
    def __init__(self, **kwargs):
        # The mechanism that produces default values, depends on parent. Without setting
        # the parent default manually, an endless recursion will occur.
        kwargs.setdefault('parent', None)
193

194
195
        super().__init__(**kwargs)
        Section.__all_instances.append(self)
196

197
198
199
200
    # TODO cache
    @property
    def attributes(self) -> Dict[str, Union['Section', Quantity]]:
        """ All attribute (sub section and quantity) definitions. """
201

202
203
204
        attributes: Dict[str, Union[Section, Quantity]] = dict(**self.quantities)
        attributes.update(**self.sub_sections)
        return attributes
205

206
207
208
209
    # TODO cache
    @property
    def quantities(self) -> Dict[str, Quantity]:
        """ All quantity definition in the given section definition. """
210

211
212
213
        return {
            quantity.name: quantity
            for quantity in self.m_data.get('Quantity', [])}
214

215
216
217
218
    # TODO cache
    @property
    def sub_sections(self) -> Dict[str, 'Section']:
        """ All sub section definitions for this section definition. """
219

220
221
222
223
        return {
            sub_section.name: sub_section
            for sub_section in Section.__all_instances
            if sub_section.parent == self}
224
225


226
227
Section.m_section = Section(repeats=True, name='Section')
Section.m_section.m_section = Section.m_section
228

229
230
231
232
Section.name = Quantity(type=str, name='name')
Section.repeats = Quantity(type=bool, name='repeats')
Section.parent = Quantity(type=Section.m_section, name='parent')
Section.extends = Quantity(type=Section.m_section, shape=['0..*'], name='extends')
233

234
235
236
237
Quantity.m_section = Section(repeats=True, parent=Section.m_section, name='Quantity')
Quantity.name = Quantity(type=str, name='name')
Quantity.type = Quantity(type=Union[type, Enum, Section], name='type')
Quantity.shape = Quantity(type=Union[str, int], shape=['0..*'], name='shape')
238
239


240
241
242
class Package(MObject):
    m_section = Section()
    name = Quantity(type=str)
243
244


245
Section.m_section.parent = Package.m_section
246
247


248
249
class Definition(MObject):
    m_section = Section(extends=[Section.m_section, Quantity.m_section, Package.m_section])
250

251
    description = Quantity(type=str)