Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions {{cookiecutter.repo_name}}/notebooks/my_nb_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
from pathlib import Path
from typing import Union


def sys_path_append(o: Union[str, os.PathLike]) -> None:
posix_path: str = o.as_posix() if isinstance(o, Path) else Path(o).as_posix()
if posix_path not in sys.path:
sys.path.insert(0, posix_path)


# Add GIT_ROOT/ and a few other subdirs
_p = subprocess.run(
["git", "rev-parse", "--show-toplevel"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
Expand Down
4 changes: 2 additions & 2 deletions {{cookiecutter.repo_name}}/notebooks/skeleton.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"\n",
"# Make sure my_nb_path is imported first (and when isort is used, it needs to be told).\n",
"import my_nb_path # isort: skip\n",
"from my_nb_color import print, rprint"
"from my_nb_color import print, pprint, inspect"
]
},
{
Expand Down Expand Up @@ -205,7 +205,7 @@
"source": [
"d = {\"A\" * 200, \"B\" * 200}\n",
"print(\"Colored:\", d)\n",
"rprint(\"Colored and wrapped:\", d)\n",
"pprint(\"Colored and wrapped:\", d)\n",
"display(d)\n",
"\n",
"for f in (logger.debug, logger.info, logger.success, logger.error):\n",
Expand Down
99 changes: 91 additions & 8 deletions {{cookiecutter.repo_name}}/src/my_nb_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Basic usage by an ``.ipynb``:

>>> # Colorize notebook outputs
>>> from my_nb_color import print, pprint, oprint
>>> from my_nb_color import print, pprint, oprint, inspect
>>>
>>> # Test-drive different behavior of print functionalities
>>> d = {"A" * 200, "B" * 200}
Expand All @@ -14,31 +14,114 @@
>>> oprint("Plain (i.e., Python's original):", d)
>>> display(d)
>>>
>>> import pandas as pd
>>> df = pd.DataFrame(dict(a=[1,2,3], b=[4,5,6]))
>>> display(d, df)
>>> df.plot();
>>>
>>> inspect(df)
>>> inspect(print)
>>> inspect(pprint)
>>> inspect(inspect)
>>>
>>> # Test-drive loguru
>>> from loguru import logger
>>> for f in (logger.debug, logger.info, logger.success, logger.error):
>>> f("Hello World!")
"""
import sys
import warnings
from typing import Callable, cast

# Try to setup rich.
try:
import rich
except ModuleNotFoundError:
print = pprint = oprint = print

def inspect(*args, **kwargs):
warnings.warn(f"{__name__}.inspect() requires rich.")

else:
oprint = print # In-case plain old behavior is needed

rich.reconfigure(force_terminal=True, force_jupyter=False)
rich.pretty.install()
print = cast(Callable, rich.get_console().out)
_pprint = rich.get_console().print
_console = rich.get_console()

print = cast(Callable, _console.out)

def pprint(*args, soft_wrap=True, **kwargs):
"""Call ``rich.console.Console(...).print(..., soft_wrap=True, ...)``."""
_console.print(*args, soft_wrap=soft_wrap, **kwargs)

class Inspect:
def __init__(self, console=_console):
self.console = _console

def __call__(self, obj, *args, **kwargs):
"""Call ``rich.inspect(..., console=<preset_console>, ...)``."""
# Do not inspect wrappers, because *args & **kwargs are not useful for callers.
#
# Implementation notes: make sure the pattern is:
#
# if <RHS> is <LHS>:
# <LHS> = <RHS>
if self is obj:
obj = rich.inspect
elif pprint is obj:
obj = self.console.print

rich.inspect(obj, *args, console=self.console, **kwargs)

inspect = Inspect()

def opinionated_rich_pretty_install():
"""Intercept any post-ipython renderings.

Known cases fixed (as of rich-11.2.0): (i) prevent pandas dataframe rendered twice (as text
and as html), (ii) do not show ``<Figure ...>`` on matplotlib figures.
"""
from IPython.core.formatters import BaseFormatter

class RichFormatterWrapper(BaseFormatter):
# See: rich.pretty.install._ipy_display_hook()
reprs = [
"_repr_html_",
"_repr_markdown_",
"_repr_json_",
"_repr_latex_",
"_repr_jpeg_",
"_repr_png_",
"_repr_svg_",
"_repr_mimebundle_",
]

def __init__(self, rich_formatter):
self.rich_formatter = rich_formatter

def __call__(self, value, *args, **kwargs):
for repr_name in self.reprs:
try:
repr_method = getattr(value, repr_name)
repr_method()
except (
AttributeError, # value object has does not have the repr attribute
Exception, # any other error
) as e:
continue
else:
return
else:
# None of the ipython methods work, hence let rich takes over
self.rich_formatter(value, *args, **kwargs)

def pprint(*args, **kwargs):
kwargs["soft_wrap"] = True
_pprint(*args, **kwargs)
rich.pretty.install()
ipy_formatters = get_ipython().display_formatter.formatters
rich_formatter = ipy_formatters["text/plain"]
if rich_formatter.__module__ == "rich.pretty":
ipy_formatters["text/plain"] = RichFormatterWrapper(rich_formatter)

inspect = rich.inspect
opinionated_rich_pretty_install()

# Try to setup loguru.
try:
Expand Down