|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing classes and functions to dump variable contents. |
|
8 """ |
|
9 |
|
10 # |
|
11 # This code was inspired by pydevd. |
|
12 # |
|
13 |
|
14 MaxItemsToHandle = 300 |
|
15 TooLargeMessage = ("Too large to show contents. Max items to show: " + |
|
16 str(MaxItemsToHandle)) |
|
17 TooLargeAttribute = "Too large to be handled." |
|
18 |
|
19 ############################################################ |
|
20 ## Classes implementing resolvers for various compund types |
|
21 ############################################################ |
|
22 |
|
23 |
|
24 class BaseResolver(object): |
|
25 """ |
|
26 Base class of the resolver class tree. |
|
27 """ |
|
28 def resolve(self, var, attribute): |
|
29 """ |
|
30 Public method to get an attribute from a variable. |
|
31 |
|
32 @param var variable to extract an attribute or value from |
|
33 @type any |
|
34 @param attribute name of the attribute to extract |
|
35 @type str |
|
36 @return value of the attribute |
|
37 @rtype any |
|
38 @exception NotImplementedError raised to indicate a missing |
|
39 implementation |
|
40 """ # __IGNORE_WARNING_D235__ |
|
41 raise NotImplementedError |
|
42 |
|
43 def getDictionary(self, var): |
|
44 """ |
|
45 Public method to get the attributes of a variable as a dictionary. |
|
46 |
|
47 @param var variable to be converted |
|
48 @type any |
|
49 @return dictionary containing the variable attributes |
|
50 @rtype dict |
|
51 @exception NotImplementedError raised to indicate a missing |
|
52 implementation |
|
53 """ # __IGNORE_WARNING_D235__ |
|
54 raise NotImplementedError |
|
55 |
|
56 |
|
57 ############################################################ |
|
58 ## Default Resolver |
|
59 ############################################################ |
|
60 |
|
61 |
|
62 class DefaultResolver(BaseResolver): |
|
63 """ |
|
64 Class used to resolve the default way. |
|
65 """ |
|
66 def resolve(self, var, attribute): |
|
67 """ |
|
68 Public method to get an attribute from a variable. |
|
69 |
|
70 @param var variable to extract an attribute or value from |
|
71 @type any |
|
72 @param attribute name of the attribute to extract |
|
73 @type str |
|
74 @return value of the attribute |
|
75 @rtype any |
|
76 """ |
|
77 return getattr(var, attribute, None) |
|
78 |
|
79 def getDictionary(self, var): |
|
80 """ |
|
81 Public method to get the attributes of a variable as a dictionary. |
|
82 |
|
83 @param var variable to be converted |
|
84 @type any |
|
85 @return dictionary containing the variable attributes |
|
86 @rtype dict |
|
87 """ |
|
88 names = dir(var) |
|
89 if not names and hasattr(var, "__members__"): |
|
90 names = var.__members__ |
|
91 |
|
92 d = {} |
|
93 for name in names: |
|
94 try: |
|
95 attribute = getattr(var, name) |
|
96 d[name] = attribute |
|
97 except Exception: |
|
98 pass # if we can't get it, simply ignore it |
|
99 |
|
100 return d |
|
101 |
|
102 |
|
103 ############################################################ |
|
104 ## Resolver for Dictionaries |
|
105 ############################################################ |
|
106 |
|
107 |
|
108 class DictResolver(BaseResolver): |
|
109 """ |
|
110 Class used to resolve from a dictionary. |
|
111 """ |
|
112 def resolve(self, var, attribute): |
|
113 """ |
|
114 Public method to get an attribute from a variable. |
|
115 |
|
116 @param var variable to extract an attribute or value from |
|
117 @type dict |
|
118 @param attribute name of the attribute to extract |
|
119 @type str |
|
120 @return value of the attribute |
|
121 @rtype any |
|
122 """ |
|
123 if attribute in ('___len___', TooLargeAttribute): |
|
124 return None |
|
125 |
|
126 if "(ID:" not in attribute: |
|
127 try: |
|
128 return var[attribute] |
|
129 except Exception: |
|
130 return getattr(var, attribute, None) |
|
131 |
|
132 expectedID = int(attribute.split("(ID:")[-1][:-1]) |
|
133 for key, value in var.items(): |
|
134 if id(key) == expectedID: |
|
135 return value |
|
136 |
|
137 return None |
|
138 |
|
139 def keyToStr(self, key): |
|
140 """ |
|
141 Public method to get a string representation for a key. |
|
142 |
|
143 @param key key to be converted |
|
144 @type any |
|
145 @return string representation of the given key |
|
146 @rtype str |
|
147 """ |
|
148 if isinstance(key, str): |
|
149 return repr(key) |
|
150 else: |
|
151 return key |
|
152 |
|
153 def getDictionary(self, var): |
|
154 """ |
|
155 Public method to get the attributes of a variable as a dictionary. |
|
156 |
|
157 @param var variable to be converted |
|
158 @type any |
|
159 @return dictionary containing the variable attributes |
|
160 @rtype dict |
|
161 """ |
|
162 d = {} |
|
163 count = 0 |
|
164 for key, value in var.items(): |
|
165 count += 1 |
|
166 key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) |
|
167 d[key] = value |
|
168 if count > MaxItemsToHandle: |
|
169 d[TooLargeAttribute] = TooLargeMessage |
|
170 break |
|
171 |
|
172 d["___len___"] = len(var) |
|
173 |
|
174 # in case it has additional fields |
|
175 additionals = defaultResolver.getDictionary(var) |
|
176 d.update(additionals) |
|
177 |
|
178 return d |
|
179 |
|
180 |
|
181 ############################################################ |
|
182 ## Resolver for Lists and Tuples |
|
183 ############################################################ |
|
184 |
|
185 |
|
186 class ListResolver(BaseResolver): |
|
187 """ |
|
188 Class used to resolve from a tuple or list. |
|
189 """ |
|
190 def resolve(self, var, attribute): |
|
191 """ |
|
192 Public method to get an attribute from a variable. |
|
193 |
|
194 @param var variable to extract an attribute or value from |
|
195 @type tuple or list |
|
196 @param attribute name of the attribute to extract |
|
197 @type str |
|
198 @return value of the attribute |
|
199 @rtype any |
|
200 """ |
|
201 if attribute in ('___len___', TooLargeAttribute): |
|
202 return None |
|
203 |
|
204 try: |
|
205 return var[int(attribute)] |
|
206 except Exception: |
|
207 return getattr(var, attribute, None) |
|
208 |
|
209 def getDictionary(self, var): |
|
210 """ |
|
211 Public method to get the attributes of a variable as a dictionary. |
|
212 |
|
213 @param var variable to be converted |
|
214 @type any |
|
215 @return dictionary containing the variable attributes |
|
216 @rtype dict |
|
217 """ |
|
218 d = {} |
|
219 count = 0 |
|
220 for value in var: |
|
221 d[str(count)] = value |
|
222 count += 1 |
|
223 if count > MaxItemsToHandle: |
|
224 d[TooLargeAttribute] = TooLargeMessage |
|
225 break |
|
226 |
|
227 d["___len___"] = len(var) |
|
228 |
|
229 # in case it has additional fields |
|
230 additionals = defaultResolver.getDictionary(var) |
|
231 d.update(additionals) |
|
232 |
|
233 return d |
|
234 |
|
235 |
|
236 ############################################################ |
|
237 ## Resolver for Sets and Frozensets |
|
238 ############################################################ |
|
239 |
|
240 |
|
241 class SetResolver(BaseResolver): |
|
242 """ |
|
243 Class used to resolve from a set or frozenset. |
|
244 """ |
|
245 def resolve(self, var, attribute): |
|
246 """ |
|
247 Public method to get an attribute from a variable. |
|
248 |
|
249 @param var variable to extract an attribute or value from |
|
250 @type tuple or list |
|
251 @param attribute id of the value to extract |
|
252 @type str |
|
253 @return value of the attribute |
|
254 @rtype any |
|
255 """ |
|
256 if attribute in ('___len___', TooLargeAttribute): |
|
257 return None |
|
258 |
|
259 if attribute.startswith("ID: "): |
|
260 attribute = attribute.split(None, 1)[1] |
|
261 try: |
|
262 attribute = int(attribute) |
|
263 except Exception: |
|
264 return getattr(var, attribute, None) |
|
265 |
|
266 for v in var: |
|
267 if id(v) == attribute: |
|
268 return v |
|
269 |
|
270 return None |
|
271 |
|
272 def getDictionary(self, var): |
|
273 """ |
|
274 Public method to get the attributes of a variable as a dictionary. |
|
275 |
|
276 @param var variable to be converted |
|
277 @type any |
|
278 @return dictionary containing the variable attributes |
|
279 @rtype dict |
|
280 """ |
|
281 d = {} |
|
282 count = 0 |
|
283 for value in var: |
|
284 count += 1 |
|
285 d["ID: " + str(id(value))] = value |
|
286 if count > MaxItemsToHandle: |
|
287 d[TooLargeAttribute] = TooLargeMessage |
|
288 break |
|
289 |
|
290 d["___len___"] = len(var) |
|
291 |
|
292 # in case it has additional fields |
|
293 additionals = defaultResolver.getDictionary(var) |
|
294 d.update(additionals) |
|
295 |
|
296 return d |
|
297 |
|
298 |
|
299 ############################################################ |
|
300 ## Resolver for Numpy Arrays |
|
301 ############################################################ |
|
302 |
|
303 |
|
304 class NdArrayResolver(BaseResolver): |
|
305 """ |
|
306 Class used to resolve from numpy ndarray including some meta data. |
|
307 """ |
|
308 def __isNumeric(self, arr): |
|
309 """ |
|
310 Private method to check, if an array is of a numeric type. |
|
311 |
|
312 @param arr array to check |
|
313 @type ndarray |
|
314 @return flag indicating a numeric array |
|
315 @rtype bool |
|
316 """ |
|
317 try: |
|
318 return arr.dtype.kind in 'biufc' |
|
319 except AttributeError: |
|
320 return False |
|
321 |
|
322 def resolve(self, var, attribute): |
|
323 """ |
|
324 Public method to get an attribute from a variable. |
|
325 |
|
326 @param var variable to extract an attribute or value from |
|
327 @type tuple or list |
|
328 @param attribute id of the value to extract |
|
329 @type str |
|
330 @return value of the attribute |
|
331 @rtype any |
|
332 """ |
|
333 if attribute == '__internals__': |
|
334 return defaultResolver.getDictionary(var) |
|
335 |
|
336 if attribute == 'min': |
|
337 if self.__isNumeric(var): |
|
338 return var.min() |
|
339 else: |
|
340 return None |
|
341 |
|
342 if attribute == 'max': |
|
343 if self.__isNumeric(var): |
|
344 return var.max() |
|
345 else: |
|
346 return None |
|
347 |
|
348 if attribute == 'mean': |
|
349 if self.__isNumeric(var): |
|
350 return var.mean() |
|
351 else: |
|
352 return None |
|
353 |
|
354 if attribute == 'shape': |
|
355 return var.shape |
|
356 |
|
357 if attribute == 'dtype': |
|
358 return var.dtype |
|
359 |
|
360 if attribute == 'size': |
|
361 return var.size |
|
362 |
|
363 if attribute.startswith('['): |
|
364 container = NdArrayItemsContainer() |
|
365 count = 0 |
|
366 for element in var: |
|
367 setattr(container, str(count), element) |
|
368 count += 1 |
|
369 if count > MaxItemsToHandle: |
|
370 setattr(container, TooLargeAttribute, TooLargeMessage) |
|
371 break |
|
372 return container |
|
373 |
|
374 return None |
|
375 |
|
376 def getDictionary(self, var): |
|
377 """ |
|
378 Public method to get the attributes of a variable as a dictionary. |
|
379 |
|
380 @param var variable to be converted |
|
381 @type any |
|
382 @return dictionary containing the variable attributes |
|
383 @rtype dict |
|
384 """ |
|
385 d = {} |
|
386 d['__internals__'] = defaultResolver.getDictionary(var) |
|
387 if var.size > 1024 * 1024: |
|
388 d['min'] = 'ndarray too big, calculating min would slow down' \ |
|
389 ' debugging' |
|
390 d['max'] = 'ndarray too big, calculating max would slow down' \ |
|
391 ' debugging' |
|
392 else: |
|
393 if self.__isNumeric(var): |
|
394 if var.size == 0: |
|
395 d['min'] = 'empty array' |
|
396 d['max'] = 'empty array' |
|
397 d['mean'] = 'empty array' |
|
398 else: |
|
399 d['min'] = var.min() |
|
400 d['max'] = var.max() |
|
401 d['mean'] = var.mean() |
|
402 else: |
|
403 d['min'] = 'not a numeric object' |
|
404 d['max'] = 'not a numeric object' |
|
405 d['mean'] = 'not a numeric object' |
|
406 d['shape'] = var.shape |
|
407 d['dtype'] = var.dtype |
|
408 d['size'] = var.size |
|
409 d['[0:{0}]'.format(len(var) - 1)] = list(var[0:MaxItemsToHandle]) |
|
410 return d |
|
411 |
|
412 |
|
413 class NdArrayItemsContainer: |
|
414 """ |
|
415 Class to store ndarray items. |
|
416 """ |
|
417 pass |
|
418 |
|
419 |
|
420 ############################################################ |
|
421 ## Resolver for Django Multi Value Dictionaries |
|
422 ############################################################ |
|
423 |
|
424 |
|
425 class MultiValueDictResolver(DictResolver): |
|
426 """ |
|
427 Class used to resolve from Django multi value dictionaries. |
|
428 """ |
|
429 def resolve(self, var, attribute): |
|
430 """ |
|
431 Public method to get an attribute from a variable. |
|
432 |
|
433 @param var variable to extract an attribute or value from |
|
434 @type dict |
|
435 @param attribute name of the attribute to extract |
|
436 @type str |
|
437 @return value of the attribute |
|
438 @rtype any |
|
439 """ |
|
440 if attribute in ('___len___', TooLargeAttribute): |
|
441 return None |
|
442 |
|
443 if "(ID:" not in attribute: |
|
444 try: |
|
445 return var[attribute] |
|
446 except Exception: |
|
447 return getattr(var, attribute, None) |
|
448 |
|
449 expectedID = int(attribute.split("(ID:")[-1][:-1]) |
|
450 for key in var.keys(): |
|
451 if id(key) == expectedID: |
|
452 value = var.getlist(key) |
|
453 return value |
|
454 |
|
455 return None |
|
456 |
|
457 def getDictionary(self, var): |
|
458 """ |
|
459 Public method to get the attributes of a variable as a dictionary. |
|
460 |
|
461 @param var variable to be converted |
|
462 @type any |
|
463 @return dictionary containing the variable attributes |
|
464 @rtype dict |
|
465 """ |
|
466 d = {} |
|
467 count = 0 |
|
468 for key in var.keys(): |
|
469 count += 1 |
|
470 value = var.getlist(key) |
|
471 key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) |
|
472 d[key] = value |
|
473 if count > MaxItemsToHandle: |
|
474 d[TooLargeAttribute] = TooLargeMessage |
|
475 break |
|
476 |
|
477 d["___len___"] = len(var) |
|
478 |
|
479 return d |
|
480 |
|
481 |
|
482 ############################################################ |
|
483 ## Resolver for array.array |
|
484 ############################################################ |
|
485 |
|
486 |
|
487 class ArrayResolver(BaseResolver): |
|
488 """ |
|
489 Class used to resolve from array.array including some meta data. |
|
490 """ |
|
491 TypeCodeMap = { |
|
492 "b": "int (signed char)", |
|
493 "B": "int (unsigned char)", |
|
494 "u": "Unicode character (Py_UNICODE)", |
|
495 "h": "int (signed short)", |
|
496 "H": "int (unsigned short)", |
|
497 "i": "int (signed int)", |
|
498 "I": "int (unsigned int)", |
|
499 "l": "int (signed long)", |
|
500 "L": "int (unsigned long)", |
|
501 "q": "int (signed long long)", |
|
502 "Q": "int (unsigned long long)", |
|
503 "f": "float (float)", |
|
504 "d": "float (double)", |
|
505 } |
|
506 |
|
507 def resolve(self, var, attribute): |
|
508 """ |
|
509 Public method to get an attribute from a variable. |
|
510 |
|
511 @param var variable to extract an attribute or value from |
|
512 @type tuple or list |
|
513 @param attribute id of the value to extract |
|
514 @type str |
|
515 @return value of the attribute |
|
516 @rtype any |
|
517 """ |
|
518 if attribute == 'itemsize': |
|
519 return var.itemsize |
|
520 |
|
521 if attribute == 'typecode': |
|
522 return var.typecode |
|
523 |
|
524 if attribute == 'type': |
|
525 if var.typecode in ArrayResolver.TypeCodeMap: |
|
526 return ArrayResolver.TypeCodeMap[var.typecode] |
|
527 else: |
|
528 return 'illegal type' |
|
529 |
|
530 if attribute.startswith('['): |
|
531 container = ArrayItemsContainer() |
|
532 count = 0 |
|
533 for element in var: |
|
534 setattr(container, str(count), element) |
|
535 count += 1 |
|
536 if count > MaxItemsToHandle: |
|
537 setattr(container, TooLargeAttribute, TooLargeMessage) |
|
538 break |
|
539 return container |
|
540 |
|
541 return None |
|
542 |
|
543 def getDictionary(self, var): |
|
544 """ |
|
545 Public method to get the attributes of a variable as a dictionary. |
|
546 |
|
547 @param var variable to be converted |
|
548 @type any |
|
549 @return dictionary containing the variable attributes |
|
550 @rtype dict |
|
551 """ |
|
552 d = {} |
|
553 d['typecode'] = var.typecode |
|
554 if var.typecode in ArrayResolver.TypeCodeMap: |
|
555 d['type'] = ArrayResolver.TypeCodeMap[var.typecode] |
|
556 else: |
|
557 d['type'] = 'illegal type' |
|
558 d['itemsize'] = var.itemsize |
|
559 d['[0:{0}]'.format(len(var) - 1)] = var.tolist()[0:MaxItemsToHandle] |
|
560 return d |
|
561 |
|
562 |
|
563 class ArrayItemsContainer: |
|
564 """ |
|
565 Class to store array.array items. |
|
566 """ |
|
567 pass |
|
568 |
|
569 |
|
570 defaultResolver = DefaultResolver() |
|
571 dictResolver = DictResolver() |
|
572 listResolver = ListResolver() |
|
573 setResolver = SetResolver() |
|
574 ndarrayResolver = NdArrayResolver() |
|
575 multiValueDictResolver = MultiValueDictResolver() |
|
576 arrayResolver = ArrayResolver() |
|
577 |
|
578 ############################################################ |
|
579 ## Methods to determine the type of a variable and the |
|
580 ## resolver class to use |
|
581 ############################################################ |
|
582 |
|
583 _TypeMap = None |
|
584 |
|
585 |
|
586 def _initTypeMap(): |
|
587 """ |
|
588 Protected function to initialize the type map. |
|
589 """ |
|
590 global _TypeMap |
|
591 |
|
592 _TypeMap = [ |
|
593 (type(None), None,), |
|
594 (int, None), |
|
595 (float, None), |
|
596 (complex, None), |
|
597 (str, None), |
|
598 (tuple, listResolver), |
|
599 (list, listResolver), |
|
600 (dict, dictResolver), |
|
601 ] |
|
602 |
|
603 try: |
|
604 _TypeMap.append((long, None)) # __IGNORE_WARNING__ |
|
605 except Exception: |
|
606 pass # not available on all python versions |
|
607 |
|
608 try: |
|
609 _TypeMap.append((unicode, None)) # __IGNORE_WARNING__ |
|
610 except Exception: |
|
611 pass # not available on all python versions |
|
612 |
|
613 try: |
|
614 _TypeMap.append((set, setResolver)) # __IGNORE_WARNING__ |
|
615 except Exception: |
|
616 pass # not available on all python versions |
|
617 |
|
618 try: |
|
619 _TypeMap.append((frozenset, setResolver)) # __IGNORE_WARNING__ |
|
620 except Exception: |
|
621 pass # not available on all python versions |
|
622 |
|
623 try: |
|
624 import array |
|
625 _TypeMap.append((array.array, arrayResolver)) |
|
626 except ImportError: |
|
627 pass # array.array may not be available |
|
628 |
|
629 try: |
|
630 import numpy |
|
631 _TypeMap.append((numpy.ndarray, ndarrayResolver)) |
|
632 except ImportError: |
|
633 pass # numpy may not be installed |
|
634 |
|
635 try: |
|
636 from django.utils.datastructures import MultiValueDict |
|
637 _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver)) |
|
638 # it should go before dict |
|
639 except ImportError: |
|
640 pass # django may not be installed |
|
641 |
|
642 |
|
643 def getType(obj): |
|
644 """ |
|
645 Public method to get the type information for an object. |
|
646 |
|
647 @param obj object to get type information for |
|
648 @type any |
|
649 @return tuple containing the type, type name, type string and resolver |
|
650 @rtype tuple of type, str, str, BaseResolver |
|
651 """ |
|
652 typeObject = type(obj) |
|
653 typeName = typeObject.__name__ |
|
654 typeStr = str(typeObject)[8:-2] |
|
655 |
|
656 if typeStr.startswith(("PyQt5.", "PyQt4.")): |
|
657 resolver = None |
|
658 else: |
|
659 if _TypeMap is None: |
|
660 _initTypeMap() |
|
661 |
|
662 for typeData in _TypeMap: |
|
663 if isinstance(obj, typeData[0]): |
|
664 resolver = typeData[1] |
|
665 break |
|
666 else: |
|
667 resolver = defaultResolver |
|
668 |
|
669 return typeObject, typeName, typeStr, resolver |
|
670 |
|
671 # |
|
672 # eflag: noqa = M702 |