From f7c078313d805598e428784cca9e8e6d6a305bd0 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 13 Jan 2021 16:51:38 +0000 Subject: [PATCH] fix TypeError in inspect --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- rich/_inspect.py | 37 ++++++++++++++++++++++--------------- tests/test_inspect.py | 24 ++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bebeff..24c1a41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [9.8.1] 0 2021-01-13 + +### Fixed + +- Fixed rich.inspect failing with attributes that claim to be callable but aren't https://github.com/willmcgugan/rich/issues/916 + ## [9.8.0] - 2021-01-11 ### Added diff --git a/pyproject.toml b/pyproject.toml index 6789c60a..5dd45252 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "rich" homepage = "https://github.com/willmcgugan/rich" documentation = "https://rich.readthedocs.io/en/latest/" -version = "9.8.0" +version = "9.8.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" authors = ["Will McGugan "] license = "MIT" diff --git a/rich/_inspect.py b/rich/_inspect.py index 4643942a..c1c9eecf 100644 --- a/rich/_inspect.py +++ b/rich/_inspect.py @@ -85,12 +85,14 @@ class Inspect(JupyterMixin): padding=(0, 1), ) - def _get_signature(self, name: str, obj: Any) -> Text: + def _get_signature(self, name: str, obj: Any) -> Optional[Text]: """Get a signature for a callable.""" try: _signature = str(signature(obj)) + ":" except ValueError: _signature = "(...)" + except TypeError: + return None source_filename: Optional[str] = None try: @@ -142,8 +144,10 @@ class Inspect(JupyterMixin): highlighter = self.highlighter if callable(obj): - yield self._get_signature("", obj) - yield "" + signature = self._get_signature("", obj) + if signature is not None: + yield signature + yield "" _doc = getdoc(obj) if _doc is not None: @@ -178,20 +182,23 @@ class Inspect(JupyterMixin): if callable(value): if not self.methods: continue + _signature_text = self._get_signature(key, value) + if _signature_text is None: + add_row(key_text, Pretty(value, highlighter=highlighter)) + else: + if self.docs: + docs = getdoc(value) + if docs is not None: + _doc = _reformat_doc(str(docs)) + if not self.help: + _doc = _first_paragraph(_doc) + _signature_text.append("\n" if "\n" in _doc else " ") + doc = highlighter(_doc) + doc.stylize("inspect.doc") + _signature_text.append(doc) - if self.docs: - docs = getdoc(value) - if docs is not None: - _doc = _reformat_doc(str(docs)) - if not self.help: - _doc = _first_paragraph(_doc) - _signature_text.append("\n" if "\n" in _doc else " ") - doc = highlighter(_doc) - doc.stylize("inspect.doc") - _signature_text.append(doc) - - add_row(key_text, _signature_text) + add_row(key_text, _signature_text) else: add_row(key_text, Pretty(value, highlighter=highlighter)) if items_table.row_count: diff --git a/tests/test_inspect.py b/tests/test_inspect.py index 5bdcf72a..0ffc313a 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -19,8 +19,8 @@ skip_py37 = pytest.mark.skipif( ) -def render(obj, methods=False, value=False) -> str: - console = Console(file=io.StringIO(), width=50, legacy_windows=False) +def render(obj, methods=False, value=False, width=50) -> str: + console = Console(file=io.StringIO(), width=width, legacy_windows=False) inspect(obj, console=console, methods=methods, value=value) return console.file.getvalue() @@ -183,3 +183,23 @@ def test_inspect_integer_with_methods(): "╰────────────────────────────────────────────────╯\n" ) assert expected == render(1, methods=True) + + +@skip_py36 +@skip_py37 +def test_broken_call_attr(): + class NotCallable: + __call__ = 5 # Passes callable() but isn't really callable + + def __repr__(self): + return "NotCallable()" + + class Foo: + foo = NotCallable() + + foo = Foo() + assert callable(foo.foo) + expected = "╭─ .Foo'> ─╮\n│ foo = NotCallable() │\n╰───────────────────────────────────────────────────────────────────╯\n" + result = render(foo, methods=True, width=100) + print(repr(result)) + assert expected == result