forked from plotly/plotly.py
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.py
209 lines (170 loc) · 6.04 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
from __future__ import absolute_import, division
import textwrap
from pprint import PrettyPrinter
from _plotly_utils.utils import *
from _plotly_utils.data_utils import *
# Pretty printing
def _list_repr_elided(v, threshold=200, edgeitems=3, indent=0, width=80):
"""
Return a string representation for of a list where list is elided if
it has more than n elements
Parameters
----------
v : list
Input list
threshold :
Maximum number of elements to display
Returns
-------
str
"""
if isinstance(v, list):
open_char, close_char = "[", "]"
elif isinstance(v, tuple):
open_char, close_char = "(", ")"
else:
raise ValueError("Invalid value of type: %s" % type(v))
if len(v) <= threshold:
disp_v = v
else:
disp_v = list(v[:edgeitems]) + ["..."] + list(v[-edgeitems:])
v_str = open_char + ", ".join([str(e) for e in disp_v]) + close_char
v_wrapped = "\n".join(
textwrap.wrap(
v_str,
width=width,
initial_indent=" " * (indent + 1),
subsequent_indent=" " * (indent + 1),
)
).strip()
return v_wrapped
class ElidedWrapper(object):
"""
Helper class that wraps values of certain types and produces a custom
__repr__() that may be elided and is suitable for use during pretty
printing
"""
def __init__(self, v, threshold, indent):
self.v = v
self.indent = indent
self.threshold = threshold
@staticmethod
def is_wrappable(v):
numpy = get_module("numpy")
if isinstance(v, (list, tuple)) and len(v) > 0 and not isinstance(v[0], dict):
return True
elif numpy and isinstance(v, numpy.ndarray):
return True
elif isinstance(v, str):
return True
else:
return False
def __repr__(self):
numpy = get_module("numpy")
if isinstance(self.v, (list, tuple)):
# Handle lists/tuples
res = _list_repr_elided(
self.v, threshold=self.threshold, indent=self.indent
)
return res
elif numpy and isinstance(self.v, numpy.ndarray):
# Handle numpy arrays
# Get original print opts
orig_opts = numpy.get_printoptions()
# Set threshold to self.max_list_elements
numpy.set_printoptions(
**dict(orig_opts, threshold=self.threshold, edgeitems=3, linewidth=80)
)
res = self.v.__repr__()
# Add indent to all but the first line
res_lines = res.split("\n")
res = ("\n" + " " * self.indent).join(res_lines)
# Restore print opts
numpy.set_printoptions(**orig_opts)
return res
elif isinstance(self.v, str):
# Handle strings
if len(self.v) > 80:
return "(" + repr(self.v[:30]) + " ... " + repr(self.v[-30:]) + ")"
else:
return self.v.__repr__()
else:
return self.v.__repr__()
class ElidedPrettyPrinter(PrettyPrinter):
"""
PrettyPrinter subclass that elides long lists/arrays/strings
"""
def __init__(self, *args, **kwargs):
self.threshold = kwargs.pop("threshold", 200)
PrettyPrinter.__init__(self, *args, **kwargs)
def _format(self, val, stream, indent, allowance, context, level):
if ElidedWrapper.is_wrappable(val):
elided_val = ElidedWrapper(val, self.threshold, indent)
return self._format(elided_val, stream, indent, allowance, context, level)
else:
return PrettyPrinter._format(
self, val, stream, indent, allowance, context, level
)
def node_generator(node, path=()):
"""
General, node-yielding generator.
Yields (node, path) tuples when it finds values that are dict
instances.
A path is a sequence of hashable values that can be used as either keys to
a mapping (dict) or indices to a sequence (list). A path is always wrt to
some object. Given an object, a path explains how to get from the top level
of that object to a nested value in the object.
:param (dict) node: Part of a dict to be traversed.
:param (tuple[str]) path: Defines the path of the current node.
:return: (Generator)
Example:
>>> for node, path in node_generator({'a': {'b': 5}}):
... print(node, path)
{'a': {'b': 5}} ()
{'b': 5} ('a',)
"""
if not isinstance(node, dict):
return # in case it's called with a non-dict node at top level
yield node, path
for key, val in node.items():
if isinstance(val, dict):
for item in node_generator(val, path + (key,)):
yield item
def get_by_path(obj, path):
"""
Iteratively get on obj for each key in path.
:param (list|dict) obj: The top-level object.
:param (tuple[str]|tuple[int]) path: Keys to access parts of obj.
:return: (*)
Example:
>>> figure = {'data': [{'x': [5]}]}
>>> path = ('data', 0, 'x')
>>> get_by_path(figure, path)
[5]
"""
for key in path:
obj = obj[key]
return obj
def decode_unicode(coll):
if isinstance(coll, list):
for no, entry in enumerate(coll):
if isinstance(entry, (dict, list)):
coll[no] = decode_unicode(entry)
else:
if isinstance(entry, str):
try:
coll[no] = str(entry)
except UnicodeEncodeError:
pass
elif isinstance(coll, dict):
keys, vals = list(coll.keys()), list(coll.values())
for key, val in zip(keys, vals):
if isinstance(val, (dict, list)):
coll[key] = decode_unicode(val)
elif isinstance(val, str):
try:
coll[key] = str(val)
except UnicodeEncodeError:
pass
coll[str(key)] = coll.pop(key)
return coll