|
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 d['min'] = var.min() |
|
395 d['max'] = var.max() |
|
396 d['mean'] = var.mean() |
|
397 else: |
|
398 d['min'] = 'not a numeric object' |
|
399 d['max'] = 'not a numeric object' |
|
400 d['mean'] = 'not a numeric object' |
|
401 d['shape'] = var.shape |
|
402 d['dtype'] = var.dtype |
|
403 d['size'] = var.size |
|
404 d['[0:{0}]'.format(len(var) - 1)] = list(var[0:MaxItemsToHandle]) |
|
405 return d |
|
406 |
|
407 |
|
408 class NdArrayItemsContainer: |
|
409 """ |
|
410 Class to store ndarray items. |
|
411 """ |
|
412 pass |
|
413 |
|
414 |
|
415 ############################################################ |
|
416 ## Resolver for Django Multi Value Dictionaries |
|
417 ############################################################ |
|
418 |
|
419 |
|
420 class MultiValueDictResolver(DictResolver): |
|
421 """ |
|
422 Class used to resolve from Django multi value dictionaries. |
|
423 """ |
|
424 def resolve(self, var, attribute): |
|
425 """ |
|
426 Public method to get an attribute from a variable. |
|
427 |
|
428 @param var variable to extract an attribute or value from |
|
429 @type dict |
|
430 @param attribute name of the attribute to extract |
|
431 @type str |
|
432 @return value of the attribute |
|
433 @rtype any |
|
434 """ |
|
435 if attribute in ('___len___', TooLargeAttribute): |
|
436 return None |
|
437 |
|
438 if "(ID:" not in attribute: |
|
439 try: |
|
440 return var[attribute] |
|
441 except Exception: |
|
442 return getattr(var, attribute, None) |
|
443 |
|
444 expectedID = int(attribute.split("(ID:")[-1][:-1]) |
|
445 for key in var.keys(): |
|
446 if id(key) == expectedID: |
|
447 value = var.getlist(key) |
|
448 return value |
|
449 |
|
450 return None |
|
451 |
|
452 def getDictionary(self, var): |
|
453 """ |
|
454 Public method to get the attributes of a variable as a dictionary. |
|
455 |
|
456 @param var variable to be converted |
|
457 @type any |
|
458 @return dictionary containing the variable attributes |
|
459 @rtype dict |
|
460 """ |
|
461 d = {} |
|
462 count = 0 |
|
463 for key in var.keys(): |
|
464 count += 1 |
|
465 value = var.getlist(key) |
|
466 key = "{0} (ID:{1})".format(self.keyToStr(key), id(key)) |
|
467 d[key] = value |
|
468 if count > MaxItemsToHandle: |
|
469 d[TooLargeAttribute] = TooLargeMessage |
|
470 break |
|
471 |
|
472 d["___len___"] = len(var) |
|
473 |
|
474 return d |
|
475 |
|
476 |
|
477 ############################################################ |
|
478 ## Resolver for array.array |
|
479 ############################################################ |
|
480 |
|
481 |
|
482 class ArrayResolver(BaseResolver): |
|
483 """ |
|
484 Class used to resolve from array.array including some meta data. |
|
485 """ |
|
486 TypeCodeMap = { |
|
487 "b": "int (signed char)", |
|
488 "B": "int (unsigned char)", |
|
489 "u": "Unicode character (Py_UNICODE)", |
|
490 "h": "int (signed short)", |
|
491 "H": "int (unsigned short)", |
|
492 "i": "int (signed int)", |
|
493 "I": "int (unsigned int)", |
|
494 "l": "int (signed long)", |
|
495 "L": "int (unsigned long)", |
|
496 "q": "int (signed long long)", |
|
497 "Q": "int (unsigned long long)", |
|
498 "f": "float (float)", |
|
499 "d": "float (double)", |
|
500 } |
|
501 |
|
502 def resolve(self, var, attribute): |
|
503 """ |
|
504 Public method to get an attribute from a variable. |
|
505 |
|
506 @param var variable to extract an attribute or value from |
|
507 @type tuple or list |
|
508 @param attribute id of the value to extract |
|
509 @type str |
|
510 @return value of the attribute |
|
511 @rtype any |
|
512 """ |
|
513 if attribute == 'itemsize': |
|
514 return var.itemsize |
|
515 |
|
516 if attribute == 'typecode': |
|
517 return var.typecode |
|
518 |
|
519 if attribute == 'type': |
|
520 if var.typecode in ArrayResolver.TypeCodeMap: |
|
521 return ArrayResolver.TypeCodeMap[var.typecode] |
|
522 else: |
|
523 return 'illegal type' |
|
524 |
|
525 if attribute.startswith('['): |
|
526 container = ArrayItemsContainer() |
|
527 count = 0 |
|
528 for element in var: |
|
529 setattr(container, str(count), element) |
|
530 count += 1 |
|
531 if count > MaxItemsToHandle: |
|
532 setattr(container, TooLargeAttribute, TooLargeMessage) |
|
533 break |
|
534 return container |
|
535 |
|
536 return None |
|
537 |
|
538 def getDictionary(self, var): |
|
539 """ |
|
540 Public method to get the attributes of a variable as a dictionary. |
|
541 |
|
542 @param var variable to be converted |
|
543 @type any |
|
544 @return dictionary containing the variable attributes |
|
545 @rtype dict |
|
546 """ |
|
547 d = {} |
|
548 d['typecode'] = var.typecode |
|
549 if var.typecode in ArrayResolver.TypeCodeMap: |
|
550 d['type'] = ArrayResolver.TypeCodeMap[var.typecode] |
|
551 else: |
|
552 d['type'] = 'illegal type' |
|
553 d['itemsize'] = var.itemsize |
|
554 d['[0:{0}]'.format(len(var) - 1)] = var.tolist()[0:MaxItemsToHandle] |
|
555 return d |
|
556 |
|
557 |
|
558 class ArrayItemsContainer: |
|
559 """ |
|
560 Class to store array.array items. |
|
561 """ |
|
562 pass |
|
563 |
|
564 |
|
565 defaultResolver = DefaultResolver() |
|
566 dictResolver = DictResolver() |
|
567 listResolver = ListResolver() |
|
568 setResolver = SetResolver() |
|
569 ndarrayResolver = NdArrayResolver() |
|
570 multiValueDictResolver = MultiValueDictResolver() |
|
571 arrayResolver = ArrayResolver() |
|
572 |
|
573 ############################################################ |
|
574 ## Methods to determine the type of a variable and the |
|
575 ## resolver class to use |
|
576 ############################################################ |
|
577 |
|
578 _TypeMap = None |
|
579 |
|
580 |
|
581 def _initTypeMap(): |
|
582 """ |
|
583 Protected function to initialize the type map. |
|
584 """ |
|
585 global _TypeMap |
|
586 |
|
587 _TypeMap = [ |
|
588 (type(None), None,), |
|
589 (int, None), |
|
590 (float, None), |
|
591 (complex, None), |
|
592 (str, None), |
|
593 (tuple, listResolver), |
|
594 (list, listResolver), |
|
595 (dict, dictResolver), |
|
596 ] |
|
597 |
|
598 try: |
|
599 _TypeMap.append((long, None)) # __IGNORE_WARNING__ |
|
600 except Exception: |
|
601 pass # not available on all python versions |
|
602 |
|
603 try: |
|
604 _TypeMap.append((unicode, None)) # __IGNORE_WARNING__ |
|
605 except Exception: |
|
606 pass # not available on all python versions |
|
607 |
|
608 try: |
|
609 _TypeMap.append((set, setResolver)) # __IGNORE_WARNING__ |
|
610 except Exception: |
|
611 pass # not available on all python versions |
|
612 |
|
613 try: |
|
614 _TypeMap.append((frozenset, setResolver)) # __IGNORE_WARNING__ |
|
615 except Exception: |
|
616 pass # not available on all python versions |
|
617 |
|
618 try: |
|
619 import array |
|
620 _TypeMap.append((array.array, arrayResolver)) |
|
621 except ImportError: |
|
622 pass # array.array may not be available |
|
623 |
|
624 try: |
|
625 import numpy |
|
626 _TypeMap.append((numpy.ndarray, ndarrayResolver)) |
|
627 except ImportError: |
|
628 pass # numpy may not be installed |
|
629 |
|
630 try: |
|
631 from django.utils.datastructures import MultiValueDict |
|
632 _TypeMap.insert(0, (MultiValueDict, multiValueDictResolver)) |
|
633 # it should go before dict |
|
634 except ImportError: |
|
635 pass # django may not be installed |
|
636 |
|
637 |
|
638 def getType(obj): |
|
639 """ |
|
640 Public method to get the type information for an object. |
|
641 |
|
642 @param obj object to get type information for |
|
643 @type any |
|
644 @return tuple containing the type, type name, type string and resolver |
|
645 @rtype tuple of type, str, str, BaseResolver |
|
646 """ |
|
647 typeObject = type(obj) |
|
648 typeName = typeObject.__name__ |
|
649 typeStr = str(typeObject)[8:-2] |
|
650 |
|
651 if typeStr.startswith(("PyQt5.", "PyQt4.")): |
|
652 resolver = None |
|
653 else: |
|
654 if _TypeMap is None: |
|
655 _initTypeMap() |
|
656 |
|
657 for typeData in _TypeMap: |
|
658 if isinstance(obj, typeData[0]): |
|
659 resolver = typeData[1] |
|
660 break |
|
661 else: |
|
662 resolver = defaultResolver |
|
663 |
|
664 return typeObject, typeName, typeStr, resolver |
|
665 |
|
666 # |
|
667 # eflag: noqa = M702 |