src/eric7/DebugClients/Python/DebugVariables.py

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

eric ide

mercurial