# HG changeset patch # User Sylvain Thénault # Date 1478704457 -3600 # Node ID 0849a5eb57b85f22ea026f4b066f1fae9ec4c3ab # Parent 42461734c3e806955eae7c33e955b435e1874f36 [rtags] Allow to 'derive' rtags Since some releases, rtags (structure underlying uicfg) have selector and may be copied using something like: new_rtags = deepcopy(original_rtags) new_rtags.__module__ = __name__ new_rtags.__select__ = custom_selector The problem is that starting from that, both rtags wil diverge and changes in original_rtags won't be considered, while we usually want to set a few specific rules only in new_rtags. To fix this problem, this cset introduces the notion of "derivated/parent" rtag, eg: new_rtags = original_rtags.derive(__name__, custom_selector) Beside easier copying, when using the above method changes in original_rtags which are not overriden by new_rtags will be considered since it only hold its specific rules but look among its parent chain for non-found keys. Along the way, flake8 unittest_rtags. Closes #16164880 diff -r 42461734c3e8 -r 0849a5eb57b8 cubicweb/rtags.py --- a/cubicweb/rtags.py Wed Nov 09 16:08:23 2016 +0100 +++ b/cubicweb/rtags.py Wed Nov 09 16:14:17 2016 +0100 @@ -49,6 +49,15 @@ return tuple(str(k) for k in key) +def rtags_chain(rtag): + """Return the rtags chain, starting from the given one, and going back through each parent rtag + up to the root (i.e. which as no parent). + """ + while rtag is not None: + yield rtag + rtag = rtag._parent + + class RegistrableRtags(RegistrableInstance): __registry__ = 'uicfg' __select__ = yes() @@ -68,8 +77,12 @@ # function given as __init__ argument and kept for bw compat _init = _initfunc = None - def __init__(self): + def __init__(self, parent=None): self._tagdefs = {} + self._parent = parent + if parent is not None: + assert parent.__class__ is self.__class__, \ + 'inconsistent class for parent rtag {0}'.format(parent) def __repr__(self): # find a way to have more infos but keep it readable @@ -105,7 +118,7 @@ (stype, rtype, otype, tagged), value, ertype) self.del_rtag(stype, rtype, otype, tagged) break - if self._init is not None: + if self._parent is None and self._init is not None: self.apply(schema, self._init) def apply(self, schema, func): @@ -122,6 +135,19 @@ # rtag declaration api #################################################### + def derive(self, module, select): + """Return a derivated of this relation tag, associated to given module and selector. + + This derivated will hold a set of specific rules but delegate to its "parent" relation tags + for unfound keys. + + >>> class_afs = uicfg.autoform_section.derive(__name__, is_instance('Class')) + """ + copied = self.__class__(self) + copied.__module__ = module + copied.__select__ = select + return copied + def tag_attribute(self, key, *args, **kwargs): key = list(key) key.append('*') @@ -163,11 +189,15 @@ del self._tagdefs[key] def get(self, *key): + """Return value for the given key, by looking from the most specific key to the more + generic (using '*' wildcards). For each key, look into this rtag and its parent rtags. + """ for key in reversed(self._get_keys(*key)): - try: - return self._tagdefs[key] - except KeyError: - continue + for rtag in rtags_chain(self): + try: + return rtag._tagdefs[key] + except KeyError: + continue return None def etype_get(self, etype, rtype, role, ttype='*'): @@ -192,12 +222,18 @@ return rtags def get(self, stype, rtype, otype, tagged): + """Return value for the given key, which is an union of the values found from the most + specific key to the more generic (using '*' wildcards). For each key, look into this rtag + and its parent rtags. + """ rtags = self.tag_container_cls() for key in self._get_keys(stype, rtype, otype, tagged): - try: - rtags.update(self._tagdefs[key]) - except KeyError: - continue + for rtag in rtags_chain(self): + try: + rtags.update(rtag._tagdefs[key]) + break + except KeyError: + continue return rtags diff -r 42461734c3e8 -r 0849a5eb57b8 cubicweb/test/unittest_rtags.py --- a/cubicweb/test/unittest_rtags.py Wed Nov 09 16:08:23 2016 +0100 +++ b/cubicweb/test/unittest_rtags.py Wed Nov 09 16:14:17 2016 +0100 @@ -20,75 +20,123 @@ from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict + class RelationTagsTC(unittest.TestCase): - def test_rtags_expansion(self): - rtags = RelationTags() - rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary') - rtags.tag_subject_of(('*', 'evaluee', '*'), 'secondary') - rtags.tag_object_of(('*', 'tags', '*'), 'generated') - self.assertEqual(rtags.get('Note', 'evaluee', '*', 'subject'), - 'secondary') - self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'), - 'primary') - self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'), - None) - self.assertEqual(rtags.get('Note', 'tags', '*', 'subject'), - None) - self.assertEqual(rtags.get('*', 'tags', 'Note', 'object'), - 'generated') - self.assertEqual(rtags.get('Tag', 'tags', '*', 'object'), - 'generated') + def setUp(self): + self.rtags = RelationTags() + self.rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary') + self.rtags.tag_subject_of(('*', 'evaluee', '*'), 'secondary') + self.rtags.tag_object_of(('*', 'tags', '*'), 'generated') -# self.assertEqual(rtags.rtag('evaluee', 'Note', 'subject'), set(('secondary', 'link'))) -# self.assertEqual(rtags.is_inlined('evaluee', 'Note', 'subject'), False) -# self.assertEqual(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) -# self.assertEqual(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) -# self.assertEqual(rtags.rtag('ecrit_par', 'Note', 'object'), set(('inlineview', 'link'))) -# self.assertEqual(rtags.is_inlined('ecrit_par', 'Note', 'object'), True) -# class Personne2(Personne): -# id = 'Personne' -# __rtags__ = { -# ('evaluee', 'Note', 'subject') : set(('inlineview',)), -# } -# self.vreg.register(Personne2) -# rtags = Personne2.rtags -# self.assertEqual(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link'))) -# self.assertEqual(rtags.is_inlined('evaluee', 'Note', 'subject'), True) -# self.assertEqual(rtags.rtag('evaluee', 'Personne', 'subject'), set(('secondary', 'link'))) -# self.assertEqual(rtags.is_inlined('evaluee', 'Personne', 'subject'), False) + def test_expansion(self): + self.assertEqual(self.rtags.get('Note', 'evaluee', '*', 'subject'), + 'secondary') + self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'), + 'primary') + self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'), + None) + self.assertEqual(self.rtags.get('Note', 'tags', '*', 'subject'), + None) + self.assertEqual(self.rtags.get('*', 'tags', 'Note', 'object'), + 'generated') + self.assertEqual(self.rtags.get('Tag', 'tags', '*', 'object'), + 'generated') + + def test_expansion_with_parent(self): + derived_rtags = self.rtags.derive(__name__, None) + derived_rtags.tag_subject_of(('Societe', 'travaille', '*'), 'secondary') + derived_rtags.tag_subject_of(('Note', 'evaluee', '*'), 'primary') + self.rtags.tag_object_of(('*', 'tags', '*'), 'hidden') + + self.assertEqual(derived_rtags.get('Note', 'evaluee', '*', 'subject'), + 'primary') + self.assertEqual(derived_rtags.get('Societe', 'evaluee', '*', 'subject'), + 'secondary') + self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'), + 'secondary') + self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'), + None) + self.assertEqual(derived_rtags.get('*', 'tags', 'Note', 'object'), + 'hidden') - def test_rtagset_expansion(self): - rtags = RelationTagsSet() - rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary') - rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary') - self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'), - set(('primary', 'secondary'))) - self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'), - set(('secondary',))) - self.assertEqual(rtags.get('Note', 'tags', "*", 'subject'), - set()) +class RelationTagsSetTC(unittest.TestCase): + + def setUp(self): + self.rtags = RelationTagsSet() + self.rtags.tag_subject_of(('Societe', 'travaille', '*'), 'primary') + self.rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary') + + def test_expansion(self): + self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'), + set(('primary', 'secondary'))) + self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'), + set(('secondary',))) + self.assertEqual(self.rtags.get('Note', 'tags', "*", 'subject'), + set()) + + def test_expansion_with_parent(self): + derived_rtags = self.rtags.derive(__name__, None) + derived_rtags.tag_subject_of(('Societe', 'travaille', '*'), 'derived_primary') + self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'), + set(('derived_primary', 'secondary'))) + self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'), + set(('secondary',))) + + derived_rtags.tag_subject_of(('*', 'travaille', '*'), 'derived_secondary') + self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'), + set(('derived_primary', 'derived_secondary'))) + self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'), + set(('derived_secondary',))) + + self.assertEqual(derived_rtags.get('Note', 'tags', "*", 'subject'), + set()) + + +class RelationTagsDictTC(unittest.TestCase): - def test_rtagdict_expansion(self): - rtags = RelationTagsDict() - rtags.tag_subject_of(('Societe', 'travaille', '*'), - {'key1': 'val1', 'key2': 'val1'}) - rtags.tag_subject_of(('*', 'travaille', '*'), - {'key1': 'val0', 'key3': 'val0'}) - rtags.tag_subject_of(('Societe', 'travaille', '*'), - {'key2': 'val2'}) - self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'), - {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'}) - self.assertEqual(rtags.get('Note', 'travaille', '*', 'subject'), - {'key1': 'val0', 'key3': 'val0'}) - self.assertEqual(rtags.get('Note', 'tags', "*", 'subject'), - {}) + def setUp(self): + self.rtags = RelationTagsDict() + self.rtags.tag_subject_of(('Societe', 'travaille', '*'), + {'key1': 'val1', 'key2': 'val1'}) + self.rtags.tag_subject_of(('*', 'travaille', '*'), + {'key1': 'val0', 'key3': 'val0'}) + self.rtags.tag_subject_of(('Societe', 'travaille', '*'), + {'key2': 'val2'}) + + def test_expansion(self): + self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'), + {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'}) + self.assertEqual(self.rtags.get('Note', 'travaille', '*', 'subject'), + {'key1': 'val0', 'key3': 'val0'}) + self.assertEqual(self.rtags.get('Note', 'tags', "*", 'subject'), + {}) - rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4') - rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4') - self.assertEqual(rtags.get('Societe', 'travaille', '*', 'subject'), - {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'}) + self.rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4') + self.rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4') + self.assertEqual(self.rtags.get('Societe', 'travaille', '*', 'subject'), + {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'}) + + def test_expansion_with_parent(self): + derived_rtags = self.rtags.derive(__name__, None) + + derived_rtags.tag_subject_of(('Societe', 'travaille', '*'), + {'key0': 'val0'}) + self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'), + {'key0': 'val0', 'key1': 'val0', 'key3': 'val0'}) + self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'), + {'key1': 'val0', 'key3': 'val0'}) + self.assertEqual(derived_rtags.get('Note', 'tags', "*", 'subject'), + {}) + + derived_rtags.tag_subject_of(('*', 'travaille', '*'), + {'key0': 'val00', 'key4': 'val4'}) + self.assertEqual(derived_rtags.get('Societe', 'travaille', '*', 'subject'), + {'key0': 'val0', 'key4': 'val4'}) + self.assertEqual(derived_rtags.get('Note', 'travaille', '*', 'subject'), + {'key0': 'val00', 'key4': 'val4'}) + if __name__ == '__main__': unittest.main() diff -r 42461734c3e8 -r 0849a5eb57b8 flake8-ok-files.txt --- a/flake8-ok-files.txt Wed Nov 09 16:08:23 2016 +0100 +++ b/flake8-ok-files.txt Wed Nov 09 16:14:17 2016 +0100 @@ -76,6 +76,7 @@ cubicweb/test/unittest_mail.py cubicweb/test/unittest_repoapi.py cubicweb/test/unittest_req.py +cubicweb/test/unittest_rtags.py cubicweb/test/unittest_schema.py cubicweb/test/unittest_toolsutils.py cubicweb/test/unittest_utils.py