eric7/DebugClients/Python/DebugVariables.py

branch
eric7
changeset 8312
800c432b34c8
parent 8243
cc717c2ae956
child 8479
903b7d3b58af
diff -r 4e8b98454baa -r 800c432b34c8 eric7/DebugClients/Python/DebugVariables.py
--- /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

eric ide

mercurial