Skip to content

Commit 4ac19fe

Browse files
delsimMark Gibbs
andauthored
Move to Dash v2.0 (#402)
* Move to Dash v2.0 * Bump version to 2.0.0 * Update readme * Add sandbox attributes to boostrap template * Record environment used to develop v2.0.0 of dpd * Fix development status Co-authored-by: Mark Gibbs <mark@gibbs.consulting>
1 parent c7914b5 commit 4ac19fe

24 files changed

+326
-142
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ Taking a very simple example inspired by the excellent [getting started](https:/
9090

9191
```python
9292
import dash
93-
import dash_core_components as dcc
94-
import dash_html_components as html
93+
from dash import dcc, html
9594

9695
from django_plotly_dash import DjangoDash
9796

demo/demo/assets/image_two.png

989 Bytes
Loading

demo/demo/dash_apps.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
#pylint: disable=no-member
88

99
import dash
10-
import dash_core_components as dcc
11-
import dash_html_components as html
10+
from dash import dcc, html
1211
import plotly.graph_objs as go
1312
#import dpd_components as dpd
1413
import numpy as np

demo/demo/plotly_apps.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@
3434
from django.core.cache import cache
3535

3636
import dash
37+
from dash import dcc, html
3738
from dash.dependencies import MATCH, ALL
38-
import dash_core_components as dcc
39-
import dash_html_components as html
4039

4140
import plotly.graph_objs as go
4241

demo/demo/settings.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,8 @@
196196
# be handled by the Django staticfiles infrastructure
197197

198198
PLOTLY_COMPONENTS = [
199-
'dash_core_components',
200-
'dash_html_components',
199+
201200
'dash_bootstrap_components',
202-
'dash_renderer',
203201
'dpd_components',
204202
'dpd_static_support',
205203
]

dev_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pytest-cov
1818
python-coveralls
1919
pytz
2020
redis
21+
setuptools
2122
sphinx
2223
sphinx-autobuild
2324
sphinx_rtd_theme

django_plotly_dash/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@
2929
from .version import __version__
3030

3131
from .dash_wrapper import DjangoDash
32+
33+
# Monkeypatching
34+
35+
import django_plotly_dash._callback

django_plotly_dash/_callback.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#
2+
# Monkey patching of register_callback
3+
#
4+
5+
import dash._callback
6+
7+
from dash._callback import (handle_grouped_callback_args,
8+
insert_callback,
9+
NoUpdate,
10+
)
11+
import collections
12+
from functools import wraps
13+
14+
from dash.dependencies import (
15+
handle_callback_args,
16+
handle_grouped_callback_args,
17+
Output,
18+
)
19+
from dash.exceptions import PreventUpdate
20+
21+
from dash._grouping import (
22+
flatten_grouping,
23+
make_grouping_by_index,
24+
grouping_len,
25+
)
26+
from dash._utils import (
27+
create_callback_id,
28+
stringify_id,
29+
to_json,
30+
)
31+
32+
from dash import _validate
33+
34+
35+
def register_callback(
36+
callback_list, callback_map, config_prevent_initial_callbacks, *_args, **_kwargs
37+
):
38+
(
39+
output,
40+
flat_inputs,
41+
flat_state,
42+
inputs_state_indices,
43+
prevent_initial_call,
44+
) = handle_grouped_callback_args(_args, _kwargs)
45+
if isinstance(output, Output):
46+
# Insert callback with scalar (non-multi) Output
47+
insert_output = output
48+
multi = False
49+
else:
50+
# Insert callback as multi Output
51+
insert_output = flatten_grouping(output)
52+
multi = True
53+
54+
output_indices = make_grouping_by_index(output, list(range(grouping_len(output))))
55+
callback_id = insert_callback(
56+
callback_list,
57+
callback_map,
58+
config_prevent_initial_callbacks,
59+
insert_output,
60+
output_indices,
61+
flat_inputs,
62+
flat_state,
63+
inputs_state_indices,
64+
prevent_initial_call,
65+
)
66+
67+
# pylint: disable=too-many-locals
68+
def wrap_func(func):
69+
@wraps(func)
70+
def add_context(*args, **kwargs):
71+
output_spec = kwargs.pop("outputs_list")
72+
_validate.validate_output_spec(insert_output, output_spec, Output)
73+
74+
func_args, func_kwargs = _validate.validate_and_group_input_args(
75+
args, inputs_state_indices
76+
)
77+
78+
func_kwargs = {**func_kwargs,
79+
**kwargs}
80+
81+
# don't touch the comment on the next line - used by debugger
82+
output_value = func(*func_args, **func_kwargs) # %% callback invoked %%
83+
84+
if isinstance(output_value, NoUpdate):
85+
raise PreventUpdate
86+
87+
if not multi:
88+
output_value, output_spec = [output_value], [output_spec]
89+
flat_output_values = output_value
90+
else:
91+
if isinstance(output_value, (list, tuple)):
92+
# For multi-output, allow top-level collection to be
93+
# list or tuple
94+
output_value = list(output_value)
95+
96+
# Flatten grouping and validate grouping structure
97+
flat_output_values = flatten_grouping(output_value, output)
98+
99+
_validate.validate_multi_return(
100+
output_spec, flat_output_values, callback_id
101+
)
102+
103+
component_ids = collections.defaultdict(dict)
104+
has_update = False
105+
for val, spec in zip(flat_output_values, output_spec):
106+
if isinstance(val, NoUpdate):
107+
continue
108+
for vali, speci in (
109+
zip(val, spec) if isinstance(spec, list) else [[val, spec]]
110+
):
111+
if not isinstance(vali, NoUpdate):
112+
has_update = True
113+
id_str = stringify_id(speci["id"])
114+
component_ids[id_str][speci["property"]] = vali
115+
116+
if not has_update:
117+
raise PreventUpdate
118+
119+
response = {"response": component_ids, "multi": True}
120+
121+
try:
122+
jsonResponse = to_json(response)
123+
except TypeError:
124+
_validate.fail_callback_output(output_value, output)
125+
126+
return jsonResponse
127+
128+
callback_map[callback_id]["callback"] = add_context
129+
130+
return func
131+
132+
return wrap_func
133+
134+
135+
dash._callback.register_callback = register_callback

django_plotly_dash/finders.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,20 @@ def __init__(self):
5656
except:
5757
components = []
5858

59+
built_ins = [('dash', ['dcc', 'html', 'dash_table', 'deps', 'dash-renderer', 'dash-renderer/build']), ]
60+
5961
for component_name in components:
6062

61-
module = importlib.import_module(component_name)
62-
path_directory = os.path.dirname(module.__file__)
63+
split_name = component_name.split('/')
64+
try:
65+
module_name = ".".join(split_name)
66+
module = importlib.import_module(module_name)
67+
path_directory = os.path.dirname(module.__file__)
68+
except:
69+
module_name = ".".join(split_name[:-1])
70+
module = importlib.import_module(module_name)
71+
path_directory = os.path.join(os.path.dirname(module.__file__),
72+
split_name[-1])
6373

6474
root = path_directory
6575
storage = FileSystemStorage(location=root)
@@ -76,6 +86,29 @@ def __init__(self):
7686
self.storages[component_name] = storage
7787
self.components[path] = component_name
7888

89+
for module_name, component_list in built_ins:
90+
91+
module = importlib.import_module(module_name)
92+
93+
for specific_component in component_list:
94+
95+
path_directory = os.path.join(os.path.dirname(module.__file__),
96+
specific_component)
97+
98+
root = path_directory
99+
component_name = f"{module_name}/{specific_component}"
100+
path = f"dash/component/{component_name}"
101+
102+
if path not in self.components:
103+
104+
storage = FileSystemStorage(location=root)
105+
storage.prefix = path
106+
107+
self.locations.append(component_name)
108+
109+
self.storages[component_name] = storage
110+
self.components[path] = component_name
111+
79112
super().__init__()
80113

81114
def find(self, path, all=False):
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
<div class="embed-responsive embed-responsive-{{aspect}}">
2-
<iframe src="{{app.base_url}}" class="embed-responsive-item"></iframe>
2+
<iframe src="{{app.base_url}}" class="embed-responsive-item" sandbox="allow-downloads allow-scripts allow-same-origin"></iframe>
33
</div>

django_plotly_dash/tests.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def test_updating(client):
349349
'value':'medium'},
350350
]}), content_type="application/json")
351351

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

355355

@@ -489,7 +489,7 @@ def test_injection_updating(client):
489489
'value':'TestIt'},
490490
]}), content_type="application/json")
491491

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

494494
assert response.content.startswith(rStart)
495495
assert response.status_code == 200
@@ -501,7 +501,7 @@ def test_injection_updating(client):
501501
'value':'TestIt'},
502502
]}), content_type="application/json")
503503

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

506506
assert response.content.startswith(rStart)
507507
assert response.status_code == 200
@@ -530,7 +530,7 @@ def test_injection_updating(client):
530530
'value':'TestIt'},
531531
]}), content_type="application/json")
532532

533-
rStart = b'{"response": {"test-output-div3": {"children": [{"props": {"id": "line-area-graph2"'
533+
rStart = b'{"response":{"test-output-div3":{"children":[{"props":{"id":"line-area-graph2"'
534534

535535
assert response.content.startswith(rStart)
536536
assert response.status_code == 200
@@ -551,7 +551,7 @@ def test_injection_updating(client):
551551
'property':'value',
552552
'value':'TestIt'},
553553
]}), content_type="application/json")
554-
rStart = b'{"response": {"test-output-div2": {"children": [{"props": {"children": ["You have '
554+
rStart = b'{"response":{"test-output-div2":{"children":[{"props":{"children":["You have '
555555

556556
assert response.content.startswith(rStart)
557557
assert response.status_code == 200

django_plotly_dash/tests_dash_contract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Union
1010

1111
import dash
12-
import dash_html_components as html
12+
from dash import html
1313
import flask
1414
from dash import Dash, no_update
1515
from dash.dependencies import MATCH, ALL, ALLSMALLER, Input, Output, State

django_plotly_dash/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from django.urls import path
2828
from django.views.decorators.csrf import csrf_exempt
2929

30-
from .views import routes, layout, dependencies, update, main_view, component_suites, component_component_suites, asset_redirection
30+
from .views import routes, layout, dependencies, update, main_view, component_suites, component_component_suites, asset_redirection, component_suites_build
3131
from .views import add_stateless_apps
3232

3333
from .app_name import app_name, main_view_label
@@ -50,6 +50,8 @@
5050
('_dash-update-component', csrf_exempt(update), 'update-component', '', ),
5151
('', main_view, main_view_label, '', ),
5252
('_dash-component-suites', component_suites, 'component-suites', '/<slug:component>/<resource>', ),
53+
('_dash-component-suites', component_suites, 'component-suites', '/<slug:component>/<slug:cpe2>/<resource>', ),
54+
('_dash-component-suites', component_suites_build, 'component-suites', '/<slug:component>/<slug:cpe2>/build/<resource>', ),
5355
('_dash-component-suites', component_component_suites, 'component-component-suites', '/<slug:component>/_components/<resource>', ),
5456
('assets', asset_redirection, 'asset-redirect', '/<path:path>', ),
5557
]:

django_plotly_dash/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
2424
'''
2525

26-
__version__ = "1.7.1"
26+
__version__ = "2.0.0"

django_plotly_dash/views.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,28 @@ def component_component_suites(request, resource=None, component=None, **kwargs)
123123
extra_element="_components/",
124124
**kwargs)
125125

126-
def component_suites(request, resource=None, component=None, extra_element="", **kwargs):
126+
127+
def component_suites_build(request, resource=None, component=None, extra_element="", cpe2=None, **kwargs):
127128
'Return part of a client-side component, served locally for some reason'
129+
return component_suites(request,
130+
resource=resource,
131+
component=component,
132+
extra_element=extra_element,
133+
cpe2=f"{cpe2}/build",
134+
**kwargs)
135+
136+
137+
def component_suites(request, resource=None, component=None, extra_element="", cpe2=None, **kwargs):
138+
'Return part of a client-side component, served locally for some reason'
139+
140+
extra_path_part = f"{cpe2}/" if cpe2 else ""
128141

129142
get_params = request.GET.urlencode()
130143
if get_params and False:
131-
redone_url = static_path("dash/component/%s/%s%s?%s" %(component, extra_element, resource, get_params))
144+
redone_url = static_path("dash/component/%s/%s%s%s?%s" %(component, extra_path_part, extra_element, resource, get_params))
132145
else:
133146
resource, _fingerprint = check_fingerprint(resource)
134-
redone_url = static_path("dash/component/%s/%s%s" % (component, extra_element, resource))
147+
redone_url = static_path("dash/component/%s/%s%s%s" % (component, extra_path_part, extra_element, resource))
135148

136149
return HttpResponseRedirect(redirect_to=redone_url)
137150

docs/configuration.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ and also providing a list of components used
6565
6666
PLOTLY_COMPONENTS = [
6767
68-
# Common components
69-
'dash_core_components',
70-
'dash_html_components',
71-
'dash_renderer',
68+
# Common components (ie within dash itself) are automatically added
7269
7370
# django-plotly-dash components
7471
'dpd_components',
@@ -80,7 +77,9 @@ and also providing a list of components used
8077
]
8178
8279
This list should be extended with any additional components that the applications
83-
use, where the components have files that have to be served locally.
80+
use, where the components have files that have to be served locally. The components that
81+
are part of the core ``dash`` package are automatically included and do not need to be
82+
provided in this list.
8483

8584
Furthermore, middleware should be added for redirection of external assets from
8685
underlying packages, such as ``dash-bootstrap-components``. With the standard

docs/extended_callbacks.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ For example, the ``plotly_apps.py`` example contains this dash application:
1818
.. code-block:: python
1919
2020
import dash
21-
import dash_core_components as dcc
22-
import dash_html_components as html
21+
from dash import dcc, html
2322
2423
from django_plotly_dash import DjangoDash
2524

0 commit comments

Comments
 (0)