mirror of https://github.com/Textualize/rich.git
Extract ipy display hook and add tests around it
This commit is contained in:
parent
23bc0a2d7c
commit
a3fcdbe210
134
rich/pretty.py
134
rich/pretty.py
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import inspect
|
||||
import os
|
||||
|
@ -52,9 +54,6 @@ if TYPE_CHECKING:
|
|||
RenderResult,
|
||||
)
|
||||
|
||||
# Matches Jupyter's special methods
|
||||
_re_jupyter_repr = re.compile(f"^_repr_.+_$")
|
||||
|
||||
|
||||
def _is_attr_object(obj: Any) -> bool:
|
||||
"""Check if an object was created with attrs module."""
|
||||
|
@ -83,6 +82,70 @@ def _is_dataclass_repr(obj: object) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _ipy_display_hook(
|
||||
value: Any,
|
||||
console: Console | None = None,
|
||||
overflow: "OverflowMethod" = "ignore",
|
||||
crop: bool = False,
|
||||
indent_guides: bool = False,
|
||||
max_length: Optional[int] = None,
|
||||
max_string: Optional[int] = None,
|
||||
expand_all: bool = False,
|
||||
) -> None:
|
||||
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:
|
||||
return
|
||||
|
||||
console = console or get_console()
|
||||
if console.is_jupyter:
|
||||
# Delegate rendering to IPython if the object (and IPython) supports it
|
||||
# https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
|
||||
ipython_repr_methods = [
|
||||
"_repr_html_",
|
||||
"_repr_markdown_",
|
||||
"_repr_json_",
|
||||
"_repr_latex_",
|
||||
"_repr_jpeg_",
|
||||
"_repr_png_",
|
||||
"_repr_svg_",
|
||||
"_repr_mimebundle_",
|
||||
]
|
||||
for repr_method in ipython_repr_methods:
|
||||
method = getattr(value, repr_method, None)
|
||||
if inspect.ismethod(method):
|
||||
# Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
|
||||
# specifies that if they return None, then they should not be rendered
|
||||
# by the notebook.
|
||||
try:
|
||||
repr_result = method()
|
||||
except Exception:
|
||||
continue # If the method raises, treat it as if it doesn't exist, try any others
|
||||
if repr_result is not None:
|
||||
return # Delegate rendering to IPython
|
||||
|
||||
# certain renderables should start on a new line
|
||||
if isinstance(value, ConsoleRenderable):
|
||||
console.line()
|
||||
|
||||
console.print(
|
||||
value
|
||||
if isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
margin=12,
|
||||
),
|
||||
crop=crop,
|
||||
new_line_start=True,
|
||||
)
|
||||
|
||||
|
||||
def install(
|
||||
console: Optional["Console"] = None,
|
||||
overflow: "OverflowMethod" = "ignore",
|
||||
|
@ -107,8 +170,6 @@ def install(
|
|||
"""
|
||||
from rich import get_console
|
||||
|
||||
from .console import ConsoleRenderable # needed here to prevent circular import
|
||||
|
||||
console = console or get_console()
|
||||
assert console is not None
|
||||
|
||||
|
@ -132,59 +193,6 @@ def install(
|
|||
)
|
||||
builtins._ = value # type: ignore
|
||||
|
||||
def ipy_display_hook(value: Any) -> None: # pragma: no cover
|
||||
assert console is not None
|
||||
# always skip rich generated jupyter renderables or None values
|
||||
if isinstance(value, JupyterRenderable) or value is None:
|
||||
return
|
||||
# on jupyter rich display, if using one of the special representations don't use rich
|
||||
|
||||
if console.is_jupyter:
|
||||
# Delegate rendering to IPython if the object (and IPython) supports it
|
||||
# https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display
|
||||
ipython_repr_methods = [
|
||||
"_repr_html_",
|
||||
"_repr_markdown_",
|
||||
"_repr_json_",
|
||||
"_repr_latex_",
|
||||
"_repr_jpeg_",
|
||||
"_repr_png_",
|
||||
"_repr_svg_",
|
||||
"_repr_mimebundle_",
|
||||
]
|
||||
for repr_method in ipython_repr_methods:
|
||||
method = getattr(value, repr_method, None)
|
||||
if inspect.ismethod(method):
|
||||
# Calling the method ourselves isn't ideal. The interface for the `_repr_*_` methods
|
||||
# specifies that if they return None, then they should not be rendered
|
||||
# by the notebook.
|
||||
try:
|
||||
repr_result = method()
|
||||
except Exception:
|
||||
continue # If the method raises, treat it as if it doesn't exist, try any others
|
||||
if repr_result is not None:
|
||||
return # Delegate rendering to IPython
|
||||
|
||||
# certain renderables should start on a new line
|
||||
if isinstance(value, ConsoleRenderable):
|
||||
console.line()
|
||||
|
||||
console.print(
|
||||
value
|
||||
if isinstance(value, RichRenderable)
|
||||
else Pretty(
|
||||
value,
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
margin=12,
|
||||
),
|
||||
crop=crop,
|
||||
new_line_start=True,
|
||||
)
|
||||
|
||||
try: # pragma: no cover
|
||||
ip = get_ipython() # type: ignore
|
||||
from IPython.core.formatters import BaseFormatter
|
||||
|
@ -194,7 +202,15 @@ def install(
|
|||
|
||||
def __call__(self, value: Any) -> Any:
|
||||
if self.pprint:
|
||||
return ipy_display_hook(value)
|
||||
return _ipy_display_hook(
|
||||
value,
|
||||
console=get_console(),
|
||||
overflow=overflow,
|
||||
indent_guides=indent_guides,
|
||||
max_length=max_length,
|
||||
max_string=max_string,
|
||||
expand_all=expand_all,
|
||||
)
|
||||
else:
|
||||
return repr(value)
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from array import array
|
||||
from collections import defaultdict, UserDict, UserList
|
||||
from dataclasses import dataclass, field
|
||||
import io
|
||||
import sys
|
||||
from array import array
|
||||
from collections import defaultdict, UserDict
|
||||
from typing import List
|
||||
|
||||
import attr
|
||||
import pytest
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from rich.console import Console
|
||||
from rich.pretty import install, Pretty, pprint, pretty_repr, Node
|
||||
|
||||
from rich.pretty import install, Pretty, pprint, pretty_repr, Node, _ipy_display_hook
|
||||
from rich.text import Text
|
||||
|
||||
skip_py36 = pytest.mark.skipif(
|
||||
sys.version_info.minor == 6 and sys.version_info.major == 3,
|
||||
|
@ -44,6 +44,85 @@ def test_install():
|
|||
assert sys.displayhook is not dh
|
||||
|
||||
|
||||
def test_ipy_display_hook__repr_html():
|
||||
console = Console(file=io.StringIO(), force_jupyter=True)
|
||||
|
||||
class Thing:
|
||||
def _repr_html_(self):
|
||||
return "hello"
|
||||
|
||||
console.begin_capture()
|
||||
_ipy_display_hook(Thing(), console=console)
|
||||
|
||||
# Rendering delegated to notebook because _repr_html_ method exists
|
||||
assert console.end_capture() == ""
|
||||
|
||||
|
||||
def test_ipy_display_hook__multiple_special_reprs():
|
||||
"""
|
||||
The case where there are multiple IPython special _repr_*_
|
||||
methods on the object, and one of them returns None but another
|
||||
one does not.
|
||||
"""
|
||||
console = Console(file=io.StringIO(), force_jupyter=True)
|
||||
|
||||
class Thing:
|
||||
def _repr_latex_(self):
|
||||
return None
|
||||
|
||||
def _repr_html_(self):
|
||||
return "hello"
|
||||
|
||||
console.begin_capture()
|
||||
_ipy_display_hook(Thing(), console=console)
|
||||
|
||||
assert console.end_capture() == ""
|
||||
|
||||
|
||||
def test_ipy_display_hook__no_special_repr_methods():
|
||||
console = Console(file=io.StringIO(), force_jupyter=True)
|
||||
|
||||
class Thing:
|
||||
def __repr__(self) -> str:
|
||||
return "hello"
|
||||
|
||||
console.begin_capture()
|
||||
_ipy_display_hook(Thing(), console=console)
|
||||
|
||||
# No IPython special repr methods, so printed by Rich
|
||||
assert console.end_capture() == "hello\n"
|
||||
|
||||
|
||||
def test_ipy_display_hook__special_repr_raises_exception():
|
||||
"""
|
||||
When an IPython special repr method raises an exception,
|
||||
we treat it as if it doesn't exist and look for the next.
|
||||
"""
|
||||
console = Console(file=io.StringIO(), force_jupyter=True)
|
||||
|
||||
class Thing:
|
||||
def _repr_markdown_(self):
|
||||
raise Exception()
|
||||
|
||||
def _repr_latex_(self):
|
||||
return None
|
||||
|
||||
def _repr_html_(self):
|
||||
return "hello"
|
||||
|
||||
console.begin_capture()
|
||||
_ipy_display_hook(Thing(), console=console)
|
||||
|
||||
assert console.end_capture() == ""
|
||||
|
||||
|
||||
def test_ipy_display_hook__console_renderables_on_newline():
|
||||
console = Console(file=io.StringIO(), force_jupyter=True)
|
||||
console.begin_capture()
|
||||
_ipy_display_hook(Text("hello"), console=console)
|
||||
assert console.end_capture() == "\nhello\n"
|
||||
|
||||
|
||||
def test_pretty():
|
||||
test = {
|
||||
"foo": [1, 2, 3, (4, 5, {6}, 7, 8, {9}), {}],
|
||||
|
@ -55,7 +134,6 @@ def test_pretty():
|
|||
|
||||
result = pretty_repr(test, max_width=80)
|
||||
print(result)
|
||||
# print(repr(result))
|
||||
expected = "{\n 'foo': [1, 2, 3, (4, 5, {6}, 7, 8, {9}), {}],\n 'bar': {\n 'egg': 'baz',\n 'words': [\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World',\n 'Hello World'\n ]\n },\n False: 'foo',\n True: '',\n 'text': ('Hello World', 'foo bar baz egg')\n}"
|
||||
print(expected)
|
||||
assert result == expected
|
||||
|
|
Loading…
Reference in New Issue