[CWEP002] Add schema finalization checks for computed relations (rules)
Related to #3546717
--- a/schema.py Fri Sep 12 14:10:03 2014 +0200
+++ b/schema.py Fri Sep 12 14:46:11 2014 +0200
@@ -41,6 +41,7 @@
fill_schema_from_namespace)
from rql import parse, nodes, RQLSyntaxError, TypeResolverException
+from rql.analyze import ETypeResolver
import cubicweb
from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized
@@ -1007,6 +1008,35 @@
def schema_by_eid(self, eid):
return self._eid_index[eid]
+ def iter_computed_relations(self):
+ for relation in self.relations():
+ if relation.rule:
+ yield relation
+
+ def finalize(self):
+ super(CubicWebSchema, self).finalize()
+ self.finalize_computed_relations()
+
+ def finalize_computed_relations(self):
+ """Build relation definitions for computed relations
+
+ The subject and object types are infered using rql analyzer.
+ """
+ analyzer = ETypeResolver(self)
+ for rschema in self.iter_computed_relations():
+ # XXX rule is valid if both S and O are defined and not in an exists
+ rqlexpr = RRQLExpression(rschema.rule)
+ rqlst = rqlexpr.snippet_rqlst
+ analyzer.visit(rqlst)
+ couples = set((sol['S'], sol['O']) for sol in rqlst.solutions)
+ for subjtype, objtype in couples:
+ if self[objtype].final:
+ raise BadSchemaDefinition('computed relations cannot be final')
+ rdef = ybo.RelationDefinition(
+ subjtype, rschema.type, objtype)
+ rdef.infered = True
+ self.add_relation_def(rdef)
+
# additional cw specific constraints ###########################################
--- a/test/unittest_schema.py Fri Sep 12 14:10:03 2014 +0200
+++ b/test/unittest_schema.py Fri Sep 12 14:46:11 2014 +0200
@@ -26,14 +26,16 @@
from yams import ValidationError, BadSchemaDefinition
from yams.constraints import SizeConstraint, StaticVocabularyConstraint
-from yams.buildobjs import RelationDefinition, EntityType, RelationType
+from yams.buildobjs import (RelationDefinition, EntityType, RelationType,
+ String, SubjectRelation, ComputedRelation)
from yams.reader import fill_schema
from cubicweb.schema import (
CubicWebSchema, CubicWebEntitySchema, CubicWebSchemaLoader,
RQLConstraint, RQLUniqueConstraint, RQLVocabularyConstraint,
RQLExpression, ERQLExpression, RRQLExpression,
- normalize_expression, order_eschemas, guess_rrqlexpr_mainvars)
+ normalize_expression, order_eschemas, guess_rrqlexpr_mainvars,
+ build_schema_from_namespace)
from cubicweb.devtools import TestServerConfiguration as TestConfiguration
from cubicweb.devtools.testlib import CubicWebTC
@@ -282,6 +284,61 @@
'delete': ('managers',)})
+class SchemaReaderComputedRelationAndAttributesTest(TestCase):
+
+ def test_infer_computed_relation(self):
+ class Person(EntityType):
+ name = String()
+
+ class Company(EntityType):
+ name = String()
+
+ class Service(EntityType):
+ name = String()
+
+ class works_for(RelationDefinition):
+ subject = 'Person'
+ object = 'Company'
+
+ class produce(RelationDefinition):
+ subject = ('Person', 'Company')
+ object = 'Service'
+
+ class achete(RelationDefinition):
+ subject = 'Person'
+ object = 'Service'
+
+ class produces_and_buys(ComputedRelation):
+ rule = 'S produce O, S achete O'
+
+ class produces_and_buys2(ComputedRelation):
+ rule = 'S works_for SO, SO produce O'
+
+ class reproduce(ComputedRelation):
+ rule = 'S produce O'
+
+ schema = build_schema_from_namespace(vars().items())
+
+ # check object/subject type
+ self.assertEqual([('Person','Service')],
+ schema['produces_and_buys'].rdefs.keys())
+ self.assertEqual([('Person','Service')],
+ schema['produces_and_buys2'].rdefs.keys())
+ self.assertEqual([('Company', 'Service'), ('Person', 'Service')],
+ schema['reproduce'].rdefs.keys())
+ # check relations as marked infered
+ self.assertTrue(
+ schema['produces_and_buys'].rdefs[('Person','Service')].infered)
+
+ del schema
+ class autoname(ComputedRelation):
+ rule = 'S produce X, X name O'
+
+ with self.assertRaises(BadSchemaDefinition) as cm:
+ build_schema_from_namespace(vars().items())
+ self.assertEqual(str(cm.exception), 'computed relations cannot be final')
+
+
class BadSchemaTC(TestCase):
def setUp(self):
self.loader = CubicWebSchemaLoader()