From 2209b2d9b246d40b5e6644520143fb8ae52367de Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 19 Oct 2020 12:00:28 -0400 Subject: [PATCH 01/24] Better magic underscore errors See https://github.com/plotly/plotly.py/issues/2072 --- .gitignore | 2 +- .../python/plotly/_plotly_utils/exceptions.py | 11 + packages/python/plotly/_plotly_utils/utils.py | 88 ++++ .../python/plotly/plotly/basedatatypes.py | 243 +++++++--- .../plotly/plotly/graph_objs/_deprecations.py | 424 +++++++++--------- .../plotly/graph_objs/layout/_annotation.py | 108 ++--- .../test_errors/test_dict_path_errors.py | 223 +++++++++ .../test_graph_objs/test_layout_subplots.py | 2 +- 8 files changed, 770 insertions(+), 331 deletions(-) create mode 100644 packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py diff --git a/.gitignore b/.gitignore index 64c3de539e2..47e425eab74 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ node_modules/ # virtual envs vv -venv +venv* # dist files build diff --git a/packages/python/plotly/_plotly_utils/exceptions.py b/packages/python/plotly/_plotly_utils/exceptions.py index c1a2d6b368e..7a9174eb1ef 100644 --- a/packages/python/plotly/_plotly_utils/exceptions.py +++ b/packages/python/plotly/_plotly_utils/exceptions.py @@ -83,3 +83,14 @@ def __init__(self, obj, path, notes=()): super(PlotlyDataTypeError, self).__init__( message=message, path=path, notes=notes ) + + +class PlotlyKeyError(LookupError): + """ + KeyErrors are not printed as beautifully as other errors (this is so that + {}[''] prints "KeyError: ''" and not "KeyError:"). So here we subclass + LookupError to make a PlotlyKeyError object which will print nicer error + messages for KeyErrors. + """ + + pass diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index cbf8d3a6b98..5deead650b1 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -2,6 +2,7 @@ import json as _json import sys import re +from functools import reduce from _plotly_utils.optional_imports import get_module from _plotly_utils.basevalidators import ImageUriValidator @@ -10,6 +11,20 @@ PY36_OR_LATER = sys.version_info >= (3, 6) +def cumsum(x): + """ + Custom cumsum to avoid a numpy import. + """ + + def _reducer(a, x): + if len(a) == 0: + return [x] + return a + [a[-1] + x] + + ret = reduce(_reducer, x, []) + return ret + + class PlotlyJSONEncoder(_json.JSONEncoder): """ Meant to be passed as the `cls` kwarg to json.dumps(obj, cls=..) @@ -256,3 +271,76 @@ def _get_int_type(): else: int_type = (int,) return int_type + + +def split_multichar(ss, chars): + """ + Split all the strings in ss at any of the characters in chars. + Example: + + >>> ss = ["a.string[0].with_separators"] + >>> chars = list(".[]_") + >>> split_multichar(ss, chars) + ['a', 'string', '0', '', 'with', 'separators'] + + :param (list) ss: A list of strings. + :param (list) chars: Is a list of chars (note: not a string). + """ + if len(chars) == 0: + return ss + c = chars.pop() + ss = reduce(lambda x, y: x + y, map(lambda x: x.split(c), ss)) + return split_multichar(ss, chars) + + +def split_string_positions(ss): + """ + Given a list of strings split using split_multichar, return a list of + integers representing the indices of the first character of every string in + the original string. + Example: + + >>> ss = ["a.string[0].with_separators"] + >>> chars = list(".[]_") + >>> ss_split = split_multichar(ss, chars) + >>> ss_split + ['a', 'string', '0', '', 'with', 'separators'] + >>> split_string_positions(ss_split) + [0, 2, 9, 11, 12, 17] + + :param (list) ss: A list of strings. + """ + return list( + map( + lambda t: t[0] + t[1], + zip(range(len(ss)), cumsum([0] + list(map(len, ss[:-1])))), + ) + ) + + +def display_string_positions(p, i=None): + """ + Return a string that is whitespace except at p[i] which is replaced with ^. + If i is None then all the indices of the string in p are replaced with ^. + Example: + + >>> ss = ["a.string[0].with_separators"] + >>> chars = list(".[]_") + >>> ss_split = split_multichar(ss, chars) + >>> ss_split + ['a', 'string', '0', '', 'with', 'separators'] + >>> ss_pos = split_string_positions(ss_split) + >>> ss[0] + 'a.string[0].with_separators' + >>> display_string_positions(ss_pos,4) + ' ^' + :param (list) p: A list of integers. + :param (integer|None) i: Optional index of p to display. + """ + s = [" " for _ in range(max(p) + 1)] + if i is None: + for p_ in p: + s[p_] = "^" + else: + s[p[i]] = "^" + return "".join(s) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 7bc0e66f56d..d4f1c6f3d79 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -9,7 +9,14 @@ from contextlib import contextmanager from copy import deepcopy, copy -from _plotly_utils.utils import _natural_sort_strings, _get_int_type +from _plotly_utils.utils import ( + _natural_sort_strings, + _get_int_type, + split_multichar, + split_string_positions, + display_string_positions, +) +from _plotly_utils.exceptions import PlotlyKeyError from .optional_imports import get_module # Create Undefined sentinel value @@ -18,6 +25,124 @@ Undefined = object() +def _str_to_dict_path_full(key_path_str): + """ + Convert a key path string into a tuple of key path elements and also + return a tuple of indices marking the beginning of each element in the + string. + + Parameters + ---------- + key_path_str : str + Key path string, where nested keys are joined on '.' characters + and array indexes are specified using brackets + (e.g. 'foo.bar[1]') + Returns + ------- + tuple[str | int] + tuple [int] + """ + key_path2 = split_multichar([key_path_str], list(".[]")) + # Split out underscore + # e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1'] + key_path3 = [] + underscore_props = BaseFigure._valid_underscore_properties + + def _make_hyphen_key(key): + if "_" in key[1:]: + # For valid properties that contain underscores (error_x) + # replace the underscores with hyphens to protect them + # from being split up + for under_prop, hyphen_prop in underscore_props.items(): + key = key.replace(under_prop, hyphen_prop) + return key + + def _make_underscore_key(key): + return key.replace("-", "_") + + key_path2b = map(_make_hyphen_key, key_path2) + key_path2c = split_multichar(key_path2b, list("_")) + key_path2d = list(map(_make_underscore_key, key_path2c)) + all_elem_idcs = tuple(split_string_positions(list(key_path2d))) + # remove empty strings, and indices pointing to them + key_elem_pairs = list(filter(lambda t: len(t[1]), enumerate(key_path2d))) + key_path3 = [x for _, x in key_elem_pairs] + elem_idcs = [all_elem_idcs[i] for i, _ in key_elem_pairs] + + # Convert elements to ints if possible. + # e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0] + for i in range(len(key_path3)): + try: + key_path3[i] = int(key_path3[i]) + except ValueError as _: + pass + + return (tuple(key_path3), elem_idcs) + + +def _remake_path_from_tuple(props): + """ + try to remake a path using the properties in props + """ + if len(props) == 0: + return "" + + def _add_square_brackets_to_number(n): + if type(n) == type(int()): + return "[%d]" % (n,) + return n + + def _prepend_dot_if_not_number(s): + if not s.startswith("["): + return "." + s + return s + + props_all_str = list(map(_add_square_brackets_to_number, props)) + props_w_underscore = props_all_str[:1] + list( + map(_prepend_dot_if_not_number, props_all_str[1:]) + ) + return "".join(props_w_underscore) + + +def _check_path_in_prop_tree(obj, path): + """ + obj: the object in which the first property is looked up + path: the path that will be split into properties to be looked up + path can also be a tuple. In this case, it is combined using . and [] + because it is impossible to reconstruct the string fully in order to + give a decent error message. + returns + an Exception object or None. The caller can raise this + exception to see where the lookup error occurred. + """ + if type(path) == type(tuple()): + path = _remake_path_from_tuple(path) + prop, prop_idcs = _str_to_dict_path_full(path) + for i, p in enumerate(prop): + try: + obj = obj[p] + except (ValueError, KeyError, IndexError) as e: + arg = ( + e.args[0] + + """ +Bad property path: +%s""" + % (path,) + ) + arg += """ +%s""" % ( + display_string_positions(prop_idcs, i), + ) + # Make KeyError more pretty by changing it to a PlotlyKeyError, + # because the Python interpreter has a special way of printing + # KeyError + if type(e) == type(KeyError()): + e = PlotlyKeyError() + e.args = (arg,) + return e + return None + + class BaseFigure(object): """ Base class for all figure types (both widget and non-widget) @@ -265,10 +390,18 @@ class is a subclass of both BaseFigure and widgets.DOMWidget. # Process kwargs # -------------- for k, v in kwargs.items(): - if k in self: + err = _check_path_in_prop_tree(self, k) + if err is None: self[k] = v elif not skip_invalid: - raise TypeError("invalid Figure property: {}".format(k)) + type_err = TypeError("invalid Figure property: {}".format(k)) + type_err.args = ( + type_err.args[0] + + """ +%s""" + % (err.args[0],), + ) + raise type_err # Magic Methods # ------------- @@ -315,6 +448,9 @@ def __setitem__(self, prop, value): # ---------------------- # e.g. ('foo', 1) else: + err = _check_path_in_prop_tree(self, orig_prop) + if err is not None: + raise err res = self for p in prop[:-1]: res = res[p] @@ -370,6 +506,9 @@ def __getitem__(self, prop): # ---------------------- # e.g. ('foo', 1) else: + err = _check_path_in_prop_tree(self, orig_prop) + if err is not None: + raise err res = self for p in prop: res = res[p] @@ -1337,7 +1476,7 @@ def _normalize_trace_indexes(self, trace_indexes): @staticmethod def _str_to_dict_path(key_path_str): """ - Convert a key path string into a tuple of key path elements + Convert a key path string into a tuple of key path elements. Parameters ---------- @@ -1361,53 +1500,8 @@ def _str_to_dict_path(key_path_str): # Nothing to do return key_path_str else: - # Split string on periods. - # e.g. 'foo.bar_baz[1]' -> ['foo', 'bar_baz[1]'] - key_path = key_path_str.split(".") - - # Split out bracket indexes. - # e.g. ['foo', 'bar_baz[1]'] -> ['foo', 'bar_baz', '1'] - key_path2 = [] - for key in key_path: - match = BaseFigure._bracket_re.match(key) - if match: - key_path2.extend(match.groups()) - else: - key_path2.append(key) - - # Split out underscore - # e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1'] - key_path3 = [] - underscore_props = BaseFigure._valid_underscore_properties - for key in key_path2: - if "_" in key[1:]: - # For valid properties that contain underscores (error_x) - # replace the underscores with hyphens to protect them - # from being split up - for under_prop, hyphen_prop in underscore_props.items(): - key = key.replace(under_prop, hyphen_prop) - - # Split key on underscores - key = key.split("_") - - # Replace hyphens with underscores to restore properties - # that include underscores - for i in range(len(key)): - key[i] = key[i].replace("-", "_") - - key_path3.extend(key) - else: - key_path3.append(key) - - # Convert elements to ints if possible. - # e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0] - for i in range(len(key_path3)): - try: - key_path3[i] = int(key_path3[i]) - except ValueError as _: - pass - - return tuple(key_path3) + ret = _str_to_dict_path_full(key_path_str)[0] + return ret @staticmethod def _set_in(d, key_path_str, v): @@ -3320,19 +3414,20 @@ def _perform_update(plotly_obj, update_obj, overwrite=False): # ------------------------------- # This should be valid even if xaxis2 hasn't been initialized: # >>> layout.update(xaxis2={'title': 'xaxis 2'}) - if isinstance(plotly_obj, BaseLayoutType): - for key in update_obj: - if key not in plotly_obj: + for key in update_obj: + err = _check_path_in_prop_tree(plotly_obj, key) + if err is not None: + if isinstance(plotly_obj, BaseLayoutType): + # try _subplot_re_match match = plotly_obj._subplot_re_match(key) if match: # We need to create a subplotid object plotly_obj[key] = {} - - # Handle invalid properties - # ------------------------- - invalid_props = [k for k in update_obj if k not in plotly_obj] - - plotly_obj._raise_on_invalid_property_error(*invalid_props) + continue + # If no match, raise the error, which should already + # contain the _raise_on_invalid_property_error + # generated message + raise err # Convert update_obj to dict # -------------------------- @@ -3536,17 +3631,20 @@ def _process_kwargs(self, **kwargs): """ invalid_kwargs = {} for k, v in kwargs.items(): - if k in self: + err = _check_path_in_prop_tree(self, k) + if err is None: # e.g. underscore kwargs like marker_line_color self[k] = v elif not self._validate: # Set extra property as-is self[k] = v - else: - invalid_kwargs[k] = v - - if invalid_kwargs and not self._skip_invalid: - self._raise_on_invalid_property_error(*invalid_kwargs.keys()) + elif not self._skip_invalid: + raise err + # No need to call _raise_on_invalid_property_error here, + # because we have it set up so that the singular case of calling + # __setitem__ will raise this. If _check_path_in_prop_tree + # raised that in its travels, it will already be in the error + # message. @property def plotly_name(self): @@ -3852,12 +3950,14 @@ def __getitem__(self, prop): # Normalize prop # -------------- # Convert into a property tuple + orig_prop = prop prop = BaseFigure._str_to_dict_path(prop) # Handle remapping # ---------------- if prop and prop[0] in self._mapped_properties: prop = self._mapped_properties[prop[0]] + prop[1:] + orig_prop = _remake_path_from_tuple(prop) # Handle scalar case # ------------------ @@ -3866,7 +3966,7 @@ def __getitem__(self, prop): # Unwrap scalar tuple prop = prop[0] if prop not in self._valid_props: - raise KeyError(prop) + self._raise_on_invalid_property_error(prop) validator = self._get_validator(prop) @@ -3904,6 +4004,9 @@ def __getitem__(self, prop): # ---------------------- # e.g. ('foo', 1), () else: + err = _check_path_in_prop_tree(self, orig_prop) + if err is not None: + raise err res = self for p in prop: res = res[p] @@ -3932,6 +4035,9 @@ def __contains__(self, prop): ------- bool """ + # TODO: We don't want to throw an error in __contains__ because any code that + # relies on it returning False will have to be changed (it will have to + # have a try except block...). prop = BaseFigure._str_to_dict_path(prop) # Handle remapping @@ -4047,6 +4153,9 @@ def __setitem__(self, prop, value): # ---------------------- # e.g. ('foo', 1), () else: + err = _check_path_in_prop_tree(self, orig_prop) + if err is not None: + raise err res = self for p in prop[:-1]: res = res[p] diff --git a/packages/python/plotly/plotly/graph_objs/_deprecations.py b/packages/python/plotly/plotly/graph_objs/_deprecations.py index 6c415286931..d8caedcb79f 100644 --- a/packages/python/plotly/plotly/graph_objs/_deprecations.py +++ b/packages/python/plotly/plotly/graph_objs/_deprecations.py @@ -7,25 +7,25 @@ class Data(list): """ - plotly.graph_objs.Data is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Data is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Data is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Data is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ warnings.warn( @@ -44,19 +44,19 @@ def __init__(self, *args, **kwargs): class Annotations(list): """ - plotly.graph_objs.Annotations is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotations is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Annotations is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotations is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ warnings.warn( @@ -72,17 +72,17 @@ def __init__(self, *args, **kwargs): class Frames(list): """ - plotly.graph_objs.Frames is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Frame + plotly.graph_objs.Frames is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Frame """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Frames is deprecated. -Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Frame + plotly.graph_objs.Frames is deprecated. + Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Frame """ warnings.warn( @@ -97,19 +97,19 @@ def __init__(self, *args, **kwargs): class AngularAxis(dict): """ - plotly.graph_objs.AngularAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.AngularAxis - - plotly.graph_objs.layout.polar.AngularAxis + plotly.graph_objs.AngularAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.AngularAxis + - plotly.graph_objs.layout.polar.AngularAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.AngularAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.AngularAxis - - plotly.graph_objs.layout.polar.AngularAxis + plotly.graph_objs.AngularAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.AngularAxis + - plotly.graph_objs.layout.polar.AngularAxis """ warnings.warn( @@ -125,19 +125,19 @@ def __init__(self, *args, **kwargs): class Annotation(dict): """ - plotly.graph_objs.Annotation is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotation is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Annotation is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotation is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ warnings.warn( @@ -153,21 +153,21 @@ def __init__(self, *args, **kwargs): class ColorBar(dict): """ - plotly.graph_objs.ColorBar is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.marker.ColorBar - - plotly.graph_objs.surface.ColorBar - - etc. + plotly.graph_objs.ColorBar is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.marker.ColorBar + - plotly.graph_objs.surface.ColorBar + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ColorBar is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.marker.ColorBar - - plotly.graph_objs.surface.ColorBar - - etc. + plotly.graph_objs.ColorBar is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.marker.ColorBar + - plotly.graph_objs.surface.ColorBar + - etc. """ warnings.warn( @@ -184,21 +184,21 @@ def __init__(self, *args, **kwargs): class Contours(dict): """ - plotly.graph_objs.Contours is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.contour.Contours - - plotly.graph_objs.surface.Contours - - etc. + plotly.graph_objs.Contours is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.contour.Contours + - plotly.graph_objs.surface.Contours + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Contours is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.contour.Contours - - plotly.graph_objs.surface.Contours - - etc. + plotly.graph_objs.Contours is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.contour.Contours + - plotly.graph_objs.surface.Contours + - etc. """ warnings.warn( @@ -215,21 +215,21 @@ def __init__(self, *args, **kwargs): class ErrorX(dict): """ - plotly.graph_objs.ErrorX is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorX - - plotly.graph_objs.histogram.ErrorX - - etc. + plotly.graph_objs.ErrorX is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorX + - plotly.graph_objs.histogram.ErrorX + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorX is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorX - - plotly.graph_objs.histogram.ErrorX - - etc. + plotly.graph_objs.ErrorX is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorX + - plotly.graph_objs.histogram.ErrorX + - etc. """ warnings.warn( @@ -246,21 +246,21 @@ def __init__(self, *args, **kwargs): class ErrorY(dict): """ - plotly.graph_objs.ErrorY is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorY - - plotly.graph_objs.histogram.ErrorY - - etc. + plotly.graph_objs.ErrorY is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorY + - plotly.graph_objs.histogram.ErrorY + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorY is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorY - - plotly.graph_objs.histogram.ErrorY - - etc. + plotly.graph_objs.ErrorY is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorY + - plotly.graph_objs.histogram.ErrorY + - etc. """ warnings.warn( @@ -277,17 +277,17 @@ def __init__(self, *args, **kwargs): class ErrorZ(dict): """ - plotly.graph_objs.ErrorZ is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter3d.ErrorZ + plotly.graph_objs.ErrorZ is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter3d.ErrorZ """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorZ is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter3d.ErrorZ + plotly.graph_objs.ErrorZ is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter3d.ErrorZ """ warnings.warn( @@ -302,21 +302,21 @@ def __init__(self, *args, **kwargs): class Font(dict): """ - plotly.graph_objs.Font is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Font - - plotly.graph_objs.layout.hoverlabel.Font - - etc. + plotly.graph_objs.Font is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Font + - plotly.graph_objs.layout.hoverlabel.Font + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Font is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Font - - plotly.graph_objs.layout.hoverlabel.Font - - etc. + plotly.graph_objs.Font is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Font + - plotly.graph_objs.layout.hoverlabel.Font + - etc. """ warnings.warn( @@ -333,17 +333,17 @@ def __init__(self, *args, **kwargs): class Legend(dict): """ - plotly.graph_objs.Legend is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Legend + plotly.graph_objs.Legend is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Legend """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Legend is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Legend + plotly.graph_objs.Legend is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Legend """ warnings.warn( @@ -358,21 +358,21 @@ def __init__(self, *args, **kwargs): class Line(dict): """ - plotly.graph_objs.Line is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Line - - plotly.graph_objs.layout.shape.Line - - etc. + plotly.graph_objs.Line is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Line + - plotly.graph_objs.layout.shape.Line + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Line is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Line - - plotly.graph_objs.layout.shape.Line - - etc. + plotly.graph_objs.Line is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Line + - plotly.graph_objs.layout.shape.Line + - etc. """ warnings.warn( @@ -389,17 +389,17 @@ def __init__(self, *args, **kwargs): class Margin(dict): """ - plotly.graph_objs.Margin is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Margin + plotly.graph_objs.Margin is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Margin """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Margin is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Margin + plotly.graph_objs.Margin is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Margin """ warnings.warn( @@ -414,21 +414,21 @@ def __init__(self, *args, **kwargs): class Marker(dict): """ - plotly.graph_objs.Marker is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Marker - - plotly.graph_objs.histogram.selected.Marker - - etc. + plotly.graph_objs.Marker is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Marker + - plotly.graph_objs.histogram.selected.Marker + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Marker is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Marker - - plotly.graph_objs.histogram.selected.Marker - - etc. + plotly.graph_objs.Marker is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Marker + - plotly.graph_objs.histogram.selected.Marker + - etc. """ warnings.warn( @@ -445,19 +445,19 @@ def __init__(self, *args, **kwargs): class RadialAxis(dict): """ - plotly.graph_objs.RadialAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.RadialAxis - - plotly.graph_objs.layout.polar.RadialAxis + plotly.graph_objs.RadialAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.RadialAxis + - plotly.graph_objs.layout.polar.RadialAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.RadialAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.RadialAxis - - plotly.graph_objs.layout.polar.RadialAxis + plotly.graph_objs.RadialAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.RadialAxis + - plotly.graph_objs.layout.polar.RadialAxis """ warnings.warn( @@ -473,17 +473,17 @@ def __init__(self, *args, **kwargs): class Scene(dict): """ - plotly.graph_objs.Scene is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Scene + plotly.graph_objs.Scene is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Scene """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Scene is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Scene + plotly.graph_objs.Scene is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Scene """ warnings.warn( @@ -498,19 +498,19 @@ def __init__(self, *args, **kwargs): class Stream(dict): """ - plotly.graph_objs.Stream is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Stream - - plotly.graph_objs.area.Stream + plotly.graph_objs.Stream is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Stream + - plotly.graph_objs.area.Stream """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Stream is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Stream - - plotly.graph_objs.area.Stream + plotly.graph_objs.Stream is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Stream + - plotly.graph_objs.area.Stream """ warnings.warn( @@ -526,19 +526,19 @@ def __init__(self, *args, **kwargs): class XAxis(dict): """ - plotly.graph_objs.XAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.XAxis - - plotly.graph_objs.layout.scene.XAxis + plotly.graph_objs.XAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.XAxis + - plotly.graph_objs.layout.scene.XAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.XAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.XAxis - - plotly.graph_objs.layout.scene.XAxis + plotly.graph_objs.XAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.XAxis + - plotly.graph_objs.layout.scene.XAxis """ warnings.warn( @@ -554,19 +554,19 @@ def __init__(self, *args, **kwargs): class YAxis(dict): """ - plotly.graph_objs.YAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.YAxis - - plotly.graph_objs.layout.scene.YAxis + plotly.graph_objs.YAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.YAxis + - plotly.graph_objs.layout.scene.YAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.YAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.YAxis - - plotly.graph_objs.layout.scene.YAxis + plotly.graph_objs.YAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.YAxis + - plotly.graph_objs.layout.scene.YAxis """ warnings.warn( @@ -582,17 +582,17 @@ def __init__(self, *args, **kwargs): class ZAxis(dict): """ - plotly.graph_objs.ZAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.scene.ZAxis + plotly.graph_objs.ZAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.scene.ZAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ZAxis is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.layout.scene.ZAxis + plotly.graph_objs.ZAxis is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.layout.scene.ZAxis """ warnings.warn( @@ -607,19 +607,19 @@ def __init__(self, *args, **kwargs): class XBins(dict): """ - plotly.graph_objs.XBins is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.XBins - - plotly.graph_objs.histogram2d.XBins + plotly.graph_objs.XBins is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.XBins + - plotly.graph_objs.histogram2d.XBins """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.XBins is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.XBins - - plotly.graph_objs.histogram2d.XBins + plotly.graph_objs.XBins is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.XBins + - plotly.graph_objs.histogram2d.XBins """ warnings.warn( @@ -635,19 +635,19 @@ def __init__(self, *args, **kwargs): class YBins(dict): """ - plotly.graph_objs.YBins is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.YBins - - plotly.graph_objs.histogram2d.YBins + plotly.graph_objs.YBins is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.YBins + - plotly.graph_objs.histogram2d.YBins """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.YBins is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.YBins - - plotly.graph_objs.histogram2d.YBins + plotly.graph_objs.YBins is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.YBins + - plotly.graph_objs.histogram2d.YBins """ warnings.warn( @@ -663,25 +663,25 @@ def __init__(self, *args, **kwargs): class Trace(dict): """ - plotly.graph_objs.Trace is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Trace is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Trace is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Trace is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ warnings.warn( @@ -700,17 +700,17 @@ def __init__(self, *args, **kwargs): class Histogram2dcontour(dict): """ - plotly.graph_objs.Histogram2dcontour is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.Histogram2dContour + plotly.graph_objs.Histogram2dcontour is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.Histogram2dContour """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Histogram2dcontour is deprecated. -Please replace it with one of the following more specific types - - plotly.graph_objs.Histogram2dContour + plotly.graph_objs.Histogram2dcontour is deprecated. + Please replace it with one of the following more specific types + - plotly.graph_objs.Histogram2dContour """ warnings.warn( diff --git a/packages/python/plotly/plotly/graph_objs/layout/_annotation.py b/packages/python/plotly/plotly/graph_objs/layout/_annotation.py index 162d001b04c..755d4d84b6f 100644 --- a/packages/python/plotly/plotly/graph_objs/layout/_annotation.py +++ b/packages/python/plotly/plotly/graph_objs/layout/_annotation.py @@ -63,7 +63,7 @@ def align(self): an effect only if `text` spans two or more lines (i.e. `text` contains one or more
HTML tags) or if an explicit width is set to override the text width. - + The 'align' property is an enumeration that may be specified as: - One of the following enumeration values: ['left', 'center', 'right'] @@ -84,7 +84,7 @@ def align(self, val): def arrowcolor(self): """ Sets the color of the annotation arrow. - + The 'arrowcolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -143,7 +143,7 @@ def arrowcolor(self, val): def arrowhead(self): """ Sets the end annotation arrow head style. - + The 'arrowhead' property is a integer and may be specified as: - An int (or float that will be cast to an int) in the interval [0, 8] @@ -164,7 +164,7 @@ def arrowhead(self, val): def arrowside(self): """ Sets the annotation arrow head position. - + The 'arrowside' property is a flaglist and may be specified as a string containing: - Any combination of ['end', 'start'] joined with '+' characters @@ -189,7 +189,7 @@ def arrowsize(self): Sets the size of the end annotation arrow head, relative to `arrowwidth`. A value of 1 (default) gives a head about 3x as wide as the line. - + The 'arrowsize' property is a number and may be specified as: - An int or float in the interval [0.3, inf] @@ -209,7 +209,7 @@ def arrowsize(self, val): def arrowwidth(self): """ Sets the width (in px) of annotation arrow line. - + The 'arrowwidth' property is a number and may be specified as: - An int or float in the interval [0.1, inf] @@ -234,7 +234,7 @@ def ax(self): `axref` is not `pixel` and is exactly the same as `xref`, this is an absolute value on that axis, like `x`, specified in the same coordinates as `xref`. - + The 'ax' property accepts values of any type Returns @@ -273,7 +273,7 @@ def axref(self): continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. - + The 'axref' property is an enumeration that may be specified as: - One of the following enumeration values: ['pixel'] @@ -301,7 +301,7 @@ def ay(self): `ayref` is not `pixel` and is exactly the same as `yref`, this is an absolute value on that axis, like `y`, specified in the same coordinates as `yref`. - + The 'ay' property accepts values of any type Returns @@ -319,6 +319,14 @@ def ay(self, val): @property def ayref(self): """ + Indicates in what terms the tail of the annotation (ax,ay) is + specified. If `pixel`, `ay` is a relative offset in pixels + from `y`. If set to a y axis id (e.g. "y" or "y2"), `ay` is + specified in the same terms as that axis. This is useful for + trendline annotations which should continue to indicate the + correct trend when zoomed. + +======= Indicates in what coordinates the tail of the annotation (ax,ay) is specified. If set to a ay axis id (e.g. "ay" or "ay2"), the `ay` position refers to a ay coordinate. If set to @@ -340,7 +348,7 @@ def ayref(self): continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. - + The 'ayref' property is an enumeration that may be specified as: - One of the following enumeration values: ['pixel'] @@ -363,7 +371,7 @@ def ayref(self, val): def bgcolor(self): """ Sets the background color of the annotation. - + The 'bgcolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -422,7 +430,7 @@ def bgcolor(self, val): def bordercolor(self): """ Sets the color of the border enclosing the annotation `text`. - + The 'bordercolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -482,7 +490,7 @@ def borderpad(self): """ Sets the padding (in px) between the `text` and the enclosing border. - + The 'borderpad' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -503,7 +511,7 @@ def borderwidth(self): """ Sets the width (in px) of the border enclosing the annotation `text`. - + The 'borderwidth' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -528,7 +536,7 @@ def captureevents(self): default `captureevents` is False unless `hovertext` is provided. If you use the event `plotly_clickannotation` without `hovertext` you must explicitly enable `captureevents`. - + The 'captureevents' property must be specified as a bool (either True, or False) @@ -559,7 +567,7 @@ def clicktoshow(self): and/or `yclick`. This is useful for example to label the side of a bar. To label markers though, `standoff` is preferred over `xclick` and `yclick`. - + The 'clicktoshow' property is an enumeration that may be specified as: - One of the following enumeration values: [False, 'onoff', 'onout'] @@ -580,17 +588,17 @@ def clicktoshow(self, val): def font(self): """ Sets the annotation text font. - + The 'font' property is an instance of Font that may be specified as: - An instance of :class:`plotly.graph_objs.layout.annotation.Font` - A dict of string/value properties that will be passed to the Font constructor - + Supported dict properties: - + color - + family HTML font family - the typeface that will be applied by the web browser. The web browser @@ -627,7 +635,7 @@ def height(self): """ Sets an explicit height for the text box. null (default) lets the text set the box height. Taller text will be clipped. - + The 'height' property is a number and may be specified as: - An int or float in the interval [1, inf] @@ -651,9 +659,9 @@ def hoverlabel(self): - An instance of :class:`plotly.graph_objs.layout.annotation.Hoverlabel` - A dict of string/value properties that will be passed to the Hoverlabel constructor - + Supported dict properties: - + bgcolor Sets the background color of the hover label. By default uses the annotation's `bgcolor` made @@ -684,7 +692,7 @@ def hovertext(self): """ Sets text to appear when hovering over this annotation. If omitted or blank, no hover label will appear. - + The 'hovertext' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -711,7 +719,7 @@ def name(self): `name` alongside your modifications (including `visible: false` or `enabled: false` to hide it). Has no effect outside of a template. - + The 'name' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -732,7 +740,7 @@ def name(self, val): def opacity(self): """ Sets the opacity of the annotation (text + arrow). - + The 'opacity' property is a number and may be specified as: - An int or float in the interval [0, 1] @@ -754,7 +762,7 @@ def showarrow(self): Determines whether or not the annotation is drawn with an arrow. If True, `text` is placed near the arrow's tail. If False, `text` lines up with the `x` and `y` provided. - + The 'showarrow' property must be specified as a bool (either True, or False) @@ -778,7 +786,7 @@ def standoff(self): edge of a marker independent of zoom. Note that this shortens the arrow from the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which moves everything by this amount. - + The 'standoff' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -798,7 +806,7 @@ def standoff(self, val): def startarrowhead(self): """ Sets the start annotation arrow head style. - + The 'startarrowhead' property is a integer and may be specified as: - An int (or float that will be cast to an int) in the interval [0, 8] @@ -821,7 +829,7 @@ def startarrowsize(self): Sets the size of the start annotation arrow head, relative to `arrowwidth`. A value of 1 (default) gives a head about 3x as wide as the line. - + The 'startarrowsize' property is a number and may be specified as: - An int or float in the interval [0.3, inf] @@ -845,7 +853,7 @@ def startstandoff(self): the edge of a marker independent of zoom. Note that this shortens the arrow from the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which moves everything by this amount. - + The 'startstandoff' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -872,7 +880,7 @@ def templateitemname(self): `enabled: false` to hide it). If there is no template or no matching item, this item will be hidden unless you explicitly show it with `visible: true`. - + The 'templateitemname' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -896,7 +904,7 @@ def text(self): subset of HTML tags to do things like newline (
), bold (), italics (), hyperlinks (). Tags , , are also supported. - + The 'text' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -918,7 +926,7 @@ def textangle(self): """ Sets the angle at which the `text` is drawn with respect to the horizontal. - + The 'textangle' property is a angle (in degrees) that may be specified as a number between -180 and 180. Numeric values outside this range are converted to the equivalent value @@ -942,7 +950,7 @@ def valign(self): Sets the vertical alignment of the `text` within the box. Has an effect only if an explicit height is set to override the text height. - + The 'valign' property is an enumeration that may be specified as: - One of the following enumeration values: ['top', 'middle', 'bottom'] @@ -963,7 +971,7 @@ def valign(self, val): def visible(self): """ Determines whether or not this annotation is visible. - + The 'visible' property must be specified as a bool (either True, or False) @@ -985,7 +993,7 @@ def width(self): Sets an explicit width for the text box. null (default) lets the text set the box width. Wider text will be clipped. There is no automatic wrapping; use
to start a new line. - + The 'width' property is a number and may be specified as: - An int or float in the interval [1, inf] @@ -1011,7 +1019,7 @@ def x(self): converted to strings. If the axis `type` is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears. - + The 'x' property accepts values of any type Returns @@ -1038,7 +1046,7 @@ def xanchor(self): for data-referenced annotations or if there is an arrow, whereas for paper-referenced with no arrow, the anchor picked corresponds to the closest side. - + The 'xanchor' property is an enumeration that may be specified as: - One of the following enumeration values: ['auto', 'left', 'center', 'right'] @@ -1060,7 +1068,7 @@ def xclick(self): """ Toggle this annotation when clicking a data point whose `x` value is `xclick` rather than the annotation's `x` value. - + The 'xclick' property accepts values of any type Returns @@ -1089,7 +1097,7 @@ def xref(self): that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. - + The 'xref' property is an enumeration that may be specified as: - One of the following enumeration values: ['paper'] @@ -1113,7 +1121,7 @@ def xshift(self): """ Shifts the position of the whole annotation and arrow to the right (positive) or left (negative) by this many pixels. - + The 'xshift' property is a number and may be specified as: - An int or float @@ -1139,7 +1147,7 @@ def y(self): converted to strings. If the axis `type` is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears. - + The 'y' property accepts values of any type Returns @@ -1166,7 +1174,7 @@ def yanchor(self): referenced annotations or if there is an arrow, whereas for paper-referenced with no arrow, the anchor picked corresponds to the closest side. - + The 'yanchor' property is an enumeration that may be specified as: - One of the following enumeration values: ['auto', 'top', 'middle', 'bottom'] @@ -1188,7 +1196,7 @@ def yclick(self): """ Toggle this annotation when clicking a data point whose `y` value is `yclick` rather than the annotation's `y` value. - + The 'yclick' property accepts values of any type Returns @@ -1217,7 +1225,7 @@ def yref(self): that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. - + The 'yref' property is an enumeration that may be specified as: - One of the following enumeration values: ['paper'] @@ -1241,7 +1249,7 @@ def yshift(self): """ Shifts the position of the whole annotation and arrow up (positive) or down (negative) by this many pixels. - + The 'yshift' property is a number and may be specified as: - An int or float @@ -1590,7 +1598,7 @@ def __init__( ): """ Construct a new Annotation object - + Parameters ---------- arg @@ -1897,8 +1905,8 @@ def __init__( else: raise ValueError( """\ -The first argument to the plotly.graph_objs.layout.Annotation -constructor must be a dict or +The first argument to the plotly.graph_objs.layout.Annotation +constructor must be a dict or an instance of :class:`plotly.graph_objs.layout.Annotation`""" ) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py new file mode 100644 index 00000000000..37f522520d9 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -0,0 +1,223 @@ +import plotly.graph_objects as go +from _plotly_utils.exceptions import PlotlyKeyError +import pytest + + +def error_substr(s, r): + """ remove a part of the error message we don't want to compare """ + return s.replace(r, "") + + +@pytest.fixture +def some_fig(): + fig = go.Figure() + fig.add_trace(go.Scatter(x=[], y=[])) + fig.add_shape(type="rect", x0=1, x1=2, y0=3, y1=4) + fig.add_shape(type="rect", x0=10, x1=20, y0=30, y1=40) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6])) + return fig + + +def test_raises_on_bad_index(some_fig): + # Check indexing errors can be detected when path used as key to go.Figure + try: + x0 = some_fig["layout.shapes[2].x0"] + except IndexError as e: + assert ( + e.args[0].find( + """Bad property path: +layout.shapes[2].x0 + ^""" + ) + >= 0 + ) + + +def test_raises_on_bad_dot_property(some_fig): + + # Check . property lookup errors can be detected when path used as key to + # go.Figure + try: + x2000 = some_fig["layout.shapes[1].x2000"] + except ValueError as e: + assert ( + e.args[0].find( + """Bad property path: +layout.shapes[1].x2000 + ^""" + ) + >= 0 + ) + + +def test_raises_on_bad_ancestor_dot_property(some_fig): + + # Check . property lookup errors but not on the last part of the path + try: + x2000 = some_fig["layout.shapa[1].x2000"] + except ValueError as e: + assert ( + e.args[0].find( + """Bad property path: +layout.shapa[1].x2000 + ^""" + ) + >= 0 + ) + + +def test_raises_on_bad_indexed_underscore_property(some_fig): + + # finds bad part when using the path as a key to figure and throws the error + # for the last good property it found in the path + try: + # get the error without using a path-like key, we compare with this error + some_fig.data[0].line["colr"] = "blue" + except ValueError as e_correct: + # remove "Bad property path: + e_correct_substr = error_substr( + e_correct.args[0], + """ +Bad property path: +colr +^""", + ) + # if the string starts with "Bad property path:" then this test cannot work + # this way. + assert len(e_correct_substr) > 0 + try: + some_fig["data[0].line_colr"] = "blue" + except ValueError as e: + e_substr = error_substr( + e.args[0], + """ +Bad property path: +data[0].line_colr + ^""", + ) + assert ( + ( + e.args[0].find( + """Bad property path: +data[0].line_colr + ^""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) + + try: + # get the error without using a path-like key + some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line=dict(colr="blue"))) + except ValueError as e_correct: + e_correct_substr = error_substr( + e_correct.args[0], + """ +Bad property path: +colr +^""", + ) + # finds bad part when using the path as a keyword argument to a subclass of + # BasePlotlyType and throws the error for the last good property found in + # the path + try: + some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line_colr="blue")) + except ValueError as e: + e_substr = error_substr( + e.args[0], + """ +Bad property path: +line_colr + ^""", + ) + assert ( + ( + e.args[0].find( + """Bad property path: +line_colr + ^""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) + + # finds bad part when using the path as a keyword argument to a subclass of + # BaseFigure and throws the error for the last good property found in + # the path + try: + fig2 = go.Figure(layout=dict(title=dict(txt="two"))) + except ValueError as e_correct: + e_correct_substr = error_substr( + e_correct.args[0], + """ +Bad property path: +txt +^""", + ) + + try: + fig2 = go.Figure(layout_title_txt="two") + except TypeError as e: + # when the Figure constructor sees the same ValueError above, a + # TypeError is raised and adds an error message in front of the same + # ValueError thrown above + e_substr = error_substr( + e.args[0], + """ +Bad property path: +layout_title_txt + ^""", + ) + # also remove the invalid Figure property string added by the Figure constructor + e_substr = error_substr( + e_substr, + """invalid Figure property: layout_title_txt +""", + ) + assert ( + ( + e.args[0].find( + """Bad property path: +layout_title_txt + ^""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) + + # this is like the above test for subclasses of BasePlotlyType but makes sure it + # works when the bad part is not the last part in the path + try: + some_fig.update_layout(geo=dict(ltaxis=dict(showgrid=True))) + except ValueError as e_correct: + e_correct_substr = error_substr( + e_correct.args[0], + """ +Bad property path: +ltaxis +^""", + ) + try: + some_fig.update_layout(geo_ltaxis_showgrid=True) + except ValueError as e: + e_substr = error_substr( + e.args[0], + """ +Bad property path: +geo_ltaxis_showgrid + ^ """, + ) + assert ( + ( + e.args[0].find( + """Bad property path: +geo_ltaxis_showgrid + ^""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) diff --git a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py index 037d2384983..0217f7c236c 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py +++ b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py @@ -39,7 +39,7 @@ def test_initial_access_subplot2(self): self.layout.xaxis2 def test_initial_access_subplot2(self): - with pytest.raises(KeyError): + with pytest.raises(ValueError): self.layout["xaxis2"] def test_assign_subplots(self): From afd5b7a100c25ecf21165f1c70a14656fb5351f4 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 20 Oct 2020 11:28:31 -0400 Subject: [PATCH 02/24] Remove whitespace-only changes --- .../plotly/plotly/graph_objs/_deprecations.py | 424 +++++++++--------- .../plotly/graph_objs/layout/_annotation.py | 108 +++-- 2 files changed, 262 insertions(+), 270 deletions(-) diff --git a/packages/python/plotly/plotly/graph_objs/_deprecations.py b/packages/python/plotly/plotly/graph_objs/_deprecations.py index d8caedcb79f..6c415286931 100644 --- a/packages/python/plotly/plotly/graph_objs/_deprecations.py +++ b/packages/python/plotly/plotly/graph_objs/_deprecations.py @@ -7,25 +7,25 @@ class Data(list): """ - plotly.graph_objs.Data is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Data is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Data is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Data is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ warnings.warn( @@ -44,19 +44,19 @@ def __init__(self, *args, **kwargs): class Annotations(list): """ - plotly.graph_objs.Annotations is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotations is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Annotations is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotations is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ warnings.warn( @@ -72,17 +72,17 @@ def __init__(self, *args, **kwargs): class Frames(list): """ - plotly.graph_objs.Frames is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Frame + plotly.graph_objs.Frames is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Frame """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Frames is deprecated. - Please replace it with a list or tuple of instances of the following types - - plotly.graph_objs.Frame + plotly.graph_objs.Frames is deprecated. +Please replace it with a list or tuple of instances of the following types + - plotly.graph_objs.Frame """ warnings.warn( @@ -97,19 +97,19 @@ def __init__(self, *args, **kwargs): class AngularAxis(dict): """ - plotly.graph_objs.AngularAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.AngularAxis - - plotly.graph_objs.layout.polar.AngularAxis + plotly.graph_objs.AngularAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.AngularAxis + - plotly.graph_objs.layout.polar.AngularAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.AngularAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.AngularAxis - - plotly.graph_objs.layout.polar.AngularAxis + plotly.graph_objs.AngularAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.AngularAxis + - plotly.graph_objs.layout.polar.AngularAxis """ warnings.warn( @@ -125,19 +125,19 @@ def __init__(self, *args, **kwargs): class Annotation(dict): """ - plotly.graph_objs.Annotation is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotation is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Annotation is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Annotation - - plotly.graph_objs.layout.scene.Annotation + plotly.graph_objs.Annotation is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Annotation + - plotly.graph_objs.layout.scene.Annotation """ warnings.warn( @@ -153,21 +153,21 @@ def __init__(self, *args, **kwargs): class ColorBar(dict): """ - plotly.graph_objs.ColorBar is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.marker.ColorBar - - plotly.graph_objs.surface.ColorBar - - etc. + plotly.graph_objs.ColorBar is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.marker.ColorBar + - plotly.graph_objs.surface.ColorBar + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ColorBar is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.marker.ColorBar - - plotly.graph_objs.surface.ColorBar - - etc. + plotly.graph_objs.ColorBar is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.marker.ColorBar + - plotly.graph_objs.surface.ColorBar + - etc. """ warnings.warn( @@ -184,21 +184,21 @@ def __init__(self, *args, **kwargs): class Contours(dict): """ - plotly.graph_objs.Contours is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.contour.Contours - - plotly.graph_objs.surface.Contours - - etc. + plotly.graph_objs.Contours is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.contour.Contours + - plotly.graph_objs.surface.Contours + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Contours is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.contour.Contours - - plotly.graph_objs.surface.Contours - - etc. + plotly.graph_objs.Contours is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.contour.Contours + - plotly.graph_objs.surface.Contours + - etc. """ warnings.warn( @@ -215,21 +215,21 @@ def __init__(self, *args, **kwargs): class ErrorX(dict): """ - plotly.graph_objs.ErrorX is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorX - - plotly.graph_objs.histogram.ErrorX - - etc. + plotly.graph_objs.ErrorX is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorX + - plotly.graph_objs.histogram.ErrorX + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorX is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorX - - plotly.graph_objs.histogram.ErrorX - - etc. + plotly.graph_objs.ErrorX is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorX + - plotly.graph_objs.histogram.ErrorX + - etc. """ warnings.warn( @@ -246,21 +246,21 @@ def __init__(self, *args, **kwargs): class ErrorY(dict): """ - plotly.graph_objs.ErrorY is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorY - - plotly.graph_objs.histogram.ErrorY - - etc. + plotly.graph_objs.ErrorY is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorY + - plotly.graph_objs.histogram.ErrorY + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorY is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.ErrorY - - plotly.graph_objs.histogram.ErrorY - - etc. + plotly.graph_objs.ErrorY is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.ErrorY + - plotly.graph_objs.histogram.ErrorY + - etc. """ warnings.warn( @@ -277,17 +277,17 @@ def __init__(self, *args, **kwargs): class ErrorZ(dict): """ - plotly.graph_objs.ErrorZ is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter3d.ErrorZ + plotly.graph_objs.ErrorZ is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter3d.ErrorZ """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ErrorZ is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter3d.ErrorZ + plotly.graph_objs.ErrorZ is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter3d.ErrorZ """ warnings.warn( @@ -302,21 +302,21 @@ def __init__(self, *args, **kwargs): class Font(dict): """ - plotly.graph_objs.Font is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Font - - plotly.graph_objs.layout.hoverlabel.Font - - etc. + plotly.graph_objs.Font is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Font + - plotly.graph_objs.layout.hoverlabel.Font + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Font is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Font - - plotly.graph_objs.layout.hoverlabel.Font - - etc. + plotly.graph_objs.Font is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Font + - plotly.graph_objs.layout.hoverlabel.Font + - etc. """ warnings.warn( @@ -333,17 +333,17 @@ def __init__(self, *args, **kwargs): class Legend(dict): """ - plotly.graph_objs.Legend is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Legend + plotly.graph_objs.Legend is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Legend """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Legend is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Legend + plotly.graph_objs.Legend is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Legend """ warnings.warn( @@ -358,21 +358,21 @@ def __init__(self, *args, **kwargs): class Line(dict): """ - plotly.graph_objs.Line is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Line - - plotly.graph_objs.layout.shape.Line - - etc. + plotly.graph_objs.Line is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Line + - plotly.graph_objs.layout.shape.Line + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Line is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Line - - plotly.graph_objs.layout.shape.Line - - etc. + plotly.graph_objs.Line is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Line + - plotly.graph_objs.layout.shape.Line + - etc. """ warnings.warn( @@ -389,17 +389,17 @@ def __init__(self, *args, **kwargs): class Margin(dict): """ - plotly.graph_objs.Margin is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Margin + plotly.graph_objs.Margin is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Margin """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Margin is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Margin + plotly.graph_objs.Margin is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Margin """ warnings.warn( @@ -414,21 +414,21 @@ def __init__(self, *args, **kwargs): class Marker(dict): """ - plotly.graph_objs.Marker is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Marker - - plotly.graph_objs.histogram.selected.Marker - - etc. + plotly.graph_objs.Marker is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Marker + - plotly.graph_objs.histogram.selected.Marker + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Marker is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Marker - - plotly.graph_objs.histogram.selected.Marker - - etc. + plotly.graph_objs.Marker is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Marker + - plotly.graph_objs.histogram.selected.Marker + - etc. """ warnings.warn( @@ -445,19 +445,19 @@ def __init__(self, *args, **kwargs): class RadialAxis(dict): """ - plotly.graph_objs.RadialAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.RadialAxis - - plotly.graph_objs.layout.polar.RadialAxis + plotly.graph_objs.RadialAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.RadialAxis + - plotly.graph_objs.layout.polar.RadialAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.RadialAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.RadialAxis - - plotly.graph_objs.layout.polar.RadialAxis + plotly.graph_objs.RadialAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.RadialAxis + - plotly.graph_objs.layout.polar.RadialAxis """ warnings.warn( @@ -473,17 +473,17 @@ def __init__(self, *args, **kwargs): class Scene(dict): """ - plotly.graph_objs.Scene is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Scene + plotly.graph_objs.Scene is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Scene """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Scene is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.Scene + plotly.graph_objs.Scene is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.Scene """ warnings.warn( @@ -498,19 +498,19 @@ def __init__(self, *args, **kwargs): class Stream(dict): """ - plotly.graph_objs.Stream is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Stream - - plotly.graph_objs.area.Stream + plotly.graph_objs.Stream is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Stream + - plotly.graph_objs.area.Stream """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Stream is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.scatter.Stream - - plotly.graph_objs.area.Stream + plotly.graph_objs.Stream is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.scatter.Stream + - plotly.graph_objs.area.Stream """ warnings.warn( @@ -526,19 +526,19 @@ def __init__(self, *args, **kwargs): class XAxis(dict): """ - plotly.graph_objs.XAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.XAxis - - plotly.graph_objs.layout.scene.XAxis + plotly.graph_objs.XAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.XAxis + - plotly.graph_objs.layout.scene.XAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.XAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.XAxis - - plotly.graph_objs.layout.scene.XAxis + plotly.graph_objs.XAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.XAxis + - plotly.graph_objs.layout.scene.XAxis """ warnings.warn( @@ -554,19 +554,19 @@ def __init__(self, *args, **kwargs): class YAxis(dict): """ - plotly.graph_objs.YAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.YAxis - - plotly.graph_objs.layout.scene.YAxis + plotly.graph_objs.YAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.YAxis + - plotly.graph_objs.layout.scene.YAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.YAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.YAxis - - plotly.graph_objs.layout.scene.YAxis + plotly.graph_objs.YAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.YAxis + - plotly.graph_objs.layout.scene.YAxis """ warnings.warn( @@ -582,17 +582,17 @@ def __init__(self, *args, **kwargs): class ZAxis(dict): """ - plotly.graph_objs.ZAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.scene.ZAxis + plotly.graph_objs.ZAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.scene.ZAxis """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.ZAxis is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.layout.scene.ZAxis + plotly.graph_objs.ZAxis is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.layout.scene.ZAxis """ warnings.warn( @@ -607,19 +607,19 @@ def __init__(self, *args, **kwargs): class XBins(dict): """ - plotly.graph_objs.XBins is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.XBins - - plotly.graph_objs.histogram2d.XBins + plotly.graph_objs.XBins is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.XBins + - plotly.graph_objs.histogram2d.XBins """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.XBins is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.XBins - - plotly.graph_objs.histogram2d.XBins + plotly.graph_objs.XBins is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.XBins + - plotly.graph_objs.histogram2d.XBins """ warnings.warn( @@ -635,19 +635,19 @@ def __init__(self, *args, **kwargs): class YBins(dict): """ - plotly.graph_objs.YBins is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.YBins - - plotly.graph_objs.histogram2d.YBins + plotly.graph_objs.YBins is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.YBins + - plotly.graph_objs.histogram2d.YBins """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.YBins is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.histogram.YBins - - plotly.graph_objs.histogram2d.YBins + plotly.graph_objs.YBins is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.histogram.YBins + - plotly.graph_objs.histogram2d.YBins """ warnings.warn( @@ -663,25 +663,25 @@ def __init__(self, *args, **kwargs): class Trace(dict): """ - plotly.graph_objs.Trace is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Trace is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Trace is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.Scatter - - plotly.graph_objs.Bar - - plotly.graph_objs.Area - - plotly.graph_objs.Histogram - - etc. + plotly.graph_objs.Trace is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.Scatter + - plotly.graph_objs.Bar + - plotly.graph_objs.Area + - plotly.graph_objs.Histogram + - etc. """ warnings.warn( @@ -700,17 +700,17 @@ def __init__(self, *args, **kwargs): class Histogram2dcontour(dict): """ - plotly.graph_objs.Histogram2dcontour is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.Histogram2dContour + plotly.graph_objs.Histogram2dcontour is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.Histogram2dContour """ def __init__(self, *args, **kwargs): """ - plotly.graph_objs.Histogram2dcontour is deprecated. - Please replace it with one of the following more specific types - - plotly.graph_objs.Histogram2dContour + plotly.graph_objs.Histogram2dcontour is deprecated. +Please replace it with one of the following more specific types + - plotly.graph_objs.Histogram2dContour """ warnings.warn( diff --git a/packages/python/plotly/plotly/graph_objs/layout/_annotation.py b/packages/python/plotly/plotly/graph_objs/layout/_annotation.py index 755d4d84b6f..162d001b04c 100644 --- a/packages/python/plotly/plotly/graph_objs/layout/_annotation.py +++ b/packages/python/plotly/plotly/graph_objs/layout/_annotation.py @@ -63,7 +63,7 @@ def align(self): an effect only if `text` spans two or more lines (i.e. `text` contains one or more
HTML tags) or if an explicit width is set to override the text width. - + The 'align' property is an enumeration that may be specified as: - One of the following enumeration values: ['left', 'center', 'right'] @@ -84,7 +84,7 @@ def align(self, val): def arrowcolor(self): """ Sets the color of the annotation arrow. - + The 'arrowcolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -143,7 +143,7 @@ def arrowcolor(self, val): def arrowhead(self): """ Sets the end annotation arrow head style. - + The 'arrowhead' property is a integer and may be specified as: - An int (or float that will be cast to an int) in the interval [0, 8] @@ -164,7 +164,7 @@ def arrowhead(self, val): def arrowside(self): """ Sets the annotation arrow head position. - + The 'arrowside' property is a flaglist and may be specified as a string containing: - Any combination of ['end', 'start'] joined with '+' characters @@ -189,7 +189,7 @@ def arrowsize(self): Sets the size of the end annotation arrow head, relative to `arrowwidth`. A value of 1 (default) gives a head about 3x as wide as the line. - + The 'arrowsize' property is a number and may be specified as: - An int or float in the interval [0.3, inf] @@ -209,7 +209,7 @@ def arrowsize(self, val): def arrowwidth(self): """ Sets the width (in px) of annotation arrow line. - + The 'arrowwidth' property is a number and may be specified as: - An int or float in the interval [0.1, inf] @@ -234,7 +234,7 @@ def ax(self): `axref` is not `pixel` and is exactly the same as `xref`, this is an absolute value on that axis, like `x`, specified in the same coordinates as `xref`. - + The 'ax' property accepts values of any type Returns @@ -273,7 +273,7 @@ def axref(self): continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. - + The 'axref' property is an enumeration that may be specified as: - One of the following enumeration values: ['pixel'] @@ -301,7 +301,7 @@ def ay(self): `ayref` is not `pixel` and is exactly the same as `yref`, this is an absolute value on that axis, like `y`, specified in the same coordinates as `yref`. - + The 'ay' property accepts values of any type Returns @@ -319,14 +319,6 @@ def ay(self, val): @property def ayref(self): """ - Indicates in what terms the tail of the annotation (ax,ay) is - specified. If `pixel`, `ay` is a relative offset in pixels - from `y`. If set to a y axis id (e.g. "y" or "y2"), `ay` is - specified in the same terms as that axis. This is useful for - trendline annotations which should continue to indicate the - correct trend when zoomed. - -======= Indicates in what coordinates the tail of the annotation (ax,ay) is specified. If set to a ay axis id (e.g. "ay" or "ay2"), the `ay` position refers to a ay coordinate. If set to @@ -348,7 +340,7 @@ def ayref(self): continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. - + The 'ayref' property is an enumeration that may be specified as: - One of the following enumeration values: ['pixel'] @@ -371,7 +363,7 @@ def ayref(self, val): def bgcolor(self): """ Sets the background color of the annotation. - + The 'bgcolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -430,7 +422,7 @@ def bgcolor(self, val): def bordercolor(self): """ Sets the color of the border enclosing the annotation `text`. - + The 'bordercolor' property is a color and may be specified as: - A hex string (e.g. '#ff0000') - An rgb/rgba string (e.g. 'rgb(255,0,0)') @@ -490,7 +482,7 @@ def borderpad(self): """ Sets the padding (in px) between the `text` and the enclosing border. - + The 'borderpad' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -511,7 +503,7 @@ def borderwidth(self): """ Sets the width (in px) of the border enclosing the annotation `text`. - + The 'borderwidth' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -536,7 +528,7 @@ def captureevents(self): default `captureevents` is False unless `hovertext` is provided. If you use the event `plotly_clickannotation` without `hovertext` you must explicitly enable `captureevents`. - + The 'captureevents' property must be specified as a bool (either True, or False) @@ -567,7 +559,7 @@ def clicktoshow(self): and/or `yclick`. This is useful for example to label the side of a bar. To label markers though, `standoff` is preferred over `xclick` and `yclick`. - + The 'clicktoshow' property is an enumeration that may be specified as: - One of the following enumeration values: [False, 'onoff', 'onout'] @@ -588,17 +580,17 @@ def clicktoshow(self, val): def font(self): """ Sets the annotation text font. - + The 'font' property is an instance of Font that may be specified as: - An instance of :class:`plotly.graph_objs.layout.annotation.Font` - A dict of string/value properties that will be passed to the Font constructor - + Supported dict properties: - + color - + family HTML font family - the typeface that will be applied by the web browser. The web browser @@ -635,7 +627,7 @@ def height(self): """ Sets an explicit height for the text box. null (default) lets the text set the box height. Taller text will be clipped. - + The 'height' property is a number and may be specified as: - An int or float in the interval [1, inf] @@ -659,9 +651,9 @@ def hoverlabel(self): - An instance of :class:`plotly.graph_objs.layout.annotation.Hoverlabel` - A dict of string/value properties that will be passed to the Hoverlabel constructor - + Supported dict properties: - + bgcolor Sets the background color of the hover label. By default uses the annotation's `bgcolor` made @@ -692,7 +684,7 @@ def hovertext(self): """ Sets text to appear when hovering over this annotation. If omitted or blank, no hover label will appear. - + The 'hovertext' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -719,7 +711,7 @@ def name(self): `name` alongside your modifications (including `visible: false` or `enabled: false` to hide it). Has no effect outside of a template. - + The 'name' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -740,7 +732,7 @@ def name(self, val): def opacity(self): """ Sets the opacity of the annotation (text + arrow). - + The 'opacity' property is a number and may be specified as: - An int or float in the interval [0, 1] @@ -762,7 +754,7 @@ def showarrow(self): Determines whether or not the annotation is drawn with an arrow. If True, `text` is placed near the arrow's tail. If False, `text` lines up with the `x` and `y` provided. - + The 'showarrow' property must be specified as a bool (either True, or False) @@ -786,7 +778,7 @@ def standoff(self): edge of a marker independent of zoom. Note that this shortens the arrow from the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which moves everything by this amount. - + The 'standoff' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -806,7 +798,7 @@ def standoff(self, val): def startarrowhead(self): """ Sets the start annotation arrow head style. - + The 'startarrowhead' property is a integer and may be specified as: - An int (or float that will be cast to an int) in the interval [0, 8] @@ -829,7 +821,7 @@ def startarrowsize(self): Sets the size of the start annotation arrow head, relative to `arrowwidth`. A value of 1 (default) gives a head about 3x as wide as the line. - + The 'startarrowsize' property is a number and may be specified as: - An int or float in the interval [0.3, inf] @@ -853,7 +845,7 @@ def startstandoff(self): the edge of a marker independent of zoom. Note that this shortens the arrow from the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which moves everything by this amount. - + The 'startstandoff' property is a number and may be specified as: - An int or float in the interval [0, inf] @@ -880,7 +872,7 @@ def templateitemname(self): `enabled: false` to hide it). If there is no template or no matching item, this item will be hidden unless you explicitly show it with `visible: true`. - + The 'templateitemname' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -904,7 +896,7 @@ def text(self): subset of HTML tags to do things like newline (
), bold (), italics (), hyperlinks (). Tags , , are also supported. - + The 'text' property is a string and must be specified as: - A string - A number that will be converted to a string @@ -926,7 +918,7 @@ def textangle(self): """ Sets the angle at which the `text` is drawn with respect to the horizontal. - + The 'textangle' property is a angle (in degrees) that may be specified as a number between -180 and 180. Numeric values outside this range are converted to the equivalent value @@ -950,7 +942,7 @@ def valign(self): Sets the vertical alignment of the `text` within the box. Has an effect only if an explicit height is set to override the text height. - + The 'valign' property is an enumeration that may be specified as: - One of the following enumeration values: ['top', 'middle', 'bottom'] @@ -971,7 +963,7 @@ def valign(self, val): def visible(self): """ Determines whether or not this annotation is visible. - + The 'visible' property must be specified as a bool (either True, or False) @@ -993,7 +985,7 @@ def width(self): Sets an explicit width for the text box. null (default) lets the text set the box width. Wider text will be clipped. There is no automatic wrapping; use
to start a new line. - + The 'width' property is a number and may be specified as: - An int or float in the interval [1, inf] @@ -1019,7 +1011,7 @@ def x(self): converted to strings. If the axis `type` is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears. - + The 'x' property accepts values of any type Returns @@ -1046,7 +1038,7 @@ def xanchor(self): for data-referenced annotations or if there is an arrow, whereas for paper-referenced with no arrow, the anchor picked corresponds to the closest side. - + The 'xanchor' property is an enumeration that may be specified as: - One of the following enumeration values: ['auto', 'left', 'center', 'right'] @@ -1068,7 +1060,7 @@ def xclick(self): """ Toggle this annotation when clicking a data point whose `x` value is `xclick` rather than the annotation's `x` value. - + The 'xclick' property accepts values of any type Returns @@ -1097,7 +1089,7 @@ def xref(self): that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. - + The 'xref' property is an enumeration that may be specified as: - One of the following enumeration values: ['paper'] @@ -1121,7 +1113,7 @@ def xshift(self): """ Shifts the position of the whole annotation and arrow to the right (positive) or left (negative) by this many pixels. - + The 'xshift' property is a number and may be specified as: - An int or float @@ -1147,7 +1139,7 @@ def y(self): converted to strings. If the axis `type` is "category", it should be numbers, using the scale where each category is assigned a serial number from zero in the order it appears. - + The 'y' property accepts values of any type Returns @@ -1174,7 +1166,7 @@ def yanchor(self): referenced annotations or if there is an arrow, whereas for paper-referenced with no arrow, the anchor picked corresponds to the closest side. - + The 'yanchor' property is an enumeration that may be specified as: - One of the following enumeration values: ['auto', 'top', 'middle', 'bottom'] @@ -1196,7 +1188,7 @@ def yclick(self): """ Toggle this annotation when clicking a data point whose `y` value is `yclick` rather than the annotation's `y` value. - + The 'yclick' property accepts values of any type Returns @@ -1225,7 +1217,7 @@ def yref(self): that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. - + The 'yref' property is an enumeration that may be specified as: - One of the following enumeration values: ['paper'] @@ -1249,7 +1241,7 @@ def yshift(self): """ Shifts the position of the whole annotation and arrow up (positive) or down (negative) by this many pixels. - + The 'yshift' property is a number and may be specified as: - An int or float @@ -1598,7 +1590,7 @@ def __init__( ): """ Construct a new Annotation object - + Parameters ---------- arg @@ -1905,8 +1897,8 @@ def __init__( else: raise ValueError( """\ -The first argument to the plotly.graph_objs.layout.Annotation -constructor must be a dict or +The first argument to the plotly.graph_objs.layout.Annotation +constructor must be a dict or an instance of :class:`plotly.graph_objs.layout.Annotation`""" ) From b894b89022da068446c8f40d472019dbe98060b3 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 20 Oct 2020 11:42:35 -0400 Subject: [PATCH 03/24] Assert that errors are raised --- .../test_errors/test_dict_path_errors.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 37f522520d9..2521b185129 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -20,9 +20,11 @@ def some_fig(): def test_raises_on_bad_index(some_fig): # Check indexing errors can be detected when path used as key to go.Figure + raised = False try: x0 = some_fig["layout.shapes[2].x0"] except IndexError as e: + raised = True assert ( e.args[0].find( """Bad property path: @@ -31,15 +33,18 @@ def test_raises_on_bad_index(some_fig): ) >= 0 ) + assert raised def test_raises_on_bad_dot_property(some_fig): # Check . property lookup errors can be detected when path used as key to # go.Figure + raised = False try: x2000 = some_fig["layout.shapes[1].x2000"] except ValueError as e: + raised = True assert ( e.args[0].find( """Bad property path: @@ -48,14 +53,17 @@ def test_raises_on_bad_dot_property(some_fig): ) >= 0 ) + assert raised def test_raises_on_bad_ancestor_dot_property(some_fig): # Check . property lookup errors but not on the last part of the path + raised = False try: x2000 = some_fig["layout.shapa[1].x2000"] except ValueError as e: + raised = True assert ( e.args[0].find( """Bad property path: @@ -64,16 +72,19 @@ def test_raises_on_bad_ancestor_dot_property(some_fig): ) >= 0 ) + assert raised def test_raises_on_bad_indexed_underscore_property(some_fig): # finds bad part when using the path as a key to figure and throws the error # for the last good property it found in the path + raised = False try: # get the error without using a path-like key, we compare with this error some_fig.data[0].line["colr"] = "blue" except ValueError as e_correct: + raised = True # remove "Bad property path: e_correct_substr = error_substr( e_correct.args[0], @@ -85,9 +96,13 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # if the string starts with "Bad property path:" then this test cannot work # this way. assert len(e_correct_substr) > 0 + assert raised + + raised = False try: some_fig["data[0].line_colr"] = "blue" except ValueError as e: + raised = True e_substr = error_substr( e.args[0], """ @@ -106,11 +121,14 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): ) and (e_substr == e_correct_substr) ) + assert raised + raised = False try: # get the error without using a path-like key some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line=dict(colr="blue"))) except ValueError as e_correct: + raised = True e_correct_substr = error_substr( e_correct.args[0], """ @@ -118,12 +136,16 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): colr ^""", ) + assert raised + + raised = False # finds bad part when using the path as a keyword argument to a subclass of # BasePlotlyType and throws the error for the last good property found in # the path try: some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line_colr="blue")) except ValueError as e: + raised = True e_substr = error_substr( e.args[0], """ @@ -142,13 +164,16 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): ) and (e_substr == e_correct_substr) ) + assert raised + raised = False # finds bad part when using the path as a keyword argument to a subclass of # BaseFigure and throws the error for the last good property found in # the path try: fig2 = go.Figure(layout=dict(title=dict(txt="two"))) except ValueError as e_correct: + raised = True e_correct_substr = error_substr( e_correct.args[0], """ @@ -156,10 +181,13 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): txt ^""", ) + assert raised + raised = False try: fig2 = go.Figure(layout_title_txt="two") except TypeError as e: + raised = True # when the Figure constructor sees the same ValueError above, a # TypeError is raised and adds an error message in front of the same # ValueError thrown above @@ -187,12 +215,15 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): ) and (e_substr == e_correct_substr) ) + assert raised + raised = False # this is like the above test for subclasses of BasePlotlyType but makes sure it # works when the bad part is not the last part in the path try: some_fig.update_layout(geo=dict(ltaxis=dict(showgrid=True))) except ValueError as e_correct: + raised = True e_correct_substr = error_substr( e_correct.args[0], """ @@ -200,9 +231,13 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): ltaxis ^""", ) + assert raised + + raised = False try: some_fig.update_layout(geo_ltaxis_showgrid=True) except ValueError as e: + raised = True e_substr = error_substr( e.args[0], """ @@ -221,3 +256,4 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): ) and (e_substr == e_correct_substr) ) + assert raised From 4c659db376cc43bc7c76f78573af3eb137daae61 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 20 Oct 2020 13:03:25 -0400 Subject: [PATCH 04/24] Also state when a property doesn't support subscripting Shows the property that doesn't support it. Need to still update the tests though. --- packages/python/plotly/_plotly_utils/utils.py | 32 ++++++++++++---- .../python/plotly/plotly/basedatatypes.py | 37 ++++++++++++++----- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 5deead650b1..11675d5cb1b 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -318,10 +318,11 @@ def split_string_positions(ss): ) -def display_string_positions(p, i=None): +def display_string_positions(p, i=None, offset=0, length=1, char="^", trim=True): """ - Return a string that is whitespace except at p[i] which is replaced with ^. - If i is None then all the indices of the string in p are replaced with ^. + Return a string that is whitespace except at p[i] which is replaced with char. + If i is None then all the indices of the string in p are replaced with char. + Example: >>> ss = ["a.string[0].with_separators"] @@ -334,13 +335,30 @@ def display_string_positions(p, i=None): 'a.string[0].with_separators' >>> display_string_positions(ss_pos,4) ' ^' + >>> display_string_positions(ss_pos,4,offset=1,length=3,char="~",trim=False) + ' ~~~ ' + >>> display_string_positions(ss_pos_) + '^ ^ ^ ^^ ^' :param (list) p: A list of integers. :param (integer|None) i: Optional index of p to display. + :param (integer) offset: Allows adding a number of spaces to the replacement. + :param (integer) length: Allows adding a replacement that is the char + repeated length times. + :param (str) char: allows customizing the replacement character. + :param (boolean) trim: trims the remaining whitespace if True. """ - s = [" " for _ in range(max(p) + 1)] + s = [" " for _ in range(max(p) + 1 + offset + length)] + maxaddr = 0 if i is None: for p_ in p: - s[p_] = "^" + for l in range(length): + maxaddr = p_ + offset + l + s[maxaddr] = char else: - s[p[i]] = "^" - return "".join(s) + for l in range(length): + maxaddr = p[i] + offset + l + s[maxaddr] = char + ret = "".join(s) + if trim: + ret = ret[: maxaddr + 1] + return ret diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index d4f1c6f3d79..04bdeaa6d70 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -119,20 +119,37 @@ def _check_path_in_prop_tree(obj, path): path = _remake_path_from_tuple(path) prop, prop_idcs = _str_to_dict_path_full(path) for i, p in enumerate(prop): + arg = "" try: obj = obj[p] - except (ValueError, KeyError, IndexError) as e: - arg = ( - e.args[0] - + """ + except (ValueError, KeyError, IndexError, TypeError) as e: + arg = e.args[0] + if obj is None: + # If obj doesn't support subscripting, state that and show the + # (valid) property that gives the object that doesn't support + # it. + # 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) + arg += """ +Property does not support subscripting: +%s +%s""" % ( + path, + display_string_positions( + prop_idcs, disp_i, length=len(prop[disp_i]), char="~" + ), + ) + else: + # State that the property for which subscripting was attempted + # is bad and indicate the start of the bad property. + arg += """ Bad property path: -%s""" - % (path,) - ) - arg += """ +%s %s""" % ( - display_string_positions(prop_idcs, i), - ) + path, + display_string_positions(prop_idcs, i, length=1, char="^"), + ) # Make KeyError more pretty by changing it to a PlotlyKeyError, # because the Python interpreter has a special way of printing # KeyError From 2bf2526bfc5e6643c6a7ceb9cf3fd75240a2164b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 20 Oct 2020 13:41:24 -0400 Subject: [PATCH 05/24] Tests for non-subscriptable property errors --- packages/python/plotly/_plotly_utils/utils.py | 2 +- .../test_errors/test_dict_path_errors.py | 90 ++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 11675d5cb1b..44bafbd674e 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -337,7 +337,7 @@ def display_string_positions(p, i=None, offset=0, length=1, char="^", trim=True) ' ^' >>> display_string_positions(ss_pos,4,offset=1,length=3,char="~",trim=False) ' ~~~ ' - >>> display_string_positions(ss_pos_) + >>> display_string_positions(ss_pos) '^ ^ ^ ^^ ^' :param (list) p: A list of integers. :param (integer|None) i: Optional index of p to display. diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 2521b185129..1200e313463 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -77,6 +77,14 @@ def test_raises_on_bad_ancestor_dot_property(some_fig): def test_raises_on_bad_indexed_underscore_property(some_fig): + # The way these tests work is first the error is raised without using + # underscores to get the Exception we expect, then the string showing the + # bad property path is removed (because it will not match the string + # returned when the same error is thrown using underscores). + # Then the error is thrown using underscores and the Exceptions are + # compared, but we adjust the expected bad property error because it will be + # different when underscores are used. + # finds bad part when using the path as a key to figure and throws the error # for the last good property it found in the path raised = False @@ -243,7 +251,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: geo_ltaxis_showgrid - ^ """, + ^""", ) assert ( ( @@ -257,3 +265,83 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): and (e_substr == e_correct_substr) ) assert raised + + +def test_describes_subscripting_error(some_fig): + # This test works like test_raises_on_bad_indexed_underscore_property but + # removes the error raised because the property does not support + # subscripting. + # Note that, to raise the error, we try to access the value rather than + # assign something to it. We have to do this, because Plotly.py tries to + # access the value to see if it is valid, so the error raised has to do with + # subscripting and not assignment (even though we are trying to assign it a + # value). + raised = False + try: + # some_fig.update_traces(text_yo="hey") but without using underscores + some_fig.data[0].text["yo"] + except TypeError as e: + raised = True + e_correct_substr = e.args[0] + assert raised + raised = False + try: + some_fig.update_traces(text_yo="hey") + except TypeError as e: + raised = True + e_substr = error_substr( + e.args[0], + """ +Property does not support subscripting: +text_yo +~~~~""", + ) + assert ( + ( + e.args[0].find( + """ +Property does not support subscripting: +text_yo +~~~~""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) + assert raised + + # Same as previous test but tests deeper path + raised = False + try: + # go.Figure(go.Scatter()).update_traces(textfont_family_yo="hey") but + # without using underscores + some_fig.data[0].textfont.family["yo"] + except TypeError as e: + raised = True + e_correct_substr = e.args[0] + assert raised + raised = False + try: + go.Figure(go.Scatter()).update_traces(textfont_family_yo="hey") + except TypeError as e: + raised = True + e_substr = error_substr( + e.args[0], + """ +Property does not support subscripting: +textfont_family_yo + ~~~~~~""", + ) + assert ( + ( + e.args[0].find( + """ +Property does not support subscripting: +textfont_family_yo + ~~~~~~""" + ) + >= 0 + ) + and (e_substr == e_correct_substr) + ) + assert raised From 56959690df481d26e75381c0019a55185cc6d200 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 20 Oct 2020 13:46:18 -0400 Subject: [PATCH 06/24] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a79a6b51985..bb4c00ff3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [4.12.0] - unreleased +### Added + +- Better magic underscore error messages. For example, `some_fig.update_layout(geo_ltaxis_showgrid=True)` shows `Bad property path:\ngeo_ltaxis_showgrid\n ^` and lists the valid properties for `geo`. + ### Updated - Updated Plotly.js to version 1.57.0. See the [plotly.js CHANGELOG](https://github.com/plotly/plotly.js/blob/v1.57.0/CHANGELOG.md) for more information. These changes are reflected in the auto-generated `plotly.graph_objects` module. From 22b3e03f86e7e723943050e4f02377d1b250debd Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 23 Oct 2020 16:01:23 -0400 Subject: [PATCH 07/24] Improved error message when subscripting types that don't support it For example, `fig.update_layout(width_x=1)` gives the following error message: TypeError: 'NoneType' object is not subscriptable Invalid value received for the 'width' property of layout The 'width' property is a number and may be specified as: - An int or float in the interval [10, inf] Property does not support subscripting: width_x ~~~~~ because `fig.layout['width']` can't be subscripted (you can't do `fig.layout['width']['x'] = 1`) --- .../python/plotly/plotly/basedatatypes.py | 16 +++++++++++++++- .../test_errors/test_dict_path_errors.py | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 04bdeaa6d70..d7b8aae3968 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -118,8 +118,10 @@ def _check_path_in_prop_tree(obj, path): if type(path) == type(tuple()): path = _remake_path_from_tuple(path) prop, prop_idcs = _str_to_dict_path_full(path) + prev_objs = [] for i, p in enumerate(prop): arg = "" + prev_objs.append(obj) try: obj = obj[p] except (ValueError, KeyError, IndexError, TypeError) as e: @@ -127,11 +129,23 @@ def _check_path_in_prop_tree(obj, path): if obj is None: # If obj doesn't support subscripting, state that and show the # (valid) property that gives the object that doesn't support - # it. + # subscripting. + if i > 0: + validator = prev_objs[i - 1]._get_validator(prop[i - 1]) + arg += """ + +Invalid value received for the '{plotly_name}' property of {parent_name} + +{description}""".format( + parent_name=validator.parent_name, + plotly_name=validator.plotly_name, + description=validator.description(), + ) # 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) arg += """ + Property does not support subscripting: %s %s""" % ( diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 1200e313463..14a7d3b0852 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -289,9 +289,18 @@ def test_describes_subscripting_error(some_fig): some_fig.update_traces(text_yo="hey") except TypeError as e: raised = True + print(e.args[0]) e_substr = error_substr( e.args[0], """ + +Invalid value received for the 'text' property of scatter + + The 'text' property is a string and must be specified as: + - A string + - A number that will be converted to a string + - A tuple, list, or one-dimensional numpy array of the above + Property does not support subscripting: text_yo ~~~~""", @@ -328,6 +337,13 @@ def test_describes_subscripting_error(some_fig): e_substr = error_substr( e.args[0], """ + +Invalid value received for the 'family' property of scatter.textfont + + The 'family' property is a string and must be specified as: + - A non-empty string + - A tuple, list, or one-dimensional numpy array of the above + Property does not support subscripting: textfont_family_yo ~~~~~~""", @@ -345,3 +361,6 @@ def test_describes_subscripting_error(some_fig): and (e_substr == e_correct_substr) ) assert raised + + +test_describes_subscripting_error(some_fig()) From c09c03e4f399f9f170b38484e0b4ac34f2edb41b Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Fri, 23 Oct 2020 16:15:31 -0400 Subject: [PATCH 08/24] Removed garbage line from test_dict_path_errors.py --- .../tests/test_core/test_errors/test_dict_path_errors.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 14a7d3b0852..a2c9beff12e 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -361,6 +361,3 @@ def test_describes_subscripting_error(some_fig): and (e_substr == e_correct_substr) ) assert raised - - -test_describes_subscripting_error(some_fig()) From f4be2b1dab24376649ca5f9fb0fc020f1be4dd05 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 4 Nov 2020 15:27:49 -0500 Subject: [PATCH 09/24] Changed PlotlyKeyError's superclass to KeyError But overrode the __str__ method. --- packages/python/plotly/_plotly_utils/exceptions.py | 11 ++++++----- packages/python/plotly/plotly/basedatatypes.py | 3 --- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/exceptions.py b/packages/python/plotly/_plotly_utils/exceptions.py index 7a9174eb1ef..836ef59e215 100644 --- a/packages/python/plotly/_plotly_utils/exceptions.py +++ b/packages/python/plotly/_plotly_utils/exceptions.py @@ -85,12 +85,13 @@ def __init__(self, obj, path, notes=()): ) -class PlotlyKeyError(LookupError): +class PlotlyKeyError(KeyError): """ KeyErrors are not printed as beautifully as other errors (this is so that - {}[''] prints "KeyError: ''" and not "KeyError:"). So here we subclass - LookupError to make a PlotlyKeyError object which will print nicer error - messages for KeyErrors. + {}[''] prints "KeyError: ''" and not "KeyError:"). So here we use + LookupError's __str__ to make a PlotlyKeyError object which will print nicer + error messages for KeyErrors. """ - pass + def __str__(self): + return LookupError.__str__(self) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index d7b8aae3968..0b3563eadad 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4066,9 +4066,6 @@ def __contains__(self, prop): ------- bool """ - # TODO: We don't want to throw an error in __contains__ because any code that - # relies on it returning False will have to be changed (it will have to - # have a try except block...). prop = BaseFigure._str_to_dict_path(prop) # Handle remapping From 73051a60f5780d8ad257344230f0907f4b6fd46e Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 4 Nov 2020 16:42:36 -0500 Subject: [PATCH 10/24] BasePlotlyType._raise_on_invalid_property_error raises PlotlyKeyError It used to raise ValueError but PlotlyKeyError subclasses KeyError and is the more relevant exception. PlotlyKeyError maintains the nice printing of the errors, unlike KeyError. --- .../python/plotly/plotly/basedatatypes.py | 4 ++-- .../test_errors/test_dict_path_errors.py | 22 +++++++++---------- .../test_graph_objs/test_layout_subplots.py | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 0b3563eadad..f06ecbed7e7 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4331,7 +4331,7 @@ def _raise_on_invalid_property_error(self, *args): Raises ------ - ValueError + PlotlyKeyError Always """ invalid_props = args @@ -4351,7 +4351,7 @@ def _raise_on_invalid_property_error(self, *args): else: full_obj_name = module_root + self.__class__.__name__ - raise ValueError( + raise PlotlyKeyError( "Invalid {prop_str} specified for object of type " "{full_obj_name}: {invalid_str}\n\n" " Valid properties:\n" diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index a2c9beff12e..829ddf75211 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -43,7 +43,7 @@ def test_raises_on_bad_dot_property(some_fig): raised = False try: x2000 = some_fig["layout.shapes[1].x2000"] - except ValueError as e: + except KeyError as e: raised = True assert ( e.args[0].find( @@ -62,7 +62,7 @@ def test_raises_on_bad_ancestor_dot_property(some_fig): raised = False try: x2000 = some_fig["layout.shapa[1].x2000"] - except ValueError as e: + except KeyError as e: raised = True assert ( e.args[0].find( @@ -91,7 +91,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): try: # get the error without using a path-like key, we compare with this error some_fig.data[0].line["colr"] = "blue" - except ValueError as e_correct: + except KeyError as e_correct: raised = True # remove "Bad property path: e_correct_substr = error_substr( @@ -109,7 +109,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): raised = False try: some_fig["data[0].line_colr"] = "blue" - except ValueError as e: + except KeyError as e: raised = True e_substr = error_substr( e.args[0], @@ -135,7 +135,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): try: # get the error without using a path-like key some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line=dict(colr="blue"))) - except ValueError as e_correct: + except KeyError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -152,7 +152,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # the path try: some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line_colr="blue")) - except ValueError as e: + except KeyError as e: raised = True e_substr = error_substr( e.args[0], @@ -180,7 +180,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # the path try: fig2 = go.Figure(layout=dict(title=dict(txt="two"))) - except ValueError as e_correct: + except KeyError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -196,9 +196,9 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): fig2 = go.Figure(layout_title_txt="two") except TypeError as e: raised = True - # when the Figure constructor sees the same ValueError above, a + # when the Figure constructor sees the same KeyError above, a # TypeError is raised and adds an error message in front of the same - # ValueError thrown above + # KeyError thrown above e_substr = error_substr( e.args[0], """ @@ -230,7 +230,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # works when the bad part is not the last part in the path try: some_fig.update_layout(geo=dict(ltaxis=dict(showgrid=True))) - except ValueError as e_correct: + except KeyError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -244,7 +244,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): raised = False try: some_fig.update_layout(geo_ltaxis_showgrid=True) - except ValueError as e: + except KeyError as e: raised = True e_substr = error_substr( e.args[0], diff --git a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py index 0217f7c236c..037d2384983 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py +++ b/packages/python/plotly/plotly/tests/test_core/test_graph_objs/test_layout_subplots.py @@ -39,7 +39,7 @@ def test_initial_access_subplot2(self): self.layout.xaxis2 def test_initial_access_subplot2(self): - with pytest.raises(ValueError): + with pytest.raises(KeyError): self.layout["xaxis2"] def test_assign_subplots(self): From 651b7129d65afaad1db927e6d3c8e05cad7e0952 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 4 Nov 2020 17:43:10 -0500 Subject: [PATCH 11/24] Cast some errors to PlotlyKeyError Because lookup in subclasses of BasePlotlyType and BaseFigure should throw KeyError. --- .../python/plotly/plotly/basedatatypes.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index f06ecbed7e7..cb1637ab7a7 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -104,13 +104,21 @@ def _prepend_dot_if_not_number(s): return "".join(props_w_underscore) -def _check_path_in_prop_tree(obj, path): +def _check_path_in_prop_tree(obj, path, error_cast=None): """ - obj: the object in which the first property is looked up - path: the path that will be split into properties to be looked up - path can also be a tuple. In this case, it is combined using . and [] - because it is impossible to reconstruct the string fully in order to - give a decent error message. + obj: the object in which the first property is looked up + path: the path that will be split into properties to be looked up + path can also be a tuple. In this case, it is combined using . + and [] because it is impossible to reconstruct the string fully + in order to give a decent error message. + error_cast: this function walks down the property tree by looking up values + in objects. So this will throw exceptions that are thrown by + __getitem__, but in some cases we are checking the path for a + different reason and would prefer throwing a more relevant + exception (e.g., __getitem__ throws KeyError but __setitem__ + throws ValueError for subclasses of BasePlotlyType and + BaseFigure). So the resulting error can be "casted" to the + passed in type, if not None. returns an Exception object or None. The caller can raise this exception to see where the lookup error occurred. @@ -169,6 +177,8 @@ def _check_path_in_prop_tree(obj, path): # KeyError if type(e) == type(KeyError()): e = PlotlyKeyError() + if error_cast is not None: + e = error_cast() e.args = (arg,) return e return None @@ -537,7 +547,7 @@ def __getitem__(self, prop): # ---------------------- # e.g. ('foo', 1) else: - err = _check_path_in_prop_tree(self, orig_prop) + err = _check_path_in_prop_tree(self, orig_prop, error_cast=PlotlyKeyError) if err is not None: raise err res = self @@ -4035,7 +4045,7 @@ def __getitem__(self, prop): # ---------------------- # e.g. ('foo', 1), () else: - err = _check_path_in_prop_tree(self, orig_prop) + err = _check_path_in_prop_tree(self, orig_prop, error_cast=PlotlyKeyError) if err is not None: raise err res = self @@ -4351,7 +4361,7 @@ def _raise_on_invalid_property_error(self, *args): else: full_obj_name = module_root + self.__class__.__name__ - raise PlotlyKeyError( + raise ValueError( "Invalid {prop_str} specified for object of type " "{full_obj_name}: {invalid_str}\n\n" " Valid properties:\n" From 9bb24706b1e2ebc73038a5e49a618905a8ab2a0f Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 4 Nov 2020 18:21:58 -0500 Subject: [PATCH 12/24] Updated the tests to reflect the new Exception behaviour I believe the new behaviour is consistent with the previous exception behaviour, but we will run the CI tests to be sure. --- .../python/plotly/plotly/basedatatypes.py | 85 +++++++++++-------- .../test_errors/test_dict_path_errors.py | 26 +++--- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index cb1637ab7a7..5e376f0c603 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -489,7 +489,7 @@ def __setitem__(self, prop, value): # ---------------------- # e.g. ('foo', 1) else: - err = _check_path_in_prop_tree(self, orig_prop) + err = _check_path_in_prop_tree(self, orig_prop, error_cast=ValueError) if err is not None: raise err res = self @@ -3456,7 +3456,7 @@ def _perform_update(plotly_obj, update_obj, overwrite=False): # This should be valid even if xaxis2 hasn't been initialized: # >>> layout.update(xaxis2={'title': 'xaxis 2'}) for key in update_obj: - err = _check_path_in_prop_tree(plotly_obj, key) + err = _check_path_in_prop_tree(plotly_obj, key, error_cast=ValueError) if err is not None: if isinstance(plotly_obj, BaseLayoutType): # try _subplot_re_match @@ -3672,7 +3672,7 @@ def _process_kwargs(self, **kwargs): """ invalid_kwargs = {} for k, v in kwargs.items(): - err = _check_path_in_prop_tree(self, k) + err = _check_path_in_prop_tree(self, k, error_cast=ValueError) if err is None: # e.g. underscore kwargs like marker_line_color self[k] = v @@ -4007,7 +4007,9 @@ def __getitem__(self, prop): # Unwrap scalar tuple prop = prop[0] if prop not in self._valid_props: - self._raise_on_invalid_property_error(prop) + self._raise_on_invalid_property_error(_error_to_raise=PlotlyKeyError)( + prop + ) validator = self._get_validator(prop) @@ -4145,7 +4147,7 @@ def __setitem__(self, prop, value): if self._validate: if prop not in self._valid_props: - self._raise_on_invalid_property_error(prop) + self._raise_on_invalid_property_error()(prop) # ### Get validator for this property ### validator = self._get_validator(prop) @@ -4219,7 +4221,7 @@ def __setattr__(self, prop, value): super(BasePlotlyType, self).__setattr__(prop, value) else: # Raise error on unknown public properties - self._raise_on_invalid_property_error(prop) + self._raise_on_invalid_property_error()(prop) def __iter__(self): """ @@ -4328,10 +4330,11 @@ def __repr__(self): return repr_str - def _raise_on_invalid_property_error(self, *args): + def _raise_on_invalid_property_error(self, _error_to_raise=None): """ - Raise informative exception when invalid property names are - encountered + Returns a function that raises informative exception when invalid + property names are encountered. The _error_to_raise argument allows + specifying the exception to raise, which is ValueError if None. Parameters ---------- @@ -4341,37 +4344,45 @@ def _raise_on_invalid_property_error(self, *args): Raises ------ - PlotlyKeyError - Always - """ - invalid_props = args - if invalid_props: - if len(invalid_props) == 1: - prop_str = "property" - invalid_str = repr(invalid_props[0]) - else: - prop_str = "properties" - invalid_str = repr(invalid_props) + ValueError by default, or _error_to_raise if not None + """ + if _error_to_raise is None: + _error_to_raise = ValueError - module_root = "plotly.graph_objs." - if self._parent_path_str: - full_obj_name = ( - module_root + self._parent_path_str + "." + self.__class__.__name__ + def _ret(*args): + invalid_props = args + if invalid_props: + if len(invalid_props) == 1: + prop_str = "property" + invalid_str = repr(invalid_props[0]) + else: + prop_str = "properties" + invalid_str = repr(invalid_props) + + module_root = "plotly.graph_objs." + if self._parent_path_str: + full_obj_name = ( + module_root + + self._parent_path_str + + "." + + self.__class__.__name__ + ) + else: + full_obj_name = module_root + self.__class__.__name__ + + raise _error_to_raise( + "Invalid {prop_str} specified for object of type " + "{full_obj_name}: {invalid_str}\n\n" + " Valid properties:\n" + "{prop_descriptions}".format( + prop_str=prop_str, + full_obj_name=full_obj_name, + invalid_str=invalid_str, + prop_descriptions=self._prop_descriptions, + ) ) - else: - full_obj_name = module_root + self.__class__.__name__ - raise ValueError( - "Invalid {prop_str} specified for object of type " - "{full_obj_name}: {invalid_str}\n\n" - " Valid properties:\n" - "{prop_descriptions}".format( - prop_str=prop_str, - full_obj_name=full_obj_name, - invalid_str=invalid_str, - prop_descriptions=self._prop_descriptions, - ) - ) + return _ret def update(self, dict1=None, overwrite=False, **kwargs): """ diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 829ddf75211..0086fc55bfc 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -23,7 +23,7 @@ def test_raises_on_bad_index(some_fig): raised = False try: x0 = some_fig["layout.shapes[2].x0"] - except IndexError as e: + except KeyError as e: raised = True assert ( e.args[0].find( @@ -91,7 +91,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): try: # get the error without using a path-like key, we compare with this error some_fig.data[0].line["colr"] = "blue" - except KeyError as e_correct: + except ValueError as e_correct: raised = True # remove "Bad property path: e_correct_substr = error_substr( @@ -109,7 +109,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): raised = False try: some_fig["data[0].line_colr"] = "blue" - except KeyError as e: + except ValueError as e: raised = True e_substr = error_substr( e.args[0], @@ -135,7 +135,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): try: # get the error without using a path-like key some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line=dict(colr="blue"))) - except KeyError as e_correct: + except ValueError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -152,7 +152,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # the path try: some_fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4], line_colr="blue")) - except KeyError as e: + except ValueError as e: raised = True e_substr = error_substr( e.args[0], @@ -180,7 +180,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # the path try: fig2 = go.Figure(layout=dict(title=dict(txt="two"))) - except KeyError as e_correct: + except ValueError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -196,9 +196,9 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): fig2 = go.Figure(layout_title_txt="two") except TypeError as e: raised = True - # when the Figure constructor sees the same KeyError above, a - # TypeError is raised and adds an error message in front of the same - # KeyError thrown above + # when the Figure constructor sees the same ValueError above, a + # ValueError is raised and adds an error message in front of the same + # ValueError thrown above e_substr = error_substr( e.args[0], """ @@ -230,7 +230,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): # works when the bad part is not the last part in the path try: some_fig.update_layout(geo=dict(ltaxis=dict(showgrid=True))) - except KeyError as e_correct: + except ValueError as e_correct: raised = True e_correct_substr = error_substr( e_correct.args[0], @@ -244,7 +244,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): raised = False try: some_fig.update_layout(geo_ltaxis_showgrid=True) - except KeyError as e: + except ValueError as e: raised = True e_substr = error_substr( e.args[0], @@ -287,7 +287,7 @@ def test_describes_subscripting_error(some_fig): raised = False try: some_fig.update_traces(text_yo="hey") - except TypeError as e: + except ValueError as e: raised = True print(e.args[0]) e_substr = error_substr( @@ -332,7 +332,7 @@ def test_describes_subscripting_error(some_fig): raised = False try: go.Figure(go.Scatter()).update_traces(textfont_family_yo="hey") - except TypeError as e: + except ValueError as e: raised = True e_substr = error_substr( e.args[0], From bf4cd97e4b07e41fcf3e16abb41b1a6b989f81cb Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 5 Nov 2020 09:48:45 -0500 Subject: [PATCH 13/24] BasePlotlyType.__setitem__ exceptions casted to ValueError --- packages/python/plotly/plotly/basedatatypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 5e376f0c603..724d2505a3b 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -4193,7 +4193,7 @@ def __setitem__(self, prop, value): # ---------------------- # e.g. ('foo', 1), () else: - err = _check_path_in_prop_tree(self, orig_prop) + err = _check_path_in_prop_tree(self, orig_prop, error_cast=ValueError) if err is not None: raise err res = self From 7d42ffe7007f491009c37036b52187892fe961be Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 5 Nov 2020 10:32:14 -0500 Subject: [PATCH 14/24] Merge master's whitespace changes --- .../plotly/_plotly_utils/basevalidators.py | 314 +++++++++--------- .../plotly/plotly/graph_objs/_choropleth.py | 2 +- .../plotly/graph_objs/_choroplethmapbox.py | 2 +- .../python/plotly/plotly/graph_objs/_cone.py | 2 +- .../plotly/plotly/graph_objs/_contour.py | 2 +- .../plotly/graph_objs/_contourcarpet.py | 2 +- .../plotly/graph_objs/_densitymapbox.py | 2 +- .../plotly/plotly/graph_objs/_heatmap.py | 2 +- .../plotly/plotly/graph_objs/_heatmapgl.py | 2 +- .../plotly/plotly/graph_objs/_histogram2d.py | 2 +- .../plotly/graph_objs/_histogram2dcontour.py | 2 +- .../plotly/plotly/graph_objs/_isosurface.py | 2 +- .../plotly/plotly/graph_objs/_layout.py | 2 +- .../plotly/plotly/graph_objs/_mesh3d.py | 2 +- .../plotly/plotly/graph_objs/_streamtube.py | 2 +- .../plotly/plotly/graph_objs/_surface.py | 2 +- .../plotly/plotly/graph_objs/_volume.py | 2 +- .../plotly/plotly/graph_objs/bar/_marker.py | 2 +- .../plotly/graph_objs/bar/marker/_line.py | 2 +- .../plotly/graph_objs/barpolar/_marker.py | 2 +- .../graph_objs/barpolar/marker/_line.py | 2 +- .../plotly/graph_objs/funnel/_marker.py | 2 +- .../plotly/graph_objs/funnel/marker/_line.py | 2 +- .../plotly/graph_objs/histogram/_marker.py | 2 +- .../graph_objs/histogram/marker/_line.py | 2 +- .../plotly/graph_objs/layout/_coloraxis.py | 2 +- .../plotly/graph_objs/layout/_colorscale.py | 6 +- .../plotly/plotly/graph_objs/parcats/_line.py | 2 +- .../plotly/graph_objs/parcoords/_line.py | 2 +- .../graph_objs/sankey/link/_colorscale.py | 2 +- .../plotly/graph_objs/scatter/_marker.py | 2 +- .../plotly/graph_objs/scatter/marker/_line.py | 2 +- .../plotly/graph_objs/scatter3d/_line.py | 2 +- .../plotly/graph_objs/scatter3d/_marker.py | 2 +- .../graph_objs/scatter3d/marker/_line.py | 2 +- .../graph_objs/scattercarpet/_marker.py | 2 +- .../graph_objs/scattercarpet/marker/_line.py | 2 +- .../plotly/graph_objs/scattergeo/_marker.py | 2 +- .../graph_objs/scattergeo/marker/_line.py | 2 +- .../plotly/graph_objs/scattergl/_marker.py | 2 +- .../graph_objs/scattergl/marker/_line.py | 2 +- .../graph_objs/scattermapbox/_marker.py | 2 +- .../plotly/graph_objs/scatterpolar/_marker.py | 2 +- .../graph_objs/scatterpolar/marker/_line.py | 2 +- .../graph_objs/scatterpolargl/_marker.py | 2 +- .../graph_objs/scatterpolargl/marker/_line.py | 2 +- .../graph_objs/scatterternary/_marker.py | 2 +- .../graph_objs/scatterternary/marker/_line.py | 2 +- .../plotly/plotly/graph_objs/splom/_marker.py | 2 +- .../plotly/graph_objs/splom/marker/_line.py | 2 +- .../plotly/graph_objs/sunburst/_marker.py | 2 +- .../plotly/graph_objs/treemap/_marker.py | 2 +- 52 files changed, 210 insertions(+), 210 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/basevalidators.py b/packages/python/plotly/_plotly_utils/basevalidators.py index 099c26598c2..190bc8fad61 100644 --- a/packages/python/plotly/_plotly_utils/basevalidators.py +++ b/packages/python/plotly/_plotly_utils/basevalidators.py @@ -354,14 +354,14 @@ def present(self, v): class DataArrayValidator(BaseValidator): """ - "data_array": { - "description": "An {array} of data. The value MUST be an - {array}, or we ignore it.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "data_array": { + "description": "An {array} of data. The value MUST be an + {array}, or we ignore it.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -394,18 +394,18 @@ def validate_coerce(self, v): class EnumeratedValidator(BaseValidator): """ - "enumerated": { - "description": "Enumerated value type. The available values are - listed in `values`.", - "requiredOpts": [ - "values" - ], - "otherOpts": [ - "dflt", - "coerceNumber", - "arrayOk" - ] - }, + "enumerated": { + "description": "Enumerated value type. The available values are + listed in `values`.", + "requiredOpts": [ + "values" + ], + "otherOpts": [ + "dflt", + "coerceNumber", + "arrayOk" + ] + }, """ def __init__( @@ -601,13 +601,13 @@ def validate_coerce(self, v): class BooleanValidator(BaseValidator): """ - "boolean": { - "description": "A boolean (true/false) value.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "boolean": { + "description": "A boolean (true/false) value.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -664,19 +664,19 @@ def validate_coerce(self, v): class NumberValidator(BaseValidator): """ - "number": { - "description": "A number or a numeric value (e.g. a number - inside a string). When applicable, values - greater (less) than `max` (`min`) are coerced to - the `dflt`.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "min", - "max", - "arrayOk" - ] - }, + "number": { + "description": "A number or a numeric value (e.g. a number + inside a string). When applicable, values + greater (less) than `max` (`min`) are coerced to + the `dflt`.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "min", + "max", + "arrayOk" + ] + }, """ def __init__( @@ -794,18 +794,18 @@ def validate_coerce(self, v): class IntegerValidator(BaseValidator): """ - "integer": { - "description": "An integer or an integer inside a string. When - applicable, values greater (less) than `max` - (`min`) are coerced to the `dflt`.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "min", - "max", - "arrayOk" - ] - }, + "integer": { + "description": "An integer or an integer inside a string. When + applicable, values greater (less) than `max` + (`min`) are coerced to the `dflt`.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "min", + "max", + "arrayOk" + ] + }, """ def __init__( @@ -925,18 +925,18 @@ def validate_coerce(self, v): class StringValidator(BaseValidator): """ - "string": { - "description": "A string value. Numbers are converted to strings - except for attributes with `strict` set to true.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "noBlank", - "strict", - "arrayOk", - "values" - ] - }, + "string": { + "description": "A string value. Numbers are converted to strings + except for attributes with `strict` set to true.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "noBlank", + "strict", + "arrayOk", + "values" + ] + }, """ def __init__( @@ -1094,21 +1094,21 @@ def validate_coerce(self, v): class ColorValidator(BaseValidator): """ - "color": { - "description": "A string describing color. Supported formats: - - hex (e.g. '#d3d3d3') - - rgb (e.g. 'rgb(255, 0, 0)') - - rgba (e.g. 'rgb(255, 0, 0, 0.5)') - - hsl (e.g. 'hsl(0, 100%, 50%)') - - hsv (e.g. 'hsv(0, 100%, 100%)') - - named colors(full list: - http://www.w3.org/TR/css3-color/#svg-color)", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "arrayOk" - ] - }, + "color": { + "description": "A string describing color. Supported formats: + - hex (e.g. '#d3d3d3') + - rgb (e.g. 'rgb(255, 0, 0)') + - rgba (e.g. 'rgb(255, 0, 0, 0.5)') + - hsl (e.g. 'hsl(0, 100%, 50%)') + - hsv (e.g. 'hsv(0, 100%, 100%)') + - named colors(full list: + http://www.w3.org/TR/css3-color/#svg-color)", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "arrayOk" + ] + }, """ re_hex = re.compile(r"#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})") @@ -1445,14 +1445,14 @@ def perform_validate_coerce(v, allow_number=None): class ColorlistValidator(BaseValidator): """ - "colorlist": { - "description": "A list of colors. Must be an {array} containing - valid colors.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - } + "colorlist": { + "description": "A list of colors. Must be an {array} containing + valid colors.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + } """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1492,20 +1492,20 @@ def validate_coerce(self, v): class ColorscaleValidator(BaseValidator): """ - "colorscale": { - "description": "A Plotly colorscale either picked by a name: - (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, - RdBu, Reds, Blues, Picnic, Rainbow, Portland, - Jet, Hot, Blackbody, Earth, Electric, Viridis) - customized as an {array} of 2-element {arrays} - where the first element is the normalized color - level value (starting at *0* and ending at *1*), - and the second item is a valid color string.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "colorscale": { + "description": "A Plotly colorscale either picked by a name: + (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, + RdBu, Reds, Blues, Picnic, Rainbow, Portland, + Jet, Hot, Blackbody, Earth, Electric, Viridis) + customized as an {array} of 2-element {arrays} + where the first element is the normalized color + level value (starting at *0* and ending at *1*), + and the second item is a valid color string.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1560,7 +1560,7 @@ def description(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: @@ -1644,13 +1644,13 @@ def present(self, v): class AngleValidator(BaseValidator): """ - "angle": { - "description": "A number (in degree) between -180 and 180.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "angle": { + "description": "A number (in degree) between -180 and 180.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1685,18 +1685,18 @@ def validate_coerce(self, v): class SubplotidValidator(BaseValidator): """ - "subplotid": { - "description": "An id string of a subplot type (given by dflt), - optionally followed by an integer >1. e.g. if - dflt='geo', we can have 'geo', 'geo2', 'geo3', - ...", - "requiredOpts": [ - "dflt" - ], - "otherOpts": [ - "regex" - ] - } + "subplotid": { + "description": "An id string of a subplot type (given by dflt), + optionally followed by an integer >1. e.g. if + dflt='geo', we can have 'geo', 'geo2', 'geo3', + ...", + "requiredOpts": [ + "dflt" + ], + "otherOpts": [ + "regex" + ] + } """ def __init__(self, plotly_name, parent_name, dflt=None, regex=None, **kwargs): @@ -1756,21 +1756,21 @@ def validate_coerce(self, v): class FlaglistValidator(BaseValidator): """ - "flaglist": { - "description": "A string representing a combination of flags - (order does not matter here). Combine any of the - available `flags` with *+*. - (e.g. ('lines+markers')). Values in `extras` - cannot be combined.", - "requiredOpts": [ - "flags" - ], - "otherOpts": [ - "dflt", - "extras", - "arrayOk" - ] - }, + "flaglist": { + "description": "A string representing a combination of flags + (order does not matter here). Combine any of the + available `flags` with *+*. + (e.g. ('lines+markers')). Values in `extras` + cannot be combined.", + "requiredOpts": [ + "flags" + ], + "otherOpts": [ + "dflt", + "extras", + "arrayOk" + ] + }, """ def __init__( @@ -1877,15 +1877,15 @@ def validate_coerce(self, v): class AnyValidator(BaseValidator): """ - "any": { - "description": "Any type.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "values", - "arrayOk" - ] - }, + "any": { + "description": "Any type.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "values", + "arrayOk" + ] + }, """ def __init__(self, plotly_name, parent_name, values=None, array_ok=False, **kwargs): @@ -1917,17 +1917,17 @@ def validate_coerce(self, v): class InfoArrayValidator(BaseValidator): """ - "info_array": { - "description": "An {array} of plot information.", - "requiredOpts": [ - "items" - ], - "otherOpts": [ - "dflt", - "freeLength", - "dimensions" - ] - } + "info_array": { + "description": "An {array} of plot information.", + "requiredOpts": [ + "items" + ], + "otherOpts": [ + "dflt", + "freeLength", + "dimensions" + ] + } """ def __init__( @@ -2707,7 +2707,7 @@ def description(self): - A string containing multiple registered template names, joined on '+' characters (e.g. 'template1+template2'). In this case the resulting - template is computed by merging together the collection of registered + template is computed by merging together the collection of registered templates""" return compound_description diff --git a/packages/python/plotly/plotly/graph_objs/_choropleth.py b/packages/python/plotly/plotly/graph_objs/_choropleth.py index b45b4376469..1b59c4c5c77 100644 --- a/packages/python/plotly/plotly/graph_objs/_choropleth.py +++ b/packages/python/plotly/plotly/graph_objs/_choropleth.py @@ -368,7 +368,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_choroplethmapbox.py b/packages/python/plotly/plotly/graph_objs/_choroplethmapbox.py index ecf8f35a73a..8090d551b66 100644 --- a/packages/python/plotly/plotly/graph_objs/_choroplethmapbox.py +++ b/packages/python/plotly/plotly/graph_objs/_choroplethmapbox.py @@ -393,7 +393,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_cone.py b/packages/python/plotly/plotly/graph_objs/_cone.py index c3d73af166d..d7a5dd37db4 100644 --- a/packages/python/plotly/plotly/graph_objs/_cone.py +++ b/packages/python/plotly/plotly/graph_objs/_cone.py @@ -485,7 +485,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_contour.py b/packages/python/plotly/plotly/graph_objs/_contour.py index 9db47b58d68..9a1f60e5034 100644 --- a/packages/python/plotly/plotly/graph_objs/_contour.py +++ b/packages/python/plotly/plotly/graph_objs/_contour.py @@ -411,7 +411,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_contourcarpet.py b/packages/python/plotly/plotly/graph_objs/_contourcarpet.py index af48b006769..ed3da268256 100644 --- a/packages/python/plotly/plotly/graph_objs/_contourcarpet.py +++ b/packages/python/plotly/plotly/graph_objs/_contourcarpet.py @@ -589,7 +589,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_densitymapbox.py b/packages/python/plotly/plotly/graph_objs/_densitymapbox.py index 483f071f151..96e0142a720 100644 --- a/packages/python/plotly/plotly/graph_objs/_densitymapbox.py +++ b/packages/python/plotly/plotly/graph_objs/_densitymapbox.py @@ -392,7 +392,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_heatmap.py b/packages/python/plotly/plotly/graph_objs/_heatmap.py index 9a9840c3941..c353cde2e50 100644 --- a/packages/python/plotly/plotly/graph_objs/_heatmap.py +++ b/packages/python/plotly/plotly/graph_objs/_heatmap.py @@ -386,7 +386,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_heatmapgl.py b/packages/python/plotly/plotly/graph_objs/_heatmapgl.py index b0fe686c565..aedf06aeda4 100644 --- a/packages/python/plotly/plotly/graph_objs/_heatmapgl.py +++ b/packages/python/plotly/plotly/graph_objs/_heatmapgl.py @@ -367,7 +367,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_histogram2d.py b/packages/python/plotly/plotly/graph_objs/_histogram2d.py index 1c998deedc6..b66573c2fb2 100644 --- a/packages/python/plotly/plotly/graph_objs/_histogram2d.py +++ b/packages/python/plotly/plotly/graph_objs/_histogram2d.py @@ -449,7 +449,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_histogram2dcontour.py b/packages/python/plotly/plotly/graph_objs/_histogram2dcontour.py index e92cec6205c..d3172826c02 100644 --- a/packages/python/plotly/plotly/graph_objs/_histogram2dcontour.py +++ b/packages/python/plotly/plotly/graph_objs/_histogram2dcontour.py @@ -473,7 +473,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_isosurface.py b/packages/python/plotly/plotly/graph_objs/_isosurface.py index cea1e3781dd..b16caeda1a2 100644 --- a/packages/python/plotly/plotly/graph_objs/_isosurface.py +++ b/packages/python/plotly/plotly/graph_objs/_isosurface.py @@ -496,7 +496,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_layout.py b/packages/python/plotly/plotly/graph_objs/_layout.py index 195f2ef670d..4b69b07ba91 100644 --- a/packages/python/plotly/plotly/graph_objs/_layout.py +++ b/packages/python/plotly/plotly/graph_objs/_layout.py @@ -3247,7 +3247,7 @@ def template(self): - A string containing multiple registered template names, joined on '+' characters (e.g. 'template1+template2'). In this case the resulting - template is computed by merging together the collection of registered + template is computed by merging together the collection of registered templates Returns diff --git a/packages/python/plotly/plotly/graph_objs/_mesh3d.py b/packages/python/plotly/plotly/graph_objs/_mesh3d.py index 8079e8b85b8..fdce7d2e2e5 100644 --- a/packages/python/plotly/plotly/graph_objs/_mesh3d.py +++ b/packages/python/plotly/plotly/graph_objs/_mesh3d.py @@ -573,7 +573,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_streamtube.py b/packages/python/plotly/plotly/graph_objs/_streamtube.py index f2632ac2745..8ecdbc2448e 100644 --- a/packages/python/plotly/plotly/graph_objs/_streamtube.py +++ b/packages/python/plotly/plotly/graph_objs/_streamtube.py @@ -463,7 +463,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_surface.py b/packages/python/plotly/plotly/graph_objs/_surface.py index fb9051b3ff2..637b38056da 100644 --- a/packages/python/plotly/plotly/graph_objs/_surface.py +++ b/packages/python/plotly/plotly/graph_objs/_surface.py @@ -465,7 +465,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/_volume.py b/packages/python/plotly/plotly/graph_objs/_volume.py index 0b0635e9a5d..df987e5457a 100644 --- a/packages/python/plotly/plotly/graph_objs/_volume.py +++ b/packages/python/plotly/plotly/graph_objs/_volume.py @@ -497,7 +497,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/bar/_marker.py b/packages/python/plotly/plotly/graph_objs/bar/_marker.py index 0564145b845..38e050c036b 100644 --- a/packages/python/plotly/plotly/graph_objs/bar/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/bar/_marker.py @@ -500,7 +500,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/bar/marker/_line.py b/packages/python/plotly/plotly/graph_objs/bar/marker/_line.py index d3379ccb7a9..eb49f27703a 100644 --- a/packages/python/plotly/plotly/graph_objs/bar/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/bar/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/barpolar/_marker.py b/packages/python/plotly/plotly/graph_objs/barpolar/_marker.py index 45a03eaca54..6989f8a7d35 100644 --- a/packages/python/plotly/plotly/graph_objs/barpolar/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/barpolar/_marker.py @@ -501,7 +501,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/barpolar/marker/_line.py b/packages/python/plotly/plotly/graph_objs/barpolar/marker/_line.py index bb3c1e63dfb..e9254e11a02 100644 --- a/packages/python/plotly/plotly/graph_objs/barpolar/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/barpolar/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/funnel/_marker.py b/packages/python/plotly/plotly/graph_objs/funnel/_marker.py index 15e41886f2d..b3b63cecaf1 100644 --- a/packages/python/plotly/plotly/graph_objs/funnel/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/funnel/_marker.py @@ -501,7 +501,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/funnel/marker/_line.py b/packages/python/plotly/plotly/graph_objs/funnel/marker/_line.py index 12521dea1ef..87ca5ea896e 100644 --- a/packages/python/plotly/plotly/graph_objs/funnel/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/funnel/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/histogram/_marker.py b/packages/python/plotly/plotly/graph_objs/histogram/_marker.py index 73248c154e5..f81c89e6af2 100644 --- a/packages/python/plotly/plotly/graph_objs/histogram/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/histogram/_marker.py @@ -501,7 +501,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/histogram/marker/_line.py b/packages/python/plotly/plotly/graph_objs/histogram/marker/_line.py index 3442e109378..de6ce5beb4d 100644 --- a/packages/python/plotly/plotly/graph_objs/histogram/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/histogram/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/layout/_coloraxis.py b/packages/python/plotly/plotly/graph_objs/layout/_coloraxis.py index c6dcb79c82b..9e1aa973b71 100644 --- a/packages/python/plotly/plotly/graph_objs/layout/_coloraxis.py +++ b/packages/python/plotly/plotly/graph_objs/layout/_coloraxis.py @@ -396,7 +396,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/layout/_colorscale.py b/packages/python/plotly/plotly/graph_objs/layout/_colorscale.py index d4385fafa71..bcc2601a442 100644 --- a/packages/python/plotly/plotly/graph_objs/layout/_colorscale.py +++ b/packages/python/plotly/plotly/graph_objs/layout/_colorscale.py @@ -24,7 +24,7 @@ def diverging(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: @@ -69,7 +69,7 @@ def sequential(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: @@ -114,7 +114,7 @@ def sequentialminus(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/parcats/_line.py b/packages/python/plotly/plotly/graph_objs/parcats/_line.py index f6af41810eb..153ef0d2d80 100644 --- a/packages/python/plotly/plotly/graph_objs/parcats/_line.py +++ b/packages/python/plotly/plotly/graph_objs/parcats/_line.py @@ -499,7 +499,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/parcoords/_line.py b/packages/python/plotly/plotly/graph_objs/parcoords/_line.py index 546312ec9e3..8428afc9a6f 100644 --- a/packages/python/plotly/plotly/graph_objs/parcoords/_line.py +++ b/packages/python/plotly/plotly/graph_objs/parcoords/_line.py @@ -497,7 +497,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/sankey/link/_colorscale.py b/packages/python/plotly/plotly/graph_objs/sankey/link/_colorscale.py index f9a39780694..1d2d08c9d7b 100644 --- a/packages/python/plotly/plotly/graph_objs/sankey/link/_colorscale.py +++ b/packages/python/plotly/plotly/graph_objs/sankey/link/_colorscale.py @@ -72,7 +72,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatter/_marker.py b/packages/python/plotly/plotly/graph_objs/scatter/_marker.py index 8520b90cc21..caa11aa835f 100644 --- a/packages/python/plotly/plotly/graph_objs/scatter/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scatter/_marker.py @@ -510,7 +510,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatter/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scatter/marker/_line.py index 39b5b865891..8cb0eaf2589 100644 --- a/packages/python/plotly/plotly/graph_objs/scatter/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatter/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatter3d/_line.py b/packages/python/plotly/plotly/graph_objs/scatter3d/_line.py index 8f07ea47a00..440c056bed5 100644 --- a/packages/python/plotly/plotly/graph_objs/scatter3d/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatter3d/_line.py @@ -499,7 +499,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatter3d/_marker.py b/packages/python/plotly/plotly/graph_objs/scatter3d/_marker.py index 6c5c07af03b..f68bdb5eb3e 100644 --- a/packages/python/plotly/plotly/graph_objs/scatter3d/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scatter3d/_marker.py @@ -507,7 +507,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatter3d/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scatter3d/marker/_line.py index cb0b60feb5f..b4598755f22 100644 --- a/packages/python/plotly/plotly/graph_objs/scatter3d/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatter3d/marker/_line.py @@ -260,7 +260,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattercarpet/_marker.py b/packages/python/plotly/plotly/graph_objs/scattercarpet/_marker.py index e9aed8ae36e..8d4cc633344 100644 --- a/packages/python/plotly/plotly/graph_objs/scattercarpet/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scattercarpet/_marker.py @@ -510,7 +510,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattercarpet/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scattercarpet/marker/_line.py index 90aa42b497b..b9b0f13f03c 100644 --- a/packages/python/plotly/plotly/graph_objs/scattercarpet/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scattercarpet/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattergeo/_marker.py b/packages/python/plotly/plotly/graph_objs/scattergeo/_marker.py index 429b2868f32..55f8dc88c21 100644 --- a/packages/python/plotly/plotly/graph_objs/scattergeo/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scattergeo/_marker.py @@ -509,7 +509,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattergeo/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scattergeo/marker/_line.py index bfe17094423..1a2d02763c0 100644 --- a/packages/python/plotly/plotly/graph_objs/scattergeo/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scattergeo/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattergl/_marker.py b/packages/python/plotly/plotly/graph_objs/scattergl/_marker.py index a49fa7a0165..aec96183f16 100644 --- a/packages/python/plotly/plotly/graph_objs/scattergl/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scattergl/_marker.py @@ -508,7 +508,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattergl/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scattergl/marker/_line.py index faff6a8ede6..89619bb4a67 100644 --- a/packages/python/plotly/plotly/graph_objs/scattergl/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scattergl/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scattermapbox/_marker.py b/packages/python/plotly/plotly/graph_objs/scattermapbox/_marker.py index 09427392508..ef8806ab667 100644 --- a/packages/python/plotly/plotly/graph_objs/scattermapbox/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scattermapbox/_marker.py @@ -574,7 +574,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterpolar/_marker.py b/packages/python/plotly/plotly/graph_objs/scatterpolar/_marker.py index 10d4cae6bc0..dfce44fdf2d 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterpolar/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scatterpolar/_marker.py @@ -510,7 +510,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterpolar/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scatterpolar/marker/_line.py index 18b0ca7ce0e..2ba89777b63 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterpolar/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatterpolar/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterpolargl/_marker.py b/packages/python/plotly/plotly/graph_objs/scatterpolargl/_marker.py index 71973615fc2..8444576b639 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterpolargl/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scatterpolargl/_marker.py @@ -508,7 +508,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterpolargl/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scatterpolargl/marker/_line.py index b41804c313e..23247ab2db2 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterpolargl/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatterpolargl/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterternary/_marker.py b/packages/python/plotly/plotly/graph_objs/scatterternary/_marker.py index bcb1f11ec76..2ba1c3f0fa5 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterternary/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/scatterternary/_marker.py @@ -510,7 +510,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/scatterternary/marker/_line.py b/packages/python/plotly/plotly/graph_objs/scatterternary/marker/_line.py index f048acdd568..6cc7bd1d3d1 100644 --- a/packages/python/plotly/plotly/graph_objs/scatterternary/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/scatterternary/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/splom/_marker.py b/packages/python/plotly/plotly/graph_objs/splom/_marker.py index 7f0ea782214..e909c76b486 100644 --- a/packages/python/plotly/plotly/graph_objs/splom/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/splom/_marker.py @@ -508,7 +508,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/splom/marker/_line.py b/packages/python/plotly/plotly/graph_objs/splom/marker/_line.py index 4ba00284a75..69cad5eea1b 100644 --- a/packages/python/plotly/plotly/graph_objs/splom/marker/_line.py +++ b/packages/python/plotly/plotly/graph_objs/splom/marker/_line.py @@ -261,7 +261,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/sunburst/_marker.py b/packages/python/plotly/plotly/graph_objs/sunburst/_marker.py index d93e9785786..e93fa3c78fe 100644 --- a/packages/python/plotly/plotly/graph_objs/sunburst/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/sunburst/_marker.py @@ -452,7 +452,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: diff --git a/packages/python/plotly/plotly/graph_objs/treemap/_marker.py b/packages/python/plotly/plotly/graph_objs/treemap/_marker.py index 2ce5ce1e583..5815f4d83bc 100644 --- a/packages/python/plotly/plotly/graph_objs/treemap/_marker.py +++ b/packages/python/plotly/plotly/graph_objs/treemap/_marker.py @@ -454,7 +454,7 @@ def colorscale(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: From 4bda7b2b62b8b6f312308cc65fe311e76738f124 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 5 Nov 2020 18:32:40 -0500 Subject: [PATCH 15/24] Now subscripting errors triggered on types throwing TypeError Before the subscripting error was only thrown for NoneType objects, but because stuff like `"hello"["hi"]` and `1["hey"]` also are invalid, and these also throw TypeError, then these throw the subscripting error message too. HOWEVER, this error is casted to ValueError on property assignment and PlotlyKeyError on property read, to keep consistency among the exception types. --- .../python/plotly/plotly/basedatatypes.py | 2 +- .../test_errors/test_dict_path_errors.py | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index e231bd29fdf..781bb941eab 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -137,7 +137,7 @@ def _check_path_in_prop_tree(obj, path, error_cast=None): obj = obj[p] except (ValueError, KeyError, IndexError, TypeError) as e: arg = e.args[0] - if obj is None: + if issubclass(e.__class__, TypeError): # If obj doesn't support subscripting, state that and show the # (valid) property that gives the object that doesn't support # subscripting. diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 0086fc55bfc..54a7b4c91be 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -361,3 +361,61 @@ def test_describes_subscripting_error(some_fig): and (e_substr == e_correct_substr) ) assert raised + + +def test_described_subscript_error_on_type_error(some_fig): + # The above tests for subscripting errors did not test for when we attempt + # to subscript an object that is not None, such as a string or a number. + # These do that. + raised = False + try: + # Trying to address with a key an object that doesn't support it (as we + # do below) reports an error listing what are valid assignments to the + # object, like when we try and assign a number to something that expects as string. + some_fig["layout_template_layout_plot_bgcolor"] = 1 + except ValueError as e: + raised = True + e_correct_substr = error_substr( + e.args[0], + """ + Invalid value of type 'builtins.int' received for the 'plot_bgcolor' property of layout + Received value: 1 +""", + ) + e_correct_substr += """ + +Property does not support subscripting: +template_layout_plot_bgcolor_x + ~~~~~~~~~~~~""" + assert raised + raised = False + try: + some_fig.update_layout(template_layout_plot_bgcolor_x=1) + except ValueError as e: + raised = True + print(e.args[0]) + e_substr = error_substr( + e.args[0], + """string indices must be integers + +Invalid value received for the 'plot_bgcolor' property of layout +""", + ) + assert e_substr == e_correct_substr + assert raised + + +def test_subscript_error_exception_types(some_fig): + # Assert that these raise the expected error types + # when width is None + with pytest.raises(ValueError): + some_fig.update_layout(width_yo=100) + with pytest.raises(KeyError): + yo = some_fig["layout_width_yo"] + + some_fig.update_layout(width=100) + # when width is specified + with pytest.raises(ValueError): + some_fig.update_layout(width_yo=100) + with pytest.raises(KeyError): + yo = some_fig["layout_width_yo"] From 9e1b6679cb897632160f0fc55291982383e0282d Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Thu, 5 Nov 2020 18:55:35 -0500 Subject: [PATCH 16/24] subscripting error tests compatible with Python2 --- .../test_errors/test_dict_path_errors.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 54a7b4c91be..d3a5c71778e 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -375,13 +375,16 @@ def test_described_subscript_error_on_type_error(some_fig): some_fig["layout_template_layout_plot_bgcolor"] = 1 except ValueError as e: raised = True - e_correct_substr = error_substr( - e.args[0], - """ - Invalid value of type 'builtins.int' received for the 'plot_bgcolor' property of layout - Received value: 1 -""", - ) + # Trim off the beginning of the error string because it is related to + # trying to assign a number to something expecting a string, whereas + # below the error will be due to trying to subscript something that + # doesn't support it. But the list of valid properties should be shown + # for both errors and this is what we extract. + # Trimmed like this because this string is different in Python2 than + # Python3 + e_correct_substr = e.args[0] + start_at = e_correct_substr.find(" The 'plot_bgcolor'") + e_correct_substr = e_correct_substr[start_at:] e_correct_substr += """ Property does not support subscripting: @@ -399,6 +402,7 @@ def test_described_subscript_error_on_type_error(some_fig): """string indices must be integers Invalid value received for the 'plot_bgcolor' property of layout + """, ) assert e_substr == e_correct_substr From d6aee64caf597d928f159daa42cd9a7da1b00606 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 9 Nov 2020 13:34:18 -0500 Subject: [PATCH 17/24] Changed dict path error display to always ^ and underneath the whole offending part of the path e.g., the_badpart_of_the_path ^^^^^^^ for both property and subscripting errors. --- .../python/plotly/plotly/basedatatypes.py | 22 ++++++++++- .../test_errors/test_dict_path_errors.py | 38 +++++++++---------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 781bb941eab..71623c8bd2e 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -28,6 +28,22 @@ Undefined = object() +def _len_dict_item(item): + """ + Because a parsed dict path is a tuple containings strings or integers, to + know the length of the resulting string when printing we might need to + convert to a string before calling len on it. + """ + if type(item) == type(str()): + return len(item) + elif type(item) == type(int()): + return len("%d" % (item,)) + else: + raise ValueError( + "Cannot find string length of an item that is neither a string nor an integer." + ) + + def _str_to_dict_path_full(key_path_str): """ Convert a key path string into a tuple of key path elements and also @@ -162,7 +178,7 @@ def _check_path_in_prop_tree(obj, path, error_cast=None): %s""" % ( path, display_string_positions( - prop_idcs, disp_i, length=len(prop[disp_i]), char="~" + prop_idcs, disp_i, length=_len_dict_item(prop[disp_i]), char="^" ), ) else: @@ -173,7 +189,9 @@ def _check_path_in_prop_tree(obj, path, error_cast=None): %s %s""" % ( path, - display_string_positions(prop_idcs, i, length=1, char="^"), + display_string_positions( + prop_idcs, i, length=_len_dict_item(prop[i]), char="^" + ), ) # Make KeyError more pretty by changing it to a PlotlyKeyError, # because the Python interpreter has a special way of printing diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index d3a5c71778e..76db87cfd86 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -49,7 +49,7 @@ def test_raises_on_bad_dot_property(some_fig): e.args[0].find( """Bad property path: layout.shapes[1].x2000 - ^""" + ^^^^^""" ) >= 0 ) @@ -68,7 +68,7 @@ def test_raises_on_bad_ancestor_dot_property(some_fig): e.args[0].find( """Bad property path: layout.shapa[1].x2000 - ^""" + ^^^^^""" ) >= 0 ) @@ -99,7 +99,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: colr -^""", +^^^^""", ) # if the string starts with "Bad property path:" then this test cannot work # this way. @@ -116,14 +116,14 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: data[0].line_colr - ^""", + ^^^^""", ) assert ( ( e.args[0].find( """Bad property path: data[0].line_colr - ^""" + ^^^^""" ) >= 0 ) @@ -142,7 +142,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: colr -^""", +^^^^""", ) assert raised @@ -159,14 +159,14 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: line_colr - ^""", + ^^^^""", ) assert ( ( e.args[0].find( """Bad property path: line_colr - ^""" + ^^^^""" ) >= 0 ) @@ -187,7 +187,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: txt -^""", +^^^""", ) assert raised @@ -204,7 +204,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: layout_title_txt - ^""", + ^^^""", ) # also remove the invalid Figure property string added by the Figure constructor e_substr = error_substr( @@ -217,7 +217,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): e.args[0].find( """Bad property path: layout_title_txt - ^""" + ^^^""" ) >= 0 ) @@ -237,7 +237,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: ltaxis -^""", +^^^^^^""", ) assert raised @@ -251,14 +251,14 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: geo_ltaxis_showgrid - ^""", + ^^^^^^""", ) assert ( ( e.args[0].find( """Bad property path: geo_ltaxis_showgrid - ^""" + ^^^^^^""" ) >= 0 ) @@ -303,7 +303,7 @@ def test_describes_subscripting_error(some_fig): Property does not support subscripting: text_yo -~~~~""", +^^^^""", ) assert ( ( @@ -311,7 +311,7 @@ def test_describes_subscripting_error(some_fig): """ Property does not support subscripting: text_yo -~~~~""" +^^^^""" ) >= 0 ) @@ -346,7 +346,7 @@ def test_describes_subscripting_error(some_fig): Property does not support subscripting: textfont_family_yo - ~~~~~~""", + ^^^^^^""", ) assert ( ( @@ -354,7 +354,7 @@ def test_describes_subscripting_error(some_fig): """ Property does not support subscripting: textfont_family_yo - ~~~~~~""" + ^^^^^^""" ) >= 0 ) @@ -389,7 +389,7 @@ def test_described_subscript_error_on_type_error(some_fig): Property does not support subscripting: template_layout_plot_bgcolor_x - ~~~~~~~~~~~~""" + ^^^^^^^^^^^^""" assert raised raised = False try: From c6e5b4d13c6e32e346adf0d22c7b78a79262abde Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 9 Nov 2020 14:25:57 -0500 Subject: [PATCH 18/24] Try taking length of string-like objects then if that fails, try and print the int, then if that fails, truely fail. This allows taking the length of unicode objects in Python2. --- packages/python/plotly/plotly/basedatatypes.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 71623c8bd2e..e9c35e1ce3c 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -34,14 +34,16 @@ def _len_dict_item(item): know the length of the resulting string when printing we might need to convert to a string before calling len on it. """ - if type(item) == type(str()): - return len(item) - elif type(item) == type(int()): - return len("%d" % (item,)) - else: - raise ValueError( - "Cannot find string length of an item that is neither a string nor an integer." - ) + try: + l = len(item) + except TypeError: + try: + l = len("%d" % (item,)) + except TypeError: + raise ValueError( + "Cannot find string length of an item that is not string-like nor an integer." + ) + return l def _str_to_dict_path_full(key_path_str): From 82f9bb1e1b6510ef71366ccbea3399b3784578e2 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Mon, 9 Nov 2020 18:29:59 -0500 Subject: [PATCH 19/24] leading, trailing, multiple underscores detected in dict path strings need to add tests --- packages/python/plotly/_plotly_utils/utils.py | 38 +++++++ .../python/plotly/plotly/basedatatypes.py | 99 ++++++++++++------- 2 files changed, 103 insertions(+), 34 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 44bafbd674e..54ccb6d4e4b 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -362,3 +362,41 @@ def display_string_positions(p, i=None, offset=0, length=1, char="^", trim=True) if trim: ret = ret[: maxaddr + 1] return ret + + +def chomp_empty_strings(strings, c): + """ + 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 + the left or "" if it is the first string. + Examples: + for c="_" + ['hey', '', 'why', '', '', 'whoa', '', ''] -> ['hey_', 'why__', 'whoa__'] + ['', 'hi', '', "I'm", 'bob', '', ''] -> ['_', 'hi_', "I'm", 'bob__'] + ['hi', "i'm", 'a', 'good', 'string'] -> ['hi', "i'm", 'a', 'good', 'string'] + Some special cases are: + [] -> [] + [''] -> [''] + ['', ''] -> ['_'] + ['', '', '', ''] -> ['___'] + """ + if not len(strings): + return strings + if sum(map(len, strings)) == 0: + return [c * (len(strings) - 1)] + + class _Chomper: + def __init__(self, c): + self.c = c + + def __call__(self, x, y): + # x is list up to now + # y is next item in list + # x should be [""] initially, and then empty strings filtered out at the + # end + if len(y) == 0: + return x[:-1] + [x[-1] + self.c] + else: + return x + [y] + + return list(filter(len, reduce(_Chomper(c), strings, [""]))) diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index e9c35e1ce3c..62c7a1fccf4 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -9,6 +9,7 @@ from contextlib import contextmanager from copy import deepcopy, copy import itertools +from functools import reduce from _plotly_utils.utils import ( _natural_sort_strings, @@ -16,6 +17,7 @@ split_multichar, split_string_positions, display_string_positions, + chomp_empty_strings, ) from _plotly_utils.exceptions import PlotlyKeyError from .optional_imports import get_module @@ -63,40 +65,69 @@ def _str_to_dict_path_full(key_path_str): tuple[str | int] tuple [int] """ - key_path2 = split_multichar([key_path_str], list(".[]")) - # Split out underscore - # e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1'] - key_path3 = [] - underscore_props = BaseFigure._valid_underscore_properties - - def _make_hyphen_key(key): - if "_" in key[1:]: - # For valid properties that contain underscores (error_x) - # replace the underscores with hyphens to protect them - # from being split up - for under_prop, hyphen_prop in underscore_props.items(): - key = key.replace(under_prop, hyphen_prop) - return key - - def _make_underscore_key(key): - return key.replace("-", "_") - - key_path2b = map(_make_hyphen_key, key_path2) - key_path2c = split_multichar(key_path2b, list("_")) - key_path2d = list(map(_make_underscore_key, key_path2c)) - all_elem_idcs = tuple(split_string_positions(list(key_path2d))) - # remove empty strings, and indices pointing to them - key_elem_pairs = list(filter(lambda t: len(t[1]), enumerate(key_path2d))) - key_path3 = [x for _, x in key_elem_pairs] - elem_idcs = [all_elem_idcs[i] for i, _ in key_elem_pairs] - - # Convert elements to ints if possible. - # e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0] - for i in range(len(key_path3)): - try: - key_path3[i] = int(key_path3[i]) - except ValueError as _: - pass + # skip all the parsing if the string is empty + if len(key_path_str): + # split string on ".[]" and filter out empty strings + key_path2 = split_multichar([key_path_str], list(".[]")) + # Split out underscore + # e.g. ['foo', 'bar_baz', '1'] -> ['foo', 'bar', 'baz', '1'] + key_path3 = [] + underscore_props = BaseFigure._valid_underscore_properties + + def _make_hyphen_key(key): + if "_" in key[1:]: + # For valid properties that contain underscores (error_x) + # replace the underscores with hyphens to protect them + # from being split up + for under_prop, hyphen_prop in underscore_props.items(): + key = key.replace(under_prop, hyphen_prop) + return key + + def _make_underscore_key(key): + return key.replace("-", "_") + + key_path2b = list(map(_make_hyphen_key, key_path2)) + # Here we want to split up each non-empty string in the list at + # underscores and recombine the strings using chomp_empty_strings so + # that leading, trailing and multiple _ will be preserved + def _split_and_chomp(s): + if not len(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, "_") + return s_chomped + + # after running _split_and_chomp on key_path2b, it will be a list + # containing strings and lists of strings; concatenate the sublists with + # the list ("lift" the items out of the sublists) + key_path2c = list( + reduce( + lambda x, y: x + y if type(y) == type(list()) else x + [y], + map(_split_and_chomp, key_path2b), + [], + ) + ) + + key_path2d = list(map(_make_underscore_key, key_path2c)) + all_elem_idcs = tuple(split_string_positions(list(key_path2d))) + # remove empty strings, and indices pointing to them + key_elem_pairs = list(filter(lambda t: len(t[1]), enumerate(key_path2d))) + key_path3 = [x for _, x in key_elem_pairs] + elem_idcs = [all_elem_idcs[i] for i, _ in key_elem_pairs] + + # Convert elements to ints if possible. + # e.g. ['foo', 'bar', '0'] -> ['foo', 'bar', 0] + for i in range(len(key_path3)): + try: + key_path3[i] = int(key_path3[i]) + except ValueError as _: + pass + else: + key_path3 = [] + elem_idcs = [] return (tuple(key_path3), elem_idcs) From 70f18ca9bda932f3b207a8134f4bb25f0bba545c Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 10 Nov 2020 11:01:22 -0500 Subject: [PATCH 20/24] Added tests for leading, trailing and embedded extra underscore errors --- .../test_errors/test_dict_path_errors.py | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 76db87cfd86..c1e71e92e6c 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -423,3 +423,174 @@ def test_subscript_error_exception_types(some_fig): some_fig.update_layout(width_yo=100) with pytest.raises(KeyError): yo = some_fig["layout_width_yo"] + + +def form_error_string(call, exception, subs): + """ + call is a function that raises exception. + exception is an exception class, e.g., KeyError. + subs is a list of replacements to be performed on the exception string. Each + replacement is only performed once on the exception string so the + replacement of multiple occurences of a pattern is specified by repeating a + (pattern,relacement) pair in the list. + returns modified exception string + """ + raised = False + try: + call() + except exception as e: + raised = True + msg = e.args[0] + for pat, rep in subs: + msg = msg.replace(pat, rep, 1) + assert raised + return msg + + +def check_error_string(call, exception, correct_str, subs): + raised = False + try: + call() + except exception as e: + raised = True + msg = e.args[0] + for pat, rep in subs: + msg = msg.replace(pat, rep, 1) + assert msg == correct_str + assert raised + + +def test_leading_underscore_errors(some_fig): + # get error string but alter it to form the final expected string + def _raise_bad_property_path_form(): + some_fig.update_layout(bogus=7) + + def _raise_bad_property_path_real(): + some_fig.update_layout(_hey_yall=7) + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "_"), ("bogus", "_hey_yall"), ("^^^^^", "^")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_trailing_underscore_errors(some_fig): + # get error string but alter it to form the final expected string + def _raise_bad_property_path_form(): + some_fig.update_layout(title_bogus="hi") + + def _raise_bad_property_path_real(): + some_fig.update_layout(title_text_="hi") + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "text_"), ("title_bogus", "title_text_")], + ) + # no need to replace ^^^^^ because bogus and text_ are same length + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_embedded_underscore_errors(some_fig): + # get error string but alter it to form the final expected string + def _raise_bad_property_path_form(): + some_fig.update_layout(title_bogus_family="hi") + + def _raise_bad_property_path_real(): + some_fig.update_layout(title_font__family="hi") + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "font_"), ("title_bogus_family", "title_font__family")], + ) + # no need to replace ^^^^^ because bogus and font_ are same length + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_solo_underscore_errors(some_fig): + # get error string but alter it to form the final expected string + def _raise_bad_property_path_form(): + some_fig.update_layout(bogus="hi") + + def _raise_bad_property_path_real(): + some_fig.update_layout(_="hi") + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "_"), ("bogus", "_"), ("^^^^^", "^")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_repeated_underscore_errors(some_fig): + # get error string but alter it to form the final expected string + def _raise_bad_property_path_form(): + some_fig.update_layout(bogus="hi") + + def _raise_bad_property_path_real(): + some_fig.update_layout(__="hi") + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "__"), ("bogus", "__"), ("^^^^^", "^^")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_leading_underscore_errors_dots_and_subscripts(some_fig): + # get error string but alter it to form the final expected string + some_fig.add_annotation(text="hi") + + def _raise_bad_property_path_form(): + some_fig["layout.annotations[0].bogus_family"] = "hi" + + def _raise_bad_property_path_real(): + some_fig["layout.annotations[0]._font_family"] = "hi" + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "_"), ("bogus", "_font"), ("^^^^^", "^")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_trailing_underscore_errors_dots_and_subscripts(some_fig): + # get error string but alter it to form the final expected string + some_fig.add_annotation(text="hi") + + def _raise_bad_property_path_form(): + some_fig["layout.annotations[0].font_bogusey"] = "hi" + + def _raise_bad_property_path_real(): + some_fig["layout.annotations[0].font_family_"] = "hi" + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogusey", "family_"), ("bogusey", "family_")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_repeated_underscore_errors_dots_and_subscripts(some_fig): + # get error string but alter it to form the final expected string + some_fig.add_annotation(text="hi") + + def _raise_bad_property_path_form(): + some_fig["layout.annotations[0].bogus_family"] = "hi" + + def _raise_bad_property_path_real(): + some_fig["layout.annotations[0].font__family"] = "hi" + + correct_err_str = form_error_string( + _raise_bad_property_path_form, + ValueError, + [("bogus", "font_"), ("bogus", "font_")], + ) + check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) From 00851fa91ec2c3e48eea6100335a1efe22c52633 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 10 Nov 2020 15:33:11 -0500 Subject: [PATCH 21/24] Complain about trailing underscores, find closest key Tests in tests/test_core/test_errors/test_dict_path_errors.py not yet updated though. --- packages/python/plotly/_plotly_utils/utils.py | 40 ++++++++++++++++++- .../python/plotly/plotly/basedatatypes.py | 33 ++++++++++++--- .../test_errors/test_dict_path_errors.py | 6 ++- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 54ccb6d4e4b..85885934021 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -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="_" @@ -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: @@ -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] diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 62c7a1fccf4..06d9977acf5 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -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 @@ -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 @@ -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: @@ -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 diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index c1e71e92e6c..42cde10dc1d 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -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 ) @@ -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 ) From d2bc4006b895b0f31f5838f7c39ad4661474d5a6 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 10 Nov 2020 16:46:21 -0500 Subject: [PATCH 22/24] Updated error messages for trailing underscores and find closest key --- .../test_errors/test_dict_path_errors.py | 95 +++++++++++++++---- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 42cde10dc1d..95dc69d00e1 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -118,14 +118,16 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: data[0].line_colr - ^^^^""", + ^^^^ +Did you mean "color"?""", ) assert ( ( e.args[0].find( """Bad property path: data[0].line_colr - ^^^^""" + ^^^^ +Did you mean "color"?""" ) >= 0 ) @@ -168,7 +170,8 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): e.args[0].find( """Bad property path: line_colr - ^^^^""" + ^^^^ +Did you mean "color"?""" ) >= 0 ) @@ -189,7 +192,8 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: txt -^^^""", +^^^ +Did you mean "text"?""", ) assert raised @@ -206,7 +210,8 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: layout_title_txt - ^^^""", + ^^^ +Did you mean "text"?""", ) # also remove the invalid Figure property string added by the Figure constructor e_substr = error_substr( @@ -219,7 +224,8 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): e.args[0].find( """Bad property path: layout_title_txt - ^^^""" + ^^^ +Did you mean "text"?""", ) >= 0 ) @@ -239,7 +245,8 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: ltaxis -^^^^^^""", +^^^^^^ +Did you mean "lataxis"?""", ) assert raised @@ -253,14 +260,16 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: geo_ltaxis_showgrid - ^^^^^^""", + ^^^^^^ +Did you mean "lataxis"?""", ) assert ( ( e.args[0].find( """Bad property path: geo_ltaxis_showgrid - ^^^^^^""" + ^^^^^^ +Did you mean "lataxis"?""" ) >= 0 ) @@ -458,6 +467,10 @@ def check_error_string(call, exception, correct_str, subs): msg = e.args[0] for pat, rep in subs: msg = msg.replace(pat, rep, 1) + print("MSG") + print(msg) + print("CORRECT") + print(correct_str) assert msg == correct_str assert raised @@ -473,7 +486,15 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "_"), ("bogus", "_hey_yall"), ("^^^^^", "^")], + # change last boxgap to geo because bogus is closest to boxgap but _hey + # closest to geo, but remember that boxgap is in the list of valid keys + # displayed by the error string + [ + ("bogus", "_hey"), + ("bogus", "_hey_yall"), + ("^^^^^", "^^^^"), + ('Did you mean "boxgap"', 'Did you mean "geo"'), + ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -481,7 +502,7 @@ def _raise_bad_property_path_real(): def test_trailing_underscore_errors(some_fig): # get error string but alter it to form the final expected string def _raise_bad_property_path_form(): - some_fig.update_layout(title_bogus="hi") + some_fig.update_layout(title_text_bogus="hi") def _raise_bad_property_path_real(): some_fig.update_layout(title_text_="hi") @@ -489,7 +510,14 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "text_"), ("title_bogus", "title_text_")], + [ + ( + "Property does not support subscripting", + "Property does not support subscripting and path has trailing underscores", + ), + ("text_bogus", "text_"), + ("^^^^", "^^^^^"), + ], ) # no need to replace ^^^^^ because bogus and text_ are same length check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -498,7 +526,7 @@ def _raise_bad_property_path_real(): def test_embedded_underscore_errors(some_fig): # get error string but alter it to form the final expected string def _raise_bad_property_path_form(): - some_fig.update_layout(title_bogus_family="hi") + some_fig.update_layout(title_font_bogusey="hi") def _raise_bad_property_path_real(): some_fig.update_layout(title_font__family="hi") @@ -506,7 +534,11 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "font_"), ("title_bogus_family", "title_font__family")], + [ + ("bogusey", "_family"), + ("bogusey", "_family"), + ('Did you mean "size"?', 'Did you mean "family"?'), + ], ) # no need to replace ^^^^^ because bogus and font_ are same length check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -523,7 +555,12 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "_"), ("bogus", "_"), ("^^^^^", "^")], + [ + ("bogus", "_"), + ("bogus", "_"), + ("^^^^^", "^"), + ('Did you mean "boxgap"', 'Did you mean "geo"'), + ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -539,7 +576,12 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "__"), ("bogus", "__"), ("^^^^^", "^^")], + [ + ("bogus", "__"), + ("bogus", "__"), + ("^^^^^", "^^"), + ('Did you mean "boxgap"', 'Did you mean "geo"'), + ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -557,7 +599,7 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "_"), ("bogus", "_font"), ("^^^^^", "^")], + [("bogus", "_font"), ("bogus", "_font"), ("^^^^^", "^^^^^")], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -567,7 +609,7 @@ def test_trailing_underscore_errors_dots_and_subscripts(some_fig): some_fig.add_annotation(text="hi") def _raise_bad_property_path_form(): - some_fig["layout.annotations[0].font_bogusey"] = "hi" + some_fig["layout.annotations[0].font_family_bogus"] = "hi" def _raise_bad_property_path_real(): some_fig["layout.annotations[0].font_family_"] = "hi" @@ -575,7 +617,14 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogusey", "family_"), ("bogusey", "family_")], + [ + ( + "Property does not support subscripting", + "Property does not support subscripting and path has trailing underscores", + ), + ("family_bogus", "family_"), + ("^^^^^^", "^^^^^^^"), + ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -585,7 +634,7 @@ def test_repeated_underscore_errors_dots_and_subscripts(some_fig): some_fig.add_annotation(text="hi") def _raise_bad_property_path_form(): - some_fig["layout.annotations[0].bogus_family"] = "hi" + some_fig["layout.annotations[0].font_bogusey"] = "hi" def _raise_bad_property_path_real(): some_fig["layout.annotations[0].font__family"] = "hi" @@ -593,6 +642,10 @@ def _raise_bad_property_path_real(): correct_err_str = form_error_string( _raise_bad_property_path_form, ValueError, - [("bogus", "font_"), ("bogus", "font_")], + [ + ("bogusey", "_family"), + ("bogusey", "_family"), + ('Did you mean "size"?', 'Did you mean "family"?'), + ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) From 20518c14b5a57fa58af5793c6ec348e7169e33f5 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Tue, 10 Nov 2020 18:10:32 -0500 Subject: [PATCH 23/24] Key guessing before and after list of valid properties Also tripped when no underscore paths are used, e.g., fig.data[0].line["colr"] = "blue" --- packages/python/plotly/_plotly_utils/utils.py | 4 +- .../python/plotly/plotly/basedatatypes.py | 31 ++++++----- .../test_errors/test_dict_path_errors.py | 51 +++++++++---------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py index 85885934021..9dfb803d486 100644 --- a/packages/python/plotly/_plotly_utils/utils.py +++ b/packages/python/plotly/_plotly_utils/utils.py @@ -433,6 +433,8 @@ def levenshtein(s1, s2): def find_closest_string(string, strings): def _key(s): - return levenshtein(s, string) + # sort by levenshtein distance and lexographically to maintain a stable + # sort for different keys with the same levenshtein distance + return (levenshtein(s, string), s) return sorted(strings, key=_key)[0] diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 06d9977acf5..7434c15e143 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -238,17 +238,6 @@ 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 @@ -4968,15 +4957,29 @@ def _ret(*args): else: full_obj_name = module_root + self.__class__.__name__ + guessed_prop = None + if len(invalid_props) == 1: + try: + guessed_prop = find_closest_string( + invalid_props[0], self._valid_props + ) + except Exception: + pass + guessed_prop_suggestion = "" + if guessed_prop is not None: + guessed_prop_suggestion = 'Did you mean "%s"?' % (guessed_prop,) raise _error_to_raise( "Invalid {prop_str} specified for object of type " - "{full_obj_name}: {invalid_str}\n\n" - " Valid properties:\n" - "{prop_descriptions}".format( + "{full_obj_name}: {invalid_str}\n" + "\n{guessed_prop_suggestion}\n" + "\n Valid properties:\n" + "{prop_descriptions}" + "\n{guessed_prop_suggestion}\n".format( prop_str=prop_str, full_obj_name=full_obj_name, invalid_str=invalid_str, prop_descriptions=self._prop_descriptions, + guessed_prop_suggestion=guessed_prop_suggestion, ) ) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 95dc69d00e1..0770e55fb99 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -49,10 +49,9 @@ 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 + and (e.args[0].find("""Did you mean "x0"?""") >= 0) >= 0 ) assert raised @@ -69,10 +68,9 @@ 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 + and (e.args[0].find("""Did you mean "shapes"?""") >= 0) >= 0 ) assert raised @@ -118,19 +116,18 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: data[0].line_colr - ^^^^ -Did you mean "color"?""", + ^^^^""", ) assert ( ( e.args[0].find( """Bad property path: data[0].line_colr - ^^^^ -Did you mean "color"?""" + ^^^^""" ) >= 0 ) + and (e.args[0].find("""Did you mean "color"?""") >= 0) and (e_substr == e_correct_substr) ) assert raised @@ -170,10 +167,9 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): e.args[0].find( """Bad property path: line_colr - ^^^^ -Did you mean "color"?""" + ^^^^""" ) - >= 0 + and (e.args[0].find("""Did you mean "color"?""") >= 0) >= 0 ) and (e_substr == e_correct_substr) ) @@ -192,8 +188,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: txt -^^^ -Did you mean "text"?""", +^^^""", ) assert raised @@ -210,8 +205,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: layout_title_txt - ^^^ -Did you mean "text"?""", + ^^^""", ) # also remove the invalid Figure property string added by the Figure constructor e_substr = error_substr( @@ -224,11 +218,11 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): e.args[0].find( """Bad property path: layout_title_txt - ^^^ -Did you mean "text"?""", + ^^^""", ) >= 0 ) + and (e.args[0].find("""Did you mean "text"?""") >= 0) and (e_substr == e_correct_substr) ) assert raised @@ -245,8 +239,7 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: ltaxis -^^^^^^ -Did you mean "lataxis"?""", +^^^^^^""", ) assert raised @@ -260,19 +253,18 @@ def test_raises_on_bad_indexed_underscore_property(some_fig): """ Bad property path: geo_ltaxis_showgrid - ^^^^^^ -Did you mean "lataxis"?""", + ^^^^^^""", ) assert ( ( e.args[0].find( """Bad property path: geo_ltaxis_showgrid - ^^^^^^ -Did you mean "lataxis"?""" + ^^^^^^""" ) >= 0 ) + and (e.args[0].find("""Did you mean "lataxis"?""") >= 0) and (e_substr == e_correct_substr) ) assert raised @@ -494,6 +486,7 @@ def _raise_bad_property_path_real(): ("bogus", "_hey_yall"), ("^^^^^", "^^^^"), ('Did you mean "boxgap"', 'Did you mean "geo"'), + ('Did you mean "boxgap"', 'Did you mean "geo"'), ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -537,7 +530,8 @@ def _raise_bad_property_path_real(): [ ("bogusey", "_family"), ("bogusey", "_family"), - ('Did you mean "size"?', 'Did you mean "family"?'), + ('Did you mean "color"?', 'Did you mean "family"?'), + ('Did you mean "color"?', 'Did you mean "family"?'), ], ) # no need to replace ^^^^^ because bogus and font_ are same length @@ -560,6 +554,7 @@ def _raise_bad_property_path_real(): ("bogus", "_"), ("^^^^^", "^"), ('Did you mean "boxgap"', 'Did you mean "geo"'), + ('Did you mean "boxgap"', 'Did you mean "geo"'), ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -581,6 +576,7 @@ def _raise_bad_property_path_real(): ("bogus", "__"), ("^^^^^", "^^"), ('Did you mean "boxgap"', 'Did you mean "geo"'), + ('Did you mean "boxgap"', 'Did you mean "geo"'), ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) @@ -645,7 +641,8 @@ def _raise_bad_property_path_real(): [ ("bogusey", "_family"), ("bogusey", "_family"), - ('Did you mean "size"?', 'Did you mean "family"?'), + ('Did you mean "color"?', 'Did you mean "family"?'), + ('Did you mean "color"?', 'Did you mean "family"?'), ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) From 4066ae253dd1c6b0ba1bfe1b91ec2b4cdff61c54 Mon Sep 17 00:00:00 2001 From: Nicholas Esterer Date: Wed, 11 Nov 2020 10:31:59 -0500 Subject: [PATCH 24/24] Test single property key guessing --- .../test_core/test_errors/test_dict_path_errors.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 0770e55fb99..271533d23a4 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -646,3 +646,13 @@ def _raise_bad_property_path_real(): ], ) check_error_string(_raise_bad_property_path_real, ValueError, correct_err_str, []) + + +def test_single_prop_path_key_guess(some_fig): + raised = False + try: + some_fig.layout.shapes[0]["typ"] = "sandwich" + except ValueError as e: + raised = True + assert e.args[0].find('Did you mean "type"?') >= 0 + assert raised