Skip to content

Commit 2209b2d

Browse files
Better magic underscore errors
See #2072
1 parent 54efc64 commit 2209b2d

File tree

8 files changed

+770
-331
lines changed

8 files changed

+770
-331
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ node_modules/
3131

3232
# virtual envs
3333
vv
34-
venv
34+
venv*
3535

3636
# dist files
3737
build

packages/python/plotly/_plotly_utils/exceptions.py

+11
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,14 @@ def __init__(self, obj, path, notes=()):
8383
super(PlotlyDataTypeError, self).__init__(
8484
message=message, path=path, notes=notes
8585
)
86+
87+
88+
class PlotlyKeyError(LookupError):
89+
"""
90+
KeyErrors are not printed as beautifully as other errors (this is so that
91+
{}[''] prints "KeyError: ''" and not "KeyError:"). So here we subclass
92+
LookupError to make a PlotlyKeyError object which will print nicer error
93+
messages for KeyErrors.
94+
"""
95+
96+
pass

packages/python/plotly/_plotly_utils/utils.py

+88
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json as _json
33
import sys
44
import re
5+
from functools import reduce
56

67
from _plotly_utils.optional_imports import get_module
78
from _plotly_utils.basevalidators import ImageUriValidator
@@ -10,6 +11,20 @@
1011
PY36_OR_LATER = sys.version_info >= (3, 6)
1112

1213

14+
def cumsum(x):
15+
"""
16+
Custom cumsum to avoid a numpy import.
17+
"""
18+
19+
def _reducer(a, x):
20+
if len(a) == 0:
21+
return [x]
22+
return a + [a[-1] + x]
23+
24+
ret = reduce(_reducer, x, [])
25+
return ret
26+
27+
1328
class PlotlyJSONEncoder(_json.JSONEncoder):
1429
"""
1530
Meant to be passed as the `cls` kwarg to json.dumps(obj, cls=..)
@@ -256,3 +271,76 @@ def _get_int_type():
256271
else:
257272
int_type = (int,)
258273
return int_type
274+
275+
276+
def split_multichar(ss, chars):
277+
"""
278+
Split all the strings in ss at any of the characters in chars.
279+
Example:
280+
281+
>>> ss = ["a.string[0].with_separators"]
282+
>>> chars = list(".[]_")
283+
>>> split_multichar(ss, chars)
284+
['a', 'string', '0', '', 'with', 'separators']
285+
286+
:param (list) ss: A list of strings.
287+
:param (list) chars: Is a list of chars (note: not a string).
288+
"""
289+
if len(chars) == 0:
290+
return ss
291+
c = chars.pop()
292+
ss = reduce(lambda x, y: x + y, map(lambda x: x.split(c), ss))
293+
return split_multichar(ss, chars)
294+
295+
296+
def split_string_positions(ss):
297+
"""
298+
Given a list of strings split using split_multichar, return a list of
299+
integers representing the indices of the first character of every string in
300+
the original string.
301+
Example:
302+
303+
>>> ss = ["a.string[0].with_separators"]
304+
>>> chars = list(".[]_")
305+
>>> ss_split = split_multichar(ss, chars)
306+
>>> ss_split
307+
['a', 'string', '0', '', 'with', 'separators']
308+
>>> split_string_positions(ss_split)
309+
[0, 2, 9, 11, 12, 17]
310+
311+
:param (list) ss: A list of strings.
312+
"""
313+
return list(
314+
map(
315+
lambda t: t[0] + t[1],
316+
zip(range(len(ss)), cumsum([0] + list(map(len, ss[:-1])))),
317+
)
318+
)
319+
320+
321+
def display_string_positions(p, i=None):
322+
"""
323+
Return a string that is whitespace except at p[i] which is replaced with ^.
324+
If i is None then all the indices of the string in p are replaced with ^.
325+
Example:
326+
327+
>>> ss = ["a.string[0].with_separators"]
328+
>>> chars = list(".[]_")
329+
>>> ss_split = split_multichar(ss, chars)
330+
>>> ss_split
331+
['a', 'string', '0', '', 'with', 'separators']
332+
>>> ss_pos = split_string_positions(ss_split)
333+
>>> ss[0]
334+
'a.string[0].with_separators'
335+
>>> display_string_positions(ss_pos,4)
336+
' ^'
337+
:param (list) p: A list of integers.
338+
:param (integer|None) i: Optional index of p to display.
339+
"""
340+
s = [" " for _ in range(max(p) + 1)]
341+
if i is None:
342+
for p_ in p:
343+
s[p_] = "^"
344+
else:
345+
s[p[i]] = "^"
346+
return "".join(s)

0 commit comments

Comments
 (0)