Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ Authors
* Oldřich Jedlička - https://github.com/oldium
* Colin Watson - https://github.com/cjwatson
* Jacob Tomlinson - https://github.com/jacobtomlinson
* Shahadat Islam Koyel - https://github.com/ShahadatTest
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

Unreleased
~~~~~~~~~~

* Avoid recursion errors when converting deep tracebacks to and from dictionaries.

3.2.2 (2025-11-12)
~~~~~~~~~~~~~~~~~~

Expand Down
80 changes: 41 additions & 39 deletions src/tblib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,26 +158,26 @@ def as_dict(self):
Converts to a dictionary representation. You can serialize the result to JSON as it only has
builtin objects like dicts, lists, ints or strings.
"""
if self.tb_next is None:
tb_next = None
else:
tb_next = self.tb_next.as_dict()

code = {
'co_filename': self.tb_frame.f_code.co_filename,
'co_name': self.tb_frame.f_code.co_name,
}
frame = {
'f_globals': self.tb_frame.f_globals,
'f_locals': self.tb_frame.f_locals,
'f_code': code,
'f_lineno': self.tb_frame.f_lineno,
}
return {
'tb_frame': frame,
'tb_lineno': self.tb_lineno,
'tb_next': tb_next,
}
result = {}
node = result
current = self
while True:
code = {
'co_filename': current.tb_frame.f_code.co_filename,
'co_name': current.tb_frame.f_code.co_name,
}
frame = {
'f_globals': current.tb_frame.f_globals,
'f_locals': current.tb_frame.f_locals,
'f_code': code,
'f_lineno': current.tb_frame.f_lineno,
}
node.update(tb_frame=frame, tb_lineno=current.tb_lineno, tb_next=None)
current = current.tb_next
if current is None:
return result
node['tb_next'] = {}
node = node['tb_next']

to_dict = as_dict

Expand All @@ -186,26 +186,28 @@ def from_dict(cls, dct):
"""
Creates an instance from a dictionary with the same structure as ``.as_dict()`` returns.
"""
if dct['tb_next']:
tb_next = cls.from_dict(dct['tb_next'])
else:
tb_next = None
nodes = [dct]
while dct['tb_next']:
dct = dct['tb_next']
nodes.append(dct)

code = _AttrDict(
co_filename=dct['tb_frame']['f_code']['co_filename'],
co_name=dct['tb_frame']['f_code']['co_name'],
)
frame = _AttrDict(
f_globals=dct['tb_frame']['f_globals'],
f_locals=dct['tb_frame'].get('f_locals', {}),
f_code=code,
f_lineno=dct['tb_frame']['f_lineno'],
)
tb = _AttrDict(
tb_frame=frame,
tb_lineno=dct['tb_lineno'],
tb_next=tb_next,
)
tb = None
for dct in reversed(nodes):
code = _AttrDict(
co_filename=dct['tb_frame']['f_code']['co_filename'],
co_name=dct['tb_frame']['f_code']['co_name'],
)
frame = _AttrDict(
f_globals=dct['tb_frame']['f_globals'],
f_locals=dct['tb_frame'].get('f_locals', {}),
f_code=code,
f_lineno=dct['tb_frame']['f_lineno'],
)
tb = _AttrDict(
tb_frame=frame,
tb_lineno=dct['tb_lineno'],
tb_next=tb,
)
return cls(tb, get_locals=get_all_locals)

@classmethod
Expand Down
43 changes: 43 additions & 0 deletions tests/test_deep_dicts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sys
from types import TracebackType

from tblib import Traceback


def test_deep_traceback_as_dict():
depth = sys.getrecursionlimit() + 10
frame = sys._getframe()
native = None
for lineno in range(depth, 0, -1):
native = TracebackType(native, frame, -1, lineno)
converted = Traceback(native).as_dict()
for lineno in range(1, depth + 1):
assert converted['tb_lineno'] == lineno
assert converted['tb_frame']['f_code']['co_name'] == 'test_deep_traceback_as_dict'
converted = converted['tb_next']
assert converted is None


def test_deep_traceback_from_dict():
depth = sys.getrecursionlimit() + 10
serialized = None
for lineno in range(depth, 0, -1):
serialized = {
'tb_frame': {
'f_globals': {'__name__': 'deep_example', '__file__': 'deep.py'},
'f_locals': {'value': lineno},
'f_code': {'co_filename': 'deep.py', 'co_name': 'recursive'},
'f_lineno': lineno,
},
'tb_lineno': lineno,
'tb_next': serialized,
}
converted = Traceback.from_dict(serialized)
for lineno in range(1, depth + 1):
assert converted.tb_lineno == lineno
assert converted.tb_frame.f_lineno == lineno
assert converted.tb_frame.f_locals == {'value': lineno}
assert converted.tb_frame.f_globals == {'__name__': 'deep_example', '__file__': 'deep.py'}
assert converted.tb_frame.f_code.co_name == 'recursive'
converted = converted.tb_next
assert converted is None