Repository: plotly/dash

Issue:
Dynamic resources are not considered for external_url
Context https://github.com/plotly/dash/issues/488#issuecomment-450075622


Discussion:

Resolving patch:
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -391,11 +391,12 @@ def _relative_url_path(relative_package_path='', namespace=''):
                             namespace=resource['namespace']
                         ))
             elif 'external_url' in resource:
-                if isinstance(resource['external_url'], str):
-                    srcs.append(resource['external_url'])
-                else:
-                    for url in resource['external_url']:
-                        srcs.append(url)
+                if not is_dynamic_resource:
+                    if isinstance(resource['external_url'], str):
+                        srcs.append(resource['external_url'])
+                    else:
+                        for url in resource['external_url']:
+                            srcs.append(url)
             elif 'absolute_path' in resource:
                 raise Exception(
                     'Serving files from absolute_path isn\'t supported yet'
diff --git a/dash/version.py b/dash/version.py
--- a/dash/version.py
+++ b/dash/version.py
@@ -1 +1 @@
-__version__ = '0.35.0'
+__version__ = '0.35.1'


Test patch:
diff --git a/tests/test_resources.py b/tests/test_resources.py
--- a/tests/test_resources.py
+++ b/tests/test_resources.py
@@ -1,164 +1,94 @@
 import unittest
-import warnings
-from dash.resources import Scripts, Css
-from dash.development._py_components_generation import generate_class
-
-
-def generate_components():
-    Div = generate_class('Div', ('children', 'id',), 'dash_html_components')
-    Span = generate_class('Span', ('children', 'id',), 'dash_html_components')
-    Input = generate_class(
-        'Input', ('children', 'id',),
-        'dash_core_components')
-    return Div, Span, Input
-
-
-def external_url(package_name):
-    return (
-        '//unpkg.com/{}@0.2.9'
-        '/{}/bundle.js'.format(
-            package_name.replace('_', '-'),
-            package_name
+import mock
+import dash_core_components as dcc
+
+import dash
+
+_monkey_patched_js_dist = [
+    {
+        'external_url': 'https://external_javascript.js',
+        'relative_package_path': 'external_javascript.js',
+        'namespace': 'dash_core_components'
+    },
+    {
+        'external_url': 'https://external_css.css',
+        'relative_package_path': 'external_css.css',
+        'namespace': 'dash_core_components'
+    },
+    {
+        'relative_package_path': 'fake_dcc.js',
+        'dev_package_path': 'fake_dcc.dev.js',
+        'external_url': 'https://component_library.bundle.js',
+        'namespace': 'dash_core_components'
+    },
+    {
+        'relative_package_path': 'fake_dcc.min.js.map',
+        'dev_package_path': 'fake_dcc.dev.js.map',
+        'external_url': 'https://component_library.bundle.js.map',
+        'namespace': 'dash_core_components',
+        'dynamic': True
+    }
+]
+
+dcc._js_dist = _monkey_patched_js_dist
+dcc.__version__ = 1
+
+
+class StatMock(object):
+    st_mtime = 1
+
+
+class Tests(unittest.TestCase):
+
+    def test_external(self):
+        app = dash.Dash(
+            __name__,
+            assets_folder='tests/assets',
+            assets_ignore='load_after.+.js'
         )
-    )
-
-
-def rel_path(package_name):
-    return '{}/bundle.js'.format(package_name)
-
-
-def abs_path(package_name):
-    return '/Users/chriddyp/{}/bundle.js'.format(package_name)
-
-
-class TestResources(unittest.TestCase):
-
-    def resource_test(self, css_or_js):
-        Div, Span, Input = generate_components()
-
-        if css_or_js == 'css':
-            # The CSS URLs and paths will look a little bit differently
-            # than the JS urls but that doesn't matter for the purposes
-            # of the test
-            Div._css_dist = Span._css_dist = [{
-                'external_url': external_url('dash_html_components'),
-                'relative_package_path': rel_path('dash_html_components')
-            }]
-
-            Input._css_dist = [{
-                'external_url': external_url('dash_core_components'),
-                'relative_package_path': rel_path('dash_core_components')
-            }]
-
-        else:
-            Div._js_dist = Span._js_dist = [{
-                'external_url': external_url('dash_html_components'),
-                'relative_package_path': rel_path('dash_html_components')
-            }]
-
-            Input._js_dist = [{
-                'external_url': external_url('dash_core_components'),
-                'relative_package_path': rel_path('dash_core_components')
-            }]
-
-        layout = Div([None, 'string', Span(), Div(Input())])
+        app.layout = dcc.Markdown()
+        app.scripts.config.serve_locally = False
 
-        if css_or_js == 'css':
-            resources = Css(layout)
-        else:
-            resources = Scripts(layout)
-
-        resources._update_layout(layout)
-
-        expected_filtered_external_resources = [
-            {
-                'external_url': external_url('dash_html_components'),
-                'namespace': 'dash_html_components'
-            },
-            {
-                'external_url': external_url('dash_core_components'),
-                'namespace': 'dash_core_components'
-            }
-        ]
-        expected_filtered_relative_resources = [
-            {
-                'relative_package_path': rel_path('dash_html_components'),
-                'namespace': 'dash_html_components'
-            },
-            {
-                'relative_package_path': rel_path('dash_core_components'),
-                'namespace': 'dash_core_components'
-            }
-        ]
-
-        if css_or_js == 'css':
-            self.assertEqual(
-                resources.get_all_css(),
-                expected_filtered_external_resources
-            )
-        else:
-            self.assertEqual(
-                resources.get_all_scripts(),
-                expected_filtered_external_resources
-            )
-
-        resources.config.serve_locally = True
-        if css_or_js == 'css':
-            self.assertEqual(
-                resources.get_all_css(),
-                expected_filtered_relative_resources
-            )
-        else:
-            self.assertEqual(
-                resources.get_all_scripts(),
-                expected_filtered_relative_resources
-            )
-
-        resources.config.serve_locally = False
-        extra_resource = {'external_url': '//cdn.bootstrap.com/min.css'}
-        expected_resources = expected_filtered_external_resources + [
-            extra_resource
-        ]
-        if css_or_js == 'css':
-            resources.append_css(extra_resource)
-            self.assertEqual(
-                resources.get_all_css(),
-                expected_resources
-            )
-        else:
-            resources.append_script(extra_resource)
-            self.assertEqual(
-                resources.get_all_scripts(),
-                expected_resources
+        with mock.patch('dash.dash.os.stat', return_value=StatMock()):
+            resource = app._collect_and_register_resources(
+                app.scripts.get_all_scripts()
             )
 
-        resources.config.serve_locally = True
-        with warnings.catch_warnings(record=True) as w:
-            warnings.simplefilter('always')
-            if css_or_js == 'css':
-                self.assertEqual(
-                    resources.get_all_css(),
-                    expected_filtered_relative_resources
-                )
-                assert len(w) == 1
-                assert 'A local version of {} is not available'.format(
-                    extra_resource['external_url']
-                ) in str(w[-1].message)
-
-            else:
-                self.assertEqual(
-                    resources.get_all_scripts(),
-                    expected_filtered_relative_resources
+        self.assertEqual(resource, [
+            'https://external_javascript.js',
+            'https://external_css.css',
+            'https://component_library.bundle.js'
+        ])
+
+    def test_internal(self):
+        app = dash.Dash(
+            __name__,
+            assets_folder='tests/assets',
+            assets_ignore='load_after.+.js'
+        )
+        app.layout = dcc.Markdown()
+        app.scripts.config.serve_locally = True
+
+        with mock.patch('dash.dash.os.stat', return_value=StatMock()):
+            with mock.patch('dash.dash.importlib.import_module',
+                            return_value=dcc):
+                resource = app._collect_and_register_resources(
+                    app.scripts.get_all_scripts()
                 )
-                assert len(w) == 1
-                assert 'A local version of {} is not available'.format(
-                    extra_resource['external_url']
-                ) in str(w[-1].message)
-
-    def test_js_resources(self):
-        # self.resource_test('js')
-        pass
 
-    def test_css_resources(self):
-        # self.resource_test('css')
-        pass
+        self.assertEqual(resource, [
+            '/_dash-component-suites/'
+            'dash_core_components/external_javascript.js?v=1&m=1',
+            '/_dash-component-suites/'
+            'dash_core_components/external_css.css?v=1&m=1',
+            '/_dash-component-suites/'
+            'dash_core_components/fake_dcc.js?v=1&m=1',
+        ])
+
+        self.assertTrue(
+            'fake_dcc.min.js.map'
+            in app.registered_paths['dash_core_components'],
+            'Dynamic resource not available in registered path {}'.format(
+                app.registered_paths['dash_core_components']
+            )
+        )