Skip to content

Commit ffc277d

Browse files
committed
Don't construct validators up front for every object when object is created.
Create them lazily, and cache then for use across graph objects of the same type
1 parent 5974cf5 commit ffc277d

File tree

3 files changed

+150
-36
lines changed

3 files changed

+150
-36
lines changed

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

+115-35
Original file line numberDiff line numberDiff line change
@@ -1945,11 +1945,12 @@ def _init_child_props(self, child):
19451945
def _initialize_layout_template(self):
19461946
import plotly.io as pio
19471947

1948-
if self._layout_obj.template is None:
1948+
if self._layout_obj._props.get("template", None) is None:
19491949
if pio.templates.default is not None:
1950-
self._layout_obj.template = pio.templates.default
1951-
else:
1952-
self._layout_obj.template = None
1950+
with validate(False):
1951+
# Assume default template is already validated
1952+
template_dict = pio.templates[pio.templates.default]
1953+
self._layout_obj.template = template_dict
19531954

19541955
@property
19551956
def layout(self):
@@ -3019,6 +3020,47 @@ def __init__(self, plotly_name, **kwargs):
30193020
# properties is modified
30203021
self._change_callbacks = {}
30213022

3023+
# ### Backing property for backward compatible _validator property ##
3024+
self.__validators = None
3025+
3026+
def _get_validator(self, prop):
3027+
from .validator_cache import ValidatorCache
3028+
3029+
return ValidatorCache.get_validator(self._path_str, prop)
3030+
3031+
@property
3032+
def _validators(self):
3033+
"""
3034+
Validators used to be stored in a private _validators property. This was
3035+
eliminated when we switched to building validators on demand using the
3036+
_get_validator method.
3037+
3038+
This property returns a simple object that
3039+
3040+
Returns
3041+
-------
3042+
dict-like interface for accessing the object's validators
3043+
"""
3044+
obj = self
3045+
if self.__validators is None:
3046+
3047+
class ValidatorCompat(object):
3048+
def __getitem__(self, item):
3049+
return obj._get_validator(item)
3050+
3051+
def __contains__(self, item):
3052+
return obj.__contains__(item)
3053+
3054+
def __iter__(self):
3055+
return iter(obj)
3056+
3057+
def items(self):
3058+
return [(k, self[k]) for k in self]
3059+
3060+
self.__validators = ValidatorCompat()
3061+
3062+
return self.__validators
3063+
30223064
def _process_kwargs(self, **kwargs):
30233065
"""
30243066
Process any extra kwargs that are not predefined as constructor params
@@ -3122,21 +3164,30 @@ def _get_child_props(self, child):
31223164
return None
31233165
else:
31243166
# ### Child a compound property ###
3125-
if child.plotly_name in self._compound_props:
3126-
return self._props.get(child.plotly_name, None)
3167+
if child.plotly_name in self:
3168+
from _plotly_utils.basevalidators import (
3169+
CompoundValidator,
3170+
CompoundArrayValidator,
3171+
)
31273172

3128-
# ### Child an element of a compound array property ###
3129-
elif child.plotly_name in self._compound_array_props:
3130-
children = self._compound_array_props[child.plotly_name]
3131-
child_ind = BaseFigure._index_is(children, child)
3132-
assert child_ind is not None
3173+
validator = self._get_validator(child.plotly_name)
31333174

3134-
children_props = self._props.get(child.plotly_name, None)
3135-
return (
3136-
children_props[child_ind]
3137-
if children_props is not None and len(children_props) > child_ind
3138-
else None
3139-
)
3175+
if isinstance(validator, CompoundValidator):
3176+
return self._props.get(child.plotly_name, None)
3177+
3178+
# ### Child an element of a compound array property ###
3179+
elif isinstance(validator, CompoundArrayValidator):
3180+
children = self[child.plotly_name]
3181+
child_ind = BaseFigure._index_is(children, child)
3182+
assert child_ind is not None
3183+
3184+
children_props = self._props.get(child.plotly_name, None)
3185+
return (
3186+
children_props[child_ind]
3187+
if children_props is not None
3188+
and len(children_props) > child_ind
3189+
else None
3190+
)
31403191

31413192
# ### Invalid child ###
31423193
else:
@@ -3282,7 +3333,7 @@ def _get_prop_validator(self, prop):
32823333

32833334
# Return validator
32843335
# ----------------
3285-
return plotly_obj._validators[prop]
3336+
return plotly_obj._get_validator(prop)
32863337

32873338
@property
32883339
def parent(self):
@@ -3344,6 +3395,11 @@ def __getitem__(self, prop):
33443395
-------
33453396
Any
33463397
"""
3398+
from _plotly_utils.basevalidators import (
3399+
CompoundValidator,
3400+
CompoundArrayValidator,
3401+
BaseDataValidator,
3402+
)
33473403

33483404
# Normalize prop
33493405
# --------------
@@ -3361,13 +3417,33 @@ def __getitem__(self, prop):
33613417
if len(prop) == 1:
33623418
# Unwrap scalar tuple
33633419
prop = prop[0]
3364-
if prop not in self._validators:
3420+
if prop not in self._valid_props:
33653421
raise KeyError(prop)
33663422

3367-
validator = self._validators[prop]
3368-
if prop in self._compound_props:
3423+
validator = self._get_validator(prop)
3424+
3425+
if isinstance(validator, CompoundValidator):
3426+
if self._compound_props.get(prop, None) is None:
3427+
# Init compound objects
3428+
self._compound_props[prop] = validator.data_class(
3429+
_parent=self, plotly_name=prop
3430+
)
3431+
# Update plotly_name value in case the validator applies
3432+
# non-standard name (e.g. imagedefaults instead of image)
3433+
self._compound_props[prop]._plotly_name = prop
3434+
33693435
return validator.present(self._compound_props[prop])
3370-
elif prop in self._compound_array_props:
3436+
elif isinstance(validator, (CompoundArrayValidator, BaseDataValidator)):
3437+
if self._compound_array_props.get(prop, None) is None:
3438+
# Init list of compound objects
3439+
if self._props is not None:
3440+
self._compound_array_props[prop] = [
3441+
validator.data_class(_parent=self)
3442+
for _ in self._props.get(prop, [])
3443+
]
3444+
else:
3445+
self._compound_array_props[prop] = []
3446+
33713447
return validator.present(self._compound_array_props[prop])
33723448
elif self._props is not None and prop in self._props:
33733449
return validator.present(self._props[prop])
@@ -3422,7 +3498,7 @@ def __contains__(self, prop):
34223498
else:
34233499
return False
34243500
else:
3425-
if obj is not None and p in obj._validators:
3501+
if obj is not None and p in obj._valid_props:
34263502
obj = obj[p]
34273503
else:
34283504
return False
@@ -3445,6 +3521,11 @@ def __setitem__(self, prop, value):
34453521
-------
34463522
None
34473523
"""
3524+
from _plotly_utils.basevalidators import (
3525+
CompoundValidator,
3526+
CompoundArrayValidator,
3527+
BaseDataValidator,
3528+
)
34483529

34493530
# Normalize prop
34503531
# --------------
@@ -3511,7 +3592,7 @@ def __setattr__(self, prop, value):
35113592
-------
35123593
None
35133594
"""
3514-
if prop.startswith("_") or hasattr(self, prop) or prop in self._validators:
3595+
if prop.startswith("_") or hasattr(self, prop) or prop in self._valid_props:
35153596
# Let known properties and private properties through
35163597
super(BasePlotlyType, self).__setattr__(prop, value)
35173598
else:
@@ -3522,7 +3603,7 @@ def __iter__(self):
35223603
"""
35233604
Return an iterator over the object's properties
35243605
"""
3525-
res = list(self._validators.keys())
3606+
res = list(self._valid_props)
35263607
for prop in self._mapped_properties:
35273608
res.append(prop)
35283609
return iter(res)
@@ -3605,8 +3686,8 @@ def __repr__(self):
36053686
props = {
36063687
p: v
36073688
for p, v in props.items()
3608-
if p in self._validators
3609-
and not isinstance(self._validators[p], LiteralValidator)
3689+
if p in self._valid_props
3690+
and not isinstance(self._get_validator(p), LiteralValidator)
36103691
}
36113692

36123693
# Elide template
@@ -3767,7 +3848,8 @@ def _set_prop(self, prop, val):
37673848

37683849
# Import value
37693850
# ------------
3770-
validator = self._validators.get(prop)
3851+
validator = self._get_validator(prop)
3852+
37713853
try:
37723854
val = validator.validate_coerce(val)
37733855
except ValueError as err:
@@ -3832,7 +3914,7 @@ def _set_compound_prop(self, prop, val):
38323914

38333915
# Import value
38343916
# ------------
3835-
validator = self._validators.get(prop)
3917+
validator = self._get_validator(prop)
38363918
val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
38373919

38383920
# Save deep copies of current and new states
@@ -3906,7 +3988,7 @@ def _set_array_prop(self, prop, val):
39063988

39073989
# Import value
39083990
# ------------
3909-
validator = self._validators.get(prop)
3991+
validator = self._get_validator(prop)
39103992
val = validator.validate_coerce(val, skip_invalid=self._skip_invalid)
39113993

39123994
# Save deep copies of current and new states
@@ -4331,10 +4413,8 @@ def _set_subplotid_prop(self, prop, value):
43314413

43324414
# Construct and add validator
43334415
# ---------------------------
4334-
if prop not in self._validators:
4335-
validator_class = self._subplotid_validators[subplot_prop]
4336-
validator = validator_class(plotly_name=prop)
4337-
self._validators[prop] = validator
4416+
if prop not in self._valid_props:
4417+
self._valid_props.add(prop)
43384418

43394419
# Import value
43404420
# ------------
@@ -4395,7 +4475,7 @@ def __getattr__(self, prop):
43954475
"""
43964476
prop = self._strip_subplot_suffix_of_1(prop)
43974477
if prop != "_subplotid_props" and prop in self._subplotid_props:
4398-
validator = self._validators[prop]
4478+
validator = self._get_validator(prop)
43994479
return validator.present(self._compound_props[prop])
44004480
else:
44014481
return super(BaseLayoutHierarchyType, self).__getattribute__(prop)

Diff for: packages/python/plotly/plotly/io/_templates.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def walk_push_to_template(fig_obj, template_obj, skip):
304304
fig_val = fig_obj[prop]
305305
template_val = template_obj[prop]
306306

307-
validator = fig_obj._validators[prop]
307+
validator = fig_obj._get_validator(prop)
308308

309309
if isinstance(validator, CompoundValidator):
310310
walk_push_to_template(fig_val, template_val, skip)

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

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import importlib
2+
from _plotly_utils.basevalidators import LiteralValidator
3+
4+
5+
class ValidatorCache(object):
6+
_cache = {}
7+
8+
@staticmethod
9+
def get_validator(parent_path, prop_name):
10+
11+
key = (parent_path, prop_name)
12+
if key not in ValidatorCache._cache:
13+
14+
if "." not in parent_path and prop_name == "type":
15+
# Special case for .type property of traces
16+
validator = LiteralValidator("type", parent_path, parent_path)
17+
else:
18+
lookup_name = None
19+
if parent_path == "layout":
20+
from .graph_objects import Layout
21+
22+
match = Layout._subplotid_prop_re.match(prop_name)
23+
if match:
24+
lookup_name = match.group(1)
25+
26+
lookup_name = lookup_name or prop_name
27+
class_name = lookup_name.title() + "Validator"
28+
validator = getattr(
29+
importlib.import_module("plotly.validators." + parent_path),
30+
class_name,
31+
)(plotly_name=prop_name)
32+
ValidatorCache._cache[key] = validator
33+
34+
return ValidatorCache._cache[key]

0 commit comments

Comments
 (0)