Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Validate component properties #264 - #340

Closed
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate
Closed

Validate component properties #264#340
rmarren1 wants to merge 96 commits into
plotly:masterfrom
rmarren1:validate

Conversation

@rmarren1

@rmarren1rmarren1 commented Aug 17, 2018

Copy link
Copy Markdown
Contributor

PR for #264

Current Prerelease

pip install dash==0.29.0rc8
pip install dash-renderer==0.15.0rc1
pip install dash-core-components==0.31.0rc2
pip install dash-html-components==0.14.0rc3

Validation is on by default. To turn it off, you can set app.config.disable_component_validation = True

Test Cases to run for demo

import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
from dash.dependencies import Input, Output
app = dash.Dash()
app.scripts.config.serve_locally=True
app.layout = html.Div([
html.Button(id='click1', children='click to return bad Div children'),
html.Div(id='output1', **{'data-cb': 'foo'}),
html.Button(id='click2', children='click to return a bad figure'),
dcc.Graph(id='output2', figure={'data': [], 'layout': {}}),
html.Button(id='click3', children='click to return a bad radio'),
dcc.RadioItems(id='output3', options=[{'value': 'okay', 'label': 'okay'}]),
html.Button(id='click4', children='click to make a figure with no id'),
html.Div(id='output4'),
])
@app.callback(Output('output1', 'children'),
[Input('click1', 'n_clicks')])
def crash_it1(clicks):
if clicks:
return [[]]
return clicks
@app.callback(Output('output2', 'figure'),
[Input('click2', 'n_clicks')])
def crash_it2(clicks):
if clicks:
return {'data': {'x': [1, 2, 3], 'y': [1, 2, 3], 'type': 'scatter'}, 'layout': {}}
return go.Figure(data=[go.Scatter(x=[1,2,3], y=[1,2,3])], layout=go.Layout()) @app.callback(Output('output3', 'options'),
[Input('click3', 'n_clicks')])
def crash_it3(clicks):
if clicks:
return [{'value': {'not okay': True}, 'labl': 'not okay'}]
return [{'value': 'okay', 'label': 'okay'}]
@app.callback(Output('output4', 'children'),
[Input('click4', 'n_clicks')])
def crash_it4(clicks):
if clicks:
return dcc.Graph()
return dcc.Graph(id='hi')
app.run_server(debug=True, port=8050)

Example Error Messages

CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `figure` prop of the
`Graph` with id `output2` by calling the
`crash_it2` function with `(1)` as arguments.
This function call returned `{'layout': {}, 'data': {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}}`, which did not pass
validation tests for the `Graph` component.
The expected schema for the `figure` prop of the
`Graph` component is:
***************************************************************
{'validator': 'plotly_figure'}
***************************************************************
The errors in validation are as follows:
* figure	<- Invalid Plotly Figure:
Invalid value of type '__builtin__.dict' received for the 'data' property of Received value: {'y': [1, 2, 3], 'x': [1, 2, 3], 'type': 'scatter'}
The 'data' property is a tuple of trace instances
that may be specified as:
- A list or tuple of trace instances
(e.g. [Scatter(...), Bar(...)])
- A list or tuple of dicts of string/value properties where:
- The 'type' property specifies the trace type
One of: ['mesh3d', 'splom', 'scattercarpet',
'scattergl', 'scatterternary', 'pie',
'surface', 'histogram', 'ohlc', 'heatmapgl',
'cone', 'scatterpolar', 'table',
'scatterpolargl', 'histogram2d', 'contour',
'carpet', 'box', 'violin', 'bar',
'contourcarpet', 'area', 'choropleth',
'candlestick', 'streamtube', 'parcoords',
'heatmap', 'barpolar', 'scattermapbox',
'scatter3d', 'pointcloud',
'histogram2dcontour', 'scatter', 'scattergeo',
'sankey']
- All remaining properties are passed to the constructor of
the specified trace type
(e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])
CallbackOutputValidationError: A Dash Callback produced an invalid value!
Dash tried to update the `options` prop of the
`RadioItems` with id `output3` by calling the
`crash_it3` function with `(1)` as arguments.
This function call returned `[{'value': {'not okay': True}, 'labl': 'not okay'}]`, which did not pass
validation tests for the `RadioItems` component.
The expected schema for the `options` prop of the
`RadioItems` component is:
***************************************************************
{'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type
* labl	<- unknown field
ComponentInitializationValidationError: A Dash Component was initialized with invalid properties!
Dash tried to create a `RadioItems` component with the
following arguments, which caused a validation failure:
***************************************************************
{'id': 'output3', 'options': [{'label': 'okay', 'value': {}}]}
***************************************************************
The expected schema for the `RadioItems` component is:
***************************************************************
{'className': {'type': 'string'},
'dashEvents': {'allowed': ['change'], 'type': ('string', 'number')},
'fireEvent': {},
'id': {'type': 'string'},
'inputClassName': {'type': 'string'},
'inputStyle': {'type': 'dict'},
'labelClassName': {'type': 'string'},
'labelStyle': {'type': 'dict'},
'options': {'schema': {'allow_unknown': False,
'nullable': False,
'schema': {'disabled': {'type': 'boolean'},
'label': {'type': 'string'},
'value': {'type': 'string'}},
'type': 'dict'},
'type': 'list'},
'setProps': {},
'style': {'type': 'dict'},
'value': {'type': 'string'}}
***************************************************************
The errors in validation are as follows:
* options
* 0
* value	<- must be of string type

PropTypes to Cerberus reference

PropTypeCerberus Schema Validated AgainstKnown Current Limitations
array{'type': 'list'}
bool{'type': 'boolean'}
func{}No validation occurs.
object{'type': 'dict'}Validates that input is explicitly a dict object. We cannot be more general (e.g. collections.abc.Mapping) since the core Component is an instance of that.
string{'type': 'string'}
symbol{}No validation occurs
node{'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}, {'type': 'list', 'schema': {'anyof': [{'type': 'component'}, {'type': 'boolean'}, {'type': 'number'}, {'type': 'string'}]}}]}
instanceOf(Object){}No validation occurs
oneOf(['val1', 2]){'allowed': [None, 'val1', 2]}Strings will have ' characters stripped off each end. This is because metadata.json generation serializes the literal values as json, so for example PropTypes.oneOf(['News', 'Photos']) serializes to ["'News'", "'Photos'"]. reactjs/react-docgen#57
oneOfType( [ PropTypes.string, PropTypes.bool ] ){'anyof': [{'type': 'string', 'type': 'boolean'}]}If one of the types is a PropType that cannot be validated individually (e.g. PropType.func), no validation will occur and the schema will effectively be {}
arrayOf( PropTypes.number ){'type': 'list', 'schema': {'type': 'number'}}
objectOf( PropTypes.number ){'type': 'dict', 'valueschema': {'type': 'number'}}
shape( { k1: PropTypes.string, k2: PropTypes.number } ){'type': 'dict', 'allow_unknown': False, 'schema': {'k1': {'type': 'string}, 'k2': {'type': 'number'}}}
any{'type': ('boolean', 'number', 'string', 'dict', 'list')}

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
def add_context(*args, **kwargs):

output_value = func(*args, **kwargs)
setattr(

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
self._components[output.component_id]._validator.validate({
output.component_property: output_value
})
if output.component_property == 'children':

This comment was marked as outdated.

Comment threaddash/dash.py Outdated
Comment threaddash/dash.py Outdated
break
matched_state_value = matched_state.get('value', None)
args.append(matched_state_value)
setattr(

This comment was marked as outdated.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
pass


cerberus.Validator.types_mapping['component'] =\

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us validate Component objects with cerberus (useful for children prop)

Comment threaddash/development/base_component.py
Comment threaddash/development/base_component.py Outdated
p not in ['dashEvents', 'fireEvent', 'setProps']] + ['**kwargs']
)

schema = {str(k): generate_property_schema(v)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make cerberus schema for this dash component type.

Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
Comment threaddash/development/base_component.py Outdated
@Bachibouzouk

Copy link
Copy Markdown

Hi, I tried to pip install this branch locally in a venv to test this PR, but I the way I did it doesn't work as I get an error by running the example code you provide above :
AttributeError: 'RadioItems' object has no attribute '_schema'

I typed the command pip install git+https://github.com/rmarren1/dash.git@validate, maybe it is not the way to proceed?

Comment threaddash/development/base_component.py Outdated



# Forward declated Component class, see below.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean 'declared'? PEP8 requires one space less :)

PS : this is my first review on dash code and I am mainly reading the code you wrote to learn more, my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my suggestion will mostly be about styling and spelling, I apologize in advance for that!

Note that we enforce PEP8 in our tests here:

pylint dash setup.py --rcfile=$PYLINTRC
flake8 dash setup.py

So, it's not necessary to review PEP8 issues in our PRs as they will get fixed before it gets merged into master.

@chriddyp

chriddyp commented Aug 20, 2018

Copy link
Copy Markdown
Member

To validate the components in the callbacks, I think we'll need to pass the component name back in the _dash-update-components API call. With Dash's stateless backend, I think this will be the most reliable way to ensure that the front-end component that is getting updated is the same one that we are validating against.

So, that is, we could modify the Dash API to include type and namespace in the payload:

https://github.com/plotly/dash-renderer/blob/0dc6f331eda404ee848145db71a4dc3bc35a759e/src/actions/index.js#L406-L408

and then do a component lookup in the callback decorator

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

^ Definitely, I was going to add this to the next iteration. You don't need this to validate against components in the initial layout, but I don't think there is any other way to validate against dynamically generated components without updating state in the backend after initialization.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@rmarren1

rmarren1 commented Aug 21, 2018

Copy link
Copy Markdown
ContributorAuthor

PR which adds 'type' and 'namespace' to the _dash-update-components payload. plotly/dash-renderer#69

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

suppress_callback_exceptions is the current name, I would just be changing the name of the config to disable validation from disable_component_validation to suppress_validation_exceptions.

@T4rk1n

Copy link
Copy Markdown
Contributor

Oh ok, I would still integrate with the dev tools so just setting debug=True is required for it.

@rmarren1

rmarren1 commented Oct 18, 2018

Copy link
Copy Markdown
ContributorAuthor

Oh I thought you meant something different, yeah it should be disabled in production.

@T4rk1n

Copy link
Copy Markdown
Contributor

@rmarren1 How would the component as prop validate ? proptype is PropTypes.node.

@rmarren1

Copy link
Copy Markdown
ContributorAuthor

@T4rk1n There is a custom type called component which makes sure objects are an instance of dash.development.base_component.Component, PropTypes.node works with this custom type:

'node': lambdax: {
'anyof': [
{'type': 'component'},
{'type': 'boolean'},
{'type': 'number'},
{'type': 'string'},
{
'type': 'list',
'schema': {
'type': (
'component',
'boolean',
'number',
'string')
}
}
]
},

Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/dash.py
Comment threaddash/development/base_component.py
@rmarren1

Copy link
Copy Markdown
ContributorAuthor

Closing in favor of #452, which is the same PR but with a more readable commit history. Will link back to here for the relevant discussions.

@rmarren1rmarren1 closed this Nov 8, 2018
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
AnnMarieW pushed a commit to AnnMarieW/dash that referenced this pull request Jan 6, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@rmarren1@Bachibouzouk@chriddyp@valentijnnieman@vanbenschoten@chubukov@T4rk1n