Newer
Older
"""cubicweb-compound application package
Library cube to handle assemblies of composite entities
"""
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from functools import wraps
from cubicweb import neg_role
from cubicweb.schema import META_RTYPES, WORKFLOW_RTYPES, SYSTEM_RTYPES
def skip_meta(func):
"""Decorator to set `skiprtypes` and `skipetypes` parameters as frozensets
including "meta" objects that should be implicitely ignored.
"""
@wraps(func)
def wrapped(*args, **kwargs):
kwargs['skiprtypes'] = frozenset(
set(kwargs.get('skiprtypes', ())) | META_RTYPES |
WORKFLOW_RTYPES | SYSTEM_RTYPES)
kwargs['skipetypes'] = frozenset(kwargs.get('skipetypes', ()))
return func(*args, **kwargs)
return wrapped
class CompositeGraph(object):
"""Represent a graph of entity types related through composite relations.
A `CompositeGraph` can be used to iterate on schema objects through
`parent_relations`/`child_relations` methods as well as on entities through
`parent_related`/`child_related` methods.
"""
@skip_meta
def __init__(self, schema, skiprtypes=(), skipetypes=()):
self.schema = schema
self.skiprtypes = skiprtypes
self.skipetypes = skipetypes
def parent_relations(self, etype):
"""Yield composite relation information items walking the graph
upstream from `etype`.
These items are `(rtype, role), parents` where `parents` is a list of
possible parent entity types reachable through `(rtype, role)`
relation.
"""
return self._composite_relations(etype, topdown=False)
def child_relations(self, etype):
"""Yield composite relation information items walking the graph
downstream from `etype`.
These items are `(rtype, role), children` where `children` is a list of
possible child entity types reachable through `(rtype, role)` relation.
"""
return self._composite_relations(etype, topdown=True)
def _composite_relations(self, etype, topdown):
"""Yield `(rtype, role), etypes` values corresponding to arcs of the
graph of compositely-related entity types reachable from a `etype`
entity type. `etypes` is a list of possible entity types reachable
through `(rtype, role)` relation "upstream" (resp. "downstream") if
`topdown` is True (resp. False).
"""
try:
eschema = self.schema[etype]
except KeyError:
return
for rschema, teschemas, role in eschema.relation_definitions():
if rschema.meta or rschema in self.skiprtypes:
continue
composite_role = role if topdown else neg_role(role)
relation, children = (rschema.type, role), []
for target in teschemas:
if target in self.skipetypes:
continue
rdef = rschema.role_rdef(eschema, target, role)
if rdef.composite != composite_role:
continue
children.append(target.type)
if children:
yield relation, children
def parent_structure(self, etype, _visited=None):
"""Return the parent structure of the graph from `etype`.
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 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):
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 parent_related(self, entity):
"""Yield information items on entities related to `entity` through
composite relations walking the graph upstream from `entity`.
These items are tuples `(child, (rtype, role), parent)` where `role` is
the role of `child` entity in `rtype` relation with `parent`.
"""
return self._composite_related(entity, False)
def child_related(self, entity):
"""Yield information items on entities related to `entity` through
composite relations walking the graph downstream from `entity`.
These items are tuples `(parent, (rtype, role), child)` where `role` is
the role of `parent` entity in `rtype` relation with `child`.
"""
return self._composite_related(entity, True)
def _composite_related(self, entity, topdown, _visited=None):
"""Yield arcs of the graph of compositely-related entities reachable
from `entity`.
An "arc" is a tuple `(l_entity, (rtype, role), r_entity)` where
`l_entity` is a "parent" (resp. "child") entity when `topdown` is True
(resp. False) and, conversely, `r_entity` is the "child" (resp.
"parent") entity. `role` is always the role of `l_entity` in `rtype`
relation.
"""
if _visited is None:
_visited = set()
for (rtype, role), targettypes in self._composite_relations(
entity.cw_etype, topdown):
rset = entity.related(rtype, role=role, targettypes=targettypes)
for target in rset.entities():
yield entity, (rtype, role), target
if target.eid in _visited:
continue
_visited.add(target.eid)
for x in self._composite_related(target, topdown, _visited):
yield x