Skip to content

Commit 8464398

Browse files
Enable kwargs forward dash (#312)
* allow to forward the kwargs arguments of DjangoDash to the WrappedDash (add test for external_stylesheets and external_scripts) * remove pipfile that was added by mistake * cleanup: - use super() instead of super(...,self) - fix typo in dataclass * handled explicitly external_stylesheets/external_scripts arguments and warn when extra arguments are given * fix typo in import Co-authored-by: GFJ138 <[email protected]>
1 parent b9b682f commit 8464398

File tree

7 files changed

+79
-22
lines changed

7 files changed

+79
-22
lines changed

demo/demo/dash_apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
from django_plotly_dash import DjangoDash
1616

1717
#from .urls import app_name
18-
app_name = "DPD demo application"
18+
# app_name = "DPD demo application"
1919

2020
dashboard_name1 = 'dash_example_1'
2121
dash_example1 = DjangoDash(name=dashboard_name1,
2222
serve_locally=True,
23-
app_name=app_name
23+
# app_name=app_name
2424
)
2525

2626
# Below is a random Dash app.

demo/demo/plotly_apps.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,31 @@ def multiple_callbacks_two(button_clicks, color_choice, **kwargs):
379379
]
380380

381381

382+
383+
external_scripts = [
384+
'https://www.google-analytics.com/analytics.js',
385+
{'src': 'https://cdn.polyfill.io/v2/polyfill.min.js'},
386+
{
387+
'src': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.js',
388+
'integrity': 'sha256-Qqd/EfdABZUcAxjOkMi8eGEivtdTkh3b65xCZL4qAQA=',
389+
'crossorigin': 'anonymous'
390+
}
391+
]
392+
external_stylesheets = [
393+
'https://codepen.io/chriddyp/pen/bWLwgP.css',
394+
{
395+
'href': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css',
396+
'rel': 'stylesheet',
397+
'integrity': 'sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO',
398+
'crossorigin': 'anonymous'
399+
}
400+
]
401+
external_scripts_stylesheets = DjangoDash("ExternalScriptStylesheets",
402+
external_stylesheets=external_stylesheets,
403+
external_scripts=external_scripts)
404+
405+
external_scripts_stylesheets.layout = html.Div()
406+
382407
flexible_expanded_callbacks = DjangoDash("FlexibleExpandedCallbacks")
383408

384409
flexible_expanded_callbacks.layout = html.Div([

django_plotly_dash/consumers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def async_send_to_pipe_channel(channel_name,
5555
class MessageConsumer(WebsocketConsumer):
5656
'Websocket handler for pipe to dash component'
5757
def __init__(self, *args, **kwargs):
58-
super(MessageConsumer, self).__init__(*args, **kwargs)
58+
super().__init__(*args, **kwargs)
5959

6060
self.channel_maps = {}
6161

@@ -67,7 +67,7 @@ def disconnect(self, reason): # pylint: disable=arguments-differ
6767
for _, pipe_group_name in self.channel_maps.items():
6868
async_to_sync(self.channel_layer.group_discard)(pipe_group_name, self.channel_name)
6969

70-
return super(MessageConsumer, self).disconnect(reason)
70+
return super().disconnect(reason)
7171

7272
def pipe_value(self, message):
7373
'Send a new value into the ws pipe'

django_plotly_dash/dash_wrapper.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import itertools
2828
import json
2929
import inspect
30+
import warnings
3031

3132
import dash
3233
from dash import Dash
@@ -50,7 +51,7 @@
5051
from dataclasses import dataclass
5152
from typing import Dict, List
5253

53-
@dataclass(frozen-True)
54+
@dataclass(frozen=True)
5455
class CallbackContext:
5556
inputs_list : List
5657
inputs: Dict
@@ -162,7 +163,17 @@ class DjangoDash:
162163
def __init__(self, name=None, serve_locally=None,
163164
add_bootstrap_links=False,
164165
suppress_callback_exceptions=False,
165-
**kwargs): # pylint: disable=unused-argument, too-many-arguments
166+
external_stylesheets=None,
167+
external_scripts=None,
168+
**kwargs): # pylint: disable=unused-argument, too-many-arguments
169+
170+
# store arguments to pass them later to the WrappedDash instance
171+
self.external_stylesheets = external_stylesheets or []
172+
self.external_scripts = external_scripts or []
173+
self._kwargs = kwargs
174+
if kwargs:
175+
warnings.warn("You are passing extra arguments {kwargs} that will be passed to Dash(...) "
176+
"but may not be properly handled by django-plotly-dash.".format(kwargs=kwargs))
166177

167178
if name is None:
168179
global uid_counter # pylint: disable=global-statement
@@ -268,7 +279,10 @@ def form_dash_instance(self, replacements=None, ndid=None, base_pathname=None):
268279
rd = WrappedDash(base_pathname=base_pathname,
269280
replacements=replacements,
270281
ndid=ndid,
271-
serve_locally=self._serve_locally)
282+
serve_locally=self._serve_locally,
283+
external_stylesheets=self.external_stylesheets,
284+
external_scripts=self.external_scripts,
285+
**self._kwargs)
272286

273287
rd.layout = self.layout
274288
rd.config['suppress_callback_exceptions'] = self._suppress_callback_exceptions
@@ -408,8 +422,7 @@ def __init__(self,
408422
kwargs['url_base_pathname'] = self._base_pathname
409423
kwargs['server'] = self._notflask
410424

411-
super(WrappedDash, self).__init__(__name__,
412-
**kwargs)
425+
super().__init__(__name__, **kwargs)
413426

414427
self.css.config.serve_locally = serve_locally
415428
self.scripts.config.serve_locally = serve_locally
@@ -577,9 +590,9 @@ def callback(self, output, inputs=[], state=[], events=[]): # pylint: disable=da
577590
else:
578591
fixed_outputs = self._fix_callback_item(output)
579592

580-
return super(WrappedDash, self).callback(fixed_outputs,
581-
[self._fix_callback_item(x) for x in inputs],
582-
[self._fix_callback_item(x) for x in state])
593+
return super().callback(fixed_outputs,
594+
[self._fix_callback_item(x) for x in inputs],
595+
[self._fix_callback_item(x) for x in state])
583596

584597
def clientside_callback(self, clientside_function, output, inputs=[], state=[]): # pylint: disable=dangerous-default-value
585598
'Invoke callback, adjusting variable names as needed'
@@ -589,10 +602,10 @@ def clientside_callback(self, clientside_function, output, inputs=[], state=[]):
589602
else:
590603
fixed_outputs = self._fix_callback_item(output)
591604

592-
return super(WrappedDash, self).clientside_callback(clientside_function,
593-
fixed_outputs,
594-
[self._fix_callback_item(x) for x in inputs],
595-
[self._fix_callback_item(x) for x in state])
605+
return super().clientside_callback(clientside_function,
606+
fixed_outputs,
607+
[self._fix_callback_item(x) for x in inputs],
608+
[self._fix_callback_item(x) for x in state])
596609

597610
def dispatch(self):
598611
'Perform dispatch, using request embedded within flask global state'
@@ -750,7 +763,7 @@ def index(self, *args, **kwargs): # pylint: disable=unused-argument
750763
def interpolate_index(self, **kwargs): #pylint: disable=arguments-differ
751764

752765
if not self._return_embedded:
753-
resp = super(WrappedDash, self).interpolate_index(**kwargs)
766+
resp = super().interpolate_index(**kwargs)
754767
return resp
755768

756769
self._return_embedded.add_css(kwargs['css'])

django_plotly_dash/finders.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(self):
7676
self.storages[component_name] = storage
7777
self.components[path] = component_name
7878

79-
super(DashComponentFinder, self).__init__()
79+
super().__init__()
8080

8181
def find(self, path, all=False):
8282
matches = []
@@ -131,7 +131,7 @@ def __init__(self):
131131
self.locations.append(app_config.name)
132132
self.storages[app_config.name] = storage
133133

134-
super(DashAppDirectoryFinder, self).__init__()
134+
super().__init__()
135135

136136
#pylint: disable=redefined-builtin
137137
def find(self, path, all=False):
@@ -184,7 +184,7 @@ def __init__(self):
184184
self.locations.append(component_name)
185185
self.storages[component_name] = storage
186186

187-
super(DashAssetFinder, self).__init__()
187+
super().__init__()
188188

189189
#pylint: disable=redefined-builtin
190190
def find(self, path, all=False):

django_plotly_dash/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
5757
exist_count = StatelessApp.objects.filter(slug__startswith=self.slug).count()
5858
if exist_count > 0:
5959
self.slug = self.slug + str(exist_count+1)
60-
return super(StatelessApp, self).save(*args, **kwargs)
60+
return super().save(*args, **kwargs)
6161

6262
def as_dash_app(self):
6363
'''
@@ -141,7 +141,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
141141
self.instance_name = "%s-%i" %(self.stateless_app.app_name, existing_count+1) # pylint: disable=no-member
142142
if not self.slug or len(self.slug) < 2:
143143
self.slug = slugify(self.instance_name)
144-
super(DashApp, self).save(*args, **kwargs)
144+
super().save(*args, **kwargs)
145145

146146
def handle_current_state(self):
147147
'''

django_plotly_dash/tests.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import pytest
3131
import json
32+
from unittest.mock import patch
3233

3334
#pylint: disable=bare-except
3435
from dash.dependencies import Input
@@ -171,6 +172,7 @@ def test_injection_app_access(client):
171172
assert did_fail
172173

173174

175+
174176
@pytest.mark.django_db
175177
def test_injection_updating_multiple_callbacks(client):
176178
'Check updating of an app using demo test data for multiple callbacks'
@@ -446,6 +448,23 @@ def test_app_loading(client):
446448
assert response.status_code == 302
447449

448450

451+
@pytest.mark.django_db
452+
def test_external_scripts_stylesheets(client):
453+
'Check external_stylesheets and external_scripts ends up in index'
454+
455+
from demo.plotly_apps import external_scripts_stylesheets
456+
dash = external_scripts_stylesheets.as_dash_instance()
457+
458+
with patch.object(dash, "interpolate_index") as mock:
459+
dash.index()
460+
461+
_, kwargs = mock.call_args
462+
assert "https://codepen.io/chriddyp/pen/bWLwgP.css" in kwargs["css"]
463+
assert "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" in kwargs["css"]
464+
assert "https://www.google-analytics.com/analytics.js" in kwargs["scripts"]
465+
assert "https://cdn.polyfill.io/v2/polyfill.min.js" in kwargs["scripts"]
466+
assert "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.js" in kwargs["scripts"]
467+
449468
def test_callback_decorator():
450469
inputs = [Input("one", "value"),
451470
Input("two", "value"),

0 commit comments

Comments
 (0)