Sun, 29 Aug 2021 19:19:31 +0200
Redesign of the internal evaluation of the variable types.
--- a/eric7/DebugClients/Python/DebugClientBase.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/DebugClients/Python/DebugClientBase.py Sun Aug 29 19:19:31 2021 +0200 @@ -27,7 +27,7 @@ import DebugVariables from DebugBase import setRecursionLimit, printerr # __IGNORE_WARNING__ from AsyncFile import AsyncFile, AsyncPendingWrite -from DebugConfig import ConfigQtNames, SpecialAttributes +from DebugConfig import SpecialAttributes, NonExpandableTypes from FlexCompleter import Completer from DebugUtilities import prepareJsonCommand from BreakpointWatch import Breakpoint, Watch @@ -126,11 +126,17 @@ """ clientCapabilities = DebugClientCapabilities.HasAll - # keep these in sync with VariablesViewer.VariableItem.Indicators - Indicators = ("()", "[]", "{:}", "{}") # __IGNORE_WARNING_M613__ - arrayTypes = { - 'list', 'tuple', 'dict', 'set', 'frozenset', "class 'dict_items'", - "class 'dict_keys'", "class 'dict_values'" + Type2Indicators = { + # Python types + 'list': '[]', + 'tuple': '()', + 'dict': '{:}', # __IGNORE_WARNING_M613__ + 'set': '{}', # __IGNORE_WARNING_M613__ + 'frozenset': '{}', # __IGNORE_WARNING_M613__ + 'numpy.ndarray': '[ndarray]', # __IGNORE_WARNING_M613__ + 'collections.abc.ItemsView': '[]', + 'collections.abc.KeysView': '[]', + 'collections.abc.ValuesView': '[]', } def __init__(self): @@ -151,9 +157,9 @@ # The list of complete lines to execute. self.buffer = '' - # The list of regexp objects to filter variables against - self.globalsFilterObjects = [] - self.localsFilterObjects = [] + # The precompiled regexp to filter variables against + self.globalsFilterObjects = None + self.localsFilterObjects = None self._fncache = {} self.dircache = [] @@ -1520,8 +1526,11 @@ else: varDict = f.f_locals + # Update known types list + DebugVariables.updateTypeMap() + varlist = [] if scope < 0 else self.__formatVariablesList( - varDict, scope, filterList) + varDict.items(), scope, filterList) self.sendJsonCommand("ResponseVariables", { "scope": scope, @@ -1565,16 +1574,19 @@ varlist = [] + # fast path if variable was looked up before (see elif) if scope != -1 and str(var) in self.resolverCache[scope]: varGen = self.resolverCache[scope][str(var)] idx, varDict = next(varGen) - var.insert(0, idx) - varlist = self.__formatVariablesList(varDict, scope, filterList) + if idx != -2: # more elements available + var.insert(0, idx) + varlist = self.__formatVariablesList( + varDict, scope, filterList) elif scope != -1: variable = varDict # Lookup the wanted attribute for attribute in var: - _, _, resolver = DebugVariables.getType(variable) + resolver = DebugVariables.getResolver(variable) if resolver: variable = resolver.resolve(variable, attribute) if variable is None: @@ -1586,22 +1598,16 @@ idx = -3 # Requested variable doesn't exist anymore # If found, get the details of attribute if variable is not None: - typeName, typeStr, resolver = DebugVariables.getType(variable) + resolver = DebugVariables.getResolver(variable) if resolver: - varGen = resolver.getDictionary(variable) + varGen = resolver.getVariableList(variable) + # cache for next lookup self.resolverCache[scope][str(var)] = varGen idx, varDict = next(varGen) - varlist = self.__formatVariablesList( - varDict, scope, filterList) - else: - # Gently handle exception which could occur as special - # cases, e.g. already deleted C++ objects, str conversion.. - try: - varlist = self.__formatQtVariable(variable, typeName) - except Exception: - varlist = [] - idx = -1 + if idx != -2: # more elements available + varlist = self.__formatVariablesList( + varDict, scope, filterList) var.insert(0, idx) @@ -1611,169 +1617,7 @@ "variables": varlist, }) - def __extractIndicators(self, var): - """ - Private method to extract the indicator string from a variable text. - - @param var variable text - @type str - @return tuple containing the variable text without indicators and the - indicator string - @rtype tuple of two str - """ - for indicator in DebugClientBase.Indicators: - if var.endswith(indicator): - return var[:-len(indicator)], indicator - - return var, "" - - def __formatQtVariable(self, value, qttype): - """ - Private method to produce a formatted output of a simple Qt5/Qt6 type. - - @param value variable to be formatted - @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. - """ - varlist = [] - if qttype == 'QChar': - varlist.append( - ("", "QChar", "{0}".format(chr(value.unicode())))) - varlist.append(("", "int", "{0:d}".format(value.unicode()))) - elif qttype == 'QByteArray': - varlist.append( - ("bytes", "QByteArray", "{0}".format(bytes(value))[2:-1])) - varlist.append( - ("hex", "QByteArray", "{0}".format(value.toHex())[2:-1])) - varlist.append( - ("base64", "QByteArray", "{0}".format(value.toBase64())[2:-1])) - varlist.append(("percent encoding", "QByteArray", - "{0}".format(value.toPercentEncoding())[2:-1])) - elif qttype == 'QString': - varlist.append(("", "QString", "{0}".format(value))) - elif qttype == 'QStringList': - for i in range(value.count()): - varlist.append( - ("{0:d}".format(i), "QString", "{0}".format(value[i]))) - elif qttype == 'QPoint': - varlist.append(("x", "int", "{0:d}".format(value.x()))) - varlist.append(("y", "int", "{0:d}".format(value.y()))) - elif qttype == 'QPointF': - varlist.append(("x", "float", "{0:g}".format(value.x()))) - varlist.append(("y", "float", "{0:g}".format(value.y()))) - elif qttype == 'QRect': - varlist.append(("x", "int", "{0:d}".format(value.x()))) - varlist.append(("y", "int", "{0:d}".format(value.y()))) - varlist.append(("width", "int", "{0:d}".format(value.width()))) - varlist.append(("height", "int", "{0:d}".format(value.height()))) - elif qttype == 'QRectF': - varlist.append(("x", "float", "{0:g}".format(value.x()))) - varlist.append(("y", "float", "{0:g}".format(value.y()))) - varlist.append(("width", "float", "{0:g}".format(value.width()))) - varlist.append(("height", "float", "{0:g}".format(value.height()))) - elif qttype == 'QSize': - varlist.append(("width", "int", "{0:d}".format(value.width()))) - varlist.append(("height", "int", "{0:d}".format(value.height()))) - elif qttype == 'QSizeF': - varlist.append(("width", "float", "{0:g}".format(value.width()))) - varlist.append(("height", "float", "{0:g}".format(value.height()))) - elif qttype == 'QColor': - varlist.append(("name", "str", "{0}".format(value.name()))) - r, g, b, a = value.getRgb() - varlist.append( - ("rgba", "int", - "{0:d}, {1:d}, {2:d}, {3:d}".format(r, g, b, a))) - h, s, v, a = value.getHsv() - varlist.append( - ("hsva", "int", - "{0:d}, {1:d}, {2:d}, {3:d}".format(h, s, v, a))) - c, m, y, k, a = value.getCmyk() - varlist.append( - ("cmyka", "int", - "{0:d}, {1:d}, {2:d}, {3:d}, {4:d}".format(c, m, y, k, a))) - elif qttype == 'QDate': - varlist.append(("", "QDate", "{0}".format(value.toString()))) - elif qttype == 'QTime': - varlist.append(("", "QTime", "{0}".format(value.toString()))) - elif qttype == 'QDateTime': - varlist.append(("", "QDateTime", "{0}".format(value.toString()))) - elif qttype == 'QDir': - varlist.append(("path", "str", "{0}".format(value.path()))) - varlist.append(("absolutePath", "str", - "{0}".format(value.absolutePath()))) - varlist.append(("canonicalPath", "str", - "{0}".format(value.canonicalPath()))) - elif qttype == 'QFile': - varlist.append(("fileName", "str", "{0}".format(value.fileName()))) - elif qttype == 'QFont': - varlist.append(("family", "str", "{0}".format(value.family()))) - varlist.append( - ("pointSize", "int", "{0:d}".format(value.pointSize()))) - varlist.append(("weight", "int", "{0:d}".format(value.weight()))) - varlist.append(("bold", "bool", "{0}".format(value.bold()))) - varlist.append(("italic", "bool", "{0}".format(value.italic()))) - elif qttype == 'QUrl': - varlist.append(("url", "str", "{0}".format(value.toString()))) - varlist.append(("scheme", "str", "{0}".format(value.scheme()))) - varlist.append(("user", "str", "{0}".format(value.userName()))) - varlist.append(("password", "str", "{0}".format(value.password()))) - varlist.append(("host", "str", "{0}".format(value.host()))) - varlist.append(("port", "int", "{0:d}".format(value.port()))) - varlist.append(("path", "str", "{0}".format(value.path()))) - elif qttype == 'QModelIndex': - varlist.append(("valid", "bool", "{0}".format(value.isValid()))) - if value.isValid(): - varlist.append(("row", "int", "{0}".format(value.row()))) - varlist.append(("column", "int", "{0}".format(value.column()))) - varlist.append( - ("internalId", "int", "{0}".format(value.internalId()))) - varlist.append(("internalPointer", "void *", - "{0}".format(value.internalPointer()))) - elif qttype in ('QRegExp', "QRegularExpression"): - varlist.append(("pattern", "str", "{0}".format(value.pattern()))) - - # GUI stuff - elif qttype == 'QAction': - varlist.append(("name", "str", "{0}".format(value.objectName()))) - varlist.append(("text", "str", "{0}".format(value.text()))) - varlist.append( - ("icon text", "str", "{0}".format(value.iconText()))) - varlist.append(("tooltip", "str", "{0}".format(value.toolTip()))) - varlist.append( - ("whatsthis", "str", "{0}".format(value.whatsThis()))) - varlist.append( - ("shortcut", "str", - "{0}".format(value.shortcut().toString()))) - elif qttype == 'QKeySequence': - varlist.append(("value", "", "{0}".format(value.toString()))) - - # XML stuff - elif qttype == 'QDomAttr': - varlist.append(("name", "str", "{0}".format(value.name()))) - varlist.append(("value", "str", "{0}".format(value.value()))) - elif qttype in ('QDomCharacterData', 'QDomComment', 'QDomText'): - varlist.append(("data", "str", "{0}".format(value.data()))) - elif qttype == 'QDomDocument': - varlist.append(("text", "str", "{0}".format(value.toString()))) - elif qttype == 'QDomElement': - varlist.append(("tagName", "str", "{0}".format(value.tagName()))) - varlist.append(("text", "str", "{0}".format(value.text()))) - - # Networking stuff - elif qttype == 'QHostAddress': - varlist.append( - ("address", "QHostAddress", "{0}".format(value.toString()))) - - # PySide specific - elif qttype == 'EnumType': # Not in PyQt possible - for key, value in value.values.items(): - varlist.append((key, qttype, "{0}".format(int(value)))) - - return varlist - - def __formatVariablesList(self, dict_, scope, filterList=None): + def __formatVariablesList(self, variables, scope, filterList=None): """ Private method to produce a formated variables list. @@ -1799,7 +1643,7 @@ its type and value. @rtype list of tuple of (str, str, str) """ - filterList = [] if filterList is None else filterList[:] + filterList = set(filterList or []) varlist = [] patternFilterObjects = ( @@ -1807,27 +1651,26 @@ if scope else self.localsFilterObjects ) - if type(dict_) == dict: - dict_ = dict_.items() - for key, value in dict_: - # no more elements available - if key == -2: - break + for variabel in variables: + valtype = None + rvalue = None + try: + key, value = variabel + except ValueError: + # Special case for some Qt variables, where the real type is + # overwritten + key, valtype, rvalue = variabel # filter based on the filter pattern - matched = False - for pat in patternFilterObjects: - if pat.match(str(key)): - matched = True - break - if matched: + if patternFilterObjects and patternFilterObjects.match(str(key)): continue # filter hidden attributes (filter #0) if '__' in filterList and str(key)[:2] == '__': continue + hasChildren = False # special handling for '__builtins__' (it's way too big) if key == '__builtins__': rvalue = '<module builtins (built-in)>' @@ -1841,67 +1684,92 @@ "builtin_function_or_method" in filterList) ): continue - else: - isQt = False + elif valtype is None: # valtypestr, e.g. class 'PyQt6.QtCore.QPoint' - valtypestr = str(type(value))[1:-1] + valtypestr = str(type(value)) _, valtype = valtypestr.split(' ', 1) - # valtype, e.g. PyQt6.QtCore.QPoint - valtype = valtype[1:-1] + # valtype is the real type, e.g. PyQt6.QtCore.QPoint + # valtype_filter is used for filtering, where the base class is + # also important + valtype = valtype_filter = valtype[1:-2] # Strip 'instance' to be equal with Python 3 if valtype == "instancemethod": - valtype = "method" - elif valtype in ("type", "classobj"): - valtype = "class" + valtype = valtype_filter = "method" + elif isinstance(value, type): + valtype_filter = "class" + if valtype == "type": + valtype = "class" elif valtype == "method-wrapper": - valtype = "builtin_function_or_method" + valtype = valtype_filter = "builtin_function_or_method" - # valtypename, e.g. QPoint - valtypename = type(value).__name__ + # Don't process variables which types are on filter list if ( - valtype in filterList or - (valtype in ("sip.enumtype", "sip.wrappertype") and + valtype_filter in filterList or + (valtype_filter in ("sip.enumtype", "sip.wrappertype") and 'class' in filterList) or - (valtype in ( + (valtype_filter in ( "sip.methoddescriptor", "method_descriptor") and 'method' in filterList) or - (valtype in ("numpy.ndarray", "array.array") and + (valtype_filter in ("numpy.ndarray", "array.array") and 'list' in filterList) or - (valtypename == "MultiValueDict" and + (valtype_filter == "django.MultiValueDict" and 'dict' in filterList) or 'instance' in filterList ): continue - - isQt = valtype.startswith(ConfigQtNames) + + length = -2 + indicator = '' + + if valtype == 'str': + rvalue = repr(value) + length = len(rvalue) + elif valtype in NonExpandableTypes: + rvalue = repr(value) + + if rvalue is not None: + varlist.append( + (key, indicator, valtype, hasChildren, length, rvalue) + ) + continue + + try: + for dtype in DebugVariables._ArrayTypes: + if isinstance(value, dtype): + try: + length = len(value) + except TypeError: + length = -1 # Uninitialized array + + dtype = str(dtype)[8:-2] + # Standard array type indicators + indicator = self.Type2Indicators.get(dtype, '') + + # Special handling of some array types + if valtype == 'array.array': + indicator = '[<{0}>]'.format(value.typecode) + elif valtype == 'collections.defaultdict': + if value.default_factory is None: + def_factory = "None" + else: + def_factory = value.default_factory.__name__ + indicator = '{{:<{0}>}}'.format(def_factory) + elif valtype == "numpy.ndarray" and length > -1: + length = "x".join(str(x) for x in value.shape) + elif valtype.endswith(".MultiValueDict"): + indicator = "{:}" + valtype = "django.MultiValueDict" # shortened type + break + else: + rvalue = repr(value) - try: - if valtype in self.arrayTypes: - rvalue = "{0:d}".format(len(value)) - elif valtype == 'array.array': - rvalue = "{0:d}|{1}".format( - len(value), value.typecode) - elif valtype == 'collections.defaultdict': - if value.default_factory is None: - factoryName = "None" - else: - factoryName = value.default_factory.__name__ - rvalue = "{0:d}|{1}".format(len(value), factoryName) - elif valtype == "numpy.ndarray": - rvalue = "x".join(str(x) for x in value.shape) - elif valtypename == "MultiValueDict": - rvalue = "{0:d}".format(len(value.keys())) - valtype = "django.MultiValueDict" # shortened type - else: - rvalue = repr(value) - if valtype.startswith('class') and rvalue[0] in '{([': - rvalue = "" - elif (isQt and rvalue.startswith("<class '")): - rvalue = rvalue[8:-2] - except Exception: - rvalue = '' - - varlist.append((key, valtype, rvalue)) + hasChildren = True + except Exception: + rvalue = '' + + varlist.append( + (key, indicator, valtype, hasChildren, length, rvalue) + ) return varlist @@ -1913,13 +1781,18 @@ variables (int) @param filterString string of filter patterns separated by ';' """ - patternFilterObjects = [] - for pattern in filterString.split(';'): - patternFilterObjects.append(re.compile('^{0}$'.format(pattern))) + patternFilterObjects = None + if filterString.strip(): + pattern = filterString.replace(';', '|') + try: + patternFilterObjects = re.compile(pattern) + except re.error: + pass + if scope: - self.globalsFilterObjects = patternFilterObjects[:] + self.globalsFilterObjects = patternFilterObjects else: - self.localsFilterObjects = patternFilterObjects[:] + self.localsFilterObjects = patternFilterObjects def __completionList(self, text): """
--- a/eric7/DebugClients/Python/DebugConfig.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/DebugClients/Python/DebugConfig.py Sun Aug 29 19:19:31 2021 +0200 @@ -12,15 +12,24 @@ "__bases__", "__class__", "__dict__", "__doc__", "__mro__", "__name__", "__qualname__", ) + BatchSize = 200 ConfigQtNames = ( 'PyQt5.', 'PyQt6.', 'PySide2.', 'PySide6.', 'Shiboken.EnumType' ) + ConfigKnownQtTypes = ( - '.QChar', '.QByteArray', '.QString', '.QStringList', '.QPoint', '.QPointF', + '.QByteArray', '.QPoint', '.QPointF', '.QLabel', '.QPushButton', '.QRect', '.QRectF', '.QSize', '.QSizeF', '.QColor', '.QDate', '.QTime', '.QDateTime', '.QDir', '.QFile', '.QFont', '.QUrl', '.QModelIndex', '.QRegExp', '.QRegularExpression', '.QAction', '.QKeySequence', '.QDomAttr', '.QDomCharacterData', '.QDomComment', '.QDomDocument', - '.QDomElement', '.QDomText', '.QHostAddress', '.EnumType' + '.QDomElement', '.QDomText', '.QHostAddress', '.EnumType', ) + +NonExpandableTypes = ( + 'int', 'float', 'bool', 'NoneType', 'bytes', 'function', 'object', + 'builtin_function_or_method', 'classmethod_descriptor', 'weakref', + 'wrapper_descriptor', 'method_descriptor', 'property', 'method', + 'getset_descriptor', 'member_descriptor', +)
--- a/eric7/DebugClients/Python/DebugVariables.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/DebugClients/Python/DebugVariables.py Sun Aug 29 19:19:31 2021 +0200 @@ -8,6 +8,9 @@ """ import contextlib +import sys + +from collections.abc import ItemsView, KeysView, ValuesView from DebugConfig import ConfigQtNames, ConfigKnownQtTypes, BatchSize @@ -16,7 +19,7 @@ # ############################################################ -## Classes implementing resolvers for various compund types +## Classes implementing resolvers for various compound types ############################################################ @@ -37,24 +40,20 @@ """ return getattr(var, attribute, None) - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @return dictionary containing the variable attributes - @rtype dict + @return list containing the variable attributes + @rtype list """ - names = dir(var) - if not names and hasattr(var, "__members__"): - names = var.__members__ - - d = {} - for name in names: + d = [] + for name in dir(var): with contextlib.suppress(Exception): attribute = getattr(var, name) - d[name] = attribute + d.append((name, attribute)) return d @@ -68,29 +67,25 @@ """ Class used to resolve the default way. """ - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - names = dir(var) - if not names and hasattr(var, "__members__"): - names = var.__members__ - - d = {} - for name in names: + d = [] + for name in dir(var): with contextlib.suppress(Exception): attribute = getattr(var, name) - d[name] = attribute + d.append((name, attribute)) yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -144,17 +139,17 @@ return key # __IGNORE_WARNING_M834__ - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 allItems = list(var.items()) try: @@ -166,23 +161,23 @@ for key, value in allItems: key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) - d[key] = value + d.append((key, value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - d = super().getDictionary(var) + d = super().getVariableList(var) yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -210,36 +205,36 @@ except Exception: return getattr(var, str(attribute), None) - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 for idx, value in enumerate(var): - d[idx] = value + d.append((idx, value)) count += 1 if count >= BatchSize: yield start, d start = idx + 1 count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - d = super().getDictionary(var) + d = super().getVariableList(var) yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -256,7 +251,7 @@ Public method to get an attribute from a variable. @param var variable to extract an attribute or value from - @type tuple or list + @type dict_items, dict_keys or dict_values @param attribute id of the value to extract @type str @return value of the attribute @@ -264,16 +259,17 @@ """ return super().resolve(list(var), attribute) - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @return dictionary containing the variable attributes - @rtype dict + @yield tuple containing the batch start index and a list + containing the variable attributes + @ytype tuple of (int, list) """ - return super().getDictionary(list(var)) + return super().getVariableList(list(var)) ############################################################ @@ -309,36 +305,36 @@ return None - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 for value in var: count += 1 - d["'ID: {0}'".format(id(value))] = value + d.append(("'ID: {0}'".format(id(value)), value)) if count >= BatchSize: yield start, d start += count count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - additionals = super().getDictionary(var) - yield -1, additionals + d = super().getVariableList(var) + yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -369,7 +365,7 @@ Public method to get an attribute from a variable. @param var variable to extract an attribute or value from - @type tuple or list + @type ndarray @param attribute id of the value to extract @type str @return value of the attribute @@ -400,17 +396,17 @@ return None - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 try: len(var) # Check if it's an unsized object, e.g. np.ndarray(()) @@ -419,45 +415,45 @@ allItems = [] for idx, value in enumerate(allItems): - d[str(idx)] = value + d.append((str(idx), value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - d = super().getDictionary(var) + d = super().getVariableList(var) if var.size > 1024 * 1024: - d['min'] = ( - 'ndarray too big, calculating min would slow down debugging') - d['max'] = ( - 'ndarray too big, calculating max would slow down debugging') - d['mean'] = ( - 'ndarray too big, calculating mean would slow down debugging') + d.append(('min', + 'ndarray too big, calculating min would slow down debugging')) + d.append(('max', + 'ndarray too big, calculating max would slow down debugging')) + d.append(('mean', + 'ndarray too big, calculating mean would slow down debugging')) elif self.__isNumeric(var): if var.size == 0: - d['min'] = 'empty array' - d['max'] = 'empty array' - d['mean'] = 'empty array' + d.append(('min', 'empty array')) + d.append(('max', 'empty array')) + d.append(('mean', 'empty array')) else: - d['min'] = var.min() - d['max'] = var.max() - d['mean'] = var.mean() + d.append(('min', var.min())) + d.append(('max', var.max())) + d.append(('mean', var.mean())) else: - d['min'] = 'not a numeric object' - d['max'] = 'not a numeric object' - d['mean'] = 'not a numeric object' + d.append(('min', 'not a numeric object')) + d.append(('max', 'not a numeric object')) + d.append(('mean', 'not a numeric object')) yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -474,7 +470,7 @@ Public method to get an attribute from a variable. @param var variable to extract an attribute or value from - @type dict + @type MultiValueDict @param attribute name of the attribute to extract @type str @return value of the attribute @@ -493,17 +489,17 @@ return None - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 allKeys = list(var.keys()) try: @@ -515,23 +511,23 @@ for key in allKeys: dkey = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) - d[dkey] = var.getlist(key) + d.append((dkey, var.getlist(key))) count += 1 if count >= BatchSize: yield start, d start += count count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - d = super().getDictionary(var) + d = super(DictResolver, self).getVariableList(var) yield -1, d while True: - yield -2, {} + yield -2, [] ############################################################ @@ -564,7 +560,7 @@ Public method to get an attribute from a variable. @param var variable to extract an attribute or value from - @type tuple or list + @type array.array @param attribute id of the value to extract @type str @return value of the attribute @@ -577,42 +573,179 @@ return None - def getDictionary(self, var): + def getVariableList(self, var): """ - Public method to get the attributes of a variable as a dictionary. + Public method to get the attributes of a variable as a list. @param var variable to be converted @type any - @yield tuple containing the batch start index and a dictionary + @yield tuple containing the batch start index and a list containing the variable attributes - @ytype tuple of (int, dict) + @ytype tuple of (int, list) """ - d = {} + d = [] start = count = 0 allItems = var.tolist() for idx, value in enumerate(allItems): - d[str(idx)] = value + d.append((str(idx), value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 - d = {} + d = [] if d: yield start, d # in case it has additional fields - d = super().getDictionary(var) + d = super().getVariableList(var) # Special data for array type: convert typecode to readable text - d['type'] = self.TypeCodeMap.get(var.typecode, 'illegal type') + d.append(('type', self.TypeCodeMap.get(var.typecode, 'illegal type'))) yield -1, d while True: - yield -2, {} + yield -2, [] + + +############################################################ +## PySide / PyQt Resolver +############################################################ + + +class QtResolver(BaseResolver): + """ + Class used to resolve the Qt implementations. + """ + 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 Qt objects + @param attribute name of the attribute to extract + @type str + @return value of the attribute + @rtype any + """ + if attribute == 'internalPointer': + return var.internalPointer() + + return getattr(var, attribute, None) + + def getVariableList(self, var): + """ + Public method to get the attributes of a variable as a list. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a list + containing the variable attributes + @ytype tuple of (int, list) + """ + d = [] + attributes = () + # Gently handle exception which could occure as special + # cases, e.g. already deleted C++ objects, str conversion.. + try: + qttype = type(var).__name__ + + if qttype in ('QLabel', 'QPushButton'): + attributes = ('text', ) + elif qttype == 'QByteArray': + d.append(('bytes', bytes(var))) + d.append(('hex', "QByteArray", "{0}".format(var.toHex()))) + d.append(('base64', "QByteArray", + "{0}".format(var.toBase64()))) + d.append(('percent encoding', "QByteArray", + "{0}".format(var.toPercentEncoding()))) + elif qttype in ('QPoint', 'QPointF'): + attributes = ('x', 'y') + elif qttype in ('QRect', 'QRectF'): + attributes = ('x', 'y', 'width', 'height') + elif qttype in ('QSize', 'QSizeF'): + attributes = ('width', 'height') + elif qttype == 'QColor': + attributes = ('name', ) + r, g, b, a = var.getRgb() + d.append( + ('rgba', "{0:d}, {1:d}, {2:d}, {3:d}".format(r, g, b, a)) + ) + h, s, v, a = var.getHsv() + d.append( + ('hsva', "{0:d}, {1:d}, {2:d}, {3:d}".format(h, s, v, a)) + ) + c, m, y, k, a = var.getCmyk() + d.append( + ('cmyka', + "{0:d}, {1:d}, {2:d}, {3:d}, {4:d}".format(c, m, y, k, a)) + ) + elif qttype in ('QDate', 'QTime', 'QDateTime'): + d.append((qttype[1:].lower(), var.toString())) + elif qttype == 'QDir': + attributes = ('path', 'absolutePath', 'canonicalPath') + elif qttype == 'QFile': + attributes = ('fileName', ) + elif qttype == 'QFont': + attributes = ( + 'family', 'pointSize', 'weight', 'bold', 'italic' + ) + elif qttype == 'QUrl': + d.append(('url', var.toString())) + attributes = ('scheme', 'userName', 'password', 'host', 'port', + 'path') + elif qttype == 'QModelIndex': + valid = var.isValid() + d.append(('valid', valid)) + if valid: + d.append(("internalPointer", var.internalPointer())) + attributes = ('row', 'column', 'internalId') + elif qttype in ('QRegExp', "QRegularExpression"): + attributes = ('pattern', ) + + # GUI stuff + elif qttype == 'QAction': + d.append(('shortcut', var.shortcut().toString())) + attributes = ('objectName', 'text', 'iconText', 'toolTip', + 'whatsThis') + + elif qttype == 'QKeySequence': + d.append(('keySequence', var.toString())) + + # XML stuff + elif qttype == 'QDomAttr': + attributes = ('name', 'var') + elif qttype in ('QDomCharacterData', 'QDomComment', 'QDomText'): + attributes = ('data', ) + elif qttype == 'QDomDocument': + d.append(('text', var.toString())) + elif qttype == 'QDomElement': + attributes = ('tagName', 'text') + + # Networking stuff + elif qttype == 'QHostAddress': + d.append(('address', var.toString())) + + # PySide specific + elif qttype == 'EnumType': # Not in PyQt possible + for key, value in var.values.items(): + d.append((key, int(value))) + except Exception: + pass + + for attribute in attributes: + d.append((attribute, getattr(var, attribute)())) + + # add additional fields + if qttype != 'EnumType': + d.extend(super().getVariableList(var)) + + yield -1, d + while True: + yield -2, [] defaultResolver = DefaultResolver() @@ -623,13 +756,17 @@ ndarrayResolver = NdArrayResolver() multiValueDictResolver = MultiValueDictResolver() arrayResolver = ArrayResolver() +qtResolver = QtResolver() + ############################################################ ## Methods to determine the type of a variable and the ## resolver class to use ############################################################ -_TypeMap = None +_TypeMap = _ArrayTypes = None +_TryArray = _TryNumpy = _TryDjango = True +_MapCount = 0 def _initTypeMap(): @@ -638,77 +775,79 @@ """ global _TypeMap + # Type map for special handling of array types. + # All other types not listed here use the default resolver. _TypeMap = [ - (type(None), None,), - (int, None), - (float, None), - (complex, None), - (str, None), (tuple, listResolver), (list, listResolver), (dict, dictResolver), (set, setResolver), (frozenset, setResolver), + (ItemsView, dictViewResolver), # Since Python 3.0 + (KeysView, dictViewResolver), + (ValuesView, dictViewResolver), ] + + +# Initialize the static type map +_initTypeMap() + + +def updateTypeMap(): + """ + Public function to update the type map based on module imports. + """ + global _TypeMap, _ArrayTypes, _TryArray, _TryNumpy, _TryDjango, _MapCount - with contextlib.suppress(Exception): - _TypeMap.append((long, None)) # __IGNORE_WARNING__ - - with contextlib.suppress(ImportError): + # array.array may not be imported (yet) + if _TryArray and 'array' in sys.modules: import array _TypeMap.append((array.array, arrayResolver)) - # array.array may not be available + _TryArray = False - with contextlib.suppress(ImportError): + # numpy may not be imported (yet) + if _TryNumpy and 'numpy' in sys.modules: import numpy _TypeMap.append((numpy.ndarray, ndarrayResolver)) - # numpy may not be installed + _TryNumpy = False - with contextlib.suppress(ImportError): + # django may not be imported (yet) + if _TryDjango and 'django' in sys.modules: from django.utils.datastructures import MultiValueDict # it should go before dict _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver)) - # django may not be installed + _TryDjango = False - with contextlib.suppress(ImportError): - from collections.abc import ItemsView, KeysView, ValuesView - _TypeMap.append((ItemsView, dictViewResolver)) - _TypeMap.append((KeysView, dictViewResolver)) - _TypeMap.append((ValuesView, dictViewResolver)) - # not available on all Python versions + # If _TypeMap changed, rebuild the _ArrayTypes tuple + if _MapCount != len(_TypeMap): + _ArrayTypes = tuple(typ for typ, _resolver in _TypeMap) + _MapCount = len(_TypeMap) -def getType(obj): +def getResolver(obj): """ - Public method to get the type information for an object. + Public method to get the resolver based on the type info of an object. - @param obj object to get type information for + @param obj object to get resolver for @type any - @return tuple containing the type name, type string and resolver - @rtype tuple of str, str, BaseResolver + @return resolver + @rtype BaseResolver """ - typeObject = type(obj) - typeName = typeObject.__name__ # Between PyQt and PySide the returned type is different (class vs. type) - typeStr = str(typeObject).split(' ', 1)[-1] + typeStr = str(type(obj)).split(' ', 1)[-1] typeStr = typeStr[1:-2] if ( typeStr.startswith(ConfigQtNames) and typeStr.endswith(ConfigKnownQtTypes) ): - resolver = None - else: - if _TypeMap is None: - _initTypeMap() - - for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__ - if isinstance(obj, typeData): - break - else: - resolver = defaultResolver + return qtResolver - return typeName, typeStr, resolver + for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__ + if isinstance(obj, typeData): + return resolver + + return defaultResolver # # eflag: noqa = Y113
--- a/eric7/Debugger/Config.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/Debugger/Config.py Sun Aug 29 19:19:31 2021 +0200 @@ -13,10 +13,8 @@ ConfigVarTypeDispStrings = { '__': QT_TRANSLATE_NOOP('Variable Types', 'Hidden Attributes'), 'NoneType': QT_TRANSLATE_NOOP('Variable Types', 'None'), - 'type': QT_TRANSLATE_NOOP('Variable Types', 'Type'), 'bool': QT_TRANSLATE_NOOP('Variable Types', 'Boolean'), 'int': QT_TRANSLATE_NOOP('Variable Types', 'Integer'), - 'long': QT_TRANSLATE_NOOP('Variable Types', 'Long Integer'), 'float': QT_TRANSLATE_NOOP('Variable Types', 'Float'), 'complex': QT_TRANSLATE_NOOP('Variable Types', 'Complex'), 'str': QT_TRANSLATE_NOOP('Variable Types', 'String'), @@ -27,7 +25,7 @@ 'set': QT_TRANSLATE_NOOP('Variable Types', 'Set'), 'frozenset': QT_TRANSLATE_NOOP('Variable Types', 'Frozen Set'), 'file': QT_TRANSLATE_NOOP('Variable Types', 'File'), - 'xrange': QT_TRANSLATE_NOOP('Variable Types', 'X Range'), + 'range': QT_TRANSLATE_NOOP('Variable Types', 'Range'), 'slice': QT_TRANSLATE_NOOP('Variable Types', 'Slice'), 'buffer': QT_TRANSLATE_NOOP('Variable Types', 'Buffer'), 'class': QT_TRANSLATE_NOOP('Variable Types', 'Class'), @@ -46,4 +44,11 @@ 'bytes': QT_TRANSLATE_NOOP('Variable Types', 'Bytes'), "special_attributes": QT_TRANSLATE_NOOP( 'Variable Types', "Special Attributes"), + 'dict_items': QT_TRANSLATE_NOOP('Variable Types', 'Dict. Items View'), + 'dict_keys': QT_TRANSLATE_NOOP('Variable Types', 'Dict. Keys View'), + 'dict_values': QT_TRANSLATE_NOOP('Variable Types', 'Dict. Values View'), + 'async_generator': QT_TRANSLATE_NOOP('Variable Types', + 'Asynchronous Generator'), + 'coroutine': QT_TRANSLATE_NOOP('Variable Types', 'Coroutine'), + 'mappingproxy': QT_TRANSLATE_NOOP('Variable Types', 'Mapping Proxy'), }
--- a/eric7/Debugger/DebugUI.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/Debugger/DebugUI.py Sun Aug 29 19:19:31 2021 +0200 @@ -1434,7 +1434,9 @@ elif scope == 0: self.debugViewer.showVariables(variables, False) elif scope == -1: - vlist = [(self.tr('No locals available.'), '', '')] + vlist = [ + (self.tr('No locals available.'), '', '', False, -2, '') + ] self.debugViewer.showVariables(vlist, False) def __clientVariable(self, scope, variables, debuggerId):
--- a/eric7/Debugger/VariablesViewer.py Sun Aug 29 17:50:13 2021 +0200 +++ b/eric7/Debugger/VariablesViewer.py Sun Aug 29 19:19:31 2021 +0200 @@ -21,7 +21,6 @@ from EricWidgets.EricApplication import ericApp from .Config import ConfigVarTypeDispStrings -from DebugClients.Python.DebugConfig import ConfigQtNames, ConfigKnownQtTypes import Preferences import Utilities @@ -33,34 +32,14 @@ """ Class implementing the data structure for all variable items. """ - Type2Indicators = { - # Python types - 'list': '[]', - 'tuple': '()', - 'dict': '{:}', # __IGNORE_WARNING_M613__ - 'set': '{}', # __IGNORE_WARNING_M613__ - 'frozenset': '{}', # __IGNORE_WARNING_M613__ - 'numpy.ndarray': '[ndarray]', # __IGNORE_WARNING_M613__ - } - # Initialize regular expression for unprintable strings rx_nonprintable = re.compile(r"""(\\x\d\d)+""") noOfItemsStr = QCoreApplication.translate("VariablesViewer", "{0} items") unsized = QCoreApplication.translate("VariablesViewer", "unsized") - arrayTypes = { - 'list', 'tuple', 'dict', 'set', 'frozenset', 'numpy.ndarray', - 'django.MultiValueDict', 'array.array', 'collections.defaultdict', - "class 'dict_items'", "class 'dict_keys'", "class 'dict_values'", - } - - nonExpandableTypes = ( - 'method_descriptor', 'wrapper_descriptor', '', 'getset_descriptor', - 'method-wrapper', 'member_descriptor', - ) - - def __init__(self, parent, dvar, dtype, dvalue): + def __init__(self, parent, dvar, indicator, dtype, hasChildren, length, + dvalue): """ Constructor @@ -68,8 +47,15 @@ @type VariableItem @param dvar variable name @type str + @param indicator type indicator appended to the name + @type str @param dtype type string @type str + @param hasChildren indicator for children + @type bool + @param length length of the array or string (-1 if uninitialized + numpy.ndarray) + @type int @param dvalue value string @type str """ @@ -79,7 +65,7 @@ self.childCount = 0 self.currentCount = -1 # -1 indicates to (re)load children # Indicator that there are children - self.hasChildren = False + self.hasChildren = hasChildren self.populated = False # Indicator that item was at least once fully populated self.wasPopulated = False @@ -97,15 +83,15 @@ self.name = '' self.sort = '' - self.type = '' - self.indicator = '' - self.value = None + vtype = ConfigVarTypeDispStrings.get(dtype, dtype) + self.type = QCoreApplication.translate("VariablesViewer", vtype) + self.indicator = indicator + self.value = dvalue self.valueShort = None self.tooltip = '' self.__getName(dvar) - self.__getType(dtype) - self.__getValue(dtype, dvalue) + self.__getValue(dtype, dvalue, indicator, length) def __getName(self, dvar): """ @@ -126,45 +112,12 @@ self.name = dvar try: # Convert numbers to strings with preceding zeros - sort = int(dvar) - sort = "{0:06}".format(sort) + asInt = int(dvar) + self.sort = "{0:06}".format(asInt) except ValueError: - sort = dvar.lower() - - self.sort = sort + self.sort = dvar.lower() - def __getType(self, dtype): - """ - Private method to process the type of the variable. - - If type is known to have children, the corresponding flag is set. - - @param dtype type string - @type str - """ - # Python class? - if dtype.startswith('class '): - dtype = dtype[7:-1] - # Qt related stuff? - elif ( - (dtype.startswith(ConfigQtNames) and - dtype.endswith(ConfigKnownQtTypes)) or - dtype in ('instance', 'class') - ): - self.hasChildren = True - - # Special Qt types should not be expanded infinite - elif ".{0}".format(dtype) in ConfigKnownQtTypes: - self.type = dtype # It's a Qt type, so skipping translation is ok - return - - vtype = ConfigVarTypeDispStrings.get(dtype, dtype) - # Unkown types should be expandable by default - if vtype is dtype and dtype not in self.nonExpandableTypes: - self.hasChildren = True - self.type = QCoreApplication.translate("VariablesViewer", vtype) - - def __getValue(self, dtype, dvalue): + def __getValue(self, dtype, dvalue, indicator, length): """ Private method to process the variables value. @@ -175,62 +128,33 @@ @type str @param dvalue value of variable encoded as utf-8 @type str + @param indicator type indicator appended to the name + @type str + @param length length of the array or string (-1 if uninitialized + numpy.ndarray) + @type int or str """ - if dtype == 'collections.defaultdict': - dvalue, default_factory = dvalue.split('|') - self.indicator = '{{:<{0}>}}'.format(default_factory) - elif dtype == 'array.array': - dvalue, typecode = dvalue.split('|') - self.indicator = '[<{0}>]'.format(typecode) - else: - self.indicator = VariableItem.Type2Indicators.get(dtype, '') - - if dtype == 'numpy.ndarray': - if dvalue: - self.childCount = int(dvalue.split('x')[0]) - dvalue = VariableItem.noOfItemsStr.format(dvalue) - else: - dvalue = VariableItem.unsized - self.hasChildren = True - - elif dtype in VariableItem.arrayTypes: - self.childCount = int(dvalue) - dvalue = VariableItem.noOfItemsStr.format(dvalue) - self.hasChildren = True - - elif dtype == "Shiboken.EnumType": - self.hasChildren = True + length_code = length + if isinstance(length, str): + length = int(length.split('x')[0]) - elif dtype == 'str': - if VariableItem.rx_nonprintable.search(dvalue) is None: - with contextlib.suppress(Exception): - dvalue = ast.literal_eval(dvalue) - dvalue = str(dvalue) + if indicator and length > -2: + self.childCount = max(0, length) # Update count if array + if dtype == 'numpy.ndarray' and length == -1: + self.value = VariableItem.unsized + else: + self.value = VariableItem.noOfItemsStr.format(length_code) - elif ( - dvalue.startswith(("{", "(", "[")) and - dvalue.endswith(("}", ")", "]")) - ): - # it is most probably a dict, tuple or list derived class + if dtype != 'str': + self.valueShort = self.value + self.tooltip = str(self.value)[:256] + return + + if VariableItem.rx_nonprintable.search(dvalue) is None: with contextlib.suppress(Exception): - value = ast.literal_eval(dvalue) - valueTypeStr = str(type(value))[8:-2] - if valueTypeStr in VariableItem.arrayTypes: - self.childCount = len(value) - self.hasChildren = True + dvalue = ast.literal_eval(dvalue) - elif ( - (dvalue.endswith("})") and "({" in dvalue) or - (dvalue.endswith("])") and "([" in dvalue) - ): - # that is probably a set derived class - with contextlib.suppress(Exception): - value = ast.literal_eval(dvalue.split("(", 1)[1][:-1]) - valueTypeStr = str(type(value))[8:-2] - if valueTypeStr in VariableItem.arrayTypes: - self.childCount = len(value) - self.hasChildren = True - + dvalue = str(dvalue) self.value = dvalue if len(dvalue) > 2048: # 2 kB @@ -296,8 +220,8 @@ self.closedItems = [] visibility = self.tr("Globals") if globalScope else self.tr("Locals") - self.rootNode = VariableItem(None, visibility, self.tr("Type"), - self.tr("Value")) + self.rootNode = VariableItem(None, visibility, '', self.tr("Type"), + True, 0, self.tr("Value")) self.__globalScope = globalScope @@ -403,8 +327,6 @@ idx = itemStartIndex parent.currentCount = idx + len(vlist) - # Sort items for Python versions where dict doesn't retain order - vlist.sort(key=lambda x: x[0]) # Now update the table endIndex = idx + len(vlist) newChild = None @@ -668,7 +590,7 @@ 3: node.sort }.get(column) except AttributeError: - return ['None', '', '', ''][column] + return ('None', '', '', '')[column] elif role == Qt.ItemDataRole.BackgroundRole: if node in node.parent.changedItems: @@ -722,7 +644,7 @@ """ Public method get the header names. - @param section the header section (row/coulumn) + @param section the header section (row/column) @type int @param orientation the header's orientation @type QtCore.Qt.Orientation