diff --git a/rich/_inspect.py b/rich/_inspect.py index 2287efc7..30446ceb 100644 --- a/rich/_inspect.py +++ b/rich/_inspect.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import inspect from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature -from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union, cast +from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union from .console import Group, RenderableType from .control import escape_control_codes @@ -235,27 +235,36 @@ class Inspect(JupyterMixin): return escape_control_codes(docs) -def object_types_tree(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: - obj_type: Type[Any] = type(obj) - if obj_type is type: - # the given object was already a type: let's use this instead: - obj_type = cast(Type[Any], obj) # I wish MyPy could infer this itself :-/ - return getattr(obj_type, "__mro__", ()) +def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: + """Returns the MRO of an object's class, or of the object itself if it's a class.""" + if not hasattr(obj, "__mro__"): + # N.B. we cannot use `if type(obj) is type` here because it doesn't work with + # some types of classes, such as the ones that use abc.ABCMeta. + obj = type(obj) + return getattr(obj, "__mro__", ()) -def object_types_tree_as_strings(obj: object) -> Tuple[str, ...]: - return tuple( - [ - f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' - for type_ in object_types_tree(obj) - ] - ) +def get_object_types_mro_as_strings(obj: object) -> Collection[str]: + """ + Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class. + + Examples: + `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']` + """ + return [ + f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' + for type_ in get_object_types_mro(obj) + ] -def object_is_one_of_types( +def is_object_one_of_types( obj: object, fully_qualified_types_names: Collection[str] ) -> bool: - for type_name in object_types_tree_as_strings(obj): + """ + Returns `True` if the given object's class (or the object itself, if it's a class) has one of the + fully qualified names in its MRO. + """ + for type_name in get_object_types_mro_as_strings(obj): if type_name in fully_qualified_types_names: return True return False diff --git a/rich/pretty.py b/rich/pretty.py index b3840a49..ae8b150f 100644 --- a/rich/pretty.py +++ b/rich/pretty.py @@ -123,7 +123,7 @@ def _ipy_display_hook( expand_all: bool = False, ) -> None: # needed here to prevent circular import: - from ._inspect import object_is_one_of_types + from ._inspect import is_object_one_of_types from .console import ConsoleRenderable # always skip rich generated jupyter renderables or None values @@ -161,7 +161,7 @@ def _ipy_display_hook( # as they result in the rendering of useless and noisy lines such as `
`. # What does this do? # --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it. - if object_is_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER): + if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER): return # certain renderables should start on a new line diff --git a/tests/test_inspect.py b/tests/test_inspect.py index fe62a7b4..53f2f932 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -7,9 +7,9 @@ import pytest from rich import inspect from rich._inspect import ( - object_is_one_of_types, - object_types_tree, - object_types_tree_as_strings, + get_object_types_mro, + get_object_types_mro_as_strings, + is_object_one_of_types, ) from rich.console import Console @@ -375,25 +375,25 @@ def test_can_handle_special_characters_in_docstrings( [FooSubclass, (FooSubclass, Foo, object)], ), ) -def test_object_types_tree(obj: object, expected_result: Sequence[Type]): - assert object_types_tree(obj) == expected_result +def test_object_types_mro(obj: object, expected_result: Sequence[Type]): + assert get_object_types_mro(obj) == expected_result @pytest.mark.parametrize( "obj,expected_result", ( # fmt: off - ["hi", ("builtins.str", "builtins.object")], - [str, ("builtins.str", "builtins.object")], - [Foo(1), (f"{__name__}.Foo", "builtins.object")], - [Foo, (f"{__name__}.Foo", "builtins.object")], - [FooSubclass(1), (f"{__name__}.FooSubclass", f"{__name__}.Foo", "builtins.object")], - [FooSubclass, (f"{__name__}.FooSubclass", f"{__name__}.Foo", "builtins.object")], + ["hi", ["builtins.str", "builtins.object"]], + [str, ["builtins.str", "builtins.object"]], + [Foo(1), [f"{__name__}.Foo", "builtins.object"]], + [Foo, [f"{__name__}.Foo", "builtins.object"]], + [FooSubclass(1), [f"{__name__}.FooSubclass", f"{__name__}.Foo", "builtins.object"]], + [FooSubclass, [f"{__name__}.FooSubclass", f"{__name__}.Foo", "builtins.object"]], # fmt: on ), ) -def test_object_types_tree_as_strings(obj: object, expected_result: Sequence[str]): - assert object_types_tree_as_strings(obj) == expected_result +def test_object_types_mro_as_strings(obj: object, expected_result: Sequence[str]): + assert get_object_types_mro_as_strings(obj) == expected_result @pytest.mark.parametrize( @@ -418,4 +418,4 @@ def test_object_types_tree_as_strings(obj: object, expected_result: Sequence[str def test_object_is_one_of_types( obj: object, types: Sequence[str], expected_result: bool ): - assert object_is_one_of_types(obj, types) is expected_result + assert is_object_one_of_types(obj, types) is expected_result