improved jupyter support

This commit is contained in:
Will McGugan 2020-05-30 11:38:47 +01:00
parent 856ee79658
commit e50a6be3c6
8 changed files with 71 additions and 57 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.ipynb
.pytype
.DS_Store
.vscode

View File

@ -5,11 +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).
## [1.2.4] Unreleased
## [1.3.0] Unreleased
### Changed
- Updated `markdown.Heading.create()` to work with subclassing.
- Console now transparently works with Jupyter
## [1.2.3] - 2020-05-24

View File

@ -7,18 +7,27 @@ if TYPE_CHECKING:
_console: Optional["Console"] = None
def get_console() -> "Console":
"""Get a global Console instance.
Returns:
Console: A console instance.
"""
global _console
if _console is None:
from .console import Console
_console = Console()
return _console
def print(
*objects: Any, sep=" ", end="\n", file: IO[str] = None, flush: bool = False,
):
from .console import Console
global _console
if _console is None:
from ._global import console
_console = console
write_console = _console if file is None else Console(file=file)
write_console = get_console() if file is None else Console(file=file)
return write_console.print(*objects, sep=sep, end=end)

View File

@ -1,6 +0,0 @@
"""A global instance of a Console."""
from .console import Console
from . import jupyter
console = jupyter.get_console() if jupyter.is_jupyter() else Console()

View File

@ -44,7 +44,6 @@ from .highlighter import NullHighlighter, ReprHighlighter
from .pretty import Pretty
from .style import Style
from .tabulate import tabulate_mapping
from . import jupyter
from . import highlighter
from . import themes
from .pretty import Pretty
@ -193,6 +192,25 @@ class ConsoleDimensions(NamedTuple):
height: int
def _is_jupyter() -> bool:
"""Check if we're running in a Jupyter notebook."""
try:
from IPython.display import display
except ImportError:
return False
try:
get_ipython # type: ignore
except NameError:
return False
shell = get_ipython().__class__.__name__ # type: ignore
if shell == "ZMQInteractiveShell":
return True # Jupyter notebook or qtconsole
elif shell == "TerminalInteractiveShell":
return False # Terminal running IPython
else:
return False # Other type (?)
COLOR_SYSTEMS = {
"standard": ColorSystem.STANDARD,
"256": ColorSystem.EIGHT_BIT,
@ -229,6 +247,8 @@ class Console:
Args:
color_system (str, optional): The color system supported by your terminal,
either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect.
force_terminal (bool, optional): Force the Console to write control codes even when a terminal is not detected. Defaults to False.
force_jupyter (bool, optional): Force the Console to write to Jupyter even when Jupyter is not detected. Defaults to False
theme (Theme, optional): An optional style theme object, or ``None`` for default theme.
file (IO, optional): A file object where the console should write to. Defaults to stdoutput.
width (int, optional): The width of the terminal. Leave as default to auto-detect width.
@ -246,10 +266,12 @@ class Console:
def __init__(
self,
*,
color_system: Optional[
Literal["auto", "standard", "256", "truecolor", "windows"]
] = "auto",
force_terminal: bool = False,
force_jupyter: bool = False,
theme: Theme = None,
file: IO[str] = None,
width: int = None,
@ -264,6 +286,10 @@ class Console:
log_time_format: str = "[%X]",
highlighter: Optional["HighlighterType"] = ReprHighlighter(),
):
self.is_jupyter = force_jupyter or _is_jupyter()
if self.is_jupyter:
width = width or 93
height = height or 100
self._styles = themes.DEFAULT.styles if theme is None else theme.styles
self._width = width
self._height = height
@ -398,9 +424,6 @@ class Console:
Returns:
ConsoleDimensions: A named tuple containing the dimensions.
"""
if jupyter.is_jupyter():
return ConsoleDimensions(93, 100)
if self._width is not None and self._height is not None:
return ConsoleDimensions(self._width, self._height)
@ -833,8 +856,10 @@ class Console:
"""Check if the buffer may be rendered."""
with self._lock:
if self._buffer_index == 0:
if jupyter.is_jupyter():
jupyter.display(self._buffer)
if self.is_jupyter:
from .jupyter import display
display(self._buffer)
del self._buffer[:]
else:
text = self._render_buffer()

View File

@ -2,45 +2,19 @@ import io
from typing import Any, Iterable, IO, List, Optional, TYPE_CHECKING, Union
# from .console import Console as BaseConsole
from .__init__ import get_console
from .segment import Segment
from .style import Style
from .terminal_theme import DEFAULT_TERMINAL_THEME
if TYPE_CHECKING:
from .console import Console
from .console import Console, RenderableType
JUPYTER_HTML_FORMAT = """\
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace">{code}</pre>
"""
_console: Optional["Console"] = None
def get_console() -> "Console":
from .console import Console
global _console
if _console is None:
_console = Console(width=100)
return _console
def is_jupyter() -> bool:
"""Check if we're running in a Jupyter notebook."""
try:
get_ipython # type: ignore
except NameError:
return False
shell = get_ipython().__class__.__name__ # type: ignore
if shell == "ZMQInteractiveShell":
return True # Jupyter notebook or qtconsole
elif shell == "TerminalInteractiveShell":
return False # Terminal running IPython
else:
return False # Other type (?)
class JupyterRenderable:
"""A shim to write html to Jupyter notebook."""
@ -48,7 +22,7 @@ class JupyterRenderable:
self.html = html
@classmethod
def render(self, rich_renderable) -> str:
def render(self, rich_renderable: "RenderableType") -> str:
console = get_console()
segments = console.render(rich_renderable, console.options)
html = _render_segments(segments)
@ -100,3 +74,9 @@ def display(segments: Iterable[Segment]) -> None:
html = _render_segments(segments)
jupyter_renderable = JupyterRenderable(html)
ipython_display(jupyter_renderable)
def print(*args, **kwargs) -> None:
"""Proxy for Console print."""
console = get_console()
return console.print(*args, **kwargs)

View File

@ -25,6 +25,7 @@ from typing import (
Union,
)
from . import get_console
from .bar import Bar
from .console import Console, JustifyValues, RenderGroup, RenderableType
from .highlighter import Highlighter
@ -387,7 +388,7 @@ class Progress:
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
TimeRemainingColumn(),
)
self.console = console or Console(file=sys.stdout)
self.console = console or get_console()
self.auto_refresh = auto_refresh
self.refresh_per_second = refresh_per_second
self.speed_estimate_period = speed_estimate_period

View File

@ -5,15 +5,18 @@ import rich
from rich.console import Console
def test_rich_print():
output = io.StringIO()
def test_get_console():
console = rich.get_console()
assert isinstance(console, Console)
assert rich._console is None
backup_file = sys.stdout
def test_rich_print():
console = rich.get_console()
output = io.StringIO()
backup_file = console.file
try:
sys.stdout = output
console.file = output
rich.print("foo")
assert isinstance(rich._console, Console)
assert output.getvalue() == "foo\n"
finally:
sys.stdout = backup_file
console.file = backup_file