Skip to content

Commit

Permalink
Merge pull request #11 from RazerM/feature/rich.pretty
Browse files Browse the repository at this point in the history
Add rich.pretty support
  • Loading branch information
RazerM authored Jan 23, 2024
2 parents 042ceca + 10dd580 commit e91c62b
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 53 deletions.
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

0 comments on commit e91c62b

Please sign in to comment.