Skip to content

Instantly share code, notes, and snippets.

@aranega
Created May 19, 2019 16:20
Show Gist options
  • Save aranega/0be5b3847d39058d8da56b7aba4af587 to your computer and use it in GitHub Desktop.
Save aranega/0be5b3847d39058d8da56b7aba4af587 to your computer and use it in GitHub Desktop.
A WIP about model validation for PyEcore
from pyecore.ecore import *
from pyecore.resources import ResourceSet
from pyecore.utils import dispatch
from enum import unique, Enum
from functools import wraps
import pyecore.ecore as ecore
from collections import namedtuple
# Definition of error levels and diagnostics
@unique
class Level(Enum):
OK = 0
INFO = 1
WARNING = 2
ERROR = 3
CRITIC = 4
diagnostic = namedtuple('diagnostic', ['rule_id', 'level', 'elements', 'message'])
OK_diagnostic = diagnostic(None, Level.OK, set(), None)
# definition of some rules for ecore files
def no_name(named_element):
"""
Check if the name feature of an object is set
"""
if named_element.name:
return OK_diagnostic
eclass_name = named_element.eClass.name
return diagnostic('no_name', Level.ERROR, {named_element},
'{} instances *must* have a name'.format(eclass_name))
def same_name_in_namespace(obj, container):
"""
Check if two objects own the same name in a same namespace
"""
problematic_elements = set()
for element in (e for e in container if e is not obj):
if element.name == obj.name:
problematic_elements.add(element)
if not problematic_elements:
return OK_diagnostic
problematic_elements.add(obj)
message = 'These objects have the exact same name in the namespace: {}'.format(problematic_elements)
return diagnostic('same_name', Level.ERROR, problematic_elements, message)
def no_type(typed_element):
"""
Check if the type of an element is well set
"""
if typed_element.eType:
return OK_diagnostic
eclass_name = typed_element.eClass.name
message = '{} instances *must* have a type.'.format(eclass_name)
return diagnostic('no_type', Level.ERROR, {typed_element}, message)
# Ecore validator, it binds rules with specific EObjects
class EcoreValidator(object):
@dispatch
def do_switch(self, obj):
return set()
def apply_rules(self, obj, rules):
diagnostics = []
for rule in rules:
diagnostic = rule(obj)
if diagnostic.level is not Level.OK:
diagnostics.append(diagnostic)
return diagnostics
@do_switch.register(EClass)
def validate_EClass(self, eclass):
eclassifiers = eclass.ePackage.eClassifiers
same_name = lambda obj: same_name_in_namespace(obj, eclassifiers)
return self.apply_rules(eclass, [no_name, same_name])
@do_switch.register(EStructuralFeature)
def validate_EStructuralFeature(self, feature):
all_features = feature.eContainingClass.eAllStructuralFeatures()
same_name = lambda obj: same_name_in_namespace(obj, all_features)
return self.apply_rules(feature, [no_name, same_name, no_type])
@do_switch.register(ETypedElement)
def validate_ETypedElement(self, typed):
return self.apply_rules(typed, [no_type])
@do_switch.register(EPackage)
def validate_EPackage(self, epackage):
rules = [no_name]
if epackage.eSuperPackage:
parent_package = epackage.eSuperPackage.eSubpackages
same_name = lambda obj: same_name_in_namespace(obj, parent_package)
rules.append(same_name)
return self.apply_rules(epackage, rules)
# The generic validate function that applies a validator to a model root
def validate(root, validator):
diagnostics = [*validator.do_switch(root)]
for element in root.eAllContents():
for diagnostic in validator.do_switch(element):
if diagnostic not in diagnostics:
diagnostics.extend(diagnostic)
return diagnostics
# A dedicated validator for ecore
def validate_ecore(root):
return validate(root, EcoreValidator())
# A small example model
pack = EPackage(name='test')
A = EClass('A')
A.eStructuralFeatures.append(EAttribute('name'))
A.eStructuralFeatures.append(EAttribute('name', EString))
B = EClass('A')
pack.eClassifiers.extend([A, B])
# We launch the validation
from pprint import pprint
pprint(validate_ecore(pack))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment