Skip to content

Deep magic underscore error messages rebase #2843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2209b2d
Better magic underscore errors
nicholas-esterer Oct 19, 2020
afd5b7a
Remove whitespace-only changes
nicholas-esterer Oct 20, 2020
b894b89
Assert that errors are raised
nicholas-esterer Oct 20, 2020
4c659db
Also state when a property doesn't support subscripting
nicholas-esterer Oct 20, 2020
2bf2526
Tests for non-subscriptable property errors
nicholas-esterer Oct 20, 2020
5695969
updated changelog
nicholas-esterer Oct 20, 2020
22b3e03
Improved error message when subscripting types that don't support it
nicholas-esterer Oct 23, 2020
c09c03e
Removed garbage line from test_dict_path_errors.py
nicholas-esterer Oct 23, 2020
f4be2b1
Changed PlotlyKeyError's superclass to KeyError
nicholas-esterer Nov 4, 2020
73051a6
BasePlotlyType._raise_on_invalid_property_error raises PlotlyKeyError
nicholas-esterer Nov 4, 2020
651b712
Cast some errors to PlotlyKeyError
nicholas-esterer Nov 4, 2020
9bb2470
Updated the tests to reflect the new Exception behaviour
nicholas-esterer Nov 4, 2020
bf4cd97
BasePlotlyType.__setitem__ exceptions casted to ValueError
nicholas-esterer Nov 5, 2020
7d42ffe
Merge master's whitespace changes
nicholas-esterer Nov 5, 2020
a18b341
Merge branch 'master' into deep-magic-underscore-error-msg-rebase
nicholas-esterer Nov 5, 2020
4bda7b2
Now subscripting errors triggered on types throwing TypeError
nicholas-esterer Nov 5, 2020
9e1b667
subscripting error tests compatible with Python2
nicholas-esterer Nov 5, 2020
d6aee64
Changed dict path error display to always ^
nicholas-esterer Nov 9, 2020
c6e5b4d
Try taking length of string-like objects
nicholas-esterer Nov 9, 2020
82f9bb1
leading, trailing, multiple underscores detected in dict path strings
nicholas-esterer Nov 9, 2020
70f18ca
Added tests for leading, trailing and embedded extra underscore errors
nicholas-esterer Nov 10, 2020
00851fa
Complain about trailing underscores, find closest key
nicholas-esterer Nov 10, 2020
d2bc400
Updated error messages for trailing underscores and find closest key
nicholas-esterer Nov 10, 2020
20518c1
Key guessing before and after list of valid properties
nicholas-esterer Nov 10, 2020
4066ae2
Test single property key guessing
nicholas-esterer Nov 11, 2020
86f4217
Merge branch 'master' into deep-magic-underscore-error-msg-rebase
nicholas-esterer Nov 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Complain about trailing underscores, find closest key
Tests in tests/test_core/test_errors/test_dict_path_errors.py
not yet updated though.
  • Loading branch information
nicholas-esterer committed Nov 10, 2020
commit 00851fa91ec2c3e48eea6100335a1efe22c52633
40 changes: 38 additions & 2 deletions packages/python/plotly/_plotly_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,10 @@ def display_string_positions(p, i=None, offset=0, length=1, char="^", trim=True)
return ret


def chomp_empty_strings(strings, c):
def chomp_empty_strings(strings, c, reverse=False):
"""
Given a list of strings, some of which are the empty string "", replace the
empty strings with "_" and combine them with the closest non-empty string on
empty strings with c and combine them with the closest non-empty string on
the left or "" if it is the first string.
Examples:
for c="_"
Expand All @@ -379,7 +379,15 @@ def chomp_empty_strings(strings, c):
[''] -> ['']
['', ''] -> ['_']
['', '', '', ''] -> ['___']
If reverse is true, empty strings are combined with closest non-empty string
on the right or "" if it is the last string.
"""

def _rev(l):
return [s[::-1] for s in l][::-1]

if reverse:
return _rev(chomp_empty_strings(_rev(strings), c))
if not len(strings):
return strings
if sum(map(len, strings)) == 0:
Expand All @@ -400,3 +408,31 @@ def __call__(self, x, y):
return x + [y]

return list(filter(len, reduce(_Chomper(c), strings, [""])))


# taken from
# https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
def levenshtein(s1, s2):
if len(s1) < len(s2):
return levenshtein(s2, s1) # len(s1) >= len(s2)
if len(s2) == 0:
return len(s1)
previous_row = range(len(s2) + 1)
for i, c1 in enumerate(s1):
current_row = [i + 1]
for j, c2 in enumerate(s2):
# j+1 instead of j since previous_row and current_row are one character longer
# than s2
insertions = previous_row[j + 1] + 1
deletions = current_row[j] + 1
substitutions = previous_row[j] + (c1 != c2)
current_row.append(min(insertions, deletions, substitutions))
previous_row = current_row
return previous_row[-1]


def find_closest_string(string, strings):
def _key(s):
return levenshtein(s, string)

return sorted(strings, key=_key)[0]
33 changes: 28 additions & 5 deletions packages/python/plotly/plotly/basedatatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
split_string_positions,
display_string_positions,
chomp_empty_strings,
find_closest_string,
)
from _plotly_utils.exceptions import PlotlyKeyError
from .optional_imports import get_module
Expand Down Expand Up @@ -95,9 +96,9 @@ def _split_and_chomp(s):
return s
s_split = split_multichar([s], list("_"))
# handle key paths like "a_path_", "_another_path", or
# "yet__another_path" by joining extra "_" to the string to the left or
# the empty string if at the beginning
s_chomped = chomp_empty_strings(s_split, "_")
# "yet__another_path" by joining extra "_" to the string to the right or
# the empty string if at the end
s_chomped = chomp_empty_strings(s_split, "_", reverse=True)
return s_chomped

# after running _split_and_chomp on key_path2b, it will be a list
Expand Down Expand Up @@ -204,14 +205,25 @@ def _check_path_in_prop_tree(obj, path, error_cast=None):
# In case i is 0, the best we can do is indicate the first
# property in the string as having caused the error
disp_i = max(i - 1, 0)
dict_item_len = _len_dict_item(prop[disp_i])
# if the path has trailing underscores, the prop string will start with "_"
trailing_underscores = ""
if prop[i][0] == "_":
trailing_underscores = " and path has trailing underscores"
# if the path has trailing underscores and the display index is
# one less than the prop index (see above), then we can also
# indicate the offending underscores
if (trailing_underscores != "") and (disp_i != i):
dict_item_len += _len_dict_item(prop[i])
arg += """

Property does not support subscripting:
Property does not support subscripting%s:
%s
%s""" % (
trailing_underscores,
path,
display_string_positions(
prop_idcs, disp_i, length=_len_dict_item(prop[disp_i]), char="^"
prop_idcs, disp_i, length=dict_item_len, char="^"
),
)
else:
Expand All @@ -226,6 +238,17 @@ def _check_path_in_prop_tree(obj, path, error_cast=None):
prop_idcs, i, length=_len_dict_item(prop[i]), char="^"
),
)
guessed_prop = None
# If obj has _valid_props then we can try and guess what key was intended
try:
guessed_prop = find_closest_string(prop[i], obj._valid_props)
except Exception:
pass
if guessed_prop is not None:
arg += """
Did you mean "%s"?""" % (
guessed_prop,
)
# Make KeyError more pretty by changing it to a PlotlyKeyError,
# because the Python interpreter has a special way of printing
# KeyError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def test_raises_on_bad_dot_property(some_fig):
e.args[0].find(
"""Bad property path:
layout.shapes[1].x2000
^^^^^"""
^^^^^
Did you mean "x0"?"""
)
>= 0
)
Expand All @@ -68,7 +69,8 @@ def test_raises_on_bad_ancestor_dot_property(some_fig):
e.args[0].find(
"""Bad property path:
layout.shapa[1].x2000
^^^^^"""
^^^^^
Did you mean "shapes"?"""
)
>= 0
)
Expand Down