Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
89c1b8f7
Commit
89c1b8f7
authored
Sep 21, 2019
by
Markus Scheidgen
Browse files
Continued on the reflection interface and checking values against definitions.
parent
b4292c1c
Changes
1
Hide whitespace changes
Inline
Side-by-side
nomad/metainfo/metainfo.py
View file @
89c1b8f7
...
...
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from
typing
import
Type
,
TypeVar
,
Union
,
Tuple
,
Iterable
,
List
,
Any
,
Dict
from
typing
import
Type
,
TypeVar
,
Union
,
Tuple
,
Iterable
,
List
,
Any
,
Dict
,
cast
import
sys
...
...
@@ -24,19 +24,15 @@ MObjectBound = TypeVar('MObjectBound', bound='MObject')
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
"""
# Reflection
class
Enum
(
list
):
pass
class
MObjectMeta
(
type
):
def
__new__
(
self
,
cls_name
,
bases
,
dct
):
...
...
@@ -48,14 +44,59 @@ class MObjectMeta(type):
Content
=
Tuple
[
MObjectBound
,
Union
[
List
[
MObjectBound
],
MObjectBound
],
str
,
MObjectBound
]
SectionDef
=
Union
[
str
,
'Section'
,
Type
[
MObjectBound
]]
class
MObject
(
metaclass
=
MObjectMeta
):
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
"""Base class for all section objects on all meta-info levels.
All metainfo objects instantiate classes that inherit from ``MObject``. Each
section or quantity definition is an ``MObject``, each actual (meta-)data carrying
section is an ``MObject``. This class consitutes the reflection interface of the
meta-info, since it allows to manipulate sections (and therefore all meta-info data)
without having to know the specific sub-class.
It also carries all the data for each section. All sub-classes only define specific
sections in terms of possible sub-sections and quantities. The data is managed here.
The reflection insterface for reading and manipulating quantity values consists of
Pythons build in ``getattr``, ``setattr``, and ``del``, as well as member functions
:func:`m_add_value`, and :func:`m_add_values`.
Sub-sections and parent sections can be read and manipulated with :data:`m_parent`,
:func:`m_sub_section`, :func:`m_create`.
```
system = run.m_create(System)
assert system.m_parent == run
assert run.m_sub_section(System, system.m_parent_index) == system
```
Attributes:
m_section: The section definition that defines this sections, its possible
sub-sections and quantities.
m_parent: The parent section instance that this section is a sub-section of.
m_parent_index: For repeatable sections, parent keep a list of sub-sections for
each section definition. This is the index of this section in the respective
parent sub-section list.
m_data: The dictionary that holds all data of this section. It keeps the quantity
values and sub-section. It should only be read directly (and never manipulated)
if you are know what you are doing. You should always use the reflection interface
if possible.
"""
def
__init__
(
self
,
m_section
:
'Section'
=
None
,
m_parent
:
'MObject'
=
None
,
**
kwargs
):
self
.
m_section
:
'Section'
=
m_section
self
.
m_parent
:
'MObject'
=
m_parent
self
.
m_parent_index
=
-
1
self
.
m_data
=
dict
(
**
kwargs
)
if
self
.
m_section
is
None
:
self
.
m_section
=
getattr
(
self
.
__class__
,
'm_section'
,
None
)
else
:
assert
self
.
m_section
==
getattr
(
self
.
__class__
,
'm_section'
,
self
.
m_section
),
\
'Section class and section definition must match'
@
classmethod
def
__init_section_cls__
(
cls
):
if
not
hasattr
(
__module__
,
'Quantity'
)
or
not
hasattr
(
__module__
,
'Section'
):
...
...
@@ -67,13 +108,107 @@ class MObject(metaclass=MObjectMeta):
m_section
=
Section
()
setattr
(
cls
,
'm_section'
,
m_section
)
m_section
.
name
=
cls
.
__name__
m_section
.
section_cls
=
cls
for
name
,
value
in
cls
.
__dict__
.
items
():
if
isinstance
(
value
,
Quantity
):
value
.
name
=
name
m_section
.
m_add
(
'Quantity'
,
value
)
# manual manipulation of m_data due to bootstrapping
m_section
.
m_data
.
setdefault
(
'Quantity'
,
[]).
append
(
value
)
@
staticmethod
def
__type_check
(
definition
:
'Quantity'
,
value
:
Any
,
check_item
:
bool
=
False
):
"""Checks if the value fits the given quantity in type and shape; raises
ValueError if not."""
def
check_value
(
value
):
if
isinstance
(
definition
.
type
,
Enum
):
if
value
not
in
definition
.
type
:
raise
ValueError
(
'Not one of the enum values.'
)
elif
isinstance
(
definition
.
type
,
type
):
if
not
isinstance
(
value
,
definition
.
type
):
raise
ValueError
(
'Value has wrong type.'
)
elif
isinstance
(
definition
.
type
,
Section
):
if
not
isinstance
(
value
,
MObject
)
or
value
.
m_section
!=
definition
.
type
:
raise
ValueError
(
'The value is not a section of wrong section definition'
)
else
:
raise
Exception
(
'Invalid quantity type: %s'
%
str
(
definition
.
type
))
shape
=
None
try
:
shape
=
definition
.
shape
except
KeyError
:
pass
if
shape
is
None
or
len
(
shape
)
==
0
or
check_item
:
check_value
(
value
)
elif
len
(
shape
)
==
1
:
if
not
isinstance
(
value
,
list
):
raise
ValueError
(
'Wrong shape'
)
for
item
in
value
:
check_value
(
item
)
else
:
# TODO
raise
Exception
(
'Higher shapes not implemented'
)
# TODO check dimension
def
__resolve_section
(
self
,
definition
:
SectionDef
)
->
'Section'
:
"""Resolves and checks the given section definition. """
if
isinstance
(
definition
,
str
):
section
=
self
.
m_section
.
sub_sections
[
definition
]
else
:
if
isinstance
(
definition
,
type
):
section
=
getattr
(
definition
,
'm_section'
)
else
:
section
=
definition
if
section
.
name
not
in
self
.
m_section
.
sub_sections
:
raise
KeyError
(
'Not a sub section.'
)
return
section
def
m_create
(
self
,
section
:
Union
[
'Section'
,
Type
[
MObjectBound
]],
**
kwargs
)
->
MObjectBound
:
def
m_sub_section
(
self
,
definition
:
SectionDef
,
parent_index
:
int
=
-
1
)
->
MObjectBound
:
"""Returns the sub section for the given section definition and possible
parent_index (for repeatable sections).
Args:
definition: The definition of the section.
parent_index: The index of the desired section. This can be omitted for non
repeatable sections. If omitted for repeatable sections a exception
will be raised, if more then one sub-section exists. Likewise, if the given
index is out of range.
Raises:
KeyError: If the definition is not for a sub section
IndexError: If the given index is wrong, or if an index is given for a non
repeatable section
"""
section_def
=
self
.
__resolve_section
(
definition
)
m_data_value
=
self
.
m_data
[
section_def
.
name
]
if
isinstance
(
m_data_value
,
list
):
m_data_values
=
m_data_value
if
parent_index
==
-
1
:
if
len
(
m_data_values
)
==
1
:
return
m_data_values
[
0
]
else
:
raise
IndexError
()
else
:
return
m_data_values
[
parent_index
]
else
:
if
parent_index
!=
-
1
:
raise
IndexError
(
'Not a repeatable sub section.'
)
else
:
return
m_data_value
def
m_create
(
self
,
definition
:
SectionDef
,
**
kwargs
)
->
MObjectBound
:
"""Creates a subsection and adds it this this section
Args:
...
...
@@ -85,44 +220,67 @@ class MObject(metaclass=MObjectMeta):
The created subsection
Raises:
ValueError: If the given section is not a subsection of this section, or
this given definition is not a section at all.
KeyError: If the given section is not a subsection of this section.
"""
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
else
:
raise
ValueError
(
'Not a section definition'
)
if
section_def
.
parent
!=
self
.
m_section
:
raise
ValueError
(
'Not a subsection'
)
section_def
:
'Section'
=
self
.
__resolve_section
(
definition
)
section_cls
=
section_def
.
section_cls
section_instance
=
section_cls
(
**
kwargs
)
section_instance
=
section_cls
(
m_section
=
section_def
,
m_parent
=
self
,
**
kwargs
)
if
section_def
.
repeats
:
self
.
m_data
.
setdefault
(
section_def
.
name
,
[]).
append
(
section_instance
)
m_data_sections
=
self
.
m_data
.
setdefault
(
section_def
.
name
,
[])
section_index
=
len
(
m_data_sections
)
m_data_sections
.
append
(
section_instance
)
section_instance
.
m_parent_index
=
section_index
else
:
self
.
m_data
[
section_def
.
name
]
=
section_instance
return
section_instance
return
cast
(
MObjectBound
,
section_instance
)
def
m_add
(
self
,
name
:
str
,
value
:
Any
):
values
=
self
.
m_data
.
setdefault
(
name
,
[])
values
.
append
(
value
)
def
__resolve_quantity
(
self
,
definition
:
Union
[
str
,
'Quantity'
])
->
'Quantity'
:
"""Resolves and checks the given quantity definition. """
if
isinstance
(
definition
,
str
):
quantity
=
self
.
m_section
.
quantities
[
definition
]
def
m_add_array_values
(
self
,
definition
:
Union
[
str
,
'Quantity'
],
values
,
offset
:
int
):
pass
else
:
if
definition
.
m_parent
!=
self
.
m_section
:
raise
KeyError
(
'Quantity is not a quantity of this section.'
)
quantity
=
definition
return
quantity
def
m_add
(
self
,
definition
:
Union
[
str
,
'Quantity'
],
value
:
Any
):
"""Adds the given value to the given quantity."""
def
m_to_dict
(
self
):
quantity
=
self
.
__resolve_quantity
(
definition
)
MObject
.
__type_check
(
quantity
,
value
,
check_item
=
True
)
m_data_values
=
self
.
m_data
.
setdefault
(
quantity
.
name
,
[])
m_data_values
.
append
(
value
)
def
m_add_values
(
self
,
definition
:
Union
[
str
,
'Quantity'
],
values
:
Iterable
[
Any
]):
"""Adds the given values to the given quantity."""
quantity
=
self
.
__resolve_quantity
(
definition
)
for
value
in
values
:
MObject
.
__type_check
(
quantity
,
value
,
check_item
=
True
)
m_data_values
=
self
.
m_data
.
setdefault
(
quantity
.
name
,
[])
for
value
in
values
:
m_data_values
.
append
(
value
)
def
m_to_dict
(
self
)
->
Dict
[
str
,
Any
]:
"""Returns the data of this section as a json serializeable dictionary. """
pass
def
m_to_json
(
self
):
"""Returns the data of this section as a json string. """
pass
def
m_all_contents
(
self
)
->
Iterable
[
Content
]:
"""Returns an iterable over all sub and sub subs sections. """
for
content
in
self
.
m_contents
():
for
sub_content
in
content
[
0
].
m_all_contents
():
yield
sub_content
...
...
@@ -130,6 +288,7 @@ class MObject(metaclass=MObjectMeta):
yield
content
def
m_contents
(
self
)
->
Iterable
[
Content
]:
"""Returns an iterable over all direct subs sections. """
for
name
,
attr
in
self
.
m_data
.
items
():
if
isinstance
(
attr
,
list
):
for
value
in
attr
:
...
...
@@ -148,10 +307,6 @@ class MObject(metaclass=MObjectMeta):
return
'%s:%s'
%
(
name
,
m_section_name
)
class
Enum
(
list
):
pass
# M3
class
Quantity
(
MObject
):
...
...
@@ -168,6 +323,7 @@ class Quantity(MObject):
return
obj
.
m_data
[
self
.
__name
]
def
__set__
(
self
,
obj
,
value
):
MObject
.
__dict__
[
'_MObject__type_check'
].
__get__
(
MObject
)(
self
,
value
)
obj
.
m_data
[
self
.
__name
]
=
value
def
__delete__
(
self
,
obj
):
...
...
@@ -176,6 +332,7 @@ class Quantity(MObject):
class
Section
(
MObject
):
m_section
:
'Section'
=
None
section_cls
:
Type
[
MObject
]
=
None
name
:
'Quantity'
=
None
repeats
:
'Quantity'
=
None
parent
:
'Quantity'
=
None
...
...
@@ -184,7 +341,6 @@ class Section(MObject):
__all_instances
:
List
[
'Section'
]
=
[]
default
=
property
(
lambda
self
:
[]
if
self
.
repeats
else
None
)
section_cls
=
property
(
lambda
self
:
self
.
__class__
)
def
__init__
(
self
,
**
kwargs
):
# The mechanism that produces default values, depends on parent. Without setting
...
...
@@ -225,6 +381,7 @@ class Section(MObject):
Section
.
m_section
=
Section
(
repeats
=
True
,
name
=
'Section'
)
Section
.
m_section
.
m_section
=
Section
.
m_section
Section
.
m_section
.
section_cls
=
Section
Section
.
name
=
Quantity
(
type
=
str
,
name
=
'name'
)
Section
.
repeats
=
Quantity
(
type
=
bool
,
name
=
'repeats'
)
...
...
@@ -232,6 +389,7 @@ Section.parent = Quantity(type=Section.m_section, name='parent')
Section
.
extends
=
Quantity
(
type
=
Section
.
m_section
,
shape
=
[
'0..*'
],
name
=
'extends'
)
Quantity
.
m_section
=
Section
(
repeats
=
True
,
parent
=
Section
.
m_section
,
name
=
'Quantity'
)
Quantity
.
m_section
.
section_cls
=
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'
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment