From 209146efd093ced573da28069e288f370a7e16e0 Mon Sep 17 00:00:00 2001 From: Ned Letcher Date: Wed, 12 Oct 2022 01:24:15 +1100 Subject: [PATCH 1/2] Attatch Dash Pages layout modules to parent package when working inside one - Dash Pages layout modules are now prefixed with `Dash.name`. Providing that the supplied `name` param matches the package name (eg `Dash(name=__package__)`), Pages modules will be attached as expected into your package namespace. eg `pages.page1` --> `my_package.pages.page1`. - To prevent the pages registry from sharing state across multiple `Dash` app instances, the pages registry is now cleared on `Dash` instance init. This is necessary as the existing tests relied on the fact that multiple app inits in the one process would clobber the contents of previous page registeries, but with the above changes, we now see conflicting entries for the same pages folder. eg `test1.pages.page1` and `test2.pages.page1`, which both reference the same `page1.py`, resulting in invalid duplicate entries in the layout and page registry. --- dash/dash.py | 10 +++++++++- .../integration/multi_page/test_pages_layout.py | 5 ++--- tests/integration/multi_page/test_pages_order.py | 16 ++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/dash/dash.py b/dash/dash.py index bb8327e670..5ab2cd8aed 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -169,6 +169,12 @@ def _get_skip(text, divider=2): no_update = _callback.NoUpdate() # pylint: disable=protected-access +def init_dash_globals(): + """Ensure that all Dash global state is re-initialised.""" + _pages.PAGE_REGISTRY.clear() + _pages.CONFIG.clear() + + # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments, too-many-locals class Dash: @@ -380,6 +386,8 @@ def __init__( # pylint: disable=too-many-statements url_base_pathname, routes_pathname_prefix, requests_pathname_prefix ) + init_dash_globals() + self.config = AttributeDict( name=name, assets_folder=os.path.join( @@ -2008,7 +2016,7 @@ def _import_layouts_from_pages(self): self.pages_folder.replace("\\", "/").lstrip("/").replace("/", ".") ) - module_name = ".".join([pages_folder, page_filename]) + module_name = ".".join([self.config.name, pages_folder, page_filename]) spec = importlib.util.spec_from_file_location( module_name, os.path.join(root, file) diff --git a/tests/integration/multi_page/test_pages_layout.py b/tests/integration/multi_page/test_pages_layout.py index d5abb51ff7..82ba0d6338 100644 --- a/tests/integration/multi_page/test_pages_layout.py +++ b/tests/integration/multi_page/test_pages_layout.py @@ -173,12 +173,11 @@ def test_pala003_meta_tags_custom(dash_duo): def test_pala004_no_layout_exception(): - error_msg = 'No layout found in module pages_error.no_layout_page\nA variable or a function named "layout" is required.' + error_msg = 'No layout found in module test_pages_layout.pages_error.no_layout_page\nA variable or a function named "layout" is required.' with pytest.raises(NoLayoutException) as err: Dash(__name__, use_pages=True, pages_folder="pages_error") - # clean up after this test, so the broken entry doesn't affect other pages tests - del dash.page_registry["pages_error.no_layout_page"] + del dash.page_registry["test_pages_layout.pages_error.no_layout_page"] assert error_msg in err.value.args[0] diff --git a/tests/integration/multi_page/test_pages_order.py b/tests/integration/multi_page/test_pages_order.py index 57d487f437..3f6715aea7 100644 --- a/tests/integration/multi_page/test_pages_order.py +++ b/tests/integration/multi_page/test_pages_order.py @@ -48,14 +48,14 @@ def test_paor001_order(dash_duo): "multi_layout3", "multi_layout2", "multi_layout1", - "pages.defaults", - "pages.metas", - "pages.not_found_404", - "pages.page1", - "pages.page2", - "pages.path_variables", - "pages.query_string", - "pages.redirect", + "test_pages_order.pages.defaults", + "test_pages_order.pages.metas", + "test_pages_order.pages.not_found_404", + "test_pages_order.pages.page1", + "test_pages_order.pages.page2", + "test_pages_order.pages.path_variables", + "test_pages_order.pages.query_string", + "test_pages_order.pages.redirect", ] dash_duo.start_server(app) From 291947ee917b6e8a405bf07a25d0ace2d39ee499 Mon Sep 17 00:00:00 2001 From: Ned Letcher Date: Fri, 14 Oct 2022 01:09:24 +1100 Subject: [PATCH 2/2] Isolate state clearing to just the registry and add more comments --- dash/dash.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dash/dash.py b/dash/dash.py index 5ab2cd8aed..81033d9557 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -172,7 +172,13 @@ def _get_skip(text, divider=2): def init_dash_globals(): """Ensure that all Dash global state is re-initialised.""" _pages.PAGE_REGISTRY.clear() - _pages.CONFIG.clear() + + # this line of thinking could be extended to potentially clear the following: + # _pages.CONFIG + # _get_paths.CONFIG + # _callback.GLOBAL_CALLBACK_MAP + # _callback.GLOBAL_CALLBACK_LIST + # _callback.GLOBAL_INLINE_SCRIPTS # pylint: disable=too-many-instance-attributes