Skip to content

Commit

Permalink
Allow to load pages from package
Browse files Browse the repository at this point in the history
This allows to load the pages from an imported python package which is
recursively scanned. This is useful if one compiles an application using
Cython where the modules are then not .py files anymore, but compiled
.so files.
  • Loading branch information
rnestler committed Jun 1, 2023
1 parent db23cdc commit 282e606
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 6 deletions.
25 changes: 19 additions & 6 deletions dash/_pages.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import collections
import importlib
import os
import pkgutil
import re
import sys
from fnmatch import fnmatch
Expand Down Expand Up @@ -411,6 +412,15 @@ def _page_meta_tags(app):
]


def _ensure_layout_is_loaded(module_name, page_module):
if (
module_name in PAGE_REGISTRY
and not PAGE_REGISTRY[module_name]["supplied_layout"]
):
_validate.validate_pages_layout(module_name, page_module)
PAGE_REGISTRY[module_name]["layout"] = getattr(page_module, "layout")


def _import_layouts_from_pages(pages_folder):
for root, dirs, files in os.walk(pages_folder):
dirs[:] = [d for d in dirs if not d.startswith(".") and not d.startswith("_")]
Expand All @@ -428,10 +438,13 @@ def _import_layouts_from_pages(pages_folder):
page_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(page_module)
sys.modules[module_name] = page_module
_ensure_layout_is_loaded(module_name, page_module)

if (
module_name in PAGE_REGISTRY
and not PAGE_REGISTRY[module_name]["supplied_layout"]
):
_validate.validate_pages_layout(module_name, page_module)
PAGE_REGISTRY[module_name]["layout"] = getattr(page_module, "layout")

def _import_layouts_from_package(pages_package):
modules = pkgutil.walk_packages(
pages_package.__path__, pages_package.__name__ + "."
)
for module in modules:
page_module = importlib.import_module(module.name)
_ensure_layout_is_loaded(module.name, page_module)
5 changes: 5 additions & 0 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
_page_meta_tags,
_path_to_page,
_import_layouts_from_pages,
_import_layouts_from_package,
)

# Add explicit mapping for map files
Expand Down Expand Up @@ -345,6 +346,7 @@ def __init__( # pylint: disable=too-many-statements
server=True,
assets_folder="assets",
pages_folder="pages",
pages_package=None,
use_pages=None,
assets_url_path="assets",
assets_ignore="",
Expand Down Expand Up @@ -444,6 +446,7 @@ def __init__( # pylint: disable=too-many-statements
_get_paths.CONFIG = self.config
_pages.CONFIG = self.config

self.pages_package = pages_package
self.pages_folder = str(pages_folder)
self.use_pages = (pages_folder != "pages") if use_pages is None else use_pages

Expand Down Expand Up @@ -1959,6 +1962,8 @@ def enable_pages(self):
return
if self.pages_folder:
_import_layouts_from_pages(self.config.pages_folder)
if self.pages_package:
_import_layouts_from_package(self.pages_package)

@self.server.before_request
def router():
Expand Down
3 changes: 3 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys

import pytest
import dash
Expand All @@ -21,6 +22,8 @@ def clear_pages_state():

def init_pages_state():
"""Clear all global state that is used by pages feature."""
for page in dash._pages.PAGE_REGISTRY.values():
sys.modules.pop(page["module"])
dash._pages.PAGE_REGISTRY.clear()
dash._pages.CONFIG.clear()
dash._pages.CONFIG.__dict__.clear()
9 changes: 9 additions & 0 deletions tests/unit/pages/test_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,12 @@ def test_import_layouts_from_pages(

page_entry = list(dash.page_registry.values())[0]
assert page_entry["module"] == expected_module_name


def test_import_layouts_from_package(clear_pages_state):
from . import custom_pages

_ = Dash(__package__, use_pages=True, pages_folder="", pages_package=custom_pages)
page_entries = list(dash.page_registry.values())
assert len(page_entries) == 1
assert page_entries[0]["module"] == "pages.custom_pages.page"

0 comments on commit 282e606

Please sign in to comment.