Skip to content

Commit

Permalink
Add output_list to callbacks for compatibility with Dash 1.11 (#244)
Browse files Browse the repository at this point in the history
* First steps towards working with dash 1.11 by adding outputs_list to callback arguments

* Set minimum version level for dash to 1.11

* Bump version to 1.4.0

Co-authored-by: Mark Gibbs <mark@gibbs.consulting>
  • Loading branch information
delsim and Mark Gibbs committed Jun 19, 2020
1 parent bd1fb7e commit 844fe40
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 19 deletions.
2 changes: 1 addition & 1 deletion dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dpd-static-support>=0.0.4
grip
pandas
pylint
pytest>=3.6
pytest>=4.6
pytest-django
pytest-cov
python-coveralls
Expand Down
9 changes: 8 additions & 1 deletion django_plotly_dash/dash_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import inspect

from dash import Dash
from dash._utils import split_callback_id
from flask import Flask

from django.urls import reverse
Expand Down Expand Up @@ -288,7 +289,9 @@ def __init__(self):
def after_request(self, *args, **kwargs):
pass
def errorhandler(self, *args, **kwargs): # pylint: disable=no-self-use
return args[0]
def eh_func(f):
return args[0]
return eh_func
def add_url_rule(self, *args, **kwargs):
route = kwargs['endpoint']
self.endpoints[route] = kwargs
Expand Down Expand Up @@ -555,6 +558,10 @@ def dispatch_with_args(self, body, argMap):
if da:
da.update_current_state(c['id'], c['property'], v)

# Dash 1.11 introduces a set of outputs
outputs_list = body.get('outputs') or split_callback_id(output)
argMap['outputs_list'] = outputs_list

# Special: intercept case of insufficient arguments
# This happens when a propery has been updated with a pipe component
# TODO see if this can be attacked from the client end
Expand Down
35 changes: 25 additions & 10 deletions django_plotly_dash/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#pylint: disable=bare-except


def test_dash_app():
'Test the import and formation of the dash app orm wrappers'

Expand All @@ -42,6 +43,7 @@ def test_dash_app():
assert stateless_a.app_name
assert str(stateless_a) == stateless_a.app_name


def test_util_error_cases(settings):
'Test handling of missing settings'

Expand All @@ -61,13 +63,15 @@ def test_util_error_cases(settings):
assert http_endpoint("fred") == '^dpd/views/fred/$'
assert not insert_demo_migrations()


def test_demo_routing():
'Test configuration options for the demo'

from django_plotly_dash.util import pipe_ws_endpoint_name, insert_demo_migrations
assert pipe_ws_endpoint_name() == 'ws/channel'
assert insert_demo_migrations()


def test_local_serving(settings):
'Test local serve settings'

Expand All @@ -76,6 +80,7 @@ def test_local_serving(settings):
assert static_asset_root() == 'dpd/assets'
assert full_asset_path('fred.jim', 'harry') == 'dpd/assets/fred/jim/harry'


@pytest.mark.django_db
def test_direct_access(client):
'Check direct use of a stateless application using demo test data'
Expand Down Expand Up @@ -106,6 +111,7 @@ def test_direct_access(client):

assert did_fail


@pytest.mark.django_db
def test_updating(client):
'Check updating of an app using demo test data'
Expand All @@ -118,7 +124,7 @@ def test_updating(client):
('', {'ident':'simpleexample-1'}),]:
url = reverse('the_django_plotly_dash:%s%s' % (prefix, route_name), kwargs=arg_map)

response = client.post(url, json.dumps({'output':{'id':'output-size', 'property':'children'},
response = client.post(url, json.dumps({'output': 'output-size.children',
'inputs':[{'id':'dropdown-color',
'property':'value',
'value':'blue'},
Expand All @@ -127,9 +133,10 @@ def test_updating(client):
'value':'medium'},
]}), content_type="application/json")

assert response.content == b'{"response": {"props": {"children": "The chosen T-shirt is a medium blue one."}}}'
assert response.content == b'{"response": {"output-size": {"children": "The chosen T-shirt is a medium blue one."}}, "multi": true}'
assert response.status_code == 200


@pytest.mark.django_db
def test_injection_app_access(client):
'Check direct use of a stateless application using demo test data'
Expand Down Expand Up @@ -160,6 +167,7 @@ def test_injection_app_access(client):

assert did_fail


@pytest.mark.django_db
def test_injection_updating_multiple_callbacks(client):
'Check updating of an app using demo test data for multiple callbacks'
Expand Down Expand Up @@ -192,6 +200,7 @@ def test_injection_updating_multiple_callbacks(client):
assert 'children' in resp_detail['output-two']
assert resp_detail['output-two']['children'] == "Output 2: 10 purple-ish yellow with a hint of greeny orange"


@pytest.mark.django_db
def test_injection_updating(client):
'Check updating of an app using demo test data'
Expand All @@ -203,13 +212,14 @@ def test_injection_updating(client):
for prefix, arg_map in [('app-', {'ident':'dash_example_1'}),]:
url = reverse('the_django_plotly_dash:%s%s' % (prefix, route_name), kwargs=arg_map)

response = client.post(url, json.dumps({'output':{'id':'test-output-div', 'property':'children'},
response = client.post(url, json.dumps({#'output':{'id':'test-output-div', 'property':'children'},
'output': "test-output-div.children",
'inputs':[{'id':'my-dropdown1',
'property':'value',
'value':'TestIt'},
]}), content_type="application/json")

rStart = b'{"response": {"props": {"children":'
rStart = b'{"response": {"test-output-div": {"children": [{"props": {"id": "line-area-graph2"'

assert response.content[:len(rStart)] == rStart
assert response.status_code == 200
Expand All @@ -221,27 +231,27 @@ def test_injection_updating(client):
'value':'TestIt'},
]}), content_type="application/json")

rStart = b'{"response": {"props": {"children":'
rStart = b'{"response": {"test-output-div": {"children": [{"props": {"id": "line-area-graph2"'

assert response.content[:len(rStart)] == rStart
assert response.status_code == 200

# Second variant has a single-entry mulitple property output
# Second variant has a single-entry mulitple property output
response = client.post(url, json.dumps({'output':'..test-output-div.children..',
'inputs':[{'id':'my-dropdown1',
'property':'value',
'value':'TestIt'},
]}), content_type="application/json")

rStart = b'{"response": {"props": {"children":'
rStart = b'{"response": {"test-output-div": {"children": {"props": {"id": "line-area-graph2"'

assert response.content[:len(rStart)] == rStart
assert response.status_code == 200

have_thrown = False

try:
client.post(url, json.dumps({'output':{'id':'test-output-div2', 'property':'children'},
client.post(url, json.dumps({'output': 'test-output-div2.children',
'inputs':[{'id':'my-dropdown2',
'property':'value',
'value':'TestIt'},
Expand All @@ -255,18 +265,19 @@ def test_injection_updating(client):
session['django_plotly_dash'] = {'django_to_dash_context': 'Test 789 content'}
session.save()

response3 = client.post(url, json.dumps({'output':{'id':'test-output-div2', 'property':'children'},
response3 = client.post(url, json.dumps({'output': 'test-output-div2.children',
'inputs':[{'id':'my-dropdown2',
'property':'value',
'value':'TestIt'},
]}), content_type="application/json")
rStart3 = b'{"response": {"props": {"children":'
rStart3 = b'{"response": {"test-output-div2": {"children": [{"props": {"children": ["You have '

assert response3.content[:len(rStart3)] == rStart3
assert response3.status_code == 200

assert response3.content.find(b'Test 789 content') > 0


@pytest.mark.django_db
def test_argument_settings(settings, client):
'Test the setting that controls how initial arguments are propagated through to the dash app'
Expand Down Expand Up @@ -308,6 +319,7 @@ def test_argument_settings(settings, client):
assert store_initial_arguments(client, None) is None
assert get_initial_arguments(client, None) is None


def test_stateless_lookup_noop():
'Test no-op stateless lookup'

Expand All @@ -318,6 +330,7 @@ def test_stateless_lookup_noop():
with pytest.raises(ImportError):
lh_hook("not an app")


def test_middleware_artifacts():
'Import and vaguely exercise middleware objects'

Expand All @@ -334,6 +347,7 @@ def test_middleware_artifacts():

assert cc._encode("fred") == b'fred'


def test_finders():
'Import and vaguely exercise staticfiles finders'

Expand All @@ -347,6 +361,7 @@ def test_finders():
assert dadf is not None
assert daf is not None


@pytest.mark.django_db
def test_app_loading(client):

Expand Down
2 changes: 1 addition & 1 deletion django_plotly_dash/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
'''

__version__ = "1.3.1"
__version__ = "1.4.0"
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
dash<1.11
dash-core-components==1.9.0
dash>=1.11
dash-core-components
dash-html-components
dash-renderer==1.3.0
dash-renderer
plotly
dpd-components

Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
'Documentation': 'http://django-plotly-dash.readthedocs.io/',
},
install_requires = ['plotly',
'dash<1.11',
'dash-core-components==1.9.0',
'dash>=1.11',
'dash-core-components',
'dash-html-components',
'dash-renderer==1.3.0',
'dash-renderer',
'dpd-components',
'Django>=3',
'Flask>=1.0.2'],
Expand Down

0 comments on commit 844fe40

Please sign in to comment.