--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/DebugClients/Python/DebugVariables.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,714 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing classes and functions to dump variable contents. +""" + +import contextlib + +from DebugConfig import ConfigQtNames, ConfigKnownQtTypes, BatchSize + +# +# This code was inspired by pydevd. +# + +############################################################ +## Classes implementing resolvers for various compund 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 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: + with contextlib.suppress(Exception): + attribute = getattr(var, name) + d[name] = attribute + + return d + + +############################################################ +## Default Resolver +############################################################ + + +class DefaultResolver(BaseResolver): + """ + Class used to resolve the default way. + """ + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + names = dir(var) + if not names and hasattr(var, "__members__"): + names = var.__members__ + + d = {} + for name in names: + with contextlib.suppress(Exception): + attribute = getattr(var, name) + d[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 getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + 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[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().getDictionary(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, attribute, None) + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + d = {} + start = count = 0 + for idx, value in enumerate(var): + d[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().getDictionary(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 tuple or list + @param attribute id of the value to extract + @type str + @return value of the attribute + @rtype any + """ + return super().resolve(list(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 + """ + return super().getDictionary(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 getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + d = {} + start = count = 0 + for value in var: + count += 1 + d["'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 + additionals = super().getDictionary(var) + yield -1, additionals + + 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 tuple or list + @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 getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + 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[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().getDictionary(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') + elif self.__isNumeric(var): + if var.size == 0: + d['min'] = 'empty array' + d['max'] = 'empty array' + d['mean'] = 'empty array' + else: + d['min'] = var.min() + d['max'] = var.max() + d['mean'] = var.mean() + else: + d['min'] = 'not a numeric object' + d['max'] = 'not a numeric object' + d['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 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 in var: + if id(key) == expectedID: + return var.getlist(key) + + return None + + def getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + 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[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().getDictionary(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 tuple or list + @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 getDictionary(self, var): + """ + Public method to get the attributes of a variable as a dictionary. + + @param var variable to be converted + @type any + @yield tuple containing the batch start index and a dictionary + containing the variable attributes + @ytype tuple of (int, dict) + """ + d = {} + start = count = 0 + allItems = var.tolist() + + for idx, value in enumerate(allItems): + d[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().getDictionary(var) + + # Special data for array type: convert typecode to readable text + d['type'] = self.TypeCodeMap.get(var.typecode, 'illegal type') + + yield -1, d + + while True: + yield -2, {} + + +defaultResolver = DefaultResolver() +dictResolver = DictResolver() +listResolver = ListResolver() +dictViewResolver = DictViewResolver() +setResolver = SetResolver() +ndarrayResolver = NdArrayResolver() +multiValueDictResolver = MultiValueDictResolver() +arrayResolver = ArrayResolver() + +############################################################ +## 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), + (set, setResolver), + (frozenset, setResolver), + ] + + with contextlib.suppress(Exception): + _TypeMap.append((long, None)) # __IGNORE_WARNING__ + + with contextlib.suppress(ImportError): + import array + _TypeMap.append((array.array, arrayResolver)) + # array.array may not be available + + with contextlib.suppress(ImportError): + import numpy + _TypeMap.append((numpy.ndarray, ndarrayResolver)) + # numpy may not be installed + + with contextlib.suppress(ImportError): + from django.utils.datastructures import MultiValueDict + # it should go before dict + _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver)) + # django may not be installed + + 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 + + +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 name, type string and resolver + @rtype tuple of str, str, 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 = 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 typeName, typeStr, resolver + +# +# eflag: noqa = Y113