src/eric7/DebugClients/Python/DebugVariables.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9462
e65379fdbd97
diff -r d23e9854aea4 -r 18a7312cfdb3 src/eric7/DebugClients/Python/DebugVariables.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/DebugClients/Python/DebugVariables.py	Sun Jul 24 11:29:56 2022 +0200
@@ -0,0 +1,860 @@
+# -*- 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

eric ide

mercurial