Skip to content

Commit

Permalink
Merge pull request #322 from plotly/pathname-prefix
Browse files Browse the repository at this point in the history
Pathname prefix from environ variables.
  • Loading branch information
T4rk1n committed Aug 14, 2018
2 parents a5b7592 + 8568403 commit 7361db5
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 9 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 0.25.0 - 2018-08-14
## Added
- Take configs values from init or environ variables (Prefixed with `DASH_`). [#322](https://github.com/plotly/dash/pull/322)

## Fixed
- Take `requests_pathname_prefix` config when creating scripts tags.
- `requests/routes_pathname_prefix` must starts and end with `/`.
- `requests_pathname_prefix` must ends with `routes_pathname_prefix`. If you supplied both `requests` and `routes` pathname before this update, make sure `requests_pathname_prefix` ends with the same value as `routes_pathname_prefix`.
- `url_base_pathname` set both `requests/routes` pathname, cannot supply it with either `requests` or `routes` pathname prefixes.


## 0.24.2 - 2018-08-13
## Fixed
- Disallow duplicate component ids in the initial layout. [#320](https://github.com/plotly/dash/pull/320)
Expand Down
104 changes: 104 additions & 0 deletions dash/_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import os

# noinspection PyCompatibility
from . import exceptions
from ._utils import AttributeDict


def env_configs():
"""
Configs from the environ.
:return: A dict with the dash environ vars
"""
return AttributeDict({x: os.getenv(x, os.getenv(x.lower())) for x in (
'DASH_APP_NAME',
'DASH_URL_BASE_PATHNAME',
'DASH_ROUTES_PATHNAME_PREFIX',
'DASH_REQUESTS_PATHNAME_PREFIX',
'DASH_SUPPRESS_CALLBACK_EXCEPTIONS',
'DASH_ASSETS_EXTERNAL_PATH',
'DASH_INCLUDE_ASSETS_FILES'
)})


def get_config(config_name, init, env, default=None):
if init is not None:
return init

env_value = env.get('DASH_{}'.format(config_name.upper()))
if env_value is None:
return default
return env_value


def pathname_configs(url_base_pathname=None,
routes_pathname_prefix=None,
requests_pathname_prefix=None,
environ_configs=None):
_pathname_config_error_message = '''
{} This is ambiguous.
To fix this, set `routes_pathname_prefix` instead of `url_base_pathname`.
Note that `requests_pathname_prefix` is the prefix for the AJAX calls that
originate from the client (the web browser) and `routes_pathname_prefix` is
the prefix for the API routes on the backend (this flask server).
`url_base_pathname` will set `requests_pathname_prefix` and
`routes_pathname_prefix` to the same value.
If you need these to be different values then you should set
`requests_pathname_prefix` and `routes_pathname_prefix`,
not `url_base_pathname`.
'''
environ_configs = environ_configs or env_configs()

url_base_pathname = get_config('url_base_pathname',
url_base_pathname,
environ_configs)

routes_pathname_prefix = get_config('routes_pathname_prefix',
routes_pathname_prefix,
environ_configs)

requests_pathname_prefix = get_config('requests_pathname_prefix',
requests_pathname_prefix,
environ_configs)

if url_base_pathname is not None and requests_pathname_prefix is not None:
raise exceptions.InvalidConfig(
_pathname_config_error_message.format(
'You supplied `url_base_pathname` and '
'`requests_pathname_prefix`.'
)
)
elif url_base_pathname is not None and routes_pathname_prefix is not None:
raise exceptions.InvalidConfig(
_pathname_config_error_message.format(
'You supplied `url_base_pathname` and '
'`routes_pathname_prefix`.')
)
elif url_base_pathname is not None and routes_pathname_prefix is None:
routes_pathname_prefix = url_base_pathname
elif routes_pathname_prefix is None:
routes_pathname_prefix = '/'

if not routes_pathname_prefix.startswith('/'):
raise exceptions.InvalidConfig(
'`routes_pathname_prefix` needs to start with `/`')
if not routes_pathname_prefix.endswith('/'):
raise exceptions.InvalidConfig(
'`routes_pathname_prefix` needs to end with `/`')

app_name = environ_configs.DASH_APP_NAME

if not requests_pathname_prefix and app_name:
requests_pathname_prefix = '/' + app_name + routes_pathname_prefix
elif requests_pathname_prefix is None:
requests_pathname_prefix = routes_pathname_prefix

if not requests_pathname_prefix.endswith(routes_pathname_prefix):
raise exceptions.InvalidConfig(
'`requests_pathname_prefix` needs to ends with '
'`routes_pathname_prefix`.'
)

return url_base_pathname, routes_pathname_prefix, requests_pathname_prefix
39 changes: 31 additions & 8 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from ._utils import AttributeDict as _AttributeDict
from ._utils import interpolate_str as _interpolate
from ._utils import format_tag as _format_tag
from . import _configs


_default_index = '''
<!DOCTYPE html>
Expand Down Expand Up @@ -71,13 +73,17 @@ def __init__(
static_folder='static',
assets_folder=None,
assets_url_path='/assets',
include_assets_files=True,
url_base_pathname='/',
assets_external_path=None,
include_assets_files=None,
url_base_pathname=None,
requests_pathname_prefix=None,
routes_pathname_prefix=None,
compress=True,
meta_tags=None,
index_string=_default_index,
external_scripts=None,
external_stylesheets=None,
suppress_callback_exceptions=None,
**kwargs):

# pylint-disable: too-many-instance-attributes
Expand All @@ -101,13 +107,30 @@ def __init__(
static_folder=self._assets_folder,
static_url_path=assets_url_path))

env_configs = _configs.env_configs()

url_base_pathname, routes_pathname_prefix, requests_pathname_prefix = \
_configs.pathname_configs(
url_base_pathname,
routes_pathname_prefix,
requests_pathname_prefix,
environ_configs=env_configs)

self.url_base_pathname = url_base_pathname
self.config = _AttributeDict({
'suppress_callback_exceptions': False,
'routes_pathname_prefix': url_base_pathname,
'requests_pathname_prefix': url_base_pathname,
'include_assets_files': include_assets_files,
'assets_external_path': '',
'suppress_callback_exceptions': _configs.get_config(
'suppress_callback_exceptions',
suppress_callback_exceptions, env_configs, False
),
'routes_pathname_prefix': routes_pathname_prefix,
'requests_pathname_prefix': requests_pathname_prefix,
'include_assets_files': _configs.get_config(
'include_assets_files',
include_assets_files,
env_configs,
True),
'assets_external_path': _configs.get_config(
'assets_external_path', assets_external_path, env_configs, ''),
})

# list of dependencies
Expand Down Expand Up @@ -273,7 +296,7 @@ def _relative_url_path(relative_package_path='', namespace=''):
self.registered_paths[namespace] = [relative_package_path]

return '{}_dash-component-suites/{}/{}?v={}'.format(
self.config['routes_pathname_prefix'],
self.config['requests_pathname_prefix'],
namespace,
relative_package_path,
importlib.import_module(namespace).__version__
Expand Down
4 changes: 4 additions & 0 deletions dash/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ class DuplicateIdError(DashException):

class InvalidCallbackReturnValue(CallbackException):
pass


class InvalidConfig(DashException):
pass
2 changes: 1 addition & 1 deletion dash/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.24.2'
__version__ = '0.25.0'
93 changes: 93 additions & 0 deletions tests/test_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import unittest
# noinspection PyProtectedMember
from dash import _configs
from dash import exceptions as _exc
import os


class MyTestCase(unittest.TestCase):

def setUp(self):
environ = _configs.env_configs()

for k in environ.keys():
if k in os.environ:
os.environ.pop(k)

def test_valid_pathname_prefix_init(self):
_, routes, req = _configs.pathname_configs()

self.assertEqual('/', routes)
self.assertEqual('/', req)

_, routes, req = _configs.pathname_configs(
routes_pathname_prefix='/dash/')

self.assertEqual('/dash/', req)

_, routes, req = _configs.pathname_configs(
requests_pathname_prefix='/my-dash-app/',
)

self.assertEqual(routes, '/')
self.assertEqual(req, '/my-dash-app/')

_, routes, req = _configs.pathname_configs(
routes_pathname_prefix='/dash/',
requests_pathname_prefix='/my-dash-app/dash/'
)

self.assertEqual('/dash/', routes)
self.assertEqual('/my-dash-app/dash/', req)

def test_invalid_pathname_prefix(self):
with self.assertRaises(_exc.InvalidConfig) as context:
_, _, _ = _configs.pathname_configs('/my-path', '/another-path')

self.assertTrue('url_base_pathname' in str(context.exception))

with self.assertRaises(_exc.InvalidConfig) as context:
_, _, _ = _configs.pathname_configs(
url_base_pathname='/invalid',
routes_pathname_prefix='/invalid')

self.assertTrue(str(context.exception).split('.')[0]
.endswith('`routes_pathname_prefix`'))

with self.assertRaises(_exc.InvalidConfig) as context:
_, _, _ = _configs.pathname_configs(
url_base_pathname='/my-path',
requests_pathname_prefix='/another-path')

self.assertTrue(str(context.exception).split('.')[0]
.endswith('`requests_pathname_prefix`'))

with self.assertRaises(_exc.InvalidConfig) as context:
_, _, _ = _configs.pathname_configs('my-path')

self.assertTrue('start with `/`' in str(context.exception))

with self.assertRaises(_exc.InvalidConfig) as context:
_, _, _ = _configs.pathname_configs('/my-path')

self.assertTrue('end with `/`' in str(context.exception))

def test_pathname_prefix_from_environ_app_name(self):
os.environ['DASH_APP_NAME'] = 'my-dash-app'
_, routes, req = _configs.pathname_configs()
self.assertEqual('/my-dash-app/', req)
self.assertEqual('/', routes)

def test_pathname_prefix_environ_routes(self):
os.environ['DASH_ROUTES_PATHNAME_PREFIX'] = '/routes/'
_, routes, req = _configs.pathname_configs()
self.assertEqual('/routes/', routes)

def test_pathname_prefix_environ_requests(self):
os.environ['DASH_REQUESTS_PATHNAME_PREFIX'] = '/requests/'
_, routes, req = _configs.pathname_configs()
self.assertEqual('/requests/', req)


if __name__ == '__main__':
unittest.main()
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ commands =
python -m unittest tests.development.test_component_loader
python -m unittest tests.test_integration
python -m unittest tests.test_resources
python -m unittest tests.test_configs

flake8 dash setup.py
pylint dash setup.py
Expand All @@ -25,5 +26,7 @@ commands =
python -m unittest tests.development.test_component_loader
python -m unittest tests.test_integration
python -m unittest tests.test_resources
python -m unittest tests.test_configs

flake8 dash setup.py
pylint dash setup.py

0 comments on commit 7361db5

Please sign in to comment.