From 99a41d2c41f0d77a55337190f0641fb5d7714758 Mon Sep 17 00:00:00 2001 From: Chris Lamb Date: Wed, 15 Feb 2023 11:19:43 -0800 Subject: [PATCH 01/12] WIP (cherry picked from commit 4ad7670c1df00f82e758aaa8a7b9aaea83b8eaba) --- sphinx/util/inspect.py | 7 +++++++ tests/test_util_inspect.py | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 8e98ca44715..40656ccbc0a 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -386,6 +386,13 @@ def object_description(object: Any) -> str: 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) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index cc67e37b3d4..0b097db3f60 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -521,6 +521,16 @@ def test_frozenset_sorting_fallback(): assert description in ("frozenset({1, None})", "frozenset({None, 1})") +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): From dcc55f74a8db6b03f67e929ba5e9bdb73bba93c5 Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 10 Apr 2023 15:11:41 +0100 Subject: [PATCH 02/12] Add nested-object-description test cases including both sortable and non-sortable elements --- tests/test_util_inspect.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 0b097db3f60..776fea2dd03 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -509,6 +509,15 @@ def test_set_sorting_fallback(): assert description in ("{1, None}", "{None, 1}") +def test_deterministic_collection_descriptions(): + _desc = inspect.object_description + # sortable + 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, 'A'},)) == "({'A', 1, None},)" + + def test_frozenset_sorting(): frozenset_ = frozenset("gfedcba") description = inspect.object_description(frozenset_) From f28b1dc61b4ef75ccceefe366fea45413b971acc Mon Sep 17 00:00:00 2001 From: Faidon Liambotis Date: Mon, 10 Apr 2023 15:17:54 +0100 Subject: [PATCH 03/12] Apply patch by @paravoid: additional test coverage and structlog reproducibility improvements Ref: https://github.com/sphinx-doc/sphinx/issues/11198#issuecomment-1459075355 Applied-by: James Addison --- sphinx/util/inspect.py | 11 ++++++----- tests/test_util_inspect.py | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 40656ccbc0a..98b9acb9b19 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -370,20 +370,21 @@ def object_description(object: Any) -> str: for key in sorted_keys) return "{%s}" % ", ".join(items) elif isinstance(object, set): + set_descr = (object_description(x) for x in object) try: - sorted_values = sorted(object) + sorted_set_descr = sorted(set_descr) except TypeError: pass # Cannot sort set values, fall back to generic repr else: - return "{%s}" % ", ".join(object_description(x) for x in sorted_values) + return "{%s}" % ", ".join(sorted_set_descr) elif isinstance(object, frozenset): + frozenset_descr = (object_description(x) for x in object) try: - sorted_values = sorted(object) + sorted_frozenset_descr = sorted(frozenset_descr) 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) + return "frozenset({%s})" % ", ".join(sorted_frozenset_descr) elif isinstance(object, enum.Enum): return f"{object.__class__.__name__}.{object.name}" elif isinstance(object, tuple): diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 776fea2dd03..f623ff1391b 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -503,10 +503,20 @@ 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}") + assert description == "{1, None}" def test_deterministic_collection_descriptions(): @@ -527,7 +537,7 @@ def test_frozenset_sorting(): def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) - assert description in ("frozenset({1, None})", "frozenset({None, 1})") + assert description == "frozenset({1, None})" def test_nested_tuple_sorting(): From 597703d30ca1e9c5686985a6cd37c751b67c4240 Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 10 Apr 2023 15:31:14 +0100 Subject: [PATCH 04/12] Fallback adjustment: use object_description as a sort key when natural sorting of set elements is not possible --- sphinx/util/inspect.py | 24 ++++++++++++------------ tests/test_util_inspect.py | 10 +++++++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 98b9acb9b19..b24d0e8bc5d 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -363,34 +363,34 @@ def object_description(object: Any) -> str: try: sorted_keys = sorted(object) except Exception: - 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])) for key in sorted_keys) return "{%s}" % ", ".join(items) elif isinstance(object, set): - set_descr = (object_description(x) for x in object) try: - sorted_set_descr = sorted(set_descr) + sorted_values = sorted(object) except TypeError: - pass # Cannot sort set values, fall back to generic repr - else: - return "{%s}" % ", ".join(sorted_set_descr) + # 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): - frozenset_descr = (object_description(x) for x in object) try: - sorted_frozenset_descr = sorted(frozenset_descr) + sorted_values = sorted(object) except TypeError: - pass # Cannot sort frozenset values, fall back to generic repr - else: - return "frozenset({%s})" % ", ".join(sorted_frozenset_descr) + # 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 "" + "," if len(object) == 1 else "", ) elif isinstance(object, list): return "[%s]" % ", ".join(object_description(x) for x in object) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index f623ff1391b..5e11aa114b1 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -513,18 +513,22 @@ class MyEnum(enum.Enum): 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 == "{1, None}" + assert description in ("{1, None}", "{None, 1}") -def test_deterministic_collection_descriptions(): +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},)" @@ -537,7 +541,7 @@ def test_frozenset_sorting(): def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) - assert description == "frozenset({1, None})" + assert description in ("frozenset({1, None})", "frozenset({None, 1})") def test_nested_tuple_sorting(): From 0ffac538489a99435837dc5925c28ea16d6c62dc Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 25 Apr 2023 16:04:42 +0100 Subject: [PATCH 05/12] util.inspect.object_description: add recursive-datastructure safety protection --- sphinx/util/inspect.py | 34 +++++++++++++++++++++++++--------- tests/test_util_inspect.py | 22 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index b24d0e8bc5d..33b3e3598e4 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -357,43 +357,59 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: raise AttributeError(name) from exc -def object_description(object: Any) -> str: +def object_description(object: Any, seen: frozenset = None) -> str: """A repr() implementation that returns text safe to use in reST context.""" + seen = seen or frozenset() if isinstance(object, dict): + if id(object) in seen: + return "dict(...)" + seen |= frozenset([id(object)]) try: sorted_keys = sorted(object) except Exception: # Cannot sort dict keys, fall back to using descriptions as a sort key - sorted_keys = sorted(object, key=object_description) + sorted_keys = sorted(object, key=lambda x: object_description(x, seen)) else: items = ("%s: %s" % - (object_description(key), object_description(object[key])) + (object_description(key, seen), object_description(object[key], seen)) for key in sorted_keys) return "{%s}" % ", ".join(items) elif isinstance(object, set): + if id(object) in seen: + return "set(...)" + seen |= frozenset([id(object)]) try: sorted_values = sorted(object) except TypeError: # 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) + sorted_values = sorted(object, key=lambda x: object_description(x, seen)) + return "{%s}" % ", ".join(object_description(x, seen) for x in sorted_values) elif isinstance(object, frozenset): + if id(object) in seen: + return "frozenset(...)" + seen |= frozenset([id(object)]) try: sorted_values = sorted(object) except TypeError: # 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) + sorted_values = sorted(object, key=lambda x: object_description(x, seen)) + return "frozenset({%s})" % ", ".join(object_description(x, seen) for x in sorted_values) elif isinstance(object, enum.Enum): return f"{object.__class__.__name__}.{object.name}" elif isinstance(object, tuple): + if id(object) in seen: + return "tuple(...)" + seen |= frozenset([id(object)]) return "(%s%s)" % ( - ", ".join(object_description(x) for x in object), + ", ".join(object_description(x, seen) for x in object), "," if len(object) == 1 else "", ) elif isinstance(object, list): - return "[%s]" % ", ".join(object_description(x) for x in object) + if id(object) in seen: + return "list(...)" + seen |= frozenset([id(object)]) + return "[%s]" % ", ".join(object_description(x, seen) for x in object) try: s = repr(object) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 5e11aa114b1..024bc5ba49c 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -554,6 +554,28 @@ def test_nested_tuple_sorting(): assert description == "({'a', 'b', 'c'}, {'d', 'e', 'f'})" +def test_recursive_collection_description(): + dict_a_, dict_b_ = {"a": 1}, {"b": 2} + dict_a_["link"], dict_b_["link"] = dict_b_, dict_a_ + description_a, description_b = ( + inspect.object_description(dict_a_), + inspect.object_description(dict_b_), + ) + assert description_a == "{'a': 1, 'link': {'b': 2, 'link': dict(...)}}" + assert description_b == "{'b': 2, 'link': {'a': 1, 'link': dict(...)}}" + + list_c_, list_d_ = [1, 2, 3, 4], [5, 6, 7, 8] + list_c_.append(list_d_) + list_d_.append(list_c_) + description_c, description_d = ( + inspect.object_description(list_c_), + inspect.object_description(list_d_), + ) + + assert description_c == "[1, 2, 3, 4, [5, 6, 7, 8, list(...)]]" + assert description_d == "[5, 6, 7, 8, [1, 2, 3, 4, list(...)]]" + + def test_dict_customtype(): class CustomType: def __init__(self, value): From 6358c23362e8eb3a84159aebd7a922e6b80957ad Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 25 Apr 2023 16:11:49 +0100 Subject: [PATCH 06/12] lint fixup: use empty (and immutable) frozenset instance as a default argument value --- sphinx/util/inspect.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 33b3e3598e4..6f93cb680fb 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -357,9 +357,8 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: raise AttributeError(name) from exc -def object_description(object: Any, seen: frozenset = None) -> str: +def object_description(object: Any, seen: frozenset = frozenset()) -> str: """A repr() implementation that returns text safe to use in reST context.""" - seen = seen or frozenset() if isinstance(object, dict): if id(object) in seen: return "dict(...)" From 9c69c6befb85b2bfffb946fdf05aff930eefeb35 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 2 May 2023 20:10:20 +0100 Subject: [PATCH 07/12] util.inspect.object_description: extend docstring to explain the introduced 'seen' variable --- sphinx/util/inspect.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 6f93cb680fb..3ec0998aae0 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -358,7 +358,10 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: def object_description(object: Any, seen: frozenset = frozenset()) -> str: - """A repr() implementation that returns text safe to use in reST context.""" + """A repr() implementation that returns text safe to use in reST context. + + Maintains a set of 'seen' objectIDs to detect and avoid infinite recursion. + """ if isinstance(object, dict): if id(object) in seen: return "dict(...)" From 04b83644cebed3fc8e5168fa786ee221b884ec94 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 20 Jul 2023 22:46:27 +0100 Subject: [PATCH 08/12] Style --- sphinx/util/inspect.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3ec0998aae0..3697b4a36f6 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -360,12 +360,12 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: def object_description(object: Any, seen: frozenset = frozenset()) -> str: """A repr() implementation that returns text safe to use in reST context. - Maintains a set of 'seen' objectIDs to detect and avoid infinite recursion. + Maintains a set of 'seen' object IDs to detect and avoid infinite recursion. """ if isinstance(object, dict): if id(object) in seen: return "dict(...)" - seen |= frozenset([id(object)]) + seen |= {id(object)} try: sorted_keys = sorted(object) except Exception: @@ -379,7 +379,7 @@ def object_description(object: Any, seen: frozenset = frozenset()) -> str: elif isinstance(object, set): if id(object) in seen: return "set(...)" - seen |= frozenset([id(object)]) + seen |= {id(object)} try: sorted_values = sorted(object) except TypeError: @@ -389,7 +389,7 @@ def object_description(object: Any, seen: frozenset = frozenset()) -> str: elif isinstance(object, frozenset): if id(object) in seen: return "frozenset(...)" - seen |= frozenset([id(object)]) + seen |= {id(object)} try: sorted_values = sorted(object) except TypeError: @@ -410,7 +410,7 @@ def object_description(object: Any, seen: frozenset = frozenset()) -> str: elif isinstance(object, list): if id(object) in seen: return "list(...)" - seen |= frozenset([id(object)]) + seen |= {id(object)} return "[%s]" % ", ".join(object_description(x, seen) for x in object) try: From bed67e6c5d870aff8396b1cf763dc4a41942e958 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 25 Jul 2023 19:22:54 +0100 Subject: [PATCH 09/12] test_util_inspect: add more stringent object description expectations --- tests/test_util_inspect.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 024bc5ba49c..a8bed931c41 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -518,6 +518,7 @@ def test_set_sorting_fallback(): set_ = {None, 1} description = inspect.object_description(set_) assert description in ("{1, None}", "{None, 1}") + assert description == "{1, None}" def test_deterministic_nested_collection_descriptions(): @@ -542,6 +543,7 @@ def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) assert description in ("frozenset({1, None})", "frozenset({None, 1})") + assert description == "frozenset({1, None})" def test_nested_tuple_sorting(): From 7ad7c2e2a63221cfba91a80ea36d862a2cbfe401 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 25 Jul 2023 19:24:16 +0100 Subject: [PATCH 10/12] sphinx.util.inspect: use more precise exception handler Suggested-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- sphinx/util/inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3697b4a36f6..7141b8673d3 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -368,7 +368,7 @@ def object_description(object: Any, seen: frozenset = frozenset()) -> str: seen |= {id(object)} try: sorted_keys = sorted(object) - except Exception: + except TypeError: # Cannot sort dict keys, fall back to using descriptions as a sort key sorted_keys = sorted(object, key=lambda x: object_description(x, seen)) else: From c9b0152509efe7f81fc7a4060789682c7e3782c1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:39:39 +0100 Subject: [PATCH 11/12] Rename seen to _seen --- sphinx/util/inspect.py | 82 +++++++++++++++++++------------------- tests/test_util_inspect.py | 15 +++---- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index cba8264b79a..7bd9422e691 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -350,64 +350,64 @@ def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any: raise AttributeError(name) from exc -def object_description(object: Any, seen: frozenset = frozenset()) -> str: +def object_description(obj: Any, *, _seen: frozenset = frozenset()) -> str: """A repr() implementation that returns text safe to use in reST context. Maintains a set of 'seen' object IDs to detect and avoid infinite recursion. """ - if isinstance(object, dict): - if id(object) in seen: - return "dict(...)" - seen |= {id(object)} + seen = _seen + if isinstance(obj, dict): + if id(obj) in seen: + return 'dict(...)' + seen |= {id(obj)} try: - sorted_keys = sorted(object) + sorted_keys = sorted(obj) except TypeError: # Cannot sort dict keys, fall back to using descriptions as a sort key - sorted_keys = sorted(object, key=lambda x: object_description(x, seen)) - else: - items = ("%s: %s" % - (object_description(key, seen), object_description(object[key], seen)) - for key in sorted_keys) - return "{%s}" % ", ".join(items) - elif isinstance(object, set): - if id(object) in seen: - return "set(...)" - seen |= {id(object)} + sorted_keys = sorted(obj, key=lambda k: object_description(k, _seen=seen)) + + items = (f'{object_description(key, _seen=seen)}: {object_description(obj[key], _seen=seen)}' + for key in sorted_keys) + return '{%s}' % ', '.join(items) + elif isinstance(obj, set): + if id(obj) in seen: + return 'set(...)' + seen |= {id(obj)} try: - sorted_values = sorted(object) + sorted_values = sorted(obj) except TypeError: # Cannot sort set values, fall back to using descriptions as a sort key - sorted_values = sorted(object, key=lambda x: object_description(x, seen)) - return "{%s}" % ", ".join(object_description(x, seen) for x in sorted_values) - elif isinstance(object, frozenset): - if id(object) in seen: - return "frozenset(...)" - seen |= {id(object)} + sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen)) + return '{%s}' % ', '.join(object_description(x, _seen=seen) for x in sorted_values) + elif isinstance(obj, frozenset): + if id(obj) in seen: + return 'frozenset(...)' + seen |= {id(obj)} try: - sorted_values = sorted(object) + sorted_values = sorted(obj) except TypeError: # Cannot sort frozenset values, fall back to using descriptions as a sort key - sorted_values = sorted(object, key=lambda x: object_description(x, seen)) - return "frozenset({%s})" % ", ".join(object_description(x, seen) + sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen)) + return 'frozenset({%s})' % ', '.join(object_description(x, _seen=seen) for x in sorted_values) - elif isinstance(object, enum.Enum): - return f"{object.__class__.__name__}.{object.name}" - elif isinstance(object, tuple): - if id(object) in seen: - return "tuple(...)" - seen |= frozenset([id(object)]) - return "(%s%s)" % ( - ", ".join(object_description(x, seen) for x in object), - "," if len(object) == 1 else "", + elif isinstance(obj, enum.Enum): + return f'{obj.__class__.__name__}.{obj.name}' + elif isinstance(obj, tuple): + if id(obj) in seen: + return 'tuple(...)' + seen |= frozenset([id(obj)]) + return '(%s%s)' % ( + ', '.join(object_description(x, _seen=seen) for x in obj), + ',' * (len(obj) == 1), ) - elif isinstance(object, list): - if id(object) in seen: - return "list(...)" - seen |= {id(object)} - return "[%s]" % ", ".join(object_description(x, seen) for x in object) + elif isinstance(obj, list): + if id(obj) in seen: + return 'list(...)' + seen |= {id(obj)} + return '[%s]' % ', '.join(object_description(x, _seen=seen) for x in obj) try: - s = repr(object) + s = repr(obj) except Exception as exc: raise ValueError from exc # Strip non-deterministic memory addresses such as diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index 834800d4ade..70201862add 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -517,20 +517,18 @@ class MyEnum(enum.Enum): def test_set_sorting_fallback(): set_ = {None, 1} description = inspect.object_description(set_) - assert description in ("{1, None}", "{None, 1}") assert description == "{1, None}" 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},)" + assert inspect.object_description([{1, 2, 3, 10}]) == "[{1, 2, 3, 10}]" + assert inspect.object_description(({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},)" + assert inspect.object_description([{None, 1}]) == "[{1, None}]" + assert inspect.object_description(({None, 1},)) == "({1, None},)" + assert inspect.object_description([{None, 1, 'A'}]) == "[{'A', 1, None}]" + assert inspect.object_description(({None, 1, 'A'},)) == "({'A', 1, None},)" def test_frozenset_sorting(): @@ -542,7 +540,6 @@ def test_frozenset_sorting(): def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) - assert description in ("frozenset({1, None})", "frozenset({None, 1})") assert description == "frozenset({1, None})" From 2d3779c3492ab98fd94a9dbfe3fe3a8401bd76e2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:43:15 +0100 Subject: [PATCH 12/12] lint --- sphinx/util/inspect.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 7bd9422e691..29407bfcd25 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -366,9 +366,9 @@ def object_description(obj: Any, *, _seen: frozenset = frozenset()) -> str: # Cannot sort dict keys, fall back to using descriptions as a sort key sorted_keys = sorted(obj, key=lambda k: object_description(k, _seen=seen)) - items = (f'{object_description(key, _seen=seen)}: {object_description(obj[key], _seen=seen)}' - for key in sorted_keys) - return '{%s}' % ', '.join(items) + items = ((object_description(key, _seen=seen), + object_description(obj[key], _seen=seen)) for key in sorted_keys) + return '{%s}' % ', '.join(f'{key}: {value}' for (key, value) in items) elif isinstance(obj, set): if id(obj) in seen: return 'set(...)'