Skip to content

Commit aec7e9a

Browse files
committed
Add optional validation using go.validate object as callable of context manager
1 parent 181e427 commit aec7e9a

File tree

4 files changed

+110
-18
lines changed

4 files changed

+110
-18
lines changed

Diff for: packages/python/plotly/_plotly_utils/basevalidators.py

+8
Original file line numberDiff line numberDiff line change
@@ -2457,6 +2457,10 @@ def validate_coerce(self, v, skip_invalid=False):
24572457
v._plotly_name = self.plotly_name
24582458
return v
24592459

2460+
def present(self, v):
2461+
# Return compound object as-is
2462+
return v
2463+
24602464

24612465
class TitleValidator(CompoundValidator):
24622466
"""
@@ -2551,6 +2555,10 @@ def validate_coerce(self, v, skip_invalid=False):
25512555

25522556
return v
25532557

2558+
def present(self, v):
2559+
# Return compound object as tuple
2560+
return tuple(v)
2561+
25542562

25552563
class BaseDataValidator(BaseValidator):
25562564
def __init__(

Diff for: packages/python/plotly/plotly/_validate.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class validate(object):
2+
_should_validate = True
3+
4+
def __init__(self, should_validate):
5+
self._old = validate._should_validate
6+
validate._should_validate = should_validate
7+
8+
def __enter__(self):
9+
pass
10+
11+
def __exit__(self, exc_type, exc_val, exc_tb):
12+
validate._should_validate = self._old

Diff for: packages/python/plotly/plotly/basedatatypes.py

+42-18
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@
1010
from copy import deepcopy, copy
1111

1212
from _plotly_utils.utils import _natural_sort_strings
13-
from plotly.subplots import (
14-
_set_trace_grid_reference,
15-
_get_grid_subplot,
16-
_get_subplot_ref_for_trace,
17-
)
13+
from plotly._validate import validate
1814
from .optional_imports import get_module
1915

2016
from _plotly_utils.basevalidators import (
@@ -3070,6 +3066,9 @@ def _process_kwargs(self, **kwargs):
30703066
if k in self:
30713067
# e.g. underscore kwargs like marker_line_color
30723068
self[k] = v
3069+
elif not validate._should_validate:
3070+
# Set extra property as-is
3071+
self[k] = v
30733072
else:
30743073
invalid_kwargs[k] = v
30753074

@@ -3551,24 +3550,49 @@ def __setitem__(self, prop, value):
35513550
# ### Unwrap scalar tuple ###
35523551
prop = prop[0]
35533552

3554-
# ### Validate prop ###
3555-
if prop not in self._validators:
3556-
self._raise_on_invalid_property_error(prop)
3553+
if validate._should_validate:
3554+
if prop not in self._valid_props:
3555+
self._raise_on_invalid_property_error(prop)
35573556

3558-
# ### Get validator for this property ###
3559-
validator = self._validators[prop]
3557+
# ### Get validator for this property ###
3558+
validator = self._get_validator(prop)
35603559

3561-
# ### Handle compound property ###
3562-
if isinstance(validator, CompoundValidator):
3563-
self._set_compound_prop(prop, value)
3560+
# ### Handle compound property ###
3561+
if isinstance(validator, CompoundValidator):
3562+
self._set_compound_prop(prop, value)
35643563

3565-
# ### Handle compound array property ###
3566-
elif isinstance(validator, (CompoundArrayValidator, BaseDataValidator)):
3567-
self._set_array_prop(prop, value)
3564+
# ### Handle compound array property ###
3565+
elif isinstance(validator, (CompoundArrayValidator, BaseDataValidator)):
3566+
self._set_array_prop(prop, value)
35683567

3569-
# ### Handle simple property ###
3568+
# ### Handle simple property ###
3569+
else:
3570+
self._set_prop(prop, value)
35703571
else:
3571-
self._set_prop(prop, value)
3572+
# Make sure properties dict is initialized
3573+
self._init_props()
3574+
3575+
if isinstance(value, BasePlotlyType):
3576+
# Extract json from graph objects
3577+
value = value.to_plotly_json()
3578+
3579+
# Check for list/tuple of graph objects
3580+
if (
3581+
isinstance(value, (list, tuple))
3582+
and value
3583+
and isinstance(value[0], BasePlotlyType)
3584+
):
3585+
value = [
3586+
v.to_plotly_json() if isinstance(v, BasePlotlyType) else v
3587+
for v in value
3588+
]
3589+
3590+
self._props[prop] = value
3591+
3592+
# Remove any already constructed graph object so that it will be
3593+
# reconstructed on property access
3594+
self._compound_props.pop(prop, None)
3595+
self._compound_array_props.pop(prop, None)
35723596

35733597
# Handle non-scalar case
35743598
# ----------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import plotly.graph_objs as go
2+
import json
3+
import pytest
4+
import plotly.io as pio
5+
6+
7+
def build_invalid_fig():
8+
return go.Figure(
9+
data=[{"type": "bar", "y": "not_a_list", "bogus": 23}],
10+
layout_title_text="valid title",
11+
layout_colorway="not a dict",
12+
)
13+
14+
15+
expected_invalid_dict = dict(
16+
data=[{"type": "bar", "y": "not_a_list", "bogus": 23}],
17+
layout={"title": {"text": "valid title"}, "colorway": "not a dict"},
18+
)
19+
20+
21+
def test_validate_false():
22+
template = pio.templates.default
23+
should_validate = go.validate._should_validate
24+
try:
25+
pio.templates.default = None
26+
27+
# Build figure with variety of invalid properties (both name and value),
28+
# make sure underscore notation is still applied properly
29+
with go.validate(False):
30+
fig = build_invalid_fig()
31+
assert json.loads(fig.to_json()) == expected_invalid_dict
32+
33+
with go.validate(True):
34+
with pytest.raises(ValueError):
35+
build_invalid_fig()
36+
37+
# Use validate as callable
38+
go.validate(False)
39+
fig = build_invalid_fig()
40+
assert json.loads(fig.to_json()) == expected_invalid_dict
41+
42+
go.validate(True)
43+
with pytest.raises(ValueError):
44+
build_invalid_fig()
45+
46+
finally:
47+
pio.templates.default = template
48+
go.validate(should_validate)

0 commit comments

Comments
 (0)