# HG changeset patch # User Lea Capgen , Sylvain Thénault , Denis Laxalde # Date 1410525971 -7200 # Node ID 0f3f965b6365bf56f143bf6ce2af48863d484f87 # Parent 8cdcbf3f4fd0062217240cabcacefffe6a9f7dee [CWEP002] Add schema finalization checks for computed relations (rules) Related to #3546717 diff -r 8cdcbf3f4fd0 -r 0f3f965b6365 schema.py --- 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 ########################################### diff -r 8cdcbf3f4fd0 -r 0f3f965b6365 test/unittest_schema.py --- 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()