-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Pathname prefix from environ variables. #322
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
Changes from all commits
ef9f047
f8bf5c0
2b9c4ed
2032f06
4e09830
8568403
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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__ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
__version__ = '0.24.2' | ||
__version__ = '0.25.0' |
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) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙇 thanks for the tests! |
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
There was a problem hiding this comment.
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 👍