Skip to content

Commit

Permalink
pythongh-90104: avoid RecursionError on recursive dataclass field repr
Browse files Browse the repository at this point in the history
  • Loading branch information
carljm committed Jan 4, 2023
1 parent 52017db commit 1f9d541
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 21 deletions.
42 changes: 21 additions & 21 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,26 @@ def __repr__(self):
# https://bugs.python.org/issue33453 for details.
_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')

# This function's logic is copied from "recursive_repr" function in
# reprlib module to avoid dependency.
def _recursive_repr(user_function):
# Decorator to make a repr function return "..." for a recursive
# call.
repr_running = set()

@functools.wraps(user_function)
def wrapper(self):
key = id(self), _thread.get_ident()
if key in repr_running:
return '...'
repr_running.add(key)
try:
result = user_function(self)
finally:
repr_running.discard(key)
return result
return wrapper

class InitVar:
__slots__ = ('type', )

Expand Down Expand Up @@ -280,6 +300,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare,
self.kw_only = kw_only
self._field_type = None

@_recursive_repr
def __repr__(self):
return ('Field('
f'name={self.name!r},'
Expand Down Expand Up @@ -403,27 +424,6 @@ def _tuple_str(obj_name, fields):
return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)'


# This function's logic is copied from "recursive_repr" function in
# reprlib module to avoid dependency.
def _recursive_repr(user_function):
# Decorator to make a repr function return "..." for a recursive
# call.
repr_running = set()

@functools.wraps(user_function)
def wrapper(self):
key = id(self), _thread.get_ident()
if key in repr_running:
return '...'
repr_running.add(key)
try:
result = user_function(self)
finally:
repr_running.discard(key)
return result
return wrapper


def _create_fn(name, args, body, *, globals=None, locals=None,
return_type=MISSING):
# Note that we may mutate locals. Callers beware!
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ def test_field_repr(self):

self.assertEqual(repr_output, expected_output)

def test_field_recursive_repr(self):
rec_field = field()
rec_field.type = rec_field
rec_field.name = "id"
repr_output = repr(rec_field)
expected_output = "Field(name='id',type=...," \
f"default={MISSING!r},default_factory={MISSING!r}," \
"init=True,repr=True,hash=None," \
"compare=True,metadata=mappingproxy({})," \
f"kw_only={MISSING!r}," \
"_field_type=None)"

self.assertEqual(repr_output, expected_output)

def test_dataclass_params_repr(self):
# Even though this is testing an internal implementation detail,
# it's testing a feature we want to make sure is correctly implemented
Expand Down

0 comments on commit 1f9d541

Please sign in to comment.