resolved RESOLVED PASS · 19 tool calls · 36 s · Textualize/rich
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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"inspect\" rich | grep \"def\" | head -20"
} | {"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\nrich/default_styles.py:39: \"inspect.attr\": Style(color=\"yellow\", italic=True),\nrich/default_full{"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\nrich/default_styles.py:39: \"inspect.attr\": Style(color=\"yellow\", italic=True),\nrich/default_styles.py:40: \"inspect.attr.dunder\": Style(color=\"yellow\", italic=True, dim=True),\nrich/default_styles.py:41: \"inspect.callable\": Style(bold=True, color=\"red\"),\nrich/default_styles.py:42: \"inspect.async_def\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:43: \"inspect.def\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:44: \"inspect.class\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:45: \"inspect.error\": Style(bold=True, color=\"red\"),\nrich/default_styles.py:46: \"inspect.equals\": Style(),\nrich/default_styles.py:47: \"inspect.help\": Style(color=\"cyan\"),\nrich/default_styles.py:48: \"inspect.doc\": Style(dim=True),\nrich/default_styles.py:49: \"inspect.value.border\": Style(color=\"green\"),\nrich/pretty.py:109: default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)\n", "stderr": "", "exit_code": 0} |
| 2-3 ×2 | read_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'"} |
| 4 | run_command | {
"command": "grep -rn \"inspect\" rich | grep \"def\""
} | {"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\nrich/default_styles.py:39: \"inspect.attr\": Style(color=\"yellow\", italic=True),\nrich/default_full{"status": "ok", "stdout": "rich/__init__.py:120:def inspect(\nrich/default_styles.py:39: \"inspect.attr\": Style(color=\"yellow\", italic=True),\nrich/default_styles.py:40: \"inspect.attr.dunder\": Style(color=\"yellow\", italic=True, dim=True),\nrich/default_styles.py:41: \"inspect.callable\": Style(bold=True, color=\"red\"),\nrich/default_styles.py:42: \"inspect.async_def\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:43: \"inspect.def\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:44: \"inspect.class\": Style(italic=True, color=\"bright_cyan\"),\nrich/default_styles.py:45: \"inspect.error\": Style(bold=True, color=\"red\"),\nrich/default_styles.py:46: \"inspect.equals\": Style(),\nrich/default_styles.py:47: \"inspect.help\": Style(color=\"cyan\"),\nrich/default_styles.py:48: \"inspect.doc\": Style(dim=True),\nrich/default_styles.py:49: \"inspect.value.border\": Style(color=\"green\"),\nrich/pretty.py:109: default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__)\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "grep -rn \"__qualname__\" rich"
} | {"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} |
| 6 | read_file | {
"filepath": "rich/_inspect.py",
"start_line": "100",
"end_line": "110"
} | {"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'"} |
| 7 | read_file | {
"filepath": "rich/_inspect.py"
} | {"status": "ok", "filepath": "rich/_inspect.py", "content": "import inspect\nfrom inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature\nfrom tfull{"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 |
| 8 | run_command | {
"command": "cat <<EOF > reproduce_issue.py\nfrom functools import lru_cache\nfrom rich import inspect\n\n@lru_cache\nclass Klaas:\n __slots__ = (\"__qualname__\",)\n\nif __name__ == \"__main__\":\n from rich.traceback import install\n install()\n try:\n inspect(Klaas.__qualname__)\n print(\"First inspect success\")\n inspect(Klaas, all=True)\n print(\"Second inspect success\")\n except Exception as e:\n import traceback\n traceback.print_exc()\nEOF\npython reproduce_issue.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\nFirst inspect success\n", "stderr": "Traceback (most recent call last):\n File \"/workspace/reproduce_issue.py\", line 14, in <module>\n inspect(Klaas, all=True)\n ~~~~~~~^^^^^^^^^^^^^^^^^\n File \"/workspace/rich/__init__.py\", line 173, in inspect\n _console.print(_inspect)\n ~~~~~~~~~~~~~~^^^^^^^^^^\n File \"/workspace/rich/console.py\ |
| 9 | read_file | {
"filepath": "rich/_inspect.py",
"start_line": "100",
"end_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'"} |
| 10 | read_file | {
"filepath": "rich/_inspect.py",
"start_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'"} |
| 11 | read_file | {
"filepath": "rich/_inspect.py",
"start_line": "100",
"end_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'"} |
| 12 | read_file | {
"filepath": "rich/_inspect.py"
} | {"status": "ok", "filepath": "rich/_inspect.py", "content": "import inspect\nfrom inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature\nfrom tfull{"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 |
| 13 | run_command | {
"command": "grep -n \"__qualname__\" rich/_inspect.py"
} | {"status": "ok", "stdout": "103: qualname = name or getattr(obj, \"__qualname__\", name)\n253: f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualfull{"status": "ok", "stdout": "103: qualname = name or getattr(obj, \"__qualname__\", name)\n253: f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n", "stderr": "", "exit_code": 0} |
| 14 | read_file | {
"end_line": "115",
"filepath": "rich/_inspect.py",
"start_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'"} |
| 15 | read_file | {
"filepath": "rich/_inspect.py",
"start_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'"} |
| 16 | read_file | {
"filepath": "rich/_inspect.py"
} | {"status": "ok", "filepath": "rich/_inspect.py", "content": "import inspect\nfrom inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature\nfrom tfull{"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 |
| 17 | run_command | {
"command": "grep -n \"__qualname__\" rich/_inspect.py"
} | {"status": "ok", "stdout": "103: qualname = name or getattr(obj, \"__qualname__\", name)\n253: f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualfull{"status": "ok", "stdout": "103: qualname = name or getattr(obj, \"__qualname__\", name)\n253: f'{getattr(type_, \"__module__\", \"\")}.{getattr(type_, \"__qualname__\", \"\")}'\n", "stderr": "", "exit_code": 0} |
| 18 | edit_file | {
"filepath": "rich/_inspect.py",
"new_string": " qualname = name\n if not qualname:\n qualname = getattr(obj, \"__qualname__\", name)\n if not isinstance(qualname, str):\n qualname = getattr(obj, \"__name__\", name)\n if not isinstance(qualname, str):\n qualname = str(qualname)",
"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@@ -100,7 +100,full{"status": "ok", "filepath": "rich/_inspect.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/_inspect.py\n+++ b/rich/_inspect.py\n@@ -100,7 +100,13 @@\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+ qualname = name\n+ if not qualname:\n+ qualname = getattr(obj, \"__qualname__\", name)\n+ if not isinstance(qualname, str):\n+ qualname = getattr(obj, \"__name__\", name)\n+ if not isinstance(qualname, str):\n+ qualname = str(qualname)\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} |
| 19 | run_command | {
"command": "python reproduce_issue.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\nFirst inspect success\n\u256d\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 <functools._lru_cache_wrapper object at 0x7d3dc4239640> \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256e\n\u2502 def Klaas(): \u2502\n\u2502 |
| 20 | submit_patch | {} | {"status": "ok", "patch_size": 1408, "files_changed": 2}full{"status": "ok", "patch_size": 1408, "files_changed": 2} |
diff --git a/reproduce_issue.py b/reproduce_issue.py
new file mode 100644
index 00000000..a04ccdb0
--- /dev/null
+++ b/reproduce_issue.py
@@ -0,0 +1,18 @@
+from functools import lru_cache
+from rich import inspect
+
+@lru_cache
+class Klaas:
+ __slots__ = ("__qualname__",)
+
+if __name__ == "__main__":
+ from rich.traceback import install
+ install()
+ try:
+ inspect(Klaas.__qualname__)
+ print("First inspect success")
+ inspect(Klaas, all=True)
+ print("Second inspect success")
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
diff --git a/rich/_inspect.py b/rich/_inspect.py
index 27d65cec..7e7ccb0b 100644
--- a/rich/_inspect.py
+++ b/rich/_inspect.py
@@ -100,7 +100,13 @@ class Inspect(JupyterMixin):
callable_name.stylize(f"link file://{source_filename}")
signature_text = self.highlighter(_signature)
- qualname = name or getattr(obj, "__qualname__", name)
+ qualname = name
+ if not qualname:
+ qualname = getattr(obj, "__qualname__", name)
+ if not isinstance(qualname, str):
+ qualname = getattr(obj, "__name__", name)
+ if not isinstance(qualname, str):
+ qualname = str(qualname)
# If obj is a module, there may be classes (which are callable) to display
if inspect.isclass(obj):
...s....sss................................... [100%] 42 passed, 4 skipped in 0.21s