metainfo.py 7.48 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
# Reflection
24

25
class MObjectMeta(type):
26

27
28
29
30
31
32
    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
33
34


35
Content = Tuple[MObjectBound, Union[List[MObjectBound], MObjectBound], str, MObjectBound]
36
37


38
class MObject(metaclass=MObjectMeta):
39

40
41
42
    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)
43

44
45
46
47
48
    @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
49

50
51
52
53
54
        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__
55

56
57
58
59
        for name, value in cls.__dict__.items():
            if isinstance(value, Quantity):
                value.name = name
                m_section.m_add('Quantity', value)
60

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

64
65
66
67
        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.
68

69
70
        Returns:
            The created subsection
71

72
73
74
75
76
77
78
79
80
        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
81
        else:
82
            raise ValueError('Not a section definition')
83

84
85
        if section_def.parent != self.m_section:
            raise ValueError('Not a subsection')
86

87
88
        section_cls = section_def.section_cls
        section_instance = section_cls(**kwargs)
89

90
91
        if section_def.repeats:
            self.m_data.setdefault(section_def.name, []).append(section_instance)
92
        else:
93
            self.m_data[section_def.name] = section_instance
94

95
        return section_instance
96

97
98
    def m_set(self, name: str, value: Any):
        self.m_data[name] = value
99

100
101
102
    def m_add(self, name: str, value: Any):
        values = self.m_data.setdefault(name, [])
        values.append(value)
103

104
105
106
    def m_get(self, name: str) -> Any:
        try:
            return self.m_data[name]
107

108
109
        except KeyError:
            return self.m_section.attributes[name].default
110

111
112
    def m_delete(self, name: str):
        del self.m_data[name]
113

114
115
    def m_to_dict(self):
        pass
116

117
118
    def m_to_json(self):
        pass
119

120
121
122
123
    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
124

125
            yield content
126

127
128
129
130
131
132
    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
133

134
135
            elif isinstance(attr, MObject):
                yield value, value, name, self
136

137
138
139
140
141
142
143
    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)
144
145


146
147
class Enum(list):
    pass
148
149


150
# M3
151

152
153
154
155
156
class Quantity(MObject):
    m_section: 'Section' = None
    name: 'Quantity' = None
    type: 'Quantity' = None
    shape: 'Quantity' = None
157

158
    __name = property(lambda self: self.m_data['name'])
159

160
    default = property(lambda self: None)
161

162
163
    def __get__(self, obj, type=None):
        return obj.m_get(self.__name)
164

165
166
    def __set__(self, obj, value):
        obj.m_set(self.__name, value)
167

168
169
    def __delete__(self, obj):
        obj.m_delete(self.__name)
170
171


172
173
174
175
176
177
class Section(MObject):
    m_section: 'Section' = None
    name: 'Quantity' = None
    repeats: 'Quantity' = None
    parent: 'Quantity' = None
    extends: 'Quantity' = None
178

179
    __all_instances: List['Section'] = []
180

181
182
    default = property(lambda self: [] if self.repeats else None)
    section_cls = property(lambda self: self.__class__)
183

184
185
186
187
    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)
188

189
190
        super().__init__(**kwargs)
        Section.__all_instances.append(self)
191

192
193
194
195
    # TODO cache
    @property
    def attributes(self) -> Dict[str, Union['Section', Quantity]]:
        """ All attribute (sub section and quantity) definitions. """
196

197
198
199
        attributes: Dict[str, Union[Section, Quantity]] = dict(**self.quantities)
        attributes.update(**self.sub_sections)
        return attributes
200

201
202
203
204
    # TODO cache
    @property
    def quantities(self) -> Dict[str, Quantity]:
        """ All quantity definition in the given section definition. """
205

206
207
208
        return {
            quantity.name: quantity
            for quantity in self.m_data.get('Quantity', [])}
209

210
211
212
213
    # TODO cache
    @property
    def sub_sections(self) -> Dict[str, 'Section']:
        """ All sub section definitions for this section definition. """
214

215
216
217
218
        return {
            sub_section.name: sub_section
            for sub_section in Section.__all_instances
            if sub_section.parent == self}
219
220


221
222
Section.m_section = Section(repeats=True, name='Section')
Section.m_section.m_section = Section.m_section
223

224
225
226
227
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')
228

229
230
231
232
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')
233
234


235
236
237
class Package(MObject):
    m_section = Section()
    name = Quantity(type=str)
238
239


240
Section.m_section.parent = Package.m_section
241
242


243
244
class Definition(MObject):
    m_section = Section(extends=[Section.m_section, Quantity.m_section, Package.m_section])
245

246
    description = Quantity(type=str)