Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 2.3k
Standalone dash renderer (for custom hooks) - March 1#367
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
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
ef0fbeb
Use DashRenderer as a standalone library
valentijnnieman 938e466
Cleanup code
valentijnnieman d916765
Update tests with renderer tag
valentijnnieman 456a090
Fix linting error
valentijnnieman 1e957c7
Refactor generate_renderer function
valentijnnieman e776e8e
Refactor _generate_renderer
valentijnnieman 3edaa12
Fixed pylint line too long error
valentijnnieman 41fdc76
Fixed pylint trailing whitespace error
valentijnnieman 6c3469f
Added test for custom renderer in index string
valentijnnieman b46f0df
Use updated dash-renderer tarball for now
valentijnnieman bd8c9fb
Also use dash-renderer tarball in dev-requirements.txt
valentijnnieman d4b0418
Update dev-requirements-py37
valentijnnieman d0120db
dash-renderer tarball in dev-requirements-py37
valentijnnieman 421cd2d
Merged with master & resolved merge conflicts
valentijnnieman 0fe6d64
Add renderer to interpolate_index
valentijnnieman 09a0511
application/json -> application/javascript
valentijnnieman ec95f0b
Add renderer to interpolate_index
valentijnnieman 2a0c922
Add (broken) test for interpolate_index renderer
valentijnnieman 92e6bd8
Fixed typo
valentijnnieman 827e1a3
Add wait_for_text_to_equal and use it in renderer tests
valentijnnieman 2486dcb
Merge branch 'master' of https://github.com/plotly/dash into standalo…
valentijnnieman 01b3216
Try dash-renderer egg instead of tarball :egg:
valentijnnieman f3a85ca
Add egg to dev-req files too :egg:
valentijnnieman ea2b623
Use egg of custom_hooks branch of dash-renderer in circleci config to…
valentijnnieman 1358dbe
Use ActionChain instead of .clear() to fix tests
valentijnnieman f9002e1
Add correct interpolated index test & fix wildcard test
valentijnnieman 0150977
Remove print from test :see_no_evil:
valentijnnieman bf651ff
Fix formatting
valentijnnieman ba95dc6
Remove unused code
valentijnnieman b94ca9a
Remove unused requirements files
valentijnnieman 11d98a1
Changed renderer param example string
valentijnnieman caf21a2
Add check for new DashRenderer
valentijnnieman d15f0ba
const -> var
valentijnnieman 4797b2a
Missing > in script tag
valentijnnieman 7fc8a80
Remove unused wait_for methods
valentijnnieman 5fbadf1
Revert renderer scripts check
valentijnnieman 1c59379
Merge branch 'master' of https://github.com/plotly/dash into standalo…
valentijnnieman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -35,7 +35,6 @@ | ||
| from . import _watch | ||
| from . import _configs | ||
| _default_index = '''<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| @@ -49,6 +48,7 @@ | ||
| <footer> | ||
| {%config%} | ||
| {%scripts%} | ||
| {%renderer%} | ||
| </footer> | ||
| </body> | ||
| </html>''' | ||
| @@ -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 | ||
| @@ -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 | ||
| @@ -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' | ||
| @@ -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: | ||
| @@ -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'), | ||
| (_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] | ||
| @@ -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.
Uh oh!There was an error while loading. Please reload this page. | ||
| """ | ||
| Called to create the initial HTML string that is loaded on page. | ||
| Override this method to provide you own custom HTML. | ||
| @@ -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. | ||
| @@ -613,6 +631,7 @@ def interpolate_index(self, **kwargs): | ||
| config=config, | ||
| scripts=scripts, | ||
| favicon=favicon, | ||
| renderer=renderer, | ||
| app_entry=app_entry) | ||
| def dependencies(self): | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| @@ -59,7 +62,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') | ||
| @@ -69,7 +80,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') | ||
| ) | ||
| @@ -111,7 +123,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') | ||
| @@ -121,7 +140,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') | ||
| ) | ||
| @@ -326,6 +346,7 @@ def test_index_customization(self): | ||
| <footer> | ||
| {%config%} | ||
| {%scripts%} | ||
| {%renderer%} | ||
| </footer> | ||
| <div id="custom-footer">My custom footer</div> | ||
| <script> | ||
| @@ -378,6 +399,7 @@ def test_assets(self): | ||
| <footer> | ||
| {%config%} | ||
| {%scripts%} | ||
| {%renderer%} | ||
| </footer> | ||
| </body> | ||
| </html> | ||
| @@ -493,6 +515,7 @@ def test_external_files_init(self): | ||
| <footer> | ||
| {%config%} | ||
| {%scripts%} | ||
| {%renderer%} | ||
| </footer> | ||
| </body> | ||
| </html> | ||
| @@ -532,6 +555,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.
Uh oh!There was an error while loading. Please reload this page. | ||
| <!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() | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.