diff --git a/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py b/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py index 0571245..ac2ed1e 100644 --- a/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py +++ b/{{cookiecutter.repo_name}}/notebooks/my_nb_path.py @@ -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 diff --git a/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb b/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb index 4ec4031..d825dc2 100644 --- a/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb +++ b/{{cookiecutter.repo_name}}/notebooks/skeleton.ipynb @@ -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" ] }, { @@ -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", diff --git a/{{cookiecutter.repo_name}}/src/my_nb_color.py b/{{cookiecutter.repo_name}}/src/my_nb_color.py index ee3dc4e..86e3ddf 100644 --- a/{{cookiecutter.repo_name}}/src/my_nb_color.py +++ b/{{cookiecutter.repo_name}}/src/my_nb_color.py @@ -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} @@ -14,12 +14,23 @@ >>> 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. @@ -27,18 +38,90 @@ 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=, ...)``.""" + # Do not inspect wrappers, because *args & **kwargs are not useful for callers. + # + # Implementation notes: make sure the pattern is: + # + # if is : + # = + 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 ``
`` 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: