[debug] add mechanism to collect uicfg declarations

authorLaurent Peuch <cortex@worlddomination.be>
changesetf316e4df5745
branchdefault
phasedraft
hiddenno
parent revision#59ee6c2deee6 [WIP] [debug-toolbar] add a rendering panel to the debug toolbar
child revision#6874f361aa1b [debug/dbglevel] add new dbglevel flag DBG_UICFG
files modified by this revision
cubicweb/rtags.py
cubicweb/server/__init__.py
# HG changeset patch
# User Laurent Peuch <cortex@worlddomination.be>
# Date 1611742835 -3600
# Wed Jan 27 11:20:35 2021 +0100
# Node ID f316e4df5745a1e1bc3e1e5ac6f2123afa7801e4
# Parent 59ee6c2deee6544b296c478f3519243189202a12
[debug] add mechanism to collect uicfg declarations

diff --git a/cubicweb/rtags.py b/cubicweb/rtags.py
@@ -36,14 +36,19 @@
1     * ``tag_attribute`` shortcut for tag_subject_of
2  """
3 
4 
5  import logging
6 +import inspect
7 +
8 +from collections import UserDict
9 
10  from logilab.common.logging_ext import set_log_methods
11  from logilab.common.registry import RegistrableInstance, yes
12 
13 +from cubicweb.server import DBG_UICFG
14 +
15 
16  def _ensure_str_key(key):
17      return tuple(str(k) for k in key)
18 
19 
@@ -54,10 +59,81 @@
20      while rtag is not None:
21          yield rtag
22          rtag = rtag._parent
23 
24 
25 +class DebugDict(UserDict):
26 +    """
27 +    Exactly a classic dict except it allows to plug in optional debug and
28 +    logging tools.
29 +    """
30 +    def __init__(self, *args, **kwargs):
31 +        self.declarations = []
32 +        super().__init__(*args, **kwargs)
33 +
34 +    def _get_declaration(self, stack, key, value):
35 +        """
36 +        Parse the stack until we reach a frame that is from an object/context
37 +        that isn't the called of __setitem__, that's when the rule declaration
38 +        is made.
39 +
40 +        Also skip the case of "self.initialization_completed" because it's just
41 +        schema initialization and we don't care about this one, those are the
42 +        default rules, we only want explicit declarations.
43 +        """
44 +        if value not in (False, True):
45 +
46 +            # this is the situation of schema initialization, not single
47 +            # declaration, avoid this situation
48 +            for frame in stack[1:]:
49 +                if "self.initialization_completed" in ("\n".join(frame.code_context) if frame.code_context else ""):
50 +                    return None
51 +
52 +            first_id = None
53 +            for frame in stack[1:]:
54 +                local_self = frame.frame.f_locals["self"] if "self" in frame.frame.f_locals else None
55 +
56 +                # skip frames about current object
57 +                if local_self is self:
58 +                    continue
59 +
60 +                if first_id is None:
61 +                    # self should always be there but meh
62 +                    first_id = id(frame.frame.f_locals["self"])
63 +
64 +                current_id = id(local_self) if "self" in frame.frame.f_locals else None
65 +
66 +                # the first time the caller of this object isn't "self" in the
67 +                # frame locals mean we are outside of all subcall of the rtag
68 +                # and se we are at the declaration of the uicfg
69 +                #
70 +                # For example this is a stack run:
71 +                # SET 140560476992904[('*', 'wf_info_for', '*', 'object')] = 'hidden'
72 +                # ===============================================
73 +                # [self id: 140560476992848 class: <class 'cubicweb.web.views.uicfg.PrimaryViewSectionRelationTags'>] line: self._tagdefs[_ensure_str_key(key)] = tag
74 +                # [self id: 140560476992848 class: <class 'cubicweb.web.views.uicfg.PrimaryViewSectionRelationTags'>] line: self.tag_relation(key, *args, **kwargs)
75 +                # [no self in locals] line: _pvs.tag_object_of(('*', 'wf_info_for', '*'), 'hidden') (('*', 'wf_info_for', '*', 'object') = hidden)
76 +                # ^ this is the declaration
77 +
78 +                if frame.code_context and first_id and first_id != current_id:
79 +                    return {
80 +                        "stack": stack,
81 +                        "frame": frame.frame,
82 +                        "inspect_frame": frame,
83 +                        "key": key,
84 +                        "value": value,
85 +                    }
86 +
87 +        return None
88 +
89 +    def __setitem__(self, key, value):
90 +        stack = inspect.stack()
91 +        declaration = self._get_declaration(stack, key, value)
92 +
93 +        return super(DebugDict, self).__setitem__(key, value)
94 +
95 +
96  class RegistrableRtags(RegistrableInstance):
97      __registry__ = 'uicfg'
98      __select__ = yes()
99 
100 
@@ -75,11 +151,11 @@
101      # function given as __init__ argument and kept for bw compat
102      _init = _initfunc = None
103 
104      def __init__(self, parent=None, __module__=None):
105          super(RelationTags, self).__init__(__module__)
106 -        self._tagdefs = {}
107 +        self._tagdefs = DebugDict()
108          self._parent = parent
109          if parent is not None:
110              assert parent.__class__ is self.__class__, \
111                  'inconsistent class for parent rtag {0}'.format(parent)
112 
diff --git a/cubicweb/server/__init__.py b/cubicweb/server/__init__.py
@@ -69,12 +69,14 @@
113  DBG_OPS = 32
114  #: security
115  DBG_SEC = 64
116  #: more verbosity
117  DBG_MORE = 128
118 +#: uicfg
119 +DBG_UICFG = 256
120  #: all level enabled
121 -DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_HOOKS + DBG_OPS + DBG_SEC + DBG_MORE
122 +DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_HOOKS + DBG_OPS + DBG_SEC + DBG_MORE + DBG_UICFG
123 
124  _SECURITY_ITEMS = []
125  _SECURITY_CAPS = ['read', 'add', 'update', 'delete', 'transition']
126 
127  #: current debug mode