Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pathname prefix from environ variables. #322

Merged
merged 6 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a while to understand this but I think I finally get it. This is the case for users that are deploying on DDS that want a different URL base name. i.e. instead of just /my-app-name they want it to be /some-base-url/another-app. I think this will be uncommon but it makes sense and it seems complete 👍

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'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure to note this in the CHANGELOG. It's definitely a bug, so we don't need to bump the major version number, but we should definitely note it as it may break users apps that have modified one value but not the other.

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)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇 thanks for the tests!


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