From ef9f04716672a9c940ff24bbea0d545754943fc8 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 2 Aug 2018 14:33:42 -0400 Subject: [PATCH 1/6] Change formatted resources prefix to use `requests_pathname_prefix`. --- dash/dash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index d5142e0a03..84f130a553 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -273,7 +273,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__ From f8bf5c0364c703d44ebf415c4e79b07969a7cc29 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Fri, 3 Aug 2018 14:45:07 -0400 Subject: [PATCH 2/6] Take requests_pathname_prefix config from os.environ --- dash/dash.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 84f130a553..5fbb1d36b1 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -105,7 +105,10 @@ def __init__( self.config = _AttributeDict({ 'suppress_callback_exceptions': False, 'routes_pathname_prefix': url_base_pathname, - 'requests_pathname_prefix': url_base_pathname, + 'requests_pathname_prefix': os.getenv( + 'DASH_REQUESTS_PATHNAME_PREFIX', + '/{}/'.format(os.environ['DASH_APP_NAME']) + if 'DASH_APP_NAME' in os.environ else url_base_pathname), 'include_assets_files': include_assets_files, 'assets_external_path': '', }) From 2b9c4ede2b6b6b3cc001acb01e5cc57f64c0fee0 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Thu, 9 Aug 2018 12:55:35 -0400 Subject: [PATCH 3/6] Add requests_pathname_prefix init arg. --- dash/dash.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index 5fbb1d36b1..8bf32ec589 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -73,6 +73,7 @@ def __init__( assets_url_path='/assets', include_assets_files=True, url_base_pathname='/', + requests_pathname_prefix='', compress=True, meta_tags=None, index_string=_default_index, @@ -105,10 +106,10 @@ def __init__( self.config = _AttributeDict({ 'suppress_callback_exceptions': False, 'routes_pathname_prefix': url_base_pathname, - 'requests_pathname_prefix': os.getenv( + 'requests_pathname_prefix': requests_pathname_prefix or os.getenv( 'DASH_REQUESTS_PATHNAME_PREFIX', - '/{}/'.format(os.environ['DASH_APP_NAME']) - if 'DASH_APP_NAME' in os.environ else url_base_pathname), + '/{}'.format(os.environ['DASH_APP_NAME']) + if 'DASH_APP_NAME' in os.environ else '') + url_base_pathname, 'include_assets_files': include_assets_files, 'assets_external_path': '', }) From 2032f06bc6693c133f89e25bb58202cb4de485dc Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 13 Aug 2018 12:38:16 -0400 Subject: [PATCH 4/6] Add configs from init or env, add pathname configs tests. --- dash/_configs.py | 104 ++++++++++++++++++++++++++++++++++++++++++ dash/dash.py | 41 ++++++++++++----- dash/exceptions.py | 4 ++ tests/test_configs.py | 93 +++++++++++++++++++++++++++++++++++++ 4 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 dash/_configs.py create mode 100644 tests/test_configs.py diff --git a/dash/_configs.py b/dash/_configs.py new file mode 100644 index 0000000000..5f5d79f857 --- /dev/null +++ b/dash/_configs.py @@ -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 choose_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 = choose_config('url_base_pathname', + url_base_pathname, + environ_configs) + + routes_pathname_prefix = choose_config('routes_pathname_prefix', + routes_pathname_prefix, + environ_configs) + + requests_pathname_prefix = choose_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 diff --git a/dash/dash.py b/dash/dash.py index 8bf32ec589..8ba6690b00 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -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 = ''' @@ -71,14 +73,17 @@ def __init__( static_folder='static', assets_folder=None, assets_url_path='/assets', - include_assets_files=True, - url_base_pathname='/', - requests_pathname_prefix='', + 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 @@ -102,16 +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': requests_pathname_prefix or os.getenv( - 'DASH_REQUESTS_PATHNAME_PREFIX', - '/{}'.format(os.environ['DASH_APP_NAME']) - if 'DASH_APP_NAME' in os.environ else '') + url_base_pathname, - 'include_assets_files': include_assets_files, - 'assets_external_path': '', + 'suppress_callback_exceptions': _configs.choose_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.choose_config( + 'include_assets_files', + include_assets_files, + env_configs, + True), + 'assets_external_path': _configs.choose_config( + 'assets_external_path', assets_external_path, env_configs, ''), }) # list of dependencies diff --git a/dash/exceptions.py b/dash/exceptions.py index 58cf322715..9cbb4e4dd9 100644 --- a/dash/exceptions.py +++ b/dash/exceptions.py @@ -56,3 +56,7 @@ class DuplicateIdError(DashException): class InvalidCallbackReturnValue(CallbackException): pass + + +class InvalidConfig(DashException): + pass diff --git a/tests/test_configs.py b/tests/test_configs.py new file mode 100644 index 0000000000..ee29673309 --- /dev/null +++ b/tests/test_configs.py @@ -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() From 4e09830381f1e264f7dc0ae7cdd9a7ab40a39910 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Mon, 13 Aug 2018 18:02:01 -0400 Subject: [PATCH 5/6] Add test_configs to tox. --- dash/_configs.py | 20 ++++++++++---------- dash/dash.py | 6 +++--- tox.ini | 3 +++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dash/_configs.py b/dash/_configs.py index 5f5d79f857..6849f5be8d 100644 --- a/dash/_configs.py +++ b/dash/_configs.py @@ -22,7 +22,7 @@ def env_configs(): )}) -def choose_config(config_name, init, env, default=None): +def get_config(config_name, init, env, default=None): if init is not None: return init @@ -51,17 +51,17 @@ def pathname_configs(url_base_pathname=None, ''' environ_configs = environ_configs or env_configs() - url_base_pathname = choose_config('url_base_pathname', - url_base_pathname, - environ_configs) + url_base_pathname = get_config('url_base_pathname', + url_base_pathname, + environ_configs) - routes_pathname_prefix = choose_config('routes_pathname_prefix', - routes_pathname_prefix, - environ_configs) + routes_pathname_prefix = get_config('routes_pathname_prefix', + routes_pathname_prefix, + environ_configs) - requests_pathname_prefix = choose_config('requests_pathname_prefix', - requests_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( diff --git a/dash/dash.py b/dash/dash.py index 8ba6690b00..80691b9f8f 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -118,18 +118,18 @@ def __init__( self.url_base_pathname = url_base_pathname self.config = _AttributeDict({ - 'suppress_callback_exceptions': _configs.choose_config( + '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.choose_config( + 'include_assets_files': _configs.get_config( 'include_assets_files', include_assets_files, env_configs, True), - 'assets_external_path': _configs.choose_config( + 'assets_external_path': _configs.get_config( 'assets_external_path', assets_external_path, env_configs, ''), }) diff --git a/tox.ini b/tox.ini index e3e73acfe7..d759241370 100644 --- a/tox.ini +++ b/tox.ini @@ -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 @@ -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 From 8568403180dd1705d717e8af48a3b7c5499f7905 Mon Sep 17 00:00:00 2001 From: Philippe Duval Date: Tue, 14 Aug 2018 10:47:01 -0400 Subject: [PATCH 6/6] Update version and CHANGELOG. --- CHANGELOG.md | 11 +++++++++++ dash/version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18cb569800..e28709da69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/dash/version.py b/dash/version.py index d1d123f305..8c308d7234 100644 --- a/dash/version.py +++ b/dash/version.py @@ -1 +1 @@ -__version__ = '0.24.2' +__version__ = '0.25.0'