Skip to content

Commit 5489a89

Browse files
author
Jon M. Mease
committed
Add pretty print based __repr__() for BaseFigure and BasePlotlyType
1 parent 920817c commit 5489a89

File tree

3 files changed

+177
-13
lines changed

3 files changed

+177
-13
lines changed

plotly/basedatatypes.py

+60-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55
from contextlib import contextmanager
66
from copy import deepcopy
7+
from pprint import PrettyPrinter
78
from typing import Dict, Tuple, Union, Callable, List
89

910
from plotly.optional_imports import get_module
@@ -15,6 +16,7 @@
1516
from plotly import animation
1617
from plotly.callbacks import (Points, BoxSelector, LassoSelector,
1718
InputDeviceState)
19+
from plotly.utils import ElidedPrettyPrinter
1820
from plotly.validators import (DataValidator, LayoutValidator, FramesValidator)
1921

2022
# Optional imports
@@ -242,6 +244,17 @@ def __eq__(self, other):
242244
return BasePlotlyType._vals_equal(self.to_plotly_json(),
243245
other.to_plotly_json())
244246

247+
def __repr__(self):
248+
"""
249+
Customize Figure representation when displayed in the
250+
terminal/notebook
251+
"""
252+
repr_str = BasePlotlyType._build_repr_for_class(
253+
props=self.to_plotly_json(),
254+
class_name=self.__class__.__name__)
255+
256+
return repr_str
257+
245258
def update(self, dict1=None, **kwargs):
246259
"""
247260
Update the properties of the figure with a dict and/or with
@@ -2556,8 +2569,53 @@ def __eq__(self, other):
25562569

25572570
# Use _vals_equal instead of `==` to handle cases where
25582571
# underlying dicts contain numpy arrays
2559-
return BasePlotlyType._vals_equal(self.to_plotly_json(),
2560-
other.to_plotly_json())
2572+
return BasePlotlyType._vals_equal(self._props,
2573+
other._props)
2574+
2575+
@staticmethod
2576+
def _build_repr_for_class(props, class_name, parent_path_str=None):
2577+
"""
2578+
Helper to build representation string for a class
2579+
2580+
Parameters
2581+
----------
2582+
class_name : str
2583+
Name of the class being represented
2584+
parent_path_str : str of None (default)
2585+
Name of the class's parent package to display
2586+
props : dict
2587+
Properties to unpack into the constructor
2588+
2589+
Returns
2590+
-------
2591+
str
2592+
The representation string
2593+
"""
2594+
if parent_path_str:
2595+
class_name = parent_path_str + '.' + class_name
2596+
2597+
pprinter = ElidedPrettyPrinter(threshold=200, width=120)
2598+
pprint_res = pprinter.pformat(props)
2599+
2600+
# pprint_res is indented by 1 space. Add extra 3 spaces for PEP8
2601+
# complaint indent
2602+
body = ' ' + pprint_res[1:-1].replace('\n', '\n ')
2603+
2604+
repr_str = class_name + '(**{\n ' + body + '\n})'
2605+
2606+
return repr_str
2607+
2608+
def __repr__(self):
2609+
"""
2610+
Customize object representation when displayed in the
2611+
terminal/notebook
2612+
"""
2613+
repr_str = BasePlotlyType._build_repr_for_class(
2614+
props=self._props,
2615+
class_name=self.__class__.__name__,
2616+
parent_path_str=self._parent_path_str)
2617+
2618+
return repr_str
25612619

25622620
def _raise_on_invalid_property_error(self, *args):
25632621
"""

plotly/callbacks.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,12 @@ def __repr__(self):
146146
ys={ys},
147147
trace_name={trace_name},
148148
trace_index={trace_index})""".format(
149-
point_inds=_list_repr_elided(self.point_inds),
150-
xs=_list_repr_elided(self.xs),
151-
ys=_list_repr_elided(self.ys),
149+
point_inds=_list_repr_elided(self.point_inds,
150+
indent=len('Points(point_inds=')),
151+
xs=_list_repr_elided(self.xs,
152+
indent=len(' xs=')),
153+
ys=_list_repr_elided(self.ys,
154+
indent=len(' ys=')),
152155
trace_name=repr(self.trace_name),
153156
trace_index=repr(self.trace_index))
154157

@@ -265,8 +268,10 @@ def __repr__(self):
265268
return """\
266269
LassoSelector(xs={xs},
267270
ys={ys})""".format(
268-
xs=_list_repr_elided(self.xs, 8),
269-
ys=_list_repr_elided(self.ys, 8))
271+
xs=_list_repr_elided(self.xs,
272+
indent=len('LassoSelector(xs=')),
273+
ys=_list_repr_elided(self.ys,
274+
indent=len(' ys=')))
270275

271276
@property
272277
def type(self):

plotly/utils.py

+107-6
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
import os.path
1111
import re
1212
import sys
13+
import textwrap
1314
import threading
1415
import decimal
1516
from collections import deque
17+
from pprint import PrettyPrinter
1618

1719
import pytz
1820
from decorator import decorator
@@ -492,7 +494,7 @@ def _memoize(*all_args, **kwargs):
492494
return decorator(_memoize)
493495

494496

495-
def _list_repr_elided(v, n=20):
497+
def _list_repr_elided(v, threshold=200, edgeitems=3, indent=0, width=80):
496498
"""
497499
Return a string representation for of a list where list is elided if
498500
it has more than n elements
@@ -501,15 +503,114 @@ def _list_repr_elided(v, n=20):
501503
----------
502504
v : list
503505
Input list
504-
n :
506+
threshold :
505507
Maximum number of elements to display
506508
507509
Returns
508510
-------
509511
str
510512
"""
511-
if len(v) <= n:
512-
return str(v)
513+
if isinstance(v, list):
514+
open_char, close_char = '[', ']'
515+
elif isinstance(v, tuple):
516+
open_char, close_char = '(', ')'
513517
else:
514-
disp_v = v[:n // 2 - 1] + ['...'] + v[-n // 2 + 1:]
515-
return '[' + ', '.join([str(e) for e in disp_v]) + ']'
518+
raise ValueError('Invalid value of type: %s' % type(v))
519+
520+
if len(v) <= threshold:
521+
disp_v = v
522+
else:
523+
disp_v = (list(v[:edgeitems])
524+
+ ['...'] +
525+
list(v[-edgeitems:]))
526+
527+
v_str = open_char + ', '.join([str(e) for e in disp_v]) + close_char
528+
529+
v_wrapped = '\n'.join(textwrap.wrap(v_str, width=width,
530+
initial_indent=' ' * (indent + 1),
531+
subsequent_indent =' ' * (indent + 1))).strip()
532+
return v_wrapped
533+
534+
535+
class ElidedWrapper:
536+
"""
537+
Helper class that wraps values of certain types and produces a custom
538+
__repr__() that may be elided and is suitable for use during pretty
539+
printing
540+
"""
541+
def __init__(self, v, threshold, indent):
542+
self.v = v
543+
self.indent = indent
544+
self.threshold = threshold
545+
546+
@staticmethod
547+
def is_wrappable(v):
548+
if (isinstance(v, (list, tuple)) and
549+
len(v) > 0 and
550+
not isinstance(v[0], dict)):
551+
return True
552+
elif numpy and isinstance(v, numpy.ndarray):
553+
return True
554+
elif isinstance(v, str):
555+
return True
556+
else:
557+
return False
558+
559+
def __repr__(self):
560+
if isinstance(self.v, (list, tuple)):
561+
# Handle lists/tuples
562+
res = _list_repr_elided(self.v,
563+
threshold=self.threshold,
564+
indent=self.indent)
565+
return res
566+
elif numpy and isinstance(self.v, numpy.ndarray):
567+
# Handle numpy arrays
568+
569+
# Get original print opts
570+
orig_opts = numpy.get_printoptions()
571+
572+
# Set threshold to self.max_list_elements
573+
numpy.set_printoptions(
574+
**dict(orig_opts,
575+
threshold=self.threshold,
576+
edgeitems=3,
577+
linewidth=80))
578+
579+
res = self.v.__repr__()
580+
581+
# Add indent to all but the first line
582+
res_lines = res.split('\n')
583+
res = ('\n' + ' '*self.indent).join(res_lines)
584+
585+
# Restore print opts
586+
numpy.set_printoptions(**orig_opts)
587+
return res
588+
elif isinstance(self.v, str):
589+
# Handle strings
590+
if len(self.v) > 80:
591+
return ('(' + repr(self.v[:30]) +
592+
' ... ' + repr(self.v[-30:]) + ')')
593+
else:
594+
return self.v.__repr__()
595+
else:
596+
return self.v.__repr__()
597+
598+
599+
class ElidedPrettyPrinter(PrettyPrinter):
600+
"""
601+
PrettyPrinter subclass that elides long lists/arrays/strings
602+
"""
603+
def __init__(self, *args, **kwargs):
604+
self.threshold = kwargs.pop('threshold', 200)
605+
super().__init__(*args, **kwargs)
606+
607+
def _format(self, val, stream, indent, allowance, context, level):
608+
if ElidedWrapper.is_wrappable(val):
609+
elided_val = ElidedWrapper(
610+
val, self.threshold, indent)
611+
612+
return self._format(
613+
elided_val, stream, indent, allowance, context, level)
614+
else:
615+
return super()._format(
616+
val, stream, indent, allowance, context, level)

0 commit comments

Comments
 (0)