|
34 | 34 | CYCLE_MARKER = object()
|
35 | 35 |
|
36 | 36 |
|
| 37 | +global_repr_processors = [] |
| 38 | + |
| 39 | + |
| 40 | +def add_global_repr_processor(processor): |
| 41 | + global_repr_processors.append(processor) |
| 42 | + |
| 43 | + |
37 | 44 | def _get_debug_hub():
|
38 | 45 | # This function is replaced by debug.py
|
39 | 46 | pass
|
@@ -307,22 +314,40 @@ def safe_repr(value):
|
307 | 314 | return u"<broken repr>"
|
308 | 315 |
|
309 | 316 |
|
310 |
| -def object_to_json(obj): |
311 |
| - def _walk(obj, depth): |
312 |
| - if depth < 4: |
| 317 | +def object_to_json(obj, remaining_depth=4, memo=None): |
| 318 | + if memo is None: |
| 319 | + memo = Memo() |
| 320 | + if memo.memoize(obj): |
| 321 | + return CYCLE_MARKER |
| 322 | + |
| 323 | + try: |
| 324 | + if remaining_depth > 0: |
| 325 | + hints = {"memo": memo, "remaining_depth": remaining_depth} |
| 326 | + for processor in global_repr_processors: |
| 327 | + with capture_internal_exceptions(): |
| 328 | + result = processor(obj, hints) |
| 329 | + if result is not NotImplemented: |
| 330 | + return result |
| 331 | + |
313 | 332 | if isinstance(obj, (list, tuple)):
|
314 | 333 | # It is not safe to iterate over another sequence types as this may raise errors or
|
315 | 334 | # bring undesired side-effects (e.g. Django querysets are executed during iteration)
|
316 |
| - return [_walk(x, depth + 1) for x in obj] |
317 |
| - if isinstance(obj, Mapping): |
318 |
| - return {safe_str(k): _walk(v, depth + 1) for k, v in obj.items()} |
| 335 | + return [ |
| 336 | + object_to_json(x, remaining_depth=remaining_depth - 1, memo=memo) |
| 337 | + for x in obj |
| 338 | + ] |
319 | 339 |
|
320 |
| - if obj is CYCLE_MARKER: |
321 |
| - return obj |
| 340 | + if isinstance(obj, Mapping): |
| 341 | + return { |
| 342 | + safe_str(k): object_to_json( |
| 343 | + v, remaining_depth=remaining_depth - 1, memo=memo |
| 344 | + ) |
| 345 | + for k, v in obj.items() |
| 346 | + } |
322 | 347 |
|
323 | 348 | return safe_repr(obj)
|
324 |
| - |
325 |
| - return _walk(break_cycles(obj), 0) |
| 349 | + finally: |
| 350 | + memo.unmemoize(obj) |
326 | 351 |
|
327 | 352 |
|
328 | 353 | def extract_locals(frame):
|
@@ -645,23 +670,18 @@ def strip_frame_mut(frame):
|
645 | 670 | frame["vars"] = strip_databag(frame["vars"])
|
646 | 671 |
|
647 | 672 |
|
648 |
| -def break_cycles(obj, memo=None): |
649 |
| - if memo is None: |
650 |
| - memo = {} |
651 |
| - if id(obj) in memo: |
652 |
| - return CYCLE_MARKER |
653 |
| - memo[id(obj)] = obj |
| 673 | +class Memo(object): |
| 674 | + def __init__(self): |
| 675 | + self._inner = {} |
654 | 676 |
|
655 |
| - try: |
656 |
| - if isinstance(obj, Mapping): |
657 |
| - return {k: break_cycles(v, memo) for k, v in obj.items()} |
658 |
| - if isinstance(obj, (list, tuple)): |
659 |
| - # It is not safe to iterate over another sequence types as this may raise errors or |
660 |
| - # bring undesired side-effects (e.g. Django querysets are executed during iteration) |
661 |
| - return [break_cycles(v, memo) for v in obj] |
662 |
| - return obj |
663 |
| - finally: |
664 |
| - del memo[id(obj)] |
| 677 | + def memoize(self, obj): |
| 678 | + if id(obj) in self._inner: |
| 679 | + return True |
| 680 | + self._inner[id(obj)] = obj |
| 681 | + return False |
| 682 | + |
| 683 | + def unmemoize(self, obj): |
| 684 | + self._inner.pop(id(obj), None) |
665 | 685 |
|
666 | 686 |
|
667 | 687 | def convert_types(obj):
|
|
0 commit comments