[rtags] Allow to 'derive' rtags
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 09 Nov 2016 16:14:17 +0100
changeset 11888 0849a5eb57b8
parent 11887 42461734c3e8
child 11891 67185e65f020
[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
cubicweb/rtags.py
cubicweb/test/unittest_rtags.py
flake8-ok-files.txt
--- 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
 
 
--- 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()
--- 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