# HG changeset patch
# User Denis Laxalde <denis.laxalde@logilab.fr>
# Date 1437142156 -7200
#      Fri Jul 17 16:09:16 2015 +0200
# Node ID 12bde733d7b204c2c8384bb97883ed55e5cb3eec
# Parent  c9088752d968cc6daed76360e629e4cb885c7edd
Add parent_structure method to CompositeGraph

This method returns a dictionary mapping entity type to composite relation
information allowing to walk the schema graph upstream from these entity
types.

diff --git a/entities.py b/entities.py
--- a/entities.py
+++ b/entities.py
@@ -112,24 +112,31 @@
             if children:
                 yield relation, children
 
-    def parent_structure(self, etype):
-        """Return the parent structure of the graph from `etype`."""
-        return self._structure(etype, False)
+    def parent_structure(self, etype, _visited=None):
+        """Return the parent structure of the graph from `etype`.
 
-    def child_structure(self, etype):
-        """Return the child structure of the graph from `etype`."""
-        return self._structure(etype, True)
+        The structure is a dictionary mapping entity type in the graph with
+        root `etype` to relation information allowing to walk the graph
+        upstream from this entity type.
+        """
+        if _visited is None:
+            _visited = set()
+        structure = {}
 
-    def _structure(self, etype, topdown):
-        """Return a nested-dict structure obtained from walking the graph of
-        schema objects from any `parent` entity type in the graph.
-        """
-        graph = {}
-        for relation, children in self._composite_relations(etype, topdown):
+        def update_structure(left, relation, right):
+            structure.setdefault(left, {}).setdefault(relation, []).append(right)
+
+        for (rtype, role), children in self.child_relations(etype):
             for child in sorted(children):
-                graph.setdefault(relation, {})[child] = self._structure(
-                    child, topdown)
-        return graph
+                update_structure(child, (rtype, neg_role(role)), etype)
+                if child in _visited:
+                    continue
+                _visited.add(child)
+                for left, rels in self.parent_structure(child, _visited).iteritems():
+                    for relation, rights in rels.iteritems():
+                        for right in rights:
+                            update_structure(left, relation, right)
+        return structure
 
     def topdown_definition(self, parent):
         """Return the container definition of the composite graph from
diff --git a/test/test_compound.py b/test/test_compound.py
--- a/test/test_compound.py
+++ b/test/test_compound.py
@@ -55,71 +55,42 @@
                               [(('relates', 'object'), ['Anecdote']),
                                (('event', 'object'), ['Biography'])])
 
-    def test_parent_structure_singleton(self):
+    def test_parent_structure_agent(self):
         graph = CompositeGraph(self.schema)
-        structure = graph.parent_structure('OnlineAccount')
-        expected = {('account', 'object'): {'Agent': {}}}
-        self.assertEqual(structure, expected)
-
-    def test_child_structure_singleton(self):
-        graph = CompositeGraph(self.schema)
-        structure = graph.child_structure('OnlineAccount')
-        self.assertEqual(structure, {})
-
-    def test_child_structure(self):
-        graph = CompositeGraph(self.schema)
-        structure = graph.child_structure('Agent')
+        structure = graph.parent_structure('Agent')
         expected = {
-            ('account', 'subject'): {
-                'OnlineAccount': {}
+            'OnlineAccount': {
+                ('account', 'object'): ['Agent'],
+            },
+            'Biography': {
+                ('biography', 'object'): ['Agent'],
             },
-            ('biography', 'subject'): {
-                'Biography': {
-                    ('event', 'subject'): {
-                        'Event': {},
-                        'Anecdote': {
-                            ('relates', 'subject'):
-                                {'Event': {}}
-                        },
-                    },
-                },
+            'Event': {
+                ('event', 'object'): ['Biography'],
+                ('relates', 'object'): ['Anecdote'],
             },
-            ('narrated_by', 'object'): {
-                'Anecdote': {
-                    ('relates', 'subject'):
-                        {'Event': {}}
-                },
+            'Anecdote': {
+                ('event', 'object'): ['Biography'],
+                ('narrated_by', 'subject'): ['Agent'],
             },
         }
         self.assertEqual(structure, expected)
 
-    def test_parent_structure(self):
+    def test_parent_structure_anecdote(self):
         graph = CompositeGraph(self.schema)
+        self.set_description('Anecdote')
         structure = graph.parent_structure('Anecdote')
-        self.assertEqual(structure, {
-            ('event', 'object'): {'Biography': {
-                ('biography', 'object'): {'Agent': {}},
-            }},
-            ('narrated_by', 'subject'): {'Agent': {}},
-        })
-        structure = graph.parent_structure('Event')
-        self.assertEqual(structure, {
-            ('event', 'object'): {
-                'Biography': {('biography', 'object'): {'Agent': {}}}
+        expected = {
+            'Event': {
+                ('relates', 'object'): ['Anecdote'],
             },
-            ('relates', 'object'): {
-                'Anecdote': {
-                    ('event', 'object'): {
-                        'Biography': {
-                            ('biography', 'object'): {'Agent': {}},
-                        },
-                    },
-                    ('narrated_by', 'subject'): {'Agent': {}},
-                },
-            },
-        })
+        }
+        self.assertEqual(structure, expected)
+
+    def test_parent_structure_leaf(self):
+        graph = CompositeGraph(self.schema)
         structure = graph.parent_structure('OnlineAccount')
-        self.assertEqual(structure, {('account', 'object'): {'Agent': {}}})
+        self.assertEqual(structure, {})
 
     def test_topdown_definition(self):
         graph = CompositeGraph(self.schema)