Commit 8112e0f3 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added constraint mechanism.

parent d5d26fc9
......@@ -65,6 +65,10 @@ class Run(MSection):
systems = SubSection(sub_section=System, repeats=True)
sccs = SubSection(sub_section=SCC, repeats=True)
def c_one_scc_per_system(self):
assert self.m_sub_section_count( == self.m_sub_section_count(Run.sccs),\
'Numbers of system does not match numbers of calculations.'
class VaspRun(Run):
""" All VASP specific quantities for section Run. """
......@@ -114,6 +118,9 @@ if __name__ == '__main__':
# To validate dimensions and custom constraints
print('errors: %s' % run.m_all_validate())
# To serialize the data:
serializable = run.m_to_dict()
# or
......@@ -135,7 +135,6 @@ See the reference of classes :class:`Section` and :class:`Quantities` for detail
# TODO event mechanism
# TODO validation and constraints
from typing import Type, TypeVar, Union, Tuple, Iterable, List, Any, Dict, Set, \
Callable as TypingCallable, cast
......@@ -731,6 +730,7 @@ class MSection(metaclass=MObjectMeta):
section_to_add_properties_to = m_def
constraints: List[str] = []
for name, attr in cls.__dict__.items():
# transfer names and descriptions for properties
if isinstance(attr, Property):
......@@ -746,6 +746,13 @@ class MSection(metaclass=MObjectMeta):
raise NotImplementedError('Unknown property kind.')
# transfer constraints
if inspect.isfunction(attr) and attr.__name__.startswith('c_'):
constraint = attr.__name__[2:]
m_def.constraints = constraints
# add section cls' section to the module's package
module_name = cls.__module__
pkg = Package.from_module(module_name)
......@@ -1081,7 +1088,7 @@ class MSection(metaclass=MObjectMeta):
return json.dumps(self.m_to_dict(), **kwargs)
def m_all_contents(self) -> Iterable[Content]:
"""Returns an iterable over all sub and sub subs sections. """
""" 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
......@@ -1173,6 +1180,34 @@ class MSection(metaclass=MObjectMeta):
return cast(MSectionBound, context)
def m_validate(self):
""" Evaluates all constraints of this section and returns a list of errors. """
errors: List[str] = []
for constraint_name in self.m_def.constraints:
constraint = getattr(self, 'c_%s' % constraint_name, None)
if constraint is None:
raise MetainfoError(
'Could not find implementation for contraint %s of section %s.' %
(constraint_name, self.m_def))
except AssertionError as e:
error_str = str(e).strip()
if error_str == '':
error_str = 'Constraint %s violated.' % constraint_name
return errors
def m_all_validate(self):
errors: List[str] = []
for section, _, _, _ in itertools.chain([(self, None, None, None)], self.m_all_contents()):
for error in section.m_validate():
return errors
def __repr__(self):
m_section_name =
# name_quantity_def = self.m_def.all_quantities.get('name', None)
......@@ -1408,6 +1443,7 @@ class Section(Definition):
base_sections: 'Quantity' = None
extends_base_section: 'Quantity' = None
constraints: 'Quantity' = None
def all_base_sections(self) -> Set['Section']:
......@@ -1549,6 +1585,17 @@ Section.extends_base_section = Quantity(
If True, the quantity definitions of this section will be added to the base section.
Only one base section is allowed.
Section.constraints = Quantity(
type=str, shape=['0..*'], name='constraints', description='''
Constraints are rules that a section must fulfil to be valid. This allows to implement
semantic checks that goes behind mere type or shape checks. This quantity takes
the names of constraints. Constraints have to be implemented as methods of the
section definition class. These constraints functions must be named ``c_<constraint name>```
and have no additional parameters. They can raise :class:`ConstraintVialated` or
an AssertionError to indicate that the constraint is not fulfilled for the ``self``
section. This quantity will be set automatically from all ``c_`` methods in the
respective section class.
SubSection.repeats = Quantity(
type=bool, name='repeats', default=False,
......@@ -161,6 +161,9 @@ class TestM2:
assert getattr(Run, 'x_vasp_raw_format', None) is not None
assert 'x_vasp_raw_format' in Run.m_def.all_quantities
def test_constraints(self):
assert len(Run.m_def.constraints) > 0
class TestM1:
""" Test for meta-info instances. """
......@@ -314,3 +317,10 @@ class TestM1:
assert run.m_resolve('/systems/0') == system
assert system.m_resolve('/systems/0') == system
def test_validate(self):
run = Run()
errors = run.m_validate()
assert len(errors) == 1
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment