[CWEP002] Add schema finalization checks for computed attributes
``finalize_computed_attributes`` essentially checks that computed attributes
types match with the type declaration of the attribute.
Related to #3546717.
--- a/schema.py Mon Jun 16 10:08:32 2014 +0200
+++ b/schema.py Mon Apr 28 14:11:23 2014 +0200
@@ -1017,8 +1017,56 @@
def finalize(self):
super(CubicWebSchema, self).finalize()
+ self.finalize_computed_attributes()
self.finalize_computed_relations()
+ def finalize_computed_attributes(self):
+ """Check consistency of computed attributes types"""
+ analyzer = ETypeResolver(self)
+ for relation in self.relations():
+ for rdef in relation.rdefs.itervalues():
+ if rdef.final and rdef.formula is not None:
+ computed_etype = rdef.subject.type
+ computed_attr = rdef.rtype
+ rqlst = parse(rdef.formula)
+ if len(rqlst.children) != 1:
+ raise BadSchemaDefinition(
+ 'computed attribute %(attr)s on %(etype)s: '
+ 'can not use UNION in formula %(form)r' %
+ dict(attr=computed_attr,
+ etype=computed_etype,
+ form=rdef.formula))
+ select = rqlst.children[0]
+ analyzer.visit(select)
+ if len(select.selection) != 1:
+ raise BadSchemaDefinition(
+ 'computed attribute %(attr)s on %(etype)s: '
+ 'can only select one term in formula %(form)r' %
+ dict(attr=computed_attr,
+ etype=computed_etype,
+ form=rdef.formula))
+ term = select.selection[0]
+ types = set(term.get_type(sol) for sol in select.solutions)
+ if len(types) != 1:
+ raise BadSchemaDefinition(
+ 'computed attribute %(attr)s on %(etype)s: '
+ 'multiple possible types (%(types)s) for formula %(form)s' %
+ dict(attr=computed_attr,
+ etype=computed_etype,
+ types=list(types),
+ form=rdef.formula))
+ computed_type = types.pop()
+ expected_type = rdef.object.type
+ if computed_type != expected_type:
+ raise BadSchemaDefinition(
+ 'computed attribute %(attr)s on %(etype)s: '
+ 'computed attribute type (%(comp_type)s) mismatch with '
+ 'specified type (%(attr_type)s)' %
+ dict(attr=computed_attr,
+ etype=computed_etype,
+ comp_type=computed_type,
+ attr_type=expected_type))
+
def finalize_computed_relations(self):
"""Build relation definitions for computed relations
--- a/test/unittest_schema.py Mon Jun 16 10:08:32 2014 +0200
+++ b/test/unittest_schema.py Mon Apr 28 14:11:23 2014 +0200
@@ -27,7 +27,7 @@
from yams import ValidationError, BadSchemaDefinition
from yams.constraints import SizeConstraint, StaticVocabularyConstraint
from yams.buildobjs import (RelationDefinition, EntityType, RelationType,
- String, SubjectRelation, ComputedRelation)
+ Int, String, SubjectRelation, ComputedRelation)
from yams.reader import fill_schema
from cubicweb.schema import (
@@ -284,6 +284,33 @@
'add': ('managers',),
'delete': ('managers',)})
+ def test_computed_attribute(self):
+ """Check schema finalization for computed attributes."""
+ class Person(EntityType):
+ salary = Int()
+
+ class works_for(RelationDefinition):
+ subject = 'Person'
+ object = 'Company'
+ cardinality = '?*'
+
+ class Company(EntityType):
+ total_salary = Int(formula='Any SUM(SA) GROUPBY X WHERE '
+ 'P works_for X, P salary SA')
+ good_schema = build_schema_from_namespace(vars().items())
+
+ class Company(EntityType):
+ total_salary = String(formula='Any SUM(SA) GROUPBY X WHERE '
+ 'P works_for X, P salary SA')
+
+ with self.assertRaises(BadSchemaDefinition) as exc:
+ bad_schema = build_schema_from_namespace(vars().items())
+
+ self.assertEqual(str(exc.exception),
+ 'computed attribute total_salary on Company: '
+ 'computed attribute type (Int) mismatch with '
+ 'specified type (String)')
+
class SchemaReaderComputedRelationAndAttributesTest(TestCase):