Wed, 14 Sep 2016 20:08:16 +0200
Started to improve the variable dumping of the debugger backend.
--- 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']:
--- 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>