src/eric7/DebugClients/Python/DebugVariables.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9462
e65379fdbd97
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing classes and functions to dump variable contents.
8 """
9
10 import contextlib
11 import sys
12
13 from collections.abc import ItemsView, KeysView, ValuesView
14
15 from DebugConfig import ConfigQtNames, ConfigKnownQtTypes, BatchSize
16
17 #
18 # This code was inspired by pydevd.
19 #
20
21 ############################################################
22 ## Classes implementing resolvers for various compound types
23 ############################################################
24
25
26 class BaseResolver:
27 """
28 Base class of the resolver class tree.
29 """
30
31 def resolve(self, var, attribute):
32 """
33 Public method to get an attribute from a variable.
34
35 @param var variable to extract an attribute or value from
36 @type any
37 @param attribute name of the attribute to extract
38 @type str
39 @return value of the attribute
40 @rtype any
41 """
42 return getattr(var, attribute, None)
43
44 def getVariableList(self, var):
45 """
46 Public method to get the attributes of a variable as a list.
47
48 @param var variable to be converted
49 @type any
50 @return list containing the variable attributes
51 @rtype list
52 """
53 d = []
54 for name in dir(var):
55 with contextlib.suppress(Exception):
56 attribute = getattr(var, name)
57 d.append((name, attribute))
58
59 return d
60
61
62 ############################################################
63 ## Default Resolver
64 ############################################################
65
66
67 class DefaultResolver(BaseResolver):
68 """
69 Class used to resolve the default way.
70 """
71
72 def getVariableList(self, var):
73 """
74 Public method to get the attributes of a variable as a list.
75
76 @param var variable to be converted
77 @type any
78 @yield tuple containing the batch start index and a list
79 containing the variable attributes
80 @ytype tuple of (int, list)
81 """
82 d = []
83 for name in dir(var):
84 with contextlib.suppress(Exception):
85 attribute = getattr(var, name)
86 d.append((name, attribute))
87
88 yield -1, d
89 while True:
90 yield -2, []
91
92
93 ############################################################
94 ## Resolver for Dictionaries
95 ############################################################
96
97
98 class DictResolver(BaseResolver):
99 """
100 Class used to resolve from a dictionary.
101 """
102
103 def resolve(self, var, attribute):
104 """
105 Public method to get an attribute from a variable.
106
107 @param var variable to extract an attribute or value from
108 @type dict
109 @param attribute name of the attribute to extract
110 @type str
111 @return value of the attribute
112 @rtype any
113 """
114 if " (ID:" not in attribute:
115 try:
116 return var[attribute]
117 except Exception:
118 return getattr(var, attribute, None)
119
120 expectedID = int(attribute.split(" (ID:")[-1][:-1])
121 for key, value in var.items():
122 if id(key) == expectedID:
123 return value
124
125 return None
126
127 def keyToStr(self, key):
128 """
129 Public method to get a string representation for a key.
130
131 @param key key to be converted
132 @type any
133 @return string representation of the given key
134 @rtype str
135 """
136 if isinstance(key, str):
137 key = repr(key)
138 # Special handling for bytes object
139 # Raw and f-Strings are always converted to str
140 if key[0] == "b":
141 key = key[1:]
142
143 return key # __IGNORE_WARNING_M834__
144
145 def getVariableList(self, var):
146 """
147 Public method to get the attributes of a variable as a list.
148
149 @param var variable to be converted
150 @type any
151 @yield tuple containing the batch start index and a list
152 containing the variable attributes
153 @ytype tuple of (int, list)
154 """
155 d = []
156 start = count = 0
157 allItems = list(var.items())
158 try:
159 # Fast path: all items from same type
160 allItems.sort(key=lambda x: x[0])
161 except TypeError:
162 # Slow path: only sort items with same type (Py3 only)
163 allItems.sort(key=lambda x: (str(x[0]), x[0]))
164
165 for key, value in allItems:
166 key = "{0} (ID:{1})".format(self.keyToStr(key), id(key))
167 d.append((key, value))
168 count += 1
169 if count >= BatchSize:
170 yield start, d
171 start += count
172 count = 0
173 d = []
174
175 if d:
176 yield start, d
177
178 # in case it has additional fields
179 d = super().getVariableList(var)
180 yield -1, d
181
182 while True:
183 yield -2, []
184
185
186 ############################################################
187 ## Resolver for Lists and Tuples
188 ############################################################
189
190
191 class ListResolver(BaseResolver):
192 """
193 Class used to resolve from a tuple or list.
194 """
195
196 def resolve(self, var, attribute):
197 """
198 Public method to get an attribute from a variable.
199
200 @param var variable to extract an attribute or value from
201 @type tuple or list
202 @param attribute name of the attribute to extract
203 @type str
204 @return value of the attribute
205 @rtype any
206 """
207 try:
208 return var[int(attribute)]
209 except Exception:
210 return getattr(var, str(attribute), None)
211
212 def getVariableList(self, var):
213 """
214 Public method to get the attributes of a variable as a list.
215
216 @param var variable to be converted
217 @type any
218 @yield tuple containing the batch start index and a list
219 containing the variable attributes
220 @ytype tuple of (int, list)
221 """
222 d = []
223 start = count = 0
224 for idx, value in enumerate(var):
225 d.append((idx, value))
226 count += 1
227 if count >= BatchSize:
228 yield start, d
229 start = idx + 1
230 count = 0
231 d = []
232
233 if d:
234 yield start, d
235
236 # in case it has additional fields
237 d = super().getVariableList(var)
238 yield -1, d
239
240 while True:
241 yield -2, []
242
243
244 ############################################################
245 ## Resolver for dict_items, dict_keys and dict_values
246 ############################################################
247
248
249 class DictViewResolver(ListResolver):
250 """
251 Class used to resolve from dict views.
252 """
253
254 def resolve(self, var, attribute):
255 """
256 Public method to get an attribute from a variable.
257
258 @param var variable to extract an attribute or value from
259 @type dict_items, dict_keys or dict_values
260 @param attribute id of the value to extract
261 @type str
262 @return value of the attribute
263 @rtype any
264 """
265 return super().resolve(list(var), attribute)
266
267 def getVariableList(self, var):
268 """
269 Public method to get the attributes of a variable as a list.
270
271 @param var variable to be converted
272 @type any
273 @yield tuple containing the batch start index and a list
274 containing the variable attributes
275 @ytype tuple of (int, list)
276 """
277 yield from super().getVariableList(list(var))
278
279
280 ############################################################
281 ## Resolver for Sets and Frozensets
282 ############################################################
283
284
285 class SetResolver(BaseResolver):
286 """
287 Class used to resolve from a set or frozenset.
288 """
289
290 def resolve(self, var, attribute):
291 """
292 Public method to get an attribute from a variable.
293
294 @param var variable to extract an attribute or value from
295 @type tuple or list
296 @param attribute id of the value to extract
297 @type str
298 @return value of the attribute
299 @rtype any
300 """
301 if attribute.startswith("'ID: "):
302 attribute = attribute.split(None, 1)[1][:-1]
303 try:
304 attribute = int(attribute)
305 except Exception:
306 return getattr(var, attribute, None)
307
308 for v in var:
309 if id(v) == attribute:
310 return v
311
312 return None
313
314 def getVariableList(self, var):
315 """
316 Public method to get the attributes of a variable as a list.
317
318 @param var variable to be converted
319 @type any
320 @yield tuple containing the batch start index and a list
321 containing the variable attributes
322 @ytype tuple of (int, list)
323 """
324 d = []
325 start = count = 0
326 for value in var:
327 count += 1
328 d.append(("'ID: {0}'".format(id(value)), value))
329 if count >= BatchSize:
330 yield start, d
331 start += count
332 count = 0
333 d = []
334
335 if d:
336 yield start, d
337
338 # in case it has additional fields
339 d = super().getVariableList(var)
340 yield -1, d
341
342 while True:
343 yield -2, []
344
345
346 ############################################################
347 ## Resolver for Numpy Arrays
348 ############################################################
349
350
351 class NdArrayResolver(BaseResolver):
352 """
353 Class used to resolve from numpy ndarray including some meta data.
354 """
355
356 def __isNumeric(self, arr):
357 """
358 Private method to check, if an array is of a numeric type.
359
360 @param arr array to check
361 @type ndarray
362 @return flag indicating a numeric array
363 @rtype bool
364 """
365 try:
366 return arr.dtype.kind in "biufc"
367 except AttributeError:
368 return False
369
370 def resolve(self, var, attribute):
371 """
372 Public method to get an attribute from a variable.
373
374 @param var variable to extract an attribute or value from
375 @type ndarray
376 @param attribute id of the value to extract
377 @type str
378 @return value of the attribute
379 @rtype any
380 """
381 if attribute == "min":
382 if self.__isNumeric(var):
383 return var.min()
384 else:
385 return None
386
387 if attribute == "max":
388 if self.__isNumeric(var):
389 return var.max()
390 else:
391 return None
392
393 if attribute == "mean":
394 if self.__isNumeric(var):
395 return var.mean()
396 else:
397 return None
398
399 try:
400 return var[int(attribute)]
401 except Exception:
402 return getattr(var, attribute, None)
403
404 return None
405
406 def getVariableList(self, var):
407 """
408 Public method to get the attributes of a variable as a list.
409
410 @param var variable to be converted
411 @type any
412 @yield tuple containing the batch start index and a list
413 containing the variable attributes
414 @ytype tuple of (int, list)
415 """
416 d = []
417 start = count = 0
418 try:
419 len(var) # Check if it's an unsized object, e.g. np.ndarray(())
420 allItems = var.tolist()
421 except TypeError: # TypeError: len() of unsized object
422 allItems = []
423
424 for idx, value in enumerate(allItems):
425 d.append((str(idx), value))
426 count += 1
427 if count >= BatchSize:
428 yield start, d
429 start += count
430 count = 0
431 d = []
432
433 if d:
434 yield start, d
435
436 # in case it has additional fields
437 d = super().getVariableList(var)
438
439 if var.size > 1024 * 1024:
440 d.append(
441 ("min", "ndarray too big, calculating min would slow down debugging")
442 )
443 d.append(
444 ("max", "ndarray too big, calculating max would slow down debugging")
445 )
446 d.append(
447 ("mean", "ndarray too big, calculating mean would slow down debugging")
448 )
449 elif self.__isNumeric(var):
450 if var.size == 0:
451 d.append(("min", "empty array"))
452 d.append(("max", "empty array"))
453 d.append(("mean", "empty array"))
454 else:
455 d.append(("min", var.min()))
456 d.append(("max", var.max()))
457 d.append(("mean", var.mean()))
458 else:
459 d.append(("min", "not a numeric object"))
460 d.append(("max", "not a numeric object"))
461 d.append(("mean", "not a numeric object"))
462
463 yield -1, d
464
465 while True:
466 yield -2, []
467
468
469 ############################################################
470 ## Resolver for Django Multi Value Dictionaries
471 ############################################################
472
473
474 class MultiValueDictResolver(DictResolver):
475 """
476 Class used to resolve from Django multi value dictionaries.
477 """
478
479 def resolve(self, var, attribute):
480 """
481 Public method to get an attribute from a variable.
482
483 @param var variable to extract an attribute or value from
484 @type MultiValueDict
485 @param attribute name of the attribute to extract
486 @type str
487 @return value of the attribute
488 @rtype any
489 """
490 if " (ID:" not in attribute:
491 try:
492 return var[attribute]
493 except Exception:
494 return getattr(var, attribute, None)
495
496 expectedID = int(attribute.split(" (ID:")[-1][:-1])
497 for key in var:
498 if id(key) == expectedID:
499 return var.getlist(key)
500
501 return None
502
503 def getVariableList(self, var):
504 """
505 Public method to get the attributes of a variable as a list.
506
507 @param var variable to be converted
508 @type any
509 @yield tuple containing the batch start index and a list
510 containing the variable attributes
511 @ytype tuple of (int, list)
512 """
513 d = []
514 start = count = 0
515 allKeys = list(var.keys())
516 try:
517 # Fast path: all items from same type
518 allKeys.sort()
519 except TypeError:
520 # Slow path: only sort items with same type (Py3 only)
521 allKeys.sort(key=lambda x: (str(x), x))
522
523 for key in allKeys:
524 dkey = "{0} (ID:{1})".format(self.keyToStr(key), id(key))
525 d.append((dkey, var.getlist(key)))
526 count += 1
527 if count >= BatchSize:
528 yield start, d
529 start += count
530 count = 0
531 d = []
532
533 if d:
534 yield start, d
535
536 # in case it has additional fields
537 d = super(DictResolver, self).getVariableList(var)
538 yield -1, d
539
540 while True:
541 yield -2, []
542
543
544 ############################################################
545 ## Resolver for array.array
546 ############################################################
547
548
549 class ArrayResolver(BaseResolver):
550 """
551 Class used to resolve from array.array including some meta data.
552 """
553
554 TypeCodeMap = {
555 "b": "int (signed char)",
556 "B": "int (unsigned char)",
557 "u": "Unicode character (Py_UNICODE)",
558 "h": "int (signed short)",
559 "H": "int (unsigned short)",
560 "i": "int (signed int)",
561 "I": "int (unsigned int)",
562 "l": "int (signed long)",
563 "L": "int (unsigned long)",
564 "q": "int (signed long long)",
565 "Q": "int (unsigned long long)",
566 "f": "float (float)",
567 "d": "float (double)",
568 }
569
570 def resolve(self, var, attribute):
571 """
572 Public method to get an attribute from a variable.
573
574 @param var variable to extract an attribute or value from
575 @type array.array
576 @param attribute id of the value to extract
577 @type str
578 @return value of the attribute
579 @rtype any
580 """
581 try:
582 return var[int(attribute)]
583 except Exception:
584 return getattr(var, attribute, None)
585
586 return None
587
588 def getVariableList(self, var):
589 """
590 Public method to get the attributes of a variable as a list.
591
592 @param var variable to be converted
593 @type any
594 @yield tuple containing the batch start index and a list
595 containing the variable attributes
596 @ytype tuple of (int, list)
597 """
598 d = []
599 start = count = 0
600 allItems = var.tolist()
601
602 for idx, value in enumerate(allItems):
603 d.append((str(idx), value))
604 count += 1
605 if count >= BatchSize:
606 yield start, d
607 start += count
608 count = 0
609 d = []
610
611 if d:
612 yield start, d
613
614 # in case it has additional fields
615 d = super().getVariableList(var)
616
617 # Special data for array type: convert typecode to readable text
618 d.append(("type", self.TypeCodeMap.get(var.typecode, "illegal type")))
619
620 yield -1, d
621
622 while True:
623 yield -2, []
624
625
626 ############################################################
627 ## PySide / PyQt Resolver
628 ############################################################
629
630
631 class QtResolver(BaseResolver):
632 """
633 Class used to resolve the Qt implementations.
634 """
635
636 def resolve(self, var, attribute):
637 """
638 Public method to get an attribute from a variable.
639
640 @param var variable to extract an attribute or value from
641 @type Qt objects
642 @param attribute name of the attribute to extract
643 @type str
644 @return value of the attribute
645 @rtype any
646 """
647 if attribute == "internalPointer":
648 return var.internalPointer()
649
650 return getattr(var, attribute, None)
651
652 def getVariableList(self, var):
653 """
654 Public method to get the attributes of a variable as a list.
655
656 @param var variable to be converted
657 @type any
658 @yield tuple containing the batch start index and a list
659 containing the variable attributes
660 @ytype tuple of (int, list)
661 """
662 d = []
663 attributes = ()
664 # Gently handle exception which could occure as special
665 # cases, e.g. already deleted C++ objects, str conversion..
666 with contextlib.suppress(Exception):
667 qttype = type(var).__name__
668
669 if qttype in ("QLabel", "QPushButton"):
670 attributes = ("text",)
671 elif qttype == "QByteArray":
672 d.append(("bytes", bytes(var)))
673 d.append(("hex", "QByteArray", "{0}".format(var.toHex())))
674 d.append(("base64", "QByteArray", "{0}".format(var.toBase64())))
675 d.append(
676 (
677 "percent encoding",
678 "QByteArray",
679 "{0}".format(var.toPercentEncoding()),
680 )
681 )
682 elif qttype in ("QPoint", "QPointF"):
683 attributes = ("x", "y")
684 elif qttype in ("QRect", "QRectF"):
685 attributes = ("x", "y", "width", "height")
686 elif qttype in ("QSize", "QSizeF"):
687 attributes = ("width", "height")
688 elif qttype == "QColor":
689 attributes = ("name",)
690 r, g, b, a = var.getRgb()
691 d.append(("rgba", "{0:d}, {1:d}, {2:d}, {3:d}".format(r, g, b, a)))
692 h, s, v, a = var.getHsv()
693 d.append(("hsva", "{0:d}, {1:d}, {2:d}, {3:d}".format(h, s, v, a)))
694 c, m, y, k, a = var.getCmyk()
695 d.append(
696 ("cmyka", "{0:d}, {1:d}, {2:d}, {3:d}, {4:d}".format(c, m, y, k, a))
697 )
698 elif qttype in ("QDate", "QTime", "QDateTime"):
699 d.append((qttype[1:].lower(), var.toString()))
700 elif qttype == "QDir":
701 attributes = ("path", "absolutePath", "canonicalPath")
702 elif qttype == "QFile":
703 attributes = ("fileName",)
704 elif qttype == "QFont":
705 attributes = ("family", "pointSize", "weight", "bold", "italic")
706 elif qttype == "QUrl":
707 d.append(("url", var.toString()))
708 attributes = ("scheme", "userName", "password", "host", "port", "path")
709 elif qttype == "QModelIndex":
710 valid = var.isValid()
711 d.append(("valid", valid))
712 if valid:
713 d.append(("internalPointer", var.internalPointer()))
714 attributes = ("row", "column", "internalId")
715 elif qttype in ("QRegExp", "QRegularExpression"):
716 attributes = ("pattern",)
717
718 # GUI stuff
719 elif qttype == "QAction":
720 d.append(("shortcut", var.shortcut().toString()))
721 attributes = ("objectName", "text", "iconText", "toolTip", "whatsThis")
722
723 elif qttype == "QKeySequence":
724 d.append(("keySequence", var.toString()))
725
726 # XML stuff
727 elif qttype == "QDomAttr":
728 attributes = ("name", "var")
729 elif qttype in ("QDomCharacterData", "QDomComment", "QDomText"):
730 attributes = ("data",)
731 elif qttype == "QDomDocument":
732 d.append(("text", var.toString()))
733 elif qttype == "QDomElement":
734 attributes = ("tagName", "text")
735
736 # Networking stuff
737 elif qttype == "QHostAddress":
738 d.append(("address", var.toString()))
739
740 # PySide specific
741 elif qttype == "EnumType": # Not in PyQt possible
742 for key, value in var.values.items():
743 d.append((key, int(value)))
744
745 for attribute in attributes:
746 d.append((attribute, getattr(var, attribute)()))
747
748 # add additional fields
749 if qttype != "EnumType":
750 d.extend(super().getVariableList(var))
751
752 yield -1, d
753 while True:
754 yield -2, []
755
756
757 defaultResolver = DefaultResolver()
758 dictResolver = DictResolver()
759 listResolver = ListResolver()
760 dictViewResolver = DictViewResolver()
761 setResolver = SetResolver()
762 ndarrayResolver = NdArrayResolver()
763 multiValueDictResolver = MultiValueDictResolver()
764 arrayResolver = ArrayResolver()
765 qtResolver = QtResolver()
766
767
768 ############################################################
769 ## Methods to determine the type of a variable and the
770 ## resolver class to use
771 ############################################################
772
773 _TypeMap = _ArrayTypes = None
774 _TryArray = _TryNumpy = _TryDjango = True
775 _MapCount = 0
776
777
778 def _initTypeMap():
779 """
780 Protected function to initialize the type map.
781 """
782 global _TypeMap
783
784 # Type map for special handling of array types.
785 # All other types not listed here use the default resolver.
786 _TypeMap = [
787 (tuple, listResolver),
788 (list, listResolver),
789 (dict, dictResolver),
790 (set, setResolver),
791 (frozenset, setResolver),
792 (ItemsView, dictViewResolver), # Since Python 3.0
793 (KeysView, dictViewResolver),
794 (ValuesView, dictViewResolver),
795 ]
796
797
798 # Initialize the static type map
799 _initTypeMap()
800
801
802 def updateTypeMap():
803 """
804 Public function to update the type map based on module imports.
805 """
806 global _TypeMap, _ArrayTypes, _TryArray, _TryNumpy, _TryDjango, _MapCount
807
808 # array.array may not be imported (yet)
809 if _TryArray and "array" in sys.modules:
810 import array
811
812 _TypeMap.append((array.array, arrayResolver))
813 _TryArray = False
814
815 # numpy may not be imported (yet)
816 if _TryNumpy and "numpy" in sys.modules:
817 import numpy
818
819 _TypeMap.append((numpy.ndarray, ndarrayResolver))
820 _TryNumpy = False
821
822 # django may not be imported (yet)
823 if _TryDjango and "django" in sys.modules:
824 from django.utils.datastructures import MultiValueDict
825
826 # it should go before dict
827 _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver))
828 _TryDjango = False
829
830 # If _TypeMap changed, rebuild the _ArrayTypes tuple
831 if _MapCount != len(_TypeMap):
832 _ArrayTypes = tuple(typ for typ, _resolver in _TypeMap)
833 _MapCount = len(_TypeMap)
834
835
836 def getResolver(obj):
837 """
838 Public method to get the resolver based on the type info of an object.
839
840 @param obj object to get resolver for
841 @type any
842 @return resolver
843 @rtype BaseResolver
844 """
845 # Between PyQt and PySide the returned type is different (class vs. type)
846 typeStr = str(type(obj)).split(" ", 1)[-1]
847 typeStr = typeStr[1:-2]
848
849 if typeStr.startswith(ConfigQtNames) and typeStr.endswith(ConfigKnownQtTypes):
850 return qtResolver
851
852 for typeData, resolver in _TypeMap: # __IGNORE_WARNING_M507__
853 if isinstance(obj, typeData):
854 return resolver
855
856 return defaultResolver
857
858
859 #
860 # eflag: noqa = Y113

eric ide

mercurial