Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Standalone dash renderer (for custom hooks) - March 1 #367

Merged
merged 37 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ef0fbeb
Use DashRenderer as a standalone library
valentijnnieman Sep 4, 2018
938e466
Cleanup code
valentijnnieman Sep 4, 2018
d916765
Update tests with renderer tag
valentijnnieman Sep 5, 2018
456a090
Fix linting error
valentijnnieman Sep 5, 2018
1e957c7
Refactor generate_renderer function
valentijnnieman Sep 5, 2018
e776e8e
Refactor _generate_renderer
valentijnnieman Sep 5, 2018
3edaa12
Fixed pylint line too long error
valentijnnieman Sep 5, 2018
41fdc76
Fixed pylint trailing whitespace error
valentijnnieman Sep 5, 2018
6c3469f
Added test for custom renderer in index string
valentijnnieman Sep 5, 2018
b46f0df
Use updated dash-renderer tarball for now
valentijnnieman Sep 5, 2018
bd8c9fb
Also use dash-renderer tarball in dev-requirements.txt
valentijnnieman Sep 5, 2018
d4b0418
Update dev-requirements-py37
valentijnnieman Sep 5, 2018
d0120db
dash-renderer tarball in dev-requirements-py37
valentijnnieman Sep 5, 2018
421cd2d
Merged with master & resolved merge conflicts
valentijnnieman Sep 7, 2018
0fe6d64
Add renderer to interpolate_index
valentijnnieman Sep 14, 2018
09a0511
application/json -> application/javascript
valentijnnieman Sep 14, 2018
ec95f0b
Add renderer to interpolate_index
valentijnnieman Sep 14, 2018
2a0c922
Add (broken) test for interpolate_index renderer
valentijnnieman Sep 14, 2018
92e6bd8
Fixed typo
valentijnnieman Sep 14, 2018
827e1a3
Add wait_for_text_to_equal and use it in renderer tests
valentijnnieman Sep 14, 2018
2486dcb
Merge branch 'master' of https://github.com/plotly/dash into standalo…
valentijnnieman Feb 15, 2019
01b3216
Try dash-renderer egg instead of tarball :egg:
valentijnnieman Feb 15, 2019
f3a85ca
Add egg to dev-req files too :egg:
valentijnnieman Feb 15, 2019
ea2b623
Use egg of custom_hooks branch of dash-renderer in circleci config to…
valentijnnieman Feb 15, 2019
1358dbe
Use ActionChain instead of .clear() to fix tests
valentijnnieman Feb 21, 2019
f9002e1
Add correct interpolated index test & fix wildcard test
valentijnnieman Feb 21, 2019
0150977
Remove print from test :see_no_evil:
valentijnnieman Feb 21, 2019
bf651ff
Fix formatting
valentijnnieman Feb 21, 2019
ba95dc6
Remove unused code
valentijnnieman Feb 21, 2019
b94ca9a
Remove unused requirements files
valentijnnieman Feb 27, 2019
11d98a1
Changed renderer param example string
valentijnnieman Feb 27, 2019
caf21a2
Add check for new DashRenderer
valentijnnieman Feb 27, 2019
d15f0ba
const -> var
valentijnnieman Feb 27, 2019
4797b2a
Missing > in script tag
valentijnnieman Feb 27, 2019
7fc8a80
Remove unused wait_for methods
valentijnnieman Feb 27, 2019
5fbadf1
Revert renderer scripts check
valentijnnieman Feb 27, 2019
1c59379
Merge branch 'master' of https://github.com/plotly/dash into standalo…
valentijnnieman Feb 28, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/requirements/dev-requirements-py37.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ dash_core_components==0.43.0
dash_html_components==0.13.5
dash-flow-example==0.0.5
dash-dangerously-set-inner-html
dash_renderer==0.17.0
git+git://github.com/plotly/dash-renderer@custom_hooks#egg=dash-renderer
percy
selenium
mock
Expand Down
2 changes: 1 addition & 1 deletion .circleci/requirements/dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ dash_core_components==0.43.0
dash_html_components==0.13.5
dash_flow_example==0.0.5
dash-dangerously-set-inner-html
dash_renderer==0.17.0
git+git://github.com/plotly/dash-renderer@custom_hooks#egg=dash-renderer
percy
selenium
mock
Expand Down
27 changes: 23 additions & 4 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
from . import _watch
from . import _configs


_default_index = '''<!DOCTYPE html>
<html>
<head>
Expand All @@ -49,6 +48,7 @@
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>'''
Expand All @@ -64,10 +64,12 @@
_re_index_entry = re.compile(r'{%app_entry%}')
_re_index_config = re.compile(r'{%config%}')
_re_index_scripts = re.compile(r'{%scripts%}')
_re_renderer_scripts = re.compile(r'{%renderer%}')

_re_index_entry_id = re.compile(r'id="react-entry-point"')
_re_index_config_id = re.compile(r'id="_dash-config"')
_re_index_scripts_id = re.compile(r'src=".*dash[-_]renderer.*"')
_re_renderer_scripts_id = re.compile(r'id="_dash-renderer')


# pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -271,6 +273,9 @@ def _add_url(self, name, view_func, methods=('GET',)):
# e.g. for adding authentication with flask_login
self.routes.append(name)

# default renderer string
self.renderer = 'var renderer = new DashRenderer();'

@property
def layout(self):
return self._layout
Expand Down Expand Up @@ -464,6 +469,13 @@ def _generate_config_html(self):
'</script>'
).format(json.dumps(self._config()))

def _generate_renderer(self):
return (
'<script id="_dash-renderer" type="application/javascript">'
'{}'
'</script>'
).format(self.renderer)

def _generate_meta_html(self):
has_ie_compat = any(
x.get('http-equiv', '') == 'X-UA-Compatible'
Expand Down Expand Up @@ -527,6 +539,7 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument
css = self._generate_css_dist_html()
config = self._generate_config_html()
metas = self._generate_meta_html()
renderer = self._generate_renderer()
title = getattr(self, 'title', 'Dash')

if self._favicon:
Expand All @@ -547,12 +560,14 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument

index = self.interpolate_index(
metas=metas, title=title, css=css, config=config,
scripts=scripts, app_entry=_app_entry, favicon=favicon)
scripts=scripts, app_entry=_app_entry, favicon=favicon,
renderer=renderer)

checks = (
(_re_index_entry_id.search(index), '#react-entry-point'),
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
(_re_index_config_id.search(index), '#_dash-configs'),
(_re_index_scripts_id.search(index), 'dash-renderer'),
(_re_renderer_scripts_id.search(index), 'new DashRenderer'),
)
missing = [missing for check, missing in checks if not check]

Expand All @@ -569,7 +584,7 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument

def interpolate_index(self,
metas='', title='', css='', config='',
scripts='', app_entry='', favicon=''):
scripts='', app_entry='', favicon='', renderer=''):
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
"""
Called to create the initial HTML string that is loaded on page.
Override this method to provide you own custom HTML.
Expand All @@ -589,19 +604,22 @@ def interpolate_index(self, **kwargs):
{app_entry}
{config}
{scripts}
{renderer}
<div id="custom-footer">My custom footer</div>
</body>
</html>
'''.format(
app_entry=kwargs.get('app_entry'),
config=kwargs.get('config'),
scripts=kwargs.get('scripts'))
scripts=kwargs.get('scripts'),
renderer=kwargs.get('renderer'))

:param metas: Collected & formatted meta tags.
:param title: The title of the app.
:param css: Collected & formatted css dependencies as <link> tags.
:param config: Configs needed by dash-renderer.
:param scripts: Collected & formatted scripts tags.
:param renderer: A script tag that instantiates the DashRenderer.
:param app_entry: Where the app will render.
:param favicon: A favicon <link> tag if found in assets folder.
:return: The interpolated HTML string for the index.
Expand All @@ -613,6 +631,7 @@ def interpolate_index(self, **kwargs):
config=config,
scripts=scripts,
favicon=favicon,
renderer=renderer,
app_entry=app_entry)

def dependencies(self):
Expand Down
196 changes: 192 additions & 4 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import dash_core_components as dcc
import dash_flow_example

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys

import dash

from dash.dependencies import Input, Output
Expand Down Expand Up @@ -57,7 +60,15 @@ def update_output(value):
self.percy_snapshot(name='simple-callback-1')

input1 = self.wait_for_element_by_id('input')
input1.clear()

chain = (ActionChains(self.driver)
.click(input1)
.send_keys(Keys.HOME)
.key_down(Keys.SHIFT)
.send_keys(Keys.END)
.key_up(Keys.SHIFT)
.send_keys(Keys.DELETE))
chain.perform()

input1.send_keys('hello world')

Expand All @@ -67,7 +78,8 @@ def update_output(value):
self.assertEqual(
call_count.value,
# an initial call to retrieve the first value
1 +
# and one for clearing the input
2 +
# one for each hello world character
len('hello world')
)
Expand Down Expand Up @@ -109,7 +121,14 @@ def update_text(data):
self.percy_snapshot(name='wildcard-callback-1')

input1 = self.wait_for_element_by_id('input')
input1.clear()
chain = (ActionChains(self.driver)
.click(input1)
.send_keys(Keys.HOME)
.key_down(Keys.SHIFT)
.send_keys(Keys.END)
.key_up(Keys.SHIFT)
.send_keys(Keys.DELETE))
chain.perform()

input1.send_keys('hello world')

Expand All @@ -119,7 +138,8 @@ def update_text(data):
self.assertEqual(
input_call_count.value,
# an initial call
1 +
# and a call for clearing the input
2 +
# one for each hello world character
len('hello world')
)
Expand Down Expand Up @@ -324,6 +344,7 @@ def test_index_customization(self):
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
<div id="custom-footer">My custom footer</div>
<script>
Expand Down Expand Up @@ -376,6 +397,7 @@ def test_assets(self):
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
Expand Down Expand Up @@ -491,6 +513,7 @@ def test_external_files_init(self):
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
Expand Down Expand Up @@ -530,6 +553,171 @@ def create_layout():
self.startServer(app)
time.sleep(0.5)

def test_with_custom_renderer(self):
app = dash.Dash(__name__)

app.index_string = '''
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
<!DOCTYPE html>
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
</head>
<body>
<div>Testing custom DashRenderer</div>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
<script id="_dash-renderer" type="application/javascript">
console.log('firing up a custom renderer!')
const renderer = new DashRenderer({
request_pre: () => {
var output = document.getElementById('output-pre')
if(output) {
output.innerHTML = 'request_pre changed this text!';
}
},
request_post: () => {
var output = document.getElementById('output-post')
if(output) {
output.innerHTML = 'request_post changed this text!';
}
}
})
</script>
</footer>
<div>With request hooks</div>
</body>
</html>
'''

app.layout = html.Div([
dcc.Input(
id='input',
value='initial value'
),
html.Div(
html.Div([
html.Div(id='output-1'),
html.Div(id='output-pre'),
html.Div(id='output-post')
])
)
])

@app.callback(Output('output-1', 'children'), [Input('input', 'value')])
def update_output(value):
return value

self.startServer(app)

input1 = self.wait_for_element_by_id('input')
chain = (ActionChains(self.driver)
.click(input1)
.send_keys(Keys.HOME)
.key_down(Keys.SHIFT)
.send_keys(Keys.END)
.key_up(Keys.SHIFT)
.send_keys(Keys.DELETE))
chain.perform()

input1.send_keys('fire request hooks')

self.wait_for_text_to_equal('#output-1', 'fire request hooks')
self.wait_for_text_to_equal('#output-pre', 'request_pre changed this text!')
self.wait_for_text_to_equal('#output-post', 'request_post changed this text!')

self.percy_snapshot(name='request-hooks')

def test_with_custom_renderer_interpolated(self):

renderer = '''
<script id="_dash-renderer" type="application/javascript">
console.log('firing up a custom renderer!')
const renderer = new DashRenderer({
request_pre: () => {
var output = document.getElementById('output-pre')
if(output) {
output.innerHTML = 'request_pre changed this text!';
}
},
request_post: () => {
var output = document.getElementById('output-post')
if(output) {
output.innerHTML = 'request_post changed this text!';
}
}
})
</script>
'''
class CustomDash(dash.Dash):

def interpolate_index(self, **kwargs):
return '''
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>

<div id="custom-header">My custom header</div>
{app_entry}
{config}
{scripts}
{renderer}
<div id="custom-footer">My custom footer</div>
</body>
</html>
'''.format(
app_entry=kwargs['app_entry'],
config=kwargs['config'],
scripts=kwargs['scripts'],
renderer=renderer)

app = CustomDash()

app.layout = html.Div([
dcc.Input(
id='input',
value='initial value'
),
html.Div(
html.Div([
html.Div(id='output-1'),
html.Div(id='output-pre'),
html.Div(id='output-post')
])
)
])

@app.callback(Output('output-1', 'children'), [Input('input', 'value')])
def update_output(value):
return value

self.startServer(app)

input1 = self.wait_for_element_by_id('input')
chain = (ActionChains(self.driver)
.click(input1)
.send_keys(Keys.HOME)
.key_down(Keys.SHIFT)
.send_keys(Keys.END)
.key_up(Keys.SHIFT)
.send_keys(Keys.DELETE))
chain.perform()

input1.send_keys('fire request hooks')

self.wait_for_text_to_equal('#output-1', 'fire request hooks')
self.wait_for_text_to_equal('#output-pre', 'request_pre changed this text!')
self.wait_for_text_to_equal('#output-post', 'request_post changed this text!')

self.percy_snapshot(name='request-hooks interpolated')

def test_late_component_register(self):
app = dash.Dash()

Expand Down