Skip to content

Commit

Permalink
Merge pull request #2806 from minrk/pretty-simplify
Browse files Browse the repository at this point in the history
match expected IPython formatter spec in rich.pretty
  • Loading branch information
willmcgugan committed Mar 4, 2023
2 parents 907a94c + 5e26525 commit 89cd92f
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 94 deletions.
90 changes: 27 additions & 63 deletions rich/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@
)


JUPYTER_CLASSES_TO_NOT_RENDER = {
# Matplotlib "Artists" manage their own rendering in a Jupyter notebook, and we should not try to render them too.
# "Typically, all [Matplotlib] visible elements in a figure are subclasses of Artist."
"matplotlib.artist.Artist",
}


def _is_attr_object(obj: Any) -> bool:
"""Check if an object was created with attrs module."""
return _has_attrs and _attr_module.has(type(obj))
Expand Down Expand Up @@ -122,69 +115,40 @@ def _ipy_display_hook(
max_string: Optional[int] = None,
max_depth: Optional[int] = None,
expand_all: bool = False,
) -> None:
) -> Union[str, None]:
# needed here to prevent circular import:
from ._inspect import is_object_one_of_types
from .console import ConsoleRenderable

# always skip rich generated jupyter renderables or None values
if _safe_isinstance(value, JupyterRenderable) or value is None:
return
return None

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

# When in a Jupyter notebook let's avoid the display of some specific classes,
# as they result in the rendering of useless and noisy lines such as `<Figure size 432x288 with 1 Axes>`.
# What does this do?
# --> if the class has "matplotlib.artist.Artist" in its hierarchy for example, we don't render it.
if is_object_one_of_types(value, JUPYTER_CLASSES_TO_NOT_RENDER):
return

# certain renderables should start on a new line
if _safe_isinstance(value, ConsoleRenderable):
console.line()

console.print(
value
if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
indent_guides=indent_guides,
max_length=max_length,
max_string=max_string,
max_depth=max_depth,
expand_all=expand_all,
margin=12,
),
crop=crop,
new_line_start=True,
)

with console.capture() as capture:
# certain renderables should start on a new line
if _safe_isinstance(value, ConsoleRenderable):
console.line()
console.print(
value
if _safe_isinstance(value, RichRenderable)
else Pretty(
value,
overflow=overflow,
indent_guides=indent_guides,
max_length=max_length,
max_string=max_string,
max_depth=max_depth,
expand_all=expand_all,
margin=12,
),
crop=crop,
new_line_start=True,
end="",
)
# strip trailing newline, not usually part of a text repr
# I'm not sure if this should be prevented at a lower level
return capture.get().rstrip("\n")


def _safe_isinstance(
Expand Down
45 changes: 14 additions & 31 deletions tests/test_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections import UserDict, defaultdict
from dataclasses import dataclass, field
from typing import Any, List, NamedTuple
from unittest.mock import patch

import attr
import pytest
Expand Down Expand Up @@ -78,16 +77,17 @@ def test_ipy_display_hook__multiple_special_reprs():
console = Console(file=io.StringIO(), force_jupyter=True)

class Thing:
def __repr__(self):
return "A 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() == ""
result = _ipy_display_hook(Thing(), console=console)
assert result == "A Thing"


def test_ipy_display_hook__no_special_repr_methods():
Expand All @@ -97,11 +97,9 @@ 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"
result = _ipy_display_hook(Thing(), console=console)
# should be repr as-is
assert result == "hello"


def test_ipy_display_hook__special_repr_raises_exception():
Expand All @@ -121,33 +119,18 @@ def _repr_latex_(self):
def _repr_html_(self):
return "hello"

console.begin_capture()
_ipy_display_hook(Thing(), console=console)
def __repr__(self):
return "therepr"

assert console.end_capture() == ""
result = _ipy_display_hook(Thing(), console=console)
assert result == "therepr"


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_ipy_display_hook__classes_to_not_render():
console = Console(file=io.StringIO(), force_jupyter=True)
console.begin_capture()

class Thing:
def __repr__(self) -> str:
return "hello"

class_fully_qualified_name = f"{__name__}.{Thing.__qualname__}"
with patch(
"rich.pretty.JUPYTER_CLASSES_TO_NOT_RENDER", {class_fully_qualified_name}
):
_ipy_display_hook(Thing(), console=console)
assert console.end_capture() == ""
result = _ipy_display_hook(Text("hello"), console=console)
assert result == "\nhello"


def test_pretty():
Expand Down

0 comments on commit 89cd92f

Please sign in to comment.