← pod_r3_v1e

rich_3894

resolved RESOLVED PASS · 21 tool calls · 151 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 (22)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"inspect\" rich | grep \"def inspect\""
}
{"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}
2run_command
{
 "command": "sed -n '120,150p' rich/__init__.py"
}
{"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", "stderr": "", "exit_code": 0}
3run_command
{
 "command": "grep -rn \"__qualname__\" rich | head -20"
}
{"status": "ok", "stdout": "rich/_inspect.py:103: qualname = name or getattr(obj, \"__qualname__\", name)\nrich/_inspect.py:253: f'{getattr(type_, \"__module__\
full
{"status": "ok", "stdout": "rich/_inspect.py:103:        qualname = name or getattr(obj, \"__qualname__\", name)\nrich/_inspect.py:253:        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n", "stderr": "", "exit_code": 0}
4run_command
{
 "command": "sed -n '90,110p' rich/_inspect.py"
}
{"status": "ok", "stdout": "\n source_filename: Optional[str] = None\n try:\n source_filename = getfile(obj)\n except (OSError, TypeError):\n # OSError is raise
full
{"status": "ok", "stdout": "\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        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", "stderr": "", "exit_code": 0}
5write_file
{
 "content": "from 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        inspect(Klaas, all=True)\n        print(\"Success\")\n    except Exception as e:\n        print(f\"Failed: {e}\")\n        import traceback\n        traceback.print_exc()\n",
 "filepath": "/tmp/repro.py"
}
{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/repro.py' escapes workspace root."}
full
{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/repro.py' escapes workspace root."}
6write_file
{
 "content": "from 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        inspect(Klaas, all=True)\n        print(\"Success\")\n    except Exception as e:\n        print(f\"Failed: {e}\")\n        import traceback\n        traceback.print_exc()\n",
 "filepath": "repro.py"
}
{"status": "ok", "filepath": "repro.py", "size": 359}
full
{"status": "ok", "filepath": "repro.py", "size": 359}
7run_command
{
 "command": "python3 repro.py"
}
{"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\nFailed: Only str or Text can be appended to Text\n", "stderr": "Traceback (most recent call last):\n  File \"/private/workspace/repro.py\", line 11, in <module>\n    inspect(Klaas, all=True)\n    ~~~~~~~^^^^^^^^^^^^^^^^^\n  File \"/private/workspace/rich/__init__.py\", line 173, in inspect\n    _console.print(_inspect)\n    ~~~~~~~~~~~~~~^^^^^^^^^^\n  F
8run_command
{
 "command": "sed -n '100,120p' rich/_inspect.py"
}
{"status": "ok", "stdout": " callable_name.stylize(f\"link file://{source_filename}\")\n signature_text = self.highlighter(_signature)\n\n qualname = name or ge
full
{"status": "ok", "stdout": "            callable_name.stylize(f\"link file://{source_filename}\")\n        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}
9run_command
{
 "command": "sed -n '250,260p' rich/_inspect.py"
}
{"status": "ok", "stdout": " `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`\n \"\"\"\n return [\n f'{g
full
{"status": "ok", "stdout": "        `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", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\nold = \"\"\"        qualname = name or getattr(obj, \"__qualname__\", name)\"\"\"\nnew = \"\"\"        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\"\"\"\nassert s.count(old) == 1\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\nold = \"\"\"        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\"\"\"\nnew = \"\"\"        qualname = getattr(type_, \"__qualname__\", \"\")\n        if not isinstance(qualname, str):\n            qualname = getattr(type_, \"__name__\", \"\")\n            if not isinstance(qualname, str):\n                qualname = \"\"\n        f'{getattr(type_, \"__module__\", \"\")}.{qualname}'\"\"\"\n# This is a list comprehension, I can't just replace it like that.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "sed -n '250,260p' rich/_inspect.py"
}
{"status": "ok", "stdout": " \"\"\"\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 `ob
full
{"status": "ok", "stdout": "    \"\"\"\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", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\nold = \"\"\"    return [\n        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n        for type_ in get_object_types_mro(obj)\n    ]\"\"\"\ndef get_qualname(type_):\n    qualname = getattr(type_, \"__qualname__\", \"\")\n    if not isinstance(qualname, str):\n        qualname = getattr(type_, \"__name__\", \"\")\n        if not isinstance(qualname, str):\n            qualname = \"\"\n    return qualname\n\nnew = \"\"\"    return [\n        f'{getattr(type_, \"__module__\", \"\")}.{get_qualname(type_)}'\n        for type_ in get_object_types_mro(obj)\n    ]\"\"\"\n\n# I need to define get_qualname somewhere.\n# Let's see where to put it.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "python3 repro.py"
}
{"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\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <functools._lru_cache_wrapper object at 0x1018e5850> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 def Klaas():                                                                 \u2502\n\u2502                                             
15run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\nold = \"\"\"    return [\n        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n        for type_ in get_object_types_mro(obj)\n    ]\"\"\"\nnew = \"\"\"    return [\n        f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", getattr(type_, \"__name__\", \"\"))}'\n        for type_ in get_object_types_mro(obj)\n    ]\"\"\"\n# Wait, if __qualname__ is not a string, getattr(type_, \"__qualname__\", ...) will still return the non-string value.\n# The issue is that it's NOT a string.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\n\n# We need to fix the list comprehension.\n# Let's replace the whole function object_types_mro_as_strings.\n\n# Find the function start\nimport re\nmatch = re.search(r\"def object_types_mro_as_strings\\(obj: Any\\) -> List\\[str\\]:\", s)\nif match:\n    start = match.start()\n    # Find the end of the function (the next def or end of file)\n    # This is a bit risky. Let's just use a more precise replacement.\n    pass\n\n# Let's try to replace the return statement and add a helper function before it.\n# But the return statement is inside a function.\n\n# Let's just use a helper function and call it in the list comprehension.\n# I'll add the helper function before object_types_mro_as_strings.\n\n# Find where object_types_mro_as_strings is defined.\n# It's around line 250.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "sed -n '240,260p' rich/_inspect.py"
}
{"status": "ok", "stdout": "def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]:\n \"\"\"Returns the MRO of an object's class, or of the
full
{"status": "ok", "stdout": "def 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", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\n\nold = \"\"\"def 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\ndef get_qualname(type_):\n    qualname = getattr(type_, \"__qualname__\", \"\")\n    if not isinstance(qualname, str):\n        qualname = getattr(type_, \"__name__\", \"\")\n        if not isinstance(qualname, str):\n            qualname = \"\"\n    return qualname\n\nnew = f\"\"\"def 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__\", \"\")}.{get_qualname(type_)}'\n        for type_ in get_object_types_mro(obj)\n    ]\n\ndef get_qualname(type_):\n    qualname = getattr(type_, \"__qualname__\", \"\")\n    if not isinstance(qualname, str):\n        qualname = getattr(type_, \"__name__\", \"\")\n        if not isinstance(qualname, str):\n            qualname = \"\"\n    return qualname\"\"\"\n\n# The above replacement is slightly wrong because it puts get_qualname after the function that uses it.\n# That's fine in Python as long as it's defined when the function is called.\n\nassert s.count(old) == 1\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"<stdin>\", line 33, in <module>\nNameError: name
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"<stdin>\", line 33, in <module>\nNameError: name 'type_' is not defined. Did you mean: 'type'?\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"<stdin>\", line 33, in <module>\nNameError: name 'type_' is not defined. Did you mean: 'type'?\n", "exit_code": 1}}
19run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/_inspect.py\")\ns = p.read_text()\n\nold = \"\"\"def 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\nnew = \"\"\"def 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__\", \"\")}.{get_qualname(type_)}'\n        for type_ in get_object_types_mro(obj)\n    ]\n\ndef get_qualname(type_):\n    qualname = getattr(type_, \"__qualname__\", \"\")\n    if not isinstance(qualname, str):\n        qualname = getattr(type_, \"__name__\", \"\")\n        if not isinstance(qualname, str):\n            qualname = \"\"\n    return qualname\"\"\"\n\nassert s.count(old) == 1\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "python3 repro.py"
}
{"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\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <functools._lru_cache_wrapper object at 0x101f19850> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 def Klaas():                                                                 \u2502\n\u2502                                             
21run_command
{
 "command": "rm repro.py"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
22submit_patch
{}
{"status": "ok", "patch_size": 1387, "files_changed": 1}
full
{"status": "ok", "patch_size": 1387, "files_changed": 1}

Patch

diff --git a/rich/_inspect.py b/rich/_inspect.py
index 27d65cec..bcf9f092 100644
--- a/rich/_inspect.py
+++ b/rich/_inspect.py
@@ -101,6 +101,10 @@ 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):
@@ -250,10 +254,18 @@ def get_object_types_mro_as_strings(obj: object) -> Collection[str]:
         `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']`
     """
     return [
-        f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}'
+        f'{getattr(type_, "__module__", "")}.{get_qualname(type_)}'
         for type_ in get_object_types_mro(obj)
     ]
 
+def get_qualname(type_):
+    qualname = getattr(type_, "__qualname__", "")
+    if not isinstance(qualname, str):
+        qualname = getattr(type_, "__name__", "")
+        if not isinstance(qualname, str):
+            qualname = ""
+    return qualname
+
 
 def is_object_one_of_types(
     obj: object, fully_qualified_types_names: Collection[str]

Test output

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