--- 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