← eval6_submission

rich_3894

resolved RESOLVED LOOP PASS · 30 tool calls · 306 s · Textualize/rich

Task input

Handle unusual __qualname__ in inspect

<!--
Please note that Rich isn't accepting any new features at this point.

If a feature can be implemented without modifying the core library, then
they should be released as a third-party module. I can accept updates to the
core library that make it easier to extend (think hooks).

Bugfixes are always welcome of course.

Sometimes it is not clear what is a feature and what is a bug fix.
If there is any doubt, please open a discussion first.

-->

<!--
*Are you contributing typo fixes?*

If your PR solely consists of typos, at least one must be in the docs to warrant an addition to CONTRIBUTORS.md
-->

## Type of changes

- [x] Bug fix
- [ ] New feature
- [ ] Documentation / docstrings
- [x] Tests
- [ ] Other

## Checklist

- [x] I've run the latest [black](https://github.com/psf/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate (see note about typos above).
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.

## Description

I discovered, that an object with an unusual `__qualname__` attribute (not a `str`, e.g. a descriptor) causes `inspect` to fail.
This can happen if an object is created this way intentionally, but I discovered it, when decorating a class with `@functools.lru_cache` which forwards the underlying attributes using a descriptor when it can't add the to it's `__dict__` because the class uses `__slots__`:

```python
from functools import lru_cache
from rich import inspect


@lru_cache
class Klaas:
    __slots__ = ("__qualname__",)


if __name__ == "__main__":
    from rich.traceback import install

    install()

    inspect(Klaas.__qualname__)
    inspect(Klaas, all=True)
```

I fixed it by checking the type of `__qualname__` falling back to `__name__` and if that isn't a `str` using the already provided fallback.

Tool calls (31)

#ToolArgumentsResult
1run_command
{
 "command": "cat <<EOF > /tmp/repro.py\nfrom functools import lru_cache\nfrom rich import inspect\n\n@lru_cache\nclass Klaas:\n    __slots__ = (\"__qualname__\",)\n\nif __name__ == \"__main__\":\n    try:\n        inspect(Klaas.__qualname__)\n        print(\"inspect(Klaas.__qualname__) succeeded\")\n    except Exception as e:\n        print(f\"inspect(Klaas.__qualname__) failed: {e}\")\n\n    try:\n        inspect(Klaas, all=True)\n        print(\"inspect(Klaas, all=True) succeeded\")\n    except Exception as e:\n        print(f\"inspect(Klaas, all=True) failed: {e}\")\nEOF\npython3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \
full
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\n\u2502 \u2502 <member '__qualname__' of 'Klaas' objects>               \u2502 \u2502\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\n\u2502                                                              \u2502\n\u2502 30 attribute(s) not shown. Run inspect(inspect) for options. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\ninspect(Klaas.__qualname__) succeeded\ninspect(Klaas, all=True) failed: Only str or Text can be appended to Text\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"inspect\" rich | grep \"def inspect\"\n"
}
{"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\n", "stderr": "", "exit_code": 0}
3-6 ×4read_file
{
 "filepath": "rich/__init__.py",
 "start_line": "120"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
7read_file
{
 "filepath": "rich/__init__.py",
 "start_line": "120",
 "end_line": "200"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
8run_command
{
 "command": "grep -n \"def inspect\" rich/__init__.py\n"
}
{"status": "ok", "stdout": "120:def inspect(\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "120:def inspect(\n", "stderr": "", "exit_code": 0}
9read_file
{
 "filepath": "rich/__init__.py"
}
{"status": "ok", "filepath": "rich/__init__.py", "content": "\"\"\"Rich text and beautiful formatting in the terminal.\"\"\"\n\nimport os\nfrom typing import IO
full
{"status": "ok", "filepath": "rich/__init__.py", "content": "\"\"\"Rich text and beautiful formatting in the terminal.\"\"\"\n\nimport os\nfrom typing import IO, TYPE_CHECKING, Any, Callable, Optional, Union\n\nfrom ._extension import load_ipython_extension  # noqa: F401\n\n__all__ = [\"get_console\", \"reconfigure\", \"print\", \"inspect\", \"print_json\"]\n\nif TYPE_CHECKING:\n    from .console import Console\n\n# Global console used by alternative print\n_console: Optional[\"Console\"] = None\n\ntry:\n    _IMPORT_CWD = os.path.abspath(os.getcwd())\nexcept FileNotFoundError:\n    # Can happen if the cwd has been deleted\n    _IMPORT_CWD = \"\"\n\n\ndef get_console() -> \"Console\":\n    \"\"\"Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console,\n    and hasn't been explicitly given one.\n\n    Returns:\n        Console: A console instance.\n    \"\"\"\n    global _console\n    if _console is None:\n        from .console import Console\n\n        _console = Console()\n\n    return _console\n\n\ndef reconfigure(*args: Any, **kwargs: Any) -> None:\n    \"\"\"Reconfigures the global console by replacing it with another.\n\n    Args:\n        *args (Any): Positional arguments for the replacement :class:`~rich.console.Console`.\n        **kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`.\n    \"\"\"\n    from rich.console import Console\n\n    new_console = Console(*args, **kwargs)\n    _console = get_console()\n    _console.__dict__ = new_console.__dict__\n\n\ndef print(\n    *objects: Any,\n    sep: str = \" \",\n    end: str = \"\\n\",\n    file: Optional[IO[str]] = None,\n    flush: bool = False,\n) -> None:\n    r\"\"\"Print object(s) supplied via positional arguments.\n    This function has an identical signature to the built-in print.\n    For more advanced features, see the :class:`~rich.console.Console` class.\n\n    Args:\n        sep (str, optional): Separator between print
10read_file
{
 "filepath": "rich/__init__.py",
 "start_line": "120"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
11run_command
{
 "command": "python3 -c \"from rich import inspect; import inspect as py_inspect; print(inspect.__code__.co_filename)\"\n"
}
{"status": "ok", "stdout": "/workspace/rich/__init__.py\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "/workspace/rich/__init__.py\n", "stderr": "", "exit_code": 0}
12read_file
{
 "filepath": "rich/__init__.py",
 "start_line": "120"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
13run_command
{
 "command": "sed -n '120,200p' rich/__init__.py\n"
}
{"status": "ok", "stdout": "def inspect(\n obj: Any,\n *,\n console: Optional[\"Console\"] = None,\n title: Optional[str] = None,\n help: bool = False,\n method
full
{"status": "ok", "stdout": "def inspect(\n    obj: Any,\n    *,\n    console: Optional[\"Console\"] = None,\n    title: Optional[str] = None,\n    help: bool = False,\n    methods: bool = False,\n    docs: bool = True,\n    private: bool = False,\n    dunder: bool = False,\n    sort: bool = True,\n    all: bool = False,\n    value: bool = True,\n) -> None:\n    \"\"\"Inspect any Python object.\n\n    * inspect(<OBJECT>) to see summarized info.\n    * inspect(<OBJECT>, methods=True) to see methods.\n    * inspect(<OBJECT>, help=True) to see full (non-abbreviated) help.\n    * inspect(<OBJECT>, private=True) to see private attributes (single underscore).\n    * inspect(<OBJECT>, dunder=True) to see attributes beginning with double underscore.\n    * inspect(<OBJECT>, all=True) to see all attributes.\n\n    Args:\n        obj (Any): An object to inspect.\n        title (str, optional): Title to display over inspect result, or None use type. Defaults to None.\n        help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.\n        methods (bool, optional): Enable inspection of callables. Defaults to False.\n        docs (bool, optional): Also render doc strings. Defaults to True.\n        private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.\n        dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.\n        sort (bool, optional): Sort attributes alphabetically. Defaults to True.\n        all (bool, optional): Show all attributes. Defaults to False.\n        value (bool, optional): Pretty print value. Defaults to True.\n    \"\"\"\n    _console = console or get_console()\n    from rich._inspect import Inspect\n\n    # Special case for inspect(inspect)\n    is_inspect = obj is inspect\n\n    _inspect = Inspect(\n        obj,\n        title=title,\n        help=is_inspect or help,\n        methods=is_inspect or methods,\n        docs=is_inspect o
14run_command
{
 "command": "grep -rn \"Inspect\" rich | grep \"class Inspect\"\n"
}
{"status": "ok", "stdout": "rich/_inspect.py:21:class Inspect(JupyterMixin):\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "rich/_inspect.py:21:class Inspect(JupyterMixin):\n", "stderr": "", "exit_code": 0}
15read_file
{
 "filepath": "rich/_inspect.py"
}
{"status": "ok", "filepath": "rich/_inspect.py", "content": "import inspect\nfrom inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature\nfrom t
full
{"status": "ok", "filepath": "rich/_inspect.py", "content": "import inspect\nfrom inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature\nfrom typing import Any, Collection, Iterable, Optional, Tuple, Type, Union\n\nfrom .console import Group, RenderableType\nfrom .control import escape_control_codes\nfrom .highlighter import ReprHighlighter\nfrom .jupyter import JupyterMixin\nfrom .panel import Panel\nfrom .pretty import Pretty\nfrom .table import Table\nfrom .text import Text, TextType\n\n\ndef _first_paragraph(doc: str) -> str:\n    \"\"\"Get the first paragraph from a docstring.\"\"\"\n    paragraph, _, _ = doc.partition(\"\\n\\n\")\n    return paragraph\n\n\nclass Inspect(JupyterMixin):\n    \"\"\"A renderable to inspect any Python Object.\n\n    Args:\n        obj (Any): An object to inspect.\n        title (str, optional): Title to display over inspect result, or None use type. Defaults to None.\n        help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.\n        methods (bool, optional): Enable inspection of callables. Defaults to False.\n        docs (bool, optional): Also render doc strings. Defaults to True.\n        private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.\n        dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.\n        sort (bool, optional): Sort attributes alphabetically. Defaults to True.\n        all (bool, optional): Show all attributes. Defaults to False.\n        value (bool, optional): Pretty print value of object. Defaults to True.\n    \"\"\"\n\n    def __init__(\n        self,\n        obj: Any,\n        *,\n        title: Optional[TextType] = None,\n        help: bool = False,\n        methods: bool = False,\n        docs: bool = True,\n        private: bool = False,\n        dunder: bool = False,\n        sort: bool = True,\n        all: bool = True,\n        value: bool = True,\n
16read_file
{
 "filepath": "rich/_inspect.py",
 "start_line": "151"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
17run_command
{
 "command": "sed -n '151,268p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": "\n if callable(obj):\n signature = self._get_signature(\"\", obj)\n if signature is not None:\n yield signature\n yield \"\"\n\n if
full
{"status": "ok", "stdout": "\n        if callable(obj):\n            signature = self._get_signature(\"\", obj)\n            if signature is not None:\n                yield signature\n                yield \"\"\n\n        if self.docs:\n            _doc = self._get_formatted_doc(obj)\n            if _doc is not None:\n                doc_text = Text(_doc, style=\"inspect.help\")\n                doc_text = highlighter(doc_text)\n                yield doc_text\n                yield \"\"\n\n        if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):\n            yield Panel(\n                Pretty(obj, indent_guides=True, max_length=10, max_string=60),\n                border_style=\"inspect.value.border\",\n            )\n            yield \"\"\n\n        for key, (error, value) in items:\n            key_text = Text.assemble(\n                (\n                    key,\n                    \"inspect.attr.dunder\" if key.startswith(\"__\") else \"inspect.attr\",\n                ),\n                (\" =\", \"inspect.equals\"),\n            )\n            if error is not None:\n                warning = key_text.copy()\n                warning.stylize(\"inspect.error\")\n                add_row(warning, highlighter(repr(error)))\n                continue\n\n            if callable(value):\n                if not self.methods:\n                    continue\n\n                _signature_text = self._get_signature(key, value)\n                if _signature_text is None:\n                    add_row(key_text, Pretty(value, highlighter=highlighter))\n                else:\n                    if self.docs:\n                        docs = self._get_formatted_doc(value)\n                        if docs is not None:\n                            _signature_text.append(\"\\n\" if \"\\n\" in docs else \" \")\n                            doc = highlighter(docs)\n                            doc.stylize(\"inspect.doc\")\n                            _signatu
18run_command
{
 "command": "python3 -c \"from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\"\n"
}
{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from rich import inspect; import functools; @functools.lru_cach
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<string>\", line 1\n    from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\n                                                ^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\n                                                ^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
19run_command
{
 "command": "python3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \
full
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\n\u2502 \u2502 <member '__qualname__' of 'Klaas' objects>               \u2502 \u2502\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\n\u2502                                                              \u2502\n\u2502 30 attribute(s) not shown. Run inspect(inspect) for options. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\ninspect(Klaas.__qualname__) succeeded\ninspect(Klaas, all=True) failed: Only str or Text can be appended to Text\n", "stderr": "", "exit_code": 0}
20read_file
{
 "filepath": "rich/_inspect.py",
 "start_line": "75",
 "end_line": "100"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
21run_command
{
 "command": "sed -n '75,100p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": " return Panel.fit(\n Group(*self._render()),\n title=self.title,\n border_style=\"scope.border\",\n padding=(0, 1),\n )\n\n def _get
full
{"status": "ok", "stdout": "        return Panel.fit(\n            Group(*self._render()),\n            title=self.title,\n            border_style=\"scope.border\",\n            padding=(0, 1),\n        )\n\n    def _get_signature(self, name: str, obj: Any) -> Optional[Text]:\n        \"\"\"Get a signature for a callable.\"\"\"\n        try:\n            _signature = str(signature(obj)) + \":\"\n        except ValueError:\n            _signature = \"(...)\"\n        except TypeError:\n            return None\n\n        source_filename: Optional[str] = None\n        try:\n            source_filename = getfile(obj)\n        except (OSError, TypeError):\n            # OSError is raised if obj has no source file, e.g. when defined in REPL.\n            pass\n\n        callable_name = Text(name, style=\"inspect.callable\")\n        if source_filename:\n            callable_name.stylize(f\"link file://{source_filename}\")\n", "stderr": "", "exit_code": 0}
22run_command
{
 "command": "sed -n '101,120p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": " signature_text = self.highlighter(_signature)\n\n qualname = name or getattr(obj, \"__qualname__\", name)\n\n # If obj is a module,
full
{"status": "ok", "stdout": "        signature_text = self.highlighter(_signature)\n\n        qualname = name or getattr(obj, \"__qualname__\", name)\n\n        # If obj is a module, there may be classes (which are callable) to display\n        if inspect.isclass(obj):\n            prefix = \"class\"\n        elif inspect.iscoroutinefunction(obj):\n            prefix = \"async def\"\n        else:\n            prefix = \"def\"\n\n        qual_signature = Text.assemble(\n            (f\"{prefix} \", f\"inspect.{prefix.replace(' ', '_')}\"),\n            (qualname, \"inspect.callable\"),\n            signature_text,\n        )\n\n        return qual_signature\n\n", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "sed -n '121,150p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": " def _render(self) -> Iterable[RenderableType]:\n \"\"\"Render object.\"\"\"\n\n def sort_items(item: Tuple[str, Any]) -> Tuple[bool
full
{"status": "ok", "stdout": "    def _render(self) -> Iterable[RenderableType]:\n        \"\"\"Render object.\"\"\"\n\n        def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]:\n            key, (_error, value) = item\n            return (callable(value), key.strip(\"_\").lower())\n\n        def safe_getattr(attr_name: str) -> Tuple[Any, Any]:\n            \"\"\"Get attribute or any exception.\"\"\"\n            try:\n                return (None, getattr(obj, attr_name))\n            except Exception as error:\n                return (error, None)\n\n        obj = self.obj\n        keys = dir(obj)\n        total_items = len(keys)\n        if not self.dunder:\n            keys = [key for key in keys if not key.startswith(\"__\")]\n        if not self.private:\n            keys = [key for key in keys if not key.startswith(\"_\")]\n        not_shown_count = total_items - len(keys)\n        items = [(key, safe_getattr(key)) for key in keys]\n        if self.sort:\n            items.sort(key=sort_items)\n\n        items_table = Table.grid(padding=(0, 1), expand=False)\n        items_table.add_column(justify=\"right\")\n        add_row = items_table.add_row\n        highlighter = self.highlighter\n", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "sed -n '151,180p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": "\n if callable(obj):\n signature = self._get_signature(\"\", obj)\n if signature is not None:\n yield signature\n yield \"\"\n\n if
full
{"status": "ok", "stdout": "\n        if callable(obj):\n            signature = self._get_signature(\"\", obj)\n            if signature is not None:\n                yield signature\n                yield \"\"\n\n        if self.docs:\n            _doc = self._get_formatted_doc(obj)\n            if _doc is not None:\n                doc_text = Text(_doc, style=\"inspect.help\")\n                doc_text = highlighter(doc_text)\n                yield doc_text\n                yield \"\"\n\n        if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)):\n            yield Panel(\n                Pretty(obj, indent_guides=True, max_length=10, max_string=60),\n                border_style=\"inspect.value.border\",\n            )\n            yield \"\"\n\n        for key, (error, value) in items:\n            key_text = Text.assemble(\n                (\n                    key,\n                    \"inspect.attr.dunder\" if key.startswith(\"__\") else \"inspect.attr\",\n                ),\n                (\" =\", \"inspect.equals\"),\n            )\n", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "sed -n '181,210p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": " if error is not None:\n warning = key_text.copy()\n warning.stylize(\"inspect.error\")\n add_row(warning, highlighter(repr(error)))
full
{"status": "ok", "stdout": "            if error is not None:\n                warning = key_text.copy()\n                warning.stylize(\"inspect.error\")\n                add_row(warning, highlighter(repr(error)))\n                continue\n\n            if callable(value):\n                if not self.methods:\n                    continue\n\n                _signature_text = self._get_signature(key, value)\n                if _signature_text is None:\n                    add_row(key_text, Pretty(value, highlighter=highlighter))\n                else:\n                    if self.docs:\n                        docs = self._get_formatted_doc(value)\n                        if docs is not None:\n                            _signature_text.append(\"\\n\" if \"\\n\" in docs else \" \")\n                            doc = highlighter(docs)\n                            doc.stylize(\"inspect.doc\")\n                            _signature_text.append(doc)\n\n                    add_row(key_text, _signature_text)\n            else:\n                add_row(key_text, Pretty(value, highlighter=highlighter))\n        if items_table.row_count:\n            yield items_table\n        elif not_shown_count:\n            yield Text.from_markup(\n                f\"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] \"\n", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "sed -n '211,268p' rich/_inspect.py\n"
}
{"status": "ok", "stdout": " f\"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options.\"\n )\n\n def _get_formatted_doc(self, object_: Any) -> Optional[
full
{"status": "ok", "stdout": "                f\"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options.\"\n            )\n\n    def _get_formatted_doc(self, object_: Any) -> Optional[str]:\n        \"\"\"\n        Extract the docstring of an object, process it and returns it.\n        The processing consists in cleaning up the docstring's indentation,\n        taking only its 1st paragraph if `self.help` is not True,\n        and escape its control codes.\n\n        Args:\n            object_ (Any): the object to get the docstring from.\n\n        Returns:\n            Optional[str]: the processed docstring, or None if no docstring was found.\n        \"\"\"\n        docs = getdoc(object_)\n        if docs is None:\n            return None\n        docs = cleandoc(docs).strip()\n        if not self.help:\n            docs = _first_paragraph(docs)\n        return escape_control_codes(docs)\n\n\ndef get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]:\n    \"\"\"Returns the MRO of an object's class, or of the object itself if it's a class.\"\"\"\n    if not hasattr(obj, \"__mro__\"):\n        # N.B. we cannot use `if type(obj) is type` here because it doesn't work with\n        # some types of classes, such as the ones that use abc.ABCMeta.\n        obj = type(obj)\n    return getattr(obj, \"__mro__\", ())\n\n\ndef get_object_types_mro_as_strings(obj: object) -> Collection[str]:\n    \"\"\"\n    Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class.\n\n    Examples:\n        `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`\n    \"\"\"\n    return [\n        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n        for type_ in get_object_types_mro(obj)\n    ]\n\n\ndef is_object_one_of_types(\n    obj: object, fully_qualified_types_names: Collection[str]\n) -> bool:\n    \"\"\"\n    Returns `True` if the giv
27run_command
{
 "command": "python3 -c \"from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\"\n"
}
{"status": "error", "error_type": "CommandError", "error_message": " File \"<string>\", line 1\n from rich import inspect; import functools; @functools.lru_cach
full
{"status": "error", "error_type": "CommandError", "error_message": "  File \"<string>\", line 1\n    from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\n                                                ^\nSyntaxError: invalid syntax\n", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from rich import inspect; import functools; @functools.lru_cache\\nclass Klaas:\\n    __slots__ = ('__qualname__',)\\ninspect(Klaas, all=True)\n                                                ^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
28run_command
{
 "command": "python3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \
full
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\n\u2502 \u2502 <member '__qualname__' of 'Klaas' objects>               \u2502 \u2502\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\n\u2502                                                              \u2502\n\u2502 30 attribute(s) not shown. Run inspect(inspect) for options. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\ninspect(Klaas.__qualname__) succeeded\ninspect(Klaas, all=True) failed: Only str or Text can be appended to Text\n", "stderr": "", "exit_code": 0}
29edit_file
{
 "filepath": "rich/_inspect.py",
 "new_string": "        qualname = name or getattr(obj, \"__qualname__\", name)\n        if not isinstance(qualname, str):\n            qualname = getattr(obj, \"__name__\", name)\n            if not isinstance(qualname, str):\n                qualname = name\n",
 "old_string": "        qualname = name or getattr(obj, \"__qualname__\", name)"
}
{"status": "ok", "filepath": "rich/_inspect.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/_inspect.py\n+++ b/rich/_inspect.py\n@@ -101,6 +101,
full
{"status": "ok", "filepath": "rich/_inspect.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/_inspect.py\n+++ b/rich/_inspect.py\n@@ -101,6 +101,11 @@\n         signature_text = self.highlighter(_signature)\n \n         qualname = name or getattr(obj, \"__qualname__\", name)\n+        if not isinstance(qualname, str):\n+            qualname = getattr(obj, \"__name__\", name)\n+            if not isinstance(qualname, str):\n+                qualname = name\n+\n \n         # If obj is a module, there may be classes (which are callable) to display\n         if inspect.isclass(obj):\n", "is_truncated": false}
30run_command
{
 "command": "python3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \
full
{"status": "ok", "stdout": "\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <class 'member_descriptor'> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 \u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e \u2502\n\u2502 \u2502 <member '__qualname__' of 'Klaas' objects>               \u2502 \u2502\n\u2502 \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f \u2502\n\u2502                                                              \u2502\n\u2502 30 attribute(s) not shown. Run inspect(inspect) for options. \u2502\n\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256f\ninspect(Klaas.__qualname__) succeeded\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <functools._lru_cache_wrapper object at 0x7896948f1590> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 def Klaas():                                                                 \u2502\n\u2502                     
31submit_patch
{}
{"status": "ok", "patch_size": 597, "files_changed": 1}
full
{"status": "ok", "patch_size": 597, "files_changed": 1}

Patch

diff --git a/rich/_inspect.py b/rich/_inspect.py
index 27d65cec..c3ab7099 100644
--- a/rich/_inspect.py
+++ b/rich/_inspect.py
@@ -101,6 +101,11 @@ class Inspect(JupyterMixin):
         signature_text = self.highlighter(_signature)
 
         qualname = name or getattr(obj, "__qualname__", name)
+        if not isinstance(qualname, str):
+            qualname = getattr(obj, "__name__", name)
+            if not isinstance(qualname, str):
+                qualname = name
+
 
         # If obj is a module, there may be classes (which are callable) to display
         if inspect.isclass(obj):

Test output

show
...s....sss...................................                           [100%]
42 passed, 4 skipped in 0.20s