Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

object inspection: produce deterministic descriptions for nested collection datastructures #11312

Merged
Show file tree
Hide file tree
Changes from 5 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
24 changes: 16 additions & 8 deletions sphinx/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ def object_description(object: Any) -> str:
try:
sorted_keys = sorted(object)
except Exception:
jayaddison marked this conversation as resolved.
Show resolved Hide resolved
pass # Cannot sort dict keys, fall back to generic repr
# Cannot sort dict keys, fall back to using descriptions as a sort key
sorted_keys = sorted(object, key=object_description)
else:
items = ("%s: %s" %
(object_description(key), object_description(object[key]))
Expand All @@ -373,19 +374,26 @@ def object_description(object: Any) -> str:
try:
sorted_values = sorted(object)
except TypeError:
pass # Cannot sort set values, fall back to generic repr
else:
return "{%s}" % ", ".join(object_description(x) for x in sorted_values)
# Cannot sort set values, fall back to using descriptions as a sort key
sorted_values = sorted(object, key=object_description)
return "{%s}" % ", ".join(object_description(x) for x in sorted_values)
elif isinstance(object, frozenset):
try:
sorted_values = sorted(object)
except TypeError:
pass # Cannot sort frozenset values, fall back to generic repr
else:
return "frozenset({%s})" % ", ".join(object_description(x)
for x in sorted_values)
# Cannot sort frozenset values, fall back to using descriptions as a sort key
sorted_values = sorted(object, key=object_description)
return "frozenset({%s})" % ", ".join(object_description(x)
for x in sorted_values)
elif isinstance(object, enum.Enum):
return f"{object.__class__.__name__}.{object.name}"
elif isinstance(object, tuple):
return "(%s%s)" % (
", ".join(object_description(x) for x in object),
"," if len(object) == 1 else "",
)
elif isinstance(object, list):
return "[%s]" % ", ".join(object_description(x) for x in object)

try:
s = repr(object)
Expand Down
33 changes: 33 additions & 0 deletions tests/test_util_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,12 +503,35 @@ def test_set_sorting():
assert description == "{'a', 'b', 'c', 'd', 'e', 'f', 'g'}"


def test_set_sorting_enum():
class MyEnum(enum.Enum):
a = 1
b = 2
c = 3

set_ = set(MyEnum)
description = inspect.object_description(set_)
assert description == "{MyEnum.a, MyEnum.b, MyEnum.c}"


def test_set_sorting_fallback():
set_ = {None, 1}
description = inspect.object_description(set_)
assert description in ("{1, None}", "{None, 1}")
jayaddison marked this conversation as resolved.
Show resolved Hide resolved


def test_deterministic_nested_collection_descriptions():
_desc = inspect.object_description
# sortable
assert _desc([{1, 2, 3, 10}]) == "[{1, 2, 3, 10}]"
assert _desc(({1, 2, 3, 10},)) == "({1, 2, 3, 10},)"
# non-sortable (elements of varying datatype)
assert _desc([{None, 1}]) == "[{1, None}]"
assert _desc(({None, 1},)) == "({1, None},)"
assert _desc([{None, 1, 'A'}]) == "[{'A', 1, None}]"
assert _desc(({None, 1, 'A'},)) == "({'A', 1, None},)"


def test_frozenset_sorting():
frozenset_ = frozenset("gfedcba")
description = inspect.object_description(frozenset_)
Expand All @@ -521,6 +544,16 @@ def test_frozenset_sorting_fallback():
assert description in ("frozenset({1, None})", "frozenset({None, 1})")
jayaddison marked this conversation as resolved.
Show resolved Hide resolved


def test_nested_tuple_sorting():
tuple_ = ({"c", "b", "a"},) # nb. trailing comma
description = inspect.object_description(tuple_)
assert description == "({'a', 'b', 'c'},)"

tuple_ = ({"c", "b", "a"}, {"f", "e", "d"})
description = inspect.object_description(tuple_)
assert description == "({'a', 'b', 'c'}, {'d', 'e', 'f'})"


def test_dict_customtype():
class CustomType:
def __init__(self, value):
Expand Down