Thu, 07 Jul 2022 11:23:56 +0200
Reorganized the project structure to use the source layout in order to support up-to-date build systems with "pyproject.toml".
# -*- coding: utf-8 -*- # Copyright (c) 2016 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing classes and functions to dump variable contents. """ import contextlib import sys from collections.abc import ItemsView, KeysView, ValuesView from DebugConfig import ConfigQtNames, ConfigKnownQtTypes, BatchSize # # This code was inspired by pydevd. # ############################################################ ## Classes implementing resolvers for various compound types ############################################################ class BaseResolver: """ 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 """ 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 @return list containing the variable attributes @rtype list """ d = [] for name in dir(var): with contextlib.suppress(Exception): attribute = getattr(var, name) d.append((name, attribute)) return d ############################################################ ## Default Resolver ############################################################ class DefaultResolver(BaseResolver): """ Class used to resolve the default way. """ 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 = [] for name in dir(var): with contextlib.suppress(Exception): attribute = getattr(var, name) d.append((name, attribute)) yield -1, d while True: yield -2, [] ############################################################ ## Resolver for Dictionaries ############################################################ 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 " (ID:" not in attribute: try: return var[attribute] except Exception: return getattr(var, attribute, None) expectedID = int(attribute.split(" (ID:")[-1][:-1]) for key, value in var.items(): if id(key) == expectedID: return value return None def keyToStr(self, key): """ Public method to get a string representation for a key. @param key key to be converted @type any @return string representation of the given key @rtype str """ if isinstance(key, str): key = repr(key) # Special handling for bytes object # Raw and f-Strings are always converted to str if key[0] == 'b': key = key[1:] return key # __IGNORE_WARNING_M834__ 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 = [] start = count = 0 allItems = list(var.items()) try: # Fast path: all items from same type allItems.sort(key=lambda x: x[0]) except TypeError: # Slow path: only sort items with same type (Py3 only) allItems.sort(key=lambda x: (str(x[0]), x[0])) for key, value in allItems: key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) d.append((key, value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 d = [] if d: yield start, d # in case it has additional fields d = super().getVariableList(var) yield -1, d while True: yield -2, [] ############################################################ ## Resolver for Lists and Tuples ############################################################ 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 """ try: return var[int(attribute)] except Exception: return getattr(var, str(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 = [] start = count = 0 for idx, value in enumerate(var): d.append((idx, value)) count += 1 if count >= BatchSize: yield start, d start = idx + 1 count = 0 d = [] if d: yield start, d # in case it has additional fields d = super().getVariableList(var) yield -1, d while True: yield -2, [] ############################################################ ## Resolver for dict_items, dict_keys and dict_values ############################################################ class DictViewResolver(ListResolver): """ Class used to resolve from dict views. """ 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_items, dict_keys or dict_values @param attribute id of the value to extract @type str @return value of the attribute @rtype any """ return super().resolve(list(var), attribute) 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) """ yield from super().getVariableList(list(var)) ############################################################ ## Resolver for Sets and Frozensets ############################################################ class SetResolver(BaseResolver): """ Class used to resolve from a set or frozenset. """ 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 id of the value to extract @type str @return value of the attribute @rtype any """ if attribute.startswith("'ID: "): attribute = attribute.split(None, 1)[1][:-1] try: attribute = int(attribute) except Exception: return getattr(var, attribute, None) for v in var: if id(v) == attribute: return v return 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 = [] start = count = 0 for value in var: count += 1 d.append(("'ID: {0}'".format(id(value)), value)) if count >= BatchSize: yield start, d start += count count = 0 d = [] if d: yield start, d # in case it has additional fields d = super().getVariableList(var) yield -1, d while True: yield -2, [] ############################################################ ## Resolver for Numpy Arrays ############################################################ class NdArrayResolver(BaseResolver): """ Class used to resolve from numpy ndarray including some meta data. """ def __isNumeric(self, arr): """ Private method to check, if an array is of a numeric type. @param arr array to check @type ndarray @return flag indicating a numeric array @rtype bool """ try: return arr.dtype.kind in 'biufc' except AttributeError: return False 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 ndarray @param attribute id of the value to extract @type str @return value of the attribute @rtype any """ if attribute == 'min': if self.__isNumeric(var): return var.min() else: return None if attribute == 'max': if self.__isNumeric(var): return var.max() else: return None if attribute == 'mean': if self.__isNumeric(var): return var.mean() else: return None try: return var[int(attribute)] except Exception: return getattr(var, attribute, None) return 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 = [] start = count = 0 try: len(var) # Check if it's an unsized object, e.g. np.ndarray(()) allItems = var.tolist() except TypeError: # TypeError: len() of unsized object allItems = [] for idx, value in enumerate(allItems): d.append((str(idx), value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 d = [] if d: yield start, d # in case it has additional fields d = super().getVariableList(var) if var.size > 1024 * 1024: 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.append(('min', 'empty array')) d.append(('max', 'empty array')) d.append(('mean', 'empty array')) else: d.append(('min', var.min())) d.append(('max', var.max())) d.append(('mean', var.mean())) else: 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, [] ############################################################ ## Resolver for Django Multi Value Dictionaries ############################################################ class MultiValueDictResolver(DictResolver): """ Class used to resolve from Django multi value dictionaries. """ 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 MultiValueDict @param attribute name of the attribute to extract @type str @return value of the attribute @rtype any """ if " (ID:" not in attribute: try: return var[attribute] except Exception: return getattr(var, attribute, None) expectedID = int(attribute.split(" (ID:")[-1][:-1]) for key in var: if id(key) == expectedID: return var.getlist(key) return 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 = [] start = count = 0 allKeys = list(var.keys()) try: # Fast path: all items from same type allKeys.sort() except TypeError: # Slow path: only sort items with same type (Py3 only) allKeys.sort(key=lambda x: (str(x), x)) for key in allKeys: dkey = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) d.append((dkey, var.getlist(key))) count += 1 if count >= BatchSize: yield start, d start += count count = 0 d = [] if d: yield start, d # in case it has additional fields d = super(DictResolver, self).getVariableList(var) yield -1, d while True: yield -2, [] ############################################################ ## Resolver for array.array ############################################################ class ArrayResolver(BaseResolver): """ Class used to resolve from array.array including some meta data. """ TypeCodeMap = { "b": "int (signed char)", "B": "int (unsigned char)", "u": "Unicode character (Py_UNICODE)", "h": "int (signed short)", "H": "int (unsigned short)", "i": "int (signed int)", "I": "int (unsigned int)", "l": "int (signed long)", "L": "int (unsigned long)", "q": "int (signed long long)", "Q": "int (unsigned long long)", "f": "float (float)", "d": "float (double)", } 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 array.array @param attribute id of the value to extract @type str @return value of the attribute @rtype any """ try: return var[int(attribute)] except Exception: return getattr(var, attribute, None) return 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 = [] start = count = 0 allItems = var.tolist() for idx, value in enumerate(allItems): d.append((str(idx), value)) count += 1 if count >= BatchSize: yield start, d start += count count = 0 d = [] if d: yield start, d # in case it has additional fields d = super().getVariableList(var) # Special data for array type: convert typecode to readable text d.append(('type', self.TypeCodeMap.get(var.typecode, 'illegal type'))) yield -1, d while True: 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.. with contextlib.suppress(Exception): 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))) 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() dictResolver = DictResolver() listResolver = ListResolver() dictViewResolver = DictViewResolver() setResolver = SetResolver() ndarrayResolver = NdArrayResolver() multiValueDictResolver = MultiValueDictResolver() arrayResolver = ArrayResolver() qtResolver = QtResolver() ############################################################ ## Methods to determine the type of a variable and the ## resolver class to use ############################################################ _TypeMap = _ArrayTypes = None _TryArray = _TryNumpy = _TryDjango = True _MapCount = 0 def _initTypeMap(): """ Protected function to initialize the type map. """ global _TypeMap # Type map for special handling of array types. # All other types not listed here use the default resolver. _TypeMap = [ (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 # array.array may not be imported (yet) if _TryArray and 'array' in sys.modules: import array _TypeMap.append((array.array, arrayResolver)) _TryArray = False # numpy may not be imported (yet) if _TryNumpy and 'numpy' in sys.modules: import numpy _TypeMap.append((numpy.ndarray, ndarrayResolver)) _TryNumpy = False # 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)) _TryDjango = False # If _TypeMap changed, rebuild the _ArrayTypes tuple if _MapCount != len(_TypeMap): _ArrayTypes = tuple(typ for typ, _resolver in _TypeMap) _MapCount = len(_TypeMap) def getResolver(obj): """ Public method to get the resolver based on the type info of an object. @param obj object to get resolver for @type any @return resolver @rtype BaseResolver """ # Between PyQt and PySide the returned type is different (class vs. type) typeStr = str(type(obj)).split(' ', 1)[-1] typeStr = typeStr[1:-2] if ( typeStr.startswith(ConfigQtNames) and typeStr.endswith(ConfigKnownQtTypes) ): return qtResolver for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__ if isinstance(obj, typeData): return resolver return defaultResolver # # eflag: noqa = Y113