Skip to content

Commit 540e1d9

Browse files
authored
implement support for __pretty__ (#36)
* implment support for __pretty__ * fix tests, use dict * uprev * fix intermittent timing test
1 parent 825f952 commit 540e1d9

File tree

5 files changed

+98
-3
lines changed

5 files changed

+98
-3
lines changed

HISTORY.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
History
44
-------
55

6-
v0.4.0 (2017-12-29)
6+
v0.5.0 (2019-01-03)
7+
...................
8+
* support ``MultiDict``, #34
9+
* support ``__pretty__`` method, #36
10+
11+
v0.4.0 (2018-12-29)
712
...................
813
* remove use of ``warnings``, include in output, #30
914
* fix rendering errors #31

devtools/prettier.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from collections import OrderedDict
55
from collections.abc import Generator
66
from typing import Any, Union
7+
from unittest.mock import _Call as MockCall
78

89
from .ansi import isatty
910

@@ -29,6 +30,8 @@
2930
(frozenset, 'frozenset({', '})'),
3031
]
3132
DEFAULT_WIDTH = int(os.getenv('PY_DEVTOOLS_WIDTH', 120))
33+
MISSING = object()
34+
PRETTY_KEY = '__prettier_formatted_value__'
3235

3336

3437
def env_true(var_name, alt=None):
@@ -39,6 +42,14 @@ def env_true(var_name, alt=None):
3942
return alt
4043

4144

45+
def fmt(v):
46+
return {PRETTY_KEY: v}
47+
48+
49+
class SkipPretty(Exception):
50+
pass
51+
52+
4253
class PrettyFormat:
4354
def __init__(self,
4455
indent_step=4,
@@ -77,6 +88,16 @@ def _format(self, value: Any, indent_current: int, indent_first: bool):
7788
if indent_first:
7889
self._stream.write(indent_current * self._c)
7990

91+
pretty_func = getattr(value, '__pretty__', None)
92+
if pretty_func and not isinstance(value, MockCall):
93+
try:
94+
gen = pretty_func(fmt=fmt, skip_exc=SkipPretty)
95+
self._render_pretty(gen, indent_current)
96+
except SkipPretty:
97+
pass
98+
else:
99+
return
100+
80101
value_repr = repr(value)
81102
if len(value_repr) <= self._simple_cutoff and not isinstance(value, Generator):
82103
self._stream.write(value_repr)
@@ -88,6 +109,26 @@ def _format(self, value: Any, indent_current: int, indent_first: bool):
88109
return
89110
self._format_raw(value, value_repr, indent_current, indent_new)
90111

112+
def _render_pretty(self, gen, indent: int):
113+
prefix = False
114+
for v in gen:
115+
if isinstance(v, int) and v in {-1, 0, 1}:
116+
indent += v * self._indent_step
117+
prefix = True
118+
else:
119+
if prefix:
120+
self._stream.write('\n' + self._c * indent)
121+
prefix = False
122+
123+
pretty_value = v.get(PRETTY_KEY, MISSING) if (isinstance(v, dict) and len(v) == 1) else MISSING
124+
if pretty_value is not MISSING:
125+
self._format(pretty_value, indent, False)
126+
elif isinstance(v, str):
127+
self._stream.write(v)
128+
else:
129+
# shouldn't happen but will
130+
self._stream.write(repr(v))
131+
91132
def _format_dict(self, value: dict, value_repr: str, indent_current: int, indent_new: int):
92133
open_, before_, split_, after_, close_ = '{\n', indent_new * self._c, ': ', ',\n', '}'
93134
if isinstance(value, OrderedDict):

devtools/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__all__ = ['VERSION']
44

5-
VERSION = StrictVersion('0.4.0')
5+
VERSION = StrictVersion('0.5.0')

tests/test_custom_pretty.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from devtools import pformat
2+
3+
4+
def test_simple():
5+
class CustomCls:
6+
def __pretty__(self, fmt, **kwargs):
7+
yield 'Thing('
8+
yield 1
9+
for i in range(3):
10+
yield fmt(list(range(i)))
11+
yield ','
12+
yield 0
13+
yield -1
14+
yield ')'
15+
16+
my_cls = CustomCls()
17+
18+
v = pformat(my_cls)
19+
assert v == """\
20+
Thing(
21+
[],
22+
[0],
23+
[0, 1],
24+
)"""
25+
26+
27+
def test_skip():
28+
class CustomCls:
29+
def __pretty__(self, fmt, skip_exc, **kwargs):
30+
raise skip_exc()
31+
yield 'Thing()'
32+
33+
def __repr__(self):
34+
return '<CustomCls repr>'
35+
36+
my_cls = CustomCls()
37+
v = pformat(my_cls)
38+
assert v == '<CustomCls repr>'
39+
40+
41+
def test_yield_other():
42+
class CustomCls:
43+
def __pretty__(self, fmt, **kwargs):
44+
yield fmt('xxx')
45+
yield 123
46+
47+
my_cls = CustomCls()
48+
v = pformat(my_cls)
49+
assert v == "'xxx'123"

tests/test_timer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_multiple_not_verbose():
4444
with t(verbose=False):
4545
sleep(i)
4646
t.summary(True)
47-
v = f.getvalue()
47+
v = re.sub('[123]s', '0s', f.getvalue())
4848
assert v == (
4949
' 0.010s elapsed\n'
5050
' 0.020s elapsed\n'

0 commit comments

Comments
 (0)