Merge branch 'master' into revert-1832-epsq-widths

This commit is contained in:
Darren Burns 2022-01-20 09:49:59 +00:00 committed by GitHub
commit 1eb4f5db0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 22 deletions

View File

@ -15,6 +15,8 @@ Edit this with a clear and concise description of what the bug.
Provide a minimal code example that demonstrates the issue if you can. If the issue is visual in nature, consider posting a screenshot.
**Platform**
<details>
<summary>Click to expand</summary>
What platform (Win/Linux/Mac) are you running on? What terminal software are you using?
@ -25,3 +27,5 @@ python -m rich.diagnose
python -m rich._windows
pip freeze | grep rich
```
</details>

View File

@ -5,7 +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).
## [11.0.0] - 2022-01-09
## [Unreleased]
### Added
- Workaround for edge case of object from Faiss with no `__class__` https://github.com/Textualize/rich/issues/1838
### Added

View File

@ -1,4 +1,4 @@
alabaster==0.7.12
Sphinx==4.3.2
Sphinx==4.4.0
sphinx-rtd-theme==1.0.0
sphinx-copybutton==0.4.0

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import
from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature
from typing import Any, Iterable, Optional, Tuple
from .console import RenderableType, Group
from .console import Group, RenderableType
from .highlighter import ReprHighlighter
from .jupyter import JupyterMixin
from .panel import Panel
@ -204,7 +204,8 @@ class Inspect(JupyterMixin):
add_row(key_text, Pretty(value, highlighter=highlighter))
if items_table.row_count:
yield items_table
else:
elif not_shown_count:
yield Text.from_markup(
f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] "
f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options."
)

View File

@ -2,7 +2,6 @@ import builtins
import dataclasses
import inspect
import os
import re
import sys
from array import array
from collections import Counter, UserDict, UserList, defaultdict, deque
@ -93,7 +92,7 @@ def _ipy_display_hook(
from .console import ConsoleRenderable # needed here to prevent circular import
# always skip rich generated jupyter renderables or None values
if isinstance(value, JupyterRenderable) or value is None:
if _safe_isinstance(value, JupyterRenderable) or value is None:
return
console = console or get_console()
@ -124,12 +123,12 @@ def _ipy_display_hook(
return # Delegate rendering to IPython
# certain renderables should start on a new line
if isinstance(value, ConsoleRenderable):
if _safe_isinstance(value, ConsoleRenderable):
console.line()
console.print(
value
if isinstance(value, RichRenderable)
if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
@ -144,6 +143,16 @@ def _ipy_display_hook(
)
def _safe_isinstance(
obj: object, class_or_tuple: Union[type, Tuple[type, ...]]
) -> bool:
"""isinstance can fail in rare cases, for example types with no __class__"""
try:
return isinstance(obj, class_or_tuple)
except Exception:
return False
def install(
console: Optional["Console"] = None,
overflow: "OverflowMethod" = "ignore",
@ -178,7 +187,7 @@ def install(
builtins._ = None # type: ignore
console.print(
value
if isinstance(value, RichRenderable)
if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
@ -355,7 +364,7 @@ _MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict)
def is_expandable(obj: Any) -> bool:
"""Check if an object may be expanded by pretty print."""
return (
isinstance(obj, _CONTAINERS)
_safe_isinstance(obj, _CONTAINERS)
or (is_dataclass(obj))
or (hasattr(obj, "__rich_repr__"))
or _is_attr_object(obj)
@ -539,7 +548,7 @@ def traverse(
"""Get repr string for an object, but catch errors."""
if (
max_string is not None
and isinstance(obj, (bytes, str))
and _safe_isinstance(obj, (bytes, str))
and len(obj) > max_string
):
truncated = len(obj) - max_string
@ -565,7 +574,7 @@ def traverse(
def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
for arg in rich_args:
if isinstance(arg, tuple):
if _safe_isinstance(arg, tuple):
if len(arg) == 3:
key, child, default = arg
if default == child:
@ -622,7 +631,7 @@ def traverse(
last=root,
)
for last, arg in loop_last(args):
if isinstance(arg, tuple):
if _safe_isinstance(arg, tuple):
key, child = arg
child_node = _traverse(child, depth=depth + 1)
child_node.last = last
@ -689,7 +698,7 @@ def traverse(
elif (
is_dataclass(obj)
and not isinstance(obj, type)
and not _safe_isinstance(obj, type)
and not fake_attributes
and (_is_dataclass_repr(obj) or py_version == (3, 6))
):
@ -722,9 +731,9 @@ def traverse(
pop_visited(obj_id)
elif isinstance(obj, _CONTAINERS):
elif _safe_isinstance(obj, _CONTAINERS):
for container_type in _CONTAINERS:
if isinstance(obj, container_type):
if _safe_isinstance(obj, container_type):
obj_type = container_type
break
@ -752,7 +761,7 @@ def traverse(
num_items = len(obj)
last_item_index = num_items - 1
if isinstance(obj, _MAPPING_CONTAINERS):
if _safe_isinstance(obj, _MAPPING_CONTAINERS):
iter_items = iter(obj.items())
if max_length is not None:
iter_items = islice(iter_items, max_length)
@ -777,7 +786,7 @@ def traverse(
pop_visited(obj_id)
else:
node = Node(value_repr=to_repr(obj), last=root)
node.is_tuple = isinstance(obj, tuple)
node.is_tuple = _safe_isinstance(obj, tuple)
return node
node = _traverse(_object, root=True)
@ -812,13 +821,13 @@ def pretty_repr(
str: A possibly multi-line representation of the object.
"""
if isinstance(_object, Node):
if _safe_isinstance(_object, Node):
node = _object
else:
node = traverse(
_object, max_length=max_length, max_string=max_string, max_depth=max_depth
)
repr_str = node.render(
repr_str: str = node.render(
max_width=max_width, indent_size=indent_size, expand_all=expand_all
)
return repr_str

View File

@ -6,7 +6,6 @@ import pytest
from rich import inspect
from rich.console import Console
skip_py36 = pytest.mark.skipif(
sys.version_info.minor == 6 and sys.version_info.major == 3,
reason="rendered differently on py3.6",
@ -260,3 +259,18 @@ def test_broken_call_attr():
result = render(foo, methods=True, width=100)
print(repr(result))
assert expected == result
def test_inspect_swig_edge_case():
"""Issue #1838 - Edge case with Faiss library - object with empty dir()"""
class Thing:
@property
def __class__(self):
raise AttributeError
thing = Thing()
try:
inspect(thing)
except Exception as e:
assert False, f"Object with no __class__ shouldn't raise {e}"