Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rich.pretty support #11

Merged
merged 1 commit into from
Jan 23, 2024
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

## [Unreleased][unreleased]

N/A
### Added

- Support for `rich.pretty` to `autorepr` and `ReprHelperMixin`.
- `RichReprHelper`.

## [2.0.0] - 2023-12-30

Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
"ipython": ("https://ipython.readthedocs.io/en/stable/", None),
"rich": ("https://rich.readthedocs.io/en/stable/", None),
}

autodoc_member_order = "bysource"
18 changes: 17 additions & 1 deletion doc/usage/automatic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Pretty Printer
--------------

:func:`~represent.core.autorepr` also provides a
:code:`_repr_pretty_` method for :mod:`IPython.lib.pretty`.
:code:`_repr_pretty_` method for :mod:`IPython.lib.pretty` and a
:code:`__rich_repr__` method for :mod:`rich.pretty`.

Therefore, with the simple example above, we can pretty print:

Expand All @@ -53,6 +54,21 @@ Therefore, with the simple example above, we can pretty print:
width=15,
height=4.5)

.. code:: python

from rich.pretty import pprint

pprint(rect)

.. code-block:: none

Rectangle(
name='Something really long to force pretty printing line break',
color='red',
width=15,
height=4.5
)

Positional Arguments
--------------------

Expand Down
12 changes: 7 additions & 5 deletions doc/usage/helper.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ Helper Mixin
If you cannot use, or prefer not to use :class:`~represent.core.ReprMixin`,
there is an alternative declarative syntax.

:class:`~represent.core.ReprHelperMixin` provides ``__repr__`` and
``_repr_pretty_`` (for :mod:`IPython.lib.pretty`), both of which look for a
user defined function called ``_repr_helper_``.
:class:`~represent.core.ReprHelperMixin` provides ``__repr__``,
``_repr_pretty_`` (for :mod:`IPython.lib.pretty`), and ``__rich_repr__`` (for
:mod:`rich.pretty`), all of which use a user defined function called
``_repr_helper_``.

All possible method calls on the passed object `r` are shown here:

Expand Down Expand Up @@ -80,5 +81,6 @@ Manual Helpers

To use the declarative style without using
:class:`~represent.core.ReprHelperMixin`, refer to the documentation for
:class:`~represent.helper.ReprHelper` and
:class:`~represent.helper.PrettyReprHelper`.
:class:`~represent.helper.ReprHelper`,
:class:`~represent.helper.PrettyReprHelper`, and
:class:`~represent.helper.RichReprHelper`.
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Documentation = "https://represent.readthedocs.io"
test = [
"ipython",
"pytest",
"rich",
]
docstest = [
"parver",
Expand All @@ -54,3 +55,6 @@ source = ["represent", ".tox/*/lib/python*/site-packages/represent"]
[tool.coverage.report]
precision = 1
exclude_lines = ["pragma: no cover", "pass"]

[tool.isort]
profile = "black"
59 changes: 51 additions & 8 deletions src/represent/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,31 @@
from functools import partial
from reprlib import recursive_repr

from .helper import PrettyReprHelper, ReprHelper
from .helper import PrettyReprHelper, ReprHelper, RichReprHelper
from .utilities import ReprInfo

__all__ = ["ReprHelperMixin", "autorepr"]


_DEFAULT_INCLUDE_PRETTY = True
_DEFAULT_INCLUDE_RICH = True


def autorepr(*args, **kwargs):
"""Class decorator to construct :code:`__repr__` **automatically**
based on the arguments to ``__init__``.

:code:`_repr_pretty_` for :py:mod:`IPython.lib.pretty` is also constructed,
:code:`_repr_pretty_` for :py:mod:`IPython.lib.pretty` is constructed
unless `include_pretty=False`.

:code:`__rich_repr__` for :py:mod:`rich.pretty` is constructed
unless `include_rich=False`.

:param positional: Mark arguments as positional by number, or a list of
argument names.
:param include_pretty: Add a ``_repr_pretty_`` to the class (defaults to
True).
:param include_rich: Add a ``__rich_repr__`` to the class (defaults to True).

Example:

Expand Down Expand Up @@ -51,6 +56,7 @@ def autorepr(*args, **kwargs):
"""
cls = positional = None
include_pretty = _DEFAULT_INCLUDE_PRETTY
include_rich = _DEFAULT_INCLUDE_RICH

# We allow using @autorepr or @autorepr(positional=..., ...), so check
# how we were called.
Expand All @@ -68,7 +74,7 @@ def autorepr(*args, **kwargs):
)

elif not args and kwargs:
valid_kwargs = {"positional", "include_pretty"}
valid_kwargs = {"positional", "include_pretty", "include_rich"}
invalid_kwargs = set(kwargs) - valid_kwargs

if invalid_kwargs:
Expand All @@ -77,6 +83,7 @@ def autorepr(*args, **kwargs):

positional = kwargs.get("positional")
include_pretty = kwargs.get("include_pretty", include_pretty)
include_rich = kwargs.get("include_rich", include_rich)

elif (args and kwargs) or (not args and not kwargs):
raise TypeError("Use bare @autorepr or @autorepr(...) with keyword args.")
Expand All @@ -87,19 +94,25 @@ def autorepr(*args, **kwargs):
def __repr__(self):
return self.__class__._represent.fstr.format(self=self)

_repr_pretty_ = None
repr_pretty = rich_repr = None
if include_pretty:
_repr_pretty_ = _make_repr_pretty()
repr_pretty = _make_repr_pretty()
if include_rich:
rich_repr = _make_rich_repr()

if cls is not None:
return _autorepr_decorate(cls, repr=__repr__, repr_pretty=_repr_pretty_)
return _autorepr_decorate(
cls, repr=__repr__, repr_pretty=repr_pretty, rich_repr=rich_repr
)
else:
return partial(
_autorepr_decorate,
repr=__repr__,
repr_pretty=_repr_pretty_,
repr_pretty=repr_pretty,
rich_repr=rich_repr,
positional=positional,
include_pretty=include_pretty,
include_rich=include_rich,
)


Expand Down Expand Up @@ -132,6 +145,23 @@ def _repr_pretty_(self, p, cycle):
return _repr_pretty_


def _make_rich_repr():
def __rich_repr__(self):
"""Pretty printer for :mod:`rich.pretty`"""
cls = self.__class__

positional_args = cls._represent.args
keyword_args = cls._represent.kw

for positional in positional_args:
yield getattr(self, positional)

for keyword in keyword_args:
yield keyword, getattr(self, keyword)

return __rich_repr__


def _getparams(cls):
signature = inspect.signature(cls)
params = list(signature.parameters)
Expand All @@ -145,7 +175,13 @@ def _getparams(cls):


def _autorepr_decorate(
cls, repr, repr_pretty, positional=None, include_pretty=_DEFAULT_INCLUDE_PRETTY
cls,
repr,
repr_pretty,
rich_repr,
positional=None,
include_pretty=_DEFAULT_INCLUDE_PRETTY,
include_rich=_DEFAULT_INCLUDE_RICH,
):
params, kwonly = _getparams(cls)

Expand Down Expand Up @@ -194,6 +230,8 @@ def _autorepr_decorate(
cls.__repr__ = repr
if include_pretty:
cls._repr_pretty_ = repr_pretty
if include_rich:
cls.__rich_repr__ = rich_repr

return cls

Expand Down Expand Up @@ -228,3 +266,8 @@ def __repr__(self):
def _repr_pretty_(self, p, cycle):
with PrettyReprHelper(self, p, cycle) as r:
self._repr_helper_(r)

def __rich_repr__(self):
r = RichReprHelper(self)
self._repr_helper_(r)
yield from r
55 changes: 54 additions & 1 deletion src/represent/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from .utilities import Parantheses, inherit_docstrings

__all__ = ["ReprHelper", "PrettyReprHelper"]
__all__ = ["ReprHelper", "PrettyReprHelper", "RichReprHelper"]


class BaseReprHelper(metaclass=ABCMeta):
Expand Down Expand Up @@ -244,3 +244,56 @@ def close(self):
self.p.text("...")
clsname = self.other_cls.__name__
self.p.end_group(len(clsname) + 1, self.parantheses.right)


class RawReprWrapper:
"""rich.pretty calls repr for us, so to support raw=True we need a wrapper
object which returns str() when repr() is called.
"""

def __init__(self, o: object):
self._object = o

def __repr__(self):
return str(self._object)


class RichReprHelper(BaseReprHelper):
"""Help manual construction of :code:`__rich_repr__` for
:py:mod:`rich.pretty`.

It should be used as follows:

.. code-block:: python

def __rich_repr__(self)
r = RichReprHelper(self)
r.keyword_from_attr('name')
yield from r
"""

def __init__(self, other):
self._tuples = []
super().__init__(other)

def positional_from_attr(self, attr_name):
if self.keyword_started:
raise ValueError("positional arguments cannot follow keyword arguments")
self._tuples.append((None, getattr(self.other, attr_name)))

def positional_with_value(self, value, raw=False):
if self.keyword_started:
raise ValueError("positional arguments cannot follow keyword arguments")
self._tuples.append((None, RawReprWrapper(value) if raw else value))

def keyword_from_attr(self, name, attr_name=None):
self.keyword_started = True
attr_name = attr_name or name
self._tuples.append((name, getattr(self.other, attr_name)))

def keyword_with_value(self, name, value, raw=False):
self.keyword_started = True
return self._tuples.append((name, RawReprWrapper(value) if raw else value))

def __iter__(self):
return iter(self._tuples)
Loading