Started to improve the variable dumping of the debugger backend.

Wed, 14 Sep 2016 20:08:16 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 14 Sep 2016 20:08:16 +0200
changeset 5169
74e000797a93
parent 5168
e4a11c02374a
child 5171
f1e9eebd5469

Started to improve the variable dumping of the debugger backend.

DebugClients/Python3/DebugBase.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugClientBase.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugConfig.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugVariables.py file | annotate | diff | comparison | revisions
Debugger/VariablesViewer.py file | annotate | diff | comparison | revisions
Documentation/Help/source.qch file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/DebugClients/Python3/DebugBase.py	Wed Sep 14 19:17:53 2016 +0200
+++ b/DebugClients/Python3/DebugBase.py	Wed Sep 14 20:08:16 2016 +0200
@@ -908,8 +908,8 @@
             'DebugClientCapabilities.py',
             'DebugClientThreads.py',
             'DebugConfig.py', 'DebugThread.py',
-            'DebugUtilities.py', 'FlexCompleter.py',
-            'PyProfile.py'] or \
+            'DebugUtilities.py', 'DebugVariables.py',
+            'FlexCompleter.py', 'PyProfile.py'] or \
            os.path.dirname(fn).endswith("coverage"):
             return True
 
--- a/DebugClients/Python3/DebugClientBase.py	Wed Sep 14 19:17:53 2016 +0200
+++ b/DebugClients/Python3/DebugClientBase.py	Wed Sep 14 20:08:16 2016 +0200
@@ -26,6 +26,7 @@
 from DebugConfig import ConfigVarTypeStrings
 from FlexCompleter import Completer
 from DebugUtilities import getargvalues, formatargvalues, prepareJsonCommand
+import DebugVariables
 
 
 DebugClientInstance = None
@@ -1430,251 +1431,26 @@
         varlist = []
         
         if scope != -1:
-            # search the correct dictionary
-            i = 0
-            rvar = var[:]
-            dictkeys = None
-            obj = None
-            isDict = False
-            formatSequences = False
-            access = ""
-            oaccess = ""
-            odict = dict
-            
-            qtVariable = False
-            qvar = None
-            qvtype = ""
-            
-            while i < len(var):
-                if len(dict):
-                    udict = dict
-                ndict = {}
-                # this has to be in line with VariablesViewer.indicators
-                if var[i][-2:] in ["[]", "()", "{}"]:   # __IGNORE_WARNING__
-                    if i + 1 == len(var):
-                        if var[i][:-2] == '...':
-                            dictkeys = [var[i - 1]]
-                        else:
-                            dictkeys = [var[i][:-2]]
-                        formatSequences = True
-                        if not access and not oaccess:
-                            if var[i][:-2] == '...':
-                                access = '["{0!s}"]'.format(var[i - 1])
-                                dict = odict
-                            else:
-                                access = '["{0!s}"]'.format(var[i][:-2])
-                        else:
-                            if var[i][:-2] == '...':
-                                if oaccess:
-                                    access = oaccess
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        access, var[i - 1])
-                                dict = odict
-                            else:
-                                if oaccess:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        oaccess, var[i][:-2])
-                                    oaccess = ''
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        access, var[i][:-2])
-                        if var[i][-2:] == "{}":     # __IGNORE_WARNING__
-                            isDict = True
-                        break
-                    else:
-                        if not access:
-                            if var[i][:-2] == '...':
-                                access = '["{0!s}"]'.format(var[i - 1])
-                                dict = odict
-                            else:
-                                access = '["{0!s}"]'.format(var[i][:-2])
-                        else:
-                            if var[i][:-2] == '...':
-                                access = '{0!s}[{1!s}]'.format(
-                                    access, var[i - 1])
-                                dict = odict
-                            else:
-                                if oaccess:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        oaccess, var[i][:-2])
-                                    oaccess = ''
-                                else:
-                                    access = '{0!s}[{1!s}]'.format(
-                                        access, var[i][:-2])
+            variable = dict
+            for attribute in var:
+                if attribute[-2:] in ["[]", "()", "{}"]:  # __IGNORE_WARNING__
+                    attribute = attribute[:-2]
+                typeObject, typeName, typeStr, resolver = \
+                    DebugVariables.getType(variable)
+                if resolver:
+                    variable = resolver.resolve(variable, attribute)
                 else:
-                    if access:
-                        if oaccess:
-                            access = '{0!s}[{1!s}]'.format(oaccess, var[i])
-                        else:
-                            access = '{0!s}[{1!s}]'.format(access, var[i])
-                        if var[i - 1][:-2] == '...':
-                            oaccess = access
-                        else:
-                            oaccess = ''
-                        try:
-                            loc = {"dict": dict}
-                            exec('mdict = dict{0!s}.__dict__\nobj = dict{0!s}'
-                                 .format(access), globals(), loc)
-                            mdict = loc["mdict"]
-                            obj = loc["obj"]
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                            ndict.update(mdict)
-                        except Exception:
-                            pass
-                        try:
-                            loc = {"dict": dict}
-                            exec('mcdict = dict{0!s}.__class__.__dict__'
-                                 .format(access), globals(), loc)
-                            ndict.update(loc["mcdict"])
-                            if mdict and "sipThis" not in mdict.keys():
-                                del rvar[0:2]
-                                access = ""
-                        except Exception:
-                            pass
-                        try:
-                            loc = {"cdict": {}, "dict": dict}
-                            exec('slv = dict{0!s}.__slots__'.format(access),
-                                 globals(), loc)
-                            for v in loc["slv"]:
-                                try:
-                                    loc["v"] = v
-                                    exec('cdict[v] = dict{0!s}.{1!s}'.format(
-                                        access, v), globals, loc)
-                                except Exception:
-                                    pass
-                            ndict.update(loc["cdict"])
-                            exec('obj = dict{0!s}'.format(access),
-                                 globals(), loc)
-                            obj = loc["obj"]
-                            access = ""
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                        except Exception:
-                            pass
-                    else:
-                        try:
-                            ndict.update(dict[var[i]].__dict__)
-                            ndict.update(dict[var[i]].__class__.__dict__)
-                            del rvar[0]
-                            obj = dict[var[i]]
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                        except Exception:
-                            pass
-                        try:
-                            slv = dict[var[i]].__slots__
-                            loc = {"cdict": {}, "dict": dict,
-                                   "var": var, "i": i}
-                            for v in slv:
-                                try:
-                                    loc["v"] = v
-                                    exec('cdict[v] = dict[var[i]].{0!s}'
-                                         .format(v),
-                                         globals(), loc)
-                                except Exception:
-                                    pass
-                            ndict.update(loc["cdict"])
-                            obj = dict[var[i]]
-                            if "PyQt4." in str(type(obj)) or \
-                                    "PyQt5." in str(type(obj)):
-                                qtVariable = True
-                                qvar = obj
-                                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                        except Exception:
-                            pass
-                    odict = dict
-                    dict = ndict
-                i += 1
-            
-            if qtVariable:
-                vlist = self.__formatQtVariable(qvar, qvtype)
-            elif ("sipThis" in dict.keys() and len(dict) == 1) or \
-                    (len(dict) == 0 and len(udict) > 0):
-                if access:
-                    loc = {"udict": udict}
-                    exec('qvar = udict{0!s}'.format(access), globals(), loc)
-                    qvar = loc["qvar"]
-                # this has to be in line with VariablesViewer.indicators
-                elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]:   # __IGNORE_WARNING__
-                    loc = {"udict": udict}
-                    exec('qvar = udict["{0!s}"][{1!s}]'.format(rvar[0][:-2],
-                         rvar[1]),
-                         globals(), loc)
-                    qvar = loc["qvar"]
-                else:
-                    qvar = udict[var[-1]]
-                qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                if qvtype.startswith(("PyQt4", "PyQt5")):
-                    vlist = self.__formatQtVariable(qvar, qvtype)
-                else:
-                    vlist = []
-            else:
-                qtVariable = False
-                if len(dict) == 0 and len(udict) > 0:
-                    if access:
-                        loc = {"udict": udict}
-                        exec('qvar = udict{0!s}'.format(access),
-                             globals(), loc)
-                        qvar = loc["qvar"]
-                    # this has to be in line with VariablesViewer.indicators
-                    elif rvar and rvar[0][-2:] in ["[]", "()", "{}"]:   # __IGNORE_WARNING__
-                        loc = {"udict": udict}
-                        exec('qvar = udict["{0!s}"][{1!s}]'.format(
-                             rvar[0][:-2], rvar[1]), globals(), loc)
-                        qvar = loc["qvar"]
-                    else:
-                        qvar = udict[var[-1]]
-                    qvtype = str(type(qvar))[1:-1].split()[1][1:-1]
-                    if qvtype.startswith(("PyQt4", "PyQt5")):
-                        qtVariable = True
-                
-                if qtVariable:
-                    vlist = self.__formatQtVariable(qvar, qvtype)
-                else:
-                    # format the dictionary found
-                    if dictkeys is None:
-                        dictkeys = dict.keys()
-                    else:
-                        # treatment for sequences and dictionaries
-                        if access:
-                            loc = {"dict": dict}
-                            exec("dict = dict{0!s}".format(access), globals(),
-                                 loc)
-                            dict = loc["dict"]
-                        else:
-                            dict = dict[dictkeys[0]]
-                        if isDict:
-                            dictkeys = dict.keys()
-                        else:
-                            dictkeys = range(len(dict))
-                    vlist = self.__formatVariablesList(
-                        dictkeys, dict, scope, filter, formatSequences)
-            varlist.extend(vlist)
-        
-            if obj is not None and not formatSequences:
-                try:
-                    if repr(obj).startswith('{'):
-                        varlist.append(
-                            ('...', 'dict', "{0:d}".format(len(obj.keys()))))
-                    elif repr(obj).startswith('['):
-                        varlist.append(
-                            ('...', 'list', "{0:d}".format(len(obj))))
-                    elif repr(obj).startswith('('):
-                        varlist.append(
-                            ('...', 'tuple', "{0:d}".format(len(obj))))
-                except Exception:
-                    pass
+                    break
+            typeObject, typeName, typeStr, resolver = \
+                DebugVariables.getType(variable)
+            if typeStr.startswith(("PyQt5.", "PyQt4.")):
+                vlist = self.__formatQtVariable(variable, typeName)
+                varlist.extend(vlist)
+            elif resolver:
+                dict = resolver.getDictionary(variable)
+                vlist = self.__formatVariablesList(
+                    list(dict.keys()), dict, scope, filter)
+                varlist.extend(vlist)
         
         self.sendJsonCommand("ResponseVariable", {
             "scope": scope,
@@ -1682,17 +1458,16 @@
             "variables": varlist,
         })
         
-    def __formatQtVariable(self, value, vtype):
+    def __formatQtVariable(self, value, qttype):
         """
         Private method to produce a formatted output of a simple Qt4/Qt5 type.
         
         @param value variable to be formatted
-        @param vtype type of the variable to be formatted (string)
+        @param qttype type of the Qt variable to be formatted (string)
         @return A tuple consisting of a list of formatted variables. Each
             variable entry is a tuple of three elements, the variable name,
             its type and value.
         """
-        qttype = vtype.split('.')[-1]
         varlist = []
         if qttype == 'QChar':
             varlist.append(("", "QChar", "{0}".format(chr(value.unicode()))))
@@ -1882,7 +1657,7 @@
                         continue
                     elif valtype == "sip.methoddescriptor":
                         if ConfigVarTypeStrings.index(
-                                'instance method') in filter:
+                                'method') in filter:
                             continue
                     elif valtype == "sip.enumtype":
                         if ConfigVarTypeStrings.index('class') in filter:
@@ -1899,7 +1674,7 @@
                                 continue
                         elif valtype == "sip.methoddescriptor":
                             if ConfigVarTypeStrings.index(
-                                    'instance method') in filter:
+                                    'method') in filter:
                                 continue
                         elif valtype == "sip.enumtype":
                             if ConfigVarTypeStrings.index('class') in filter:
--- a/DebugClients/Python3/DebugConfig.py	Wed Sep 14 19:17:53 2016 +0200
+++ b/DebugClients/Python3/DebugConfig.py	Wed Sep 14 20:08:16 2016 +0200
@@ -13,7 +13,7 @@
     'str', 'unicode', 'tuple', 'list',
     'dict', 'dict-proxy', 'set', 'file', 'xrange',
     'slice', 'buffer', 'class', 'instance',
-    'instance method', 'property', 'generator',
+    'method', 'property', 'generator',
     'function', 'builtin_function_or_method', 'code', 'module',
     'ellipsis', 'traceback', 'frame', 'other'
 ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DebugClients/Python3/DebugVariables.py	Wed Sep 14 20:08:16 2016 +0200
@@ -0,0 +1,282 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing classes and functions to dump variable contents.
+"""
+
+#
+# This code was inspired by pydevd.
+#
+
+MaxItemsToHandle = 300
+TooLargeMessage = ("Too large to show contents. Max items to show: " + 
+                   str(MaxItemsToHandle))
+TooLargeAttribute = "Too large to be handled."
+
+############################################################
+## Classes implementing resolvers for various compund types
+############################################################
+
+class BaseResolver(object):
+    """
+    Base class of the resolver class tree.
+    """
+    def resolve(self, var, attribute):
+        """
+        Public method to get an attribute from a variable.
+        
+        @param var variable to extract an attribute or value from
+        @type any
+        @param attribute name of the attribute to extract
+        @type str
+        @return value of the attribute
+        @rtype any
+        """
+        raise NotImplementedError
+    
+    def getDictionary(self, var):
+        """
+        Public method to get the attributes of a variable as a dictionary.
+        
+        @param var variable to be converted
+        @type any
+        @return dictionary containing the variable attributes
+        @rtype dict
+        """
+        raise NotImplementedError
+
+
+class DefaultResolver(BaseResolver):
+    """
+    Class used to resolve the default way.
+    """
+    def resolve(self, var, attribute):
+        """
+        Public method to get an attribute from a variable.
+        
+        @param var variable to extract an attribute or value from
+        @type any
+        @param attribute name of the attribute to extract
+        @type str
+        @return value of the attribute
+        @rtype any
+        """
+        return getattr(var, attribute)
+    
+    def getDictionary(self, var):
+        """
+        Public method to get the attributes of a variable as a dictionary.
+        
+        @param var variable to be converted
+        @type any
+        @return dictionary containing the variable attributes
+        @rtype dict
+        """
+        names = dir(var)
+        if not names and hasattr(var, "__members__"):
+            names = var.__members__
+        
+        d = {}
+        for name in names:
+            try:
+                attribute = getattr(var, name)
+                d[name] = attribute
+            except Exception:
+                pass    # if we can't get it, simply ignore it
+        
+        return d
+
+
+class DictResolver(BaseResolver):
+    """
+    Class used to resolve from a dictionary.
+    """
+    def resolve(self, var, attribute):
+        """
+        Public method to get an attribute from a variable.
+        
+        @param var variable to extract an attribute or value from
+        @type dict
+        @param attribute name of the attribute to extract
+        @type str
+        @return value of the attribute
+        @rtype any
+        """
+        if attribute in ('__len__', TooLargeAttribute):
+            return None
+        
+        if "(" not in attribute:
+            try:
+##                if attribute[0] == "'" and attribute[-1] == "'":
+##                    attribute = attribute[1:-1]
+                return var[attribute]
+            except Exception:
+                return getattr(var, attribute)
+        
+        expectedID = int(attribute.split("(")[-1][:-1])
+        for key, value in var.items():
+            if id(key) == expectedID:
+                return value
+        
+        return None
+    
+    def __keyToStr(self, key):
+        if isinstance(key, str):
+            return repr(key)
+        else:
+            return key
+    
+    def getDictionary(self, var):
+        """
+        Public method to get the attributes of a variable as a dictionary.
+        
+        @param var variable to be converted
+        @type any
+        @return dictionary containing the variable attributes
+        @rtype dict
+        """
+        d = {}
+        count = 0
+        for key, value in var.items():
+            count += 1
+            key = "{0} ({1})".format(self.__keyToStr(key), id(key))
+            d[key] = value
+            if count > MaxItemsToHandle:
+                d[TooLargeAttribute] = TooLargeMessage
+                break
+        
+        d["__len__"] = len(var)
+        
+        # in case it has additional fields
+        additionals = defaultResolver.getDictionary(var)
+        d.update(additionals)
+        
+        return d
+
+
+class ListResolver(BaseResolver):
+    """
+    Class used to resolve from a tuple or list.
+    """
+    def resolve(self, var, attribute):
+        """
+        Public method to get an attribute from a variable.
+        
+        @param var variable to extract an attribute or value from
+        @type tuple or list
+        @param attribute name of the attribute to extract
+        @type str
+        @return value of the attribute
+        @rtype any
+        """
+        if attribute in ('__len__', TooLargeAttribute):
+            return None
+
+        try:
+            return var[int(attribute)]
+        except Exception:
+            return getattr(var, attribute)
+    
+    def getDictionary(self, var):
+        """
+        Public method to get the attributes of a variable as a dictionary.
+        
+        @param var variable to be converted
+        @type any
+        @return dictionary containing the variable attributes
+        @rtype dict
+        """
+        length = len(var)
+        d = {}
+        formatStr = "{0:0" + str(len(str(length))) + "d}"
+        count = 0
+        for value in var:
+            d[formatStr.format(count)] = value
+            count += 1
+            if count > MaxItemsToHandle:
+                d[TooLargeAttribute] = TooLargeMessage
+                break
+        
+        d["__len__"] = length
+        
+        # in case it has additional fields
+        additionals = defaultResolver.getDictionary(var)
+        d.update(additionals)
+        
+        return d
+
+
+defaultResolver = DefaultResolver()
+dictResolver = DictResolver()
+listResolver = ListResolver()
+
+# TODO: add resolver for set and frozenset
+# TODO: add resolver for numpy arrays
+# TODO: add resolver for Django MultiValueDict
+# TODO: add resolver for collections.deque
+
+############################################################
+## Methods to determine the type of a variable and the
+## resolver class to use
+############################################################
+
+_TypeMap = None
+
+def _initTypeMap():
+    """
+    Protected function to initialize the type map
+    """
+    global _TypeMap
+    
+    _TypeMap = [
+        (type(None), None,),
+        (int, None),
+        (float, None),
+        (complex, None),
+        (str, None),
+        (tuple, listResolver),
+        (list, listResolver),
+        (dict, dictResolver),
+    ]
+    
+    try:
+        _TypeMap.append((long, None))           # __IGNORE_WARNING__
+    except Exception:
+        pass    # not available on all python versions
+
+    try:
+        _TypeMap.append((unicode, None))        # __IGNORE_WARNING__
+    except Exception:
+        pass    # not available on all python versions
+
+
+def getType(obj):
+    """
+    Public method to get the type information for an object.
+    
+    @param obj object to get type information for
+    @type any
+    @return tuple containing the type, type name, type string and resolver
+    @rtype tuple of type, str, str, BaseResolver
+    """
+    typeObject = type(obj)
+    typeName = typeObject.__name__
+    typeStr = str(typeObject)[8:-2]
+    
+    if typeStr.startswith(("PyQt5.", "PyQt4.")):
+        resolver = None
+    else:
+        if _TypeMap is None:
+            _initTypeMap()
+        
+        for typeData in _TypeMap:
+            if isinstance(obj, typeData[0]):
+                resolver = typeData[1]
+                break
+        else:
+            resolver = defaultResolver
+    
+    return typeObject, typeName, typeStr, resolver
--- a/Debugger/VariablesViewer.py	Wed Sep 14 19:17:53 2016 +0200
+++ b/Debugger/VariablesViewer.py	Wed Sep 14 20:08:16 2016 +0200
@@ -544,7 +544,7 @@
         try:
             dvar = '{0}{1}'.format(var, self.indicators[vtype])
         except KeyError:
-            dvar = '{0}'.format(var)
+            dvar = var
         dvtype = self.__getDispType(vtype)
         
         if vtype in ['list', 'Array', 'tuple', 'dict', 'Hash']:
Binary file Documentation/Help/source.qch has changed
--- a/eric6.e4p	Wed Sep 14 19:17:53 2016 +0200
+++ b/eric6.e4p	Wed Sep 14 20:08:16 2016 +0200
@@ -83,6 +83,7 @@
     <Source>DebugClients/Python3/DebugConfig.py</Source>
     <Source>DebugClients/Python3/DebugThread.py</Source>
     <Source>DebugClients/Python3/DebugUtilities.py</Source>
+    <Source>DebugClients/Python3/DebugVariables.py</Source>
     <Source>DebugClients/Python3/FlexCompleter.py</Source>
     <Source>DebugClients/Python3/PyProfile.py</Source>
     <Source>DebugClients/Python3/__init__.py</Source>
@@ -1965,14 +1966,14 @@
   <Interfaces/>
   <Others>
     <Other>.hgignore</Other>
-    <Other>APIs/Python/zope-2.10.7.api</Other>
-    <Other>APIs/Python/zope-2.11.2.api</Other>
-    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/Python3/PyQt4.bas</Other>
     <Other>APIs/Python3/PyQt5.bas</Other>
     <Other>APIs/Python3/QScintilla2.bas</Other>
     <Other>APIs/Python3/eric6.api</Other>
     <Other>APIs/Python3/eric6.bas</Other>
+    <Other>APIs/Python/zope-2.10.7.api</Other>
+    <Other>APIs/Python/zope-2.11.2.api</Other>
+    <Other>APIs/Python/zope-3.3.1.api</Other>
     <Other>APIs/QSS/qss.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>

eric ide

mercurial