Repository: plotly/dash (python)

Issue:
Assets are not prefixed with `requests_pathname_prefix`.
The assets files should be prefixed with the `requests_pathname_prefix` like other resources.


Pull request description:
Prefix assets with `requests_pathname_prefix`.
Fix #349, the assets were not prefixed with the `requests_pathname_prefix` resulting in 404 on the dash deployment server.

- Add `get_asset_url` method to dash, retrieve the url of an asset by it's filename (eg: `app.get_asset_url('style.css')` will return `/assets/style.css`), it is automatically prefixed with `requests_pathname_prefix`.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e96b0267e..5e8d53aab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 0.26.3 - 2018-08-27
+## Fixed
+- Prefix assets files with `requests_pathname_prefix`. [#351](https://github.com/plotly/dash/pull/351)
+
+## Added
+- `Dash.get_asset_url` will give the prefixed url for the asset file.
+
 ## 0.26.2 - 2018-08-26
 ## Fixed
 - Only create the assets blueprint once for app that provide the same flask instance to multiple dash instance. [#343](https://github.com/plotly/dash/pull/343)
diff --git a/dash/_utils.py b/dash/_utils.py
index bc71fde95..9b1a48faa 100644
--- a/dash/_utils.py
+++ b/dash/_utils.py
@@ -20,6 +20,18 @@ def format_tag(tag_name, attributes, inner='', closed=False, opened=False):
             '{}="{}"'.format(k, v) for k, v in attributes.items()]))
 
 
+def get_asset_path(requests_pathname, routes_pathname, asset_path):
+    i = requests_pathname.rfind(routes_pathname)
+    req = requests_pathname[:i]
+
+    return '/'.join([
+        # Only take the first part of the pathname
+        req,
+        'assets',
+        asset_path
+    ])
+
+
 class AttributeDict(dict):
     """
     Dictionary subclass enabling attribute lookup/assignment of keys/values.
diff --git a/dash/dash.py b/dash/dash.py
index 4cfd0d468..817776108 100644
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -24,6 +24,7 @@ from . import exceptions
 from ._utils import AttributeDict as _AttributeDict
 from ._utils import interpolate_str as _interpolate
 from ._utils import format_tag as _format_tag
+from ._utils import get_asset_path as _get_asset_path
 from . import _configs
 
 
@@ -329,9 +330,9 @@ class Dash(object):
                     'Serving files from absolute_path isn\'t supported yet'
                 )
             elif 'asset_path' in resource:
-                static_url = flask.url_for('assets.static',
-                                           filename=resource['asset_path'],
-                                           mod=resource['ts'])
+                static_url = self.get_asset_url(resource['asset_path'])
+                # Add a bust query param
+                static_url += '?m={}'.format(resource['ts'])
                 srcs.append(static_url)
         return srcs
 
@@ -942,6 +943,12 @@ class Dash(object):
                 elif f == 'favicon.ico':
                     self._favicon = path
 
+    def get_asset_url(self, path):
+        return _get_asset_path(
+            self.config.requests_pathname_prefix,
+            self.config.routes_pathname_prefix,
+            path)
+
     def run_server(self,
                    port=8050,
                    debug=False,
diff --git a/dash/version.py b/dash/version.py
index c42375ce9..be363825c 100644
--- a/dash/version.py
+++ b/dash/version.py
@@ -1,1 +1,1 @@
-__version__ = '0.26.2'
+__version__ = '0.26.3'


Test patch:
diff --git a/tests/test_configs.py b/tests/test_configs.py
index ee2967330..6b65613a9 100644
--- a/tests/test_configs.py
+++ b/tests/test_configs.py
@@ -2,6 +2,7 @@ import unittest
 # noinspection PyProtectedMember
 from dash import _configs
 from dash import exceptions as _exc
+from dash._utils import get_asset_path
 import os
 
 
@@ -88,6 +89,21 @@ class MyTestCase(unittest.TestCase):
         _, routes, req = _configs.pathname_configs()
         self.assertEqual('/requests/', req)
 
+    def test_pathname_prefix_assets(self):
+        req = '/'
+        routes = '/'
+        path = get_asset_path(req, routes, 'reset.css')
+        self.assertEqual('/assets/reset.css', path)
+
+        req = '/requests/'
+        path = get_asset_path(req, routes, 'reset.css')
+        self.assertEqual('/requests/assets/reset.css', path)
+
+        req = '/requests/routes/'
+        routes = '/routes/'
+        path = get_asset_path(req, routes, 'reset.css')
+        self.assertEqual('/requests/assets/reset.css', path)
+
 
 if __name__ == '__main__':
     unittest.main()