Skip to content

Commit 282e606

Browse files
committed
Allow to load pages from package
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.
1 parent db23cdc commit 282e606

File tree

4 files changed

+36
-6
lines changed

4 files changed

+36
-6
lines changed

dash/_pages.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections
22
import importlib
33
import os
4+
import pkgutil
45
import re
56
import sys
67
from fnmatch import fnmatch
@@ -411,6 +412,15 @@ def _page_meta_tags(app):
411412
]
412413

413414

415+
def _ensure_layout_is_loaded(module_name, page_module):
416+
if (
417+
module_name in PAGE_REGISTRY
418+
and not PAGE_REGISTRY[module_name]["supplied_layout"]
419+
):
420+
_validate.validate_pages_layout(module_name, page_module)
421+
PAGE_REGISTRY[module_name]["layout"] = getattr(page_module, "layout")
422+
423+
414424
def _import_layouts_from_pages(pages_folder):
415425
for root, dirs, files in os.walk(pages_folder):
416426
dirs[:] = [d for d in dirs if not d.startswith(".") and not d.startswith("_")]
@@ -428,10 +438,13 @@ def _import_layouts_from_pages(pages_folder):
428438
page_module = importlib.util.module_from_spec(spec)
429439
spec.loader.exec_module(page_module)
430440
sys.modules[module_name] = page_module
441+
_ensure_layout_is_loaded(module_name, page_module)
431442

432-
if (
433-
module_name in PAGE_REGISTRY
434-
and not PAGE_REGISTRY[module_name]["supplied_layout"]
435-
):
436-
_validate.validate_pages_layout(module_name, page_module)
437-
PAGE_REGISTRY[module_name]["layout"] = getattr(page_module, "layout")
443+
444+
def _import_layouts_from_package(pages_package):
445+
modules = pkgutil.walk_packages(
446+
pages_package.__path__, pages_package.__name__ + "."
447+
)
448+
for module in modules:
449+
page_module = importlib.import_module(module.name)
450+
_ensure_layout_is_loaded(module.name, page_module)

dash/dash.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
_page_meta_tags,
6868
_path_to_page,
6969
_import_layouts_from_pages,
70+
_import_layouts_from_package,
7071
)
7172

7273
# Add explicit mapping for map files
@@ -345,6 +346,7 @@ def __init__( # pylint: disable=too-many-statements
345346
server=True,
346347
assets_folder="assets",
347348
pages_folder="pages",
349+
pages_package=None,
348350
use_pages=None,
349351
assets_url_path="assets",
350352
assets_ignore="",
@@ -444,6 +446,7 @@ def __init__( # pylint: disable=too-many-statements
444446
_get_paths.CONFIG = self.config
445447
_pages.CONFIG = self.config
446448

449+
self.pages_package = pages_package
447450
self.pages_folder = str(pages_folder)
448451
self.use_pages = (pages_folder != "pages") if use_pages is None else use_pages
449452

@@ -1959,6 +1962,8 @@ def enable_pages(self):
19591962
return
19601963
if self.pages_folder:
19611964
_import_layouts_from_pages(self.config.pages_folder)
1965+
if self.pages_package:
1966+
_import_layouts_from_package(self.pages_package)
19621967

19631968
@self.server.before_request
19641969
def router():

tests/conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import sys
23

34
import pytest
45
import dash
@@ -21,6 +22,8 @@ def clear_pages_state():
2122

2223
def init_pages_state():
2324
"""Clear all global state that is used by pages feature."""
25+
for page in dash._pages.PAGE_REGISTRY.values():
26+
sys.modules.pop(page["module"])
2427
dash._pages.PAGE_REGISTRY.clear()
2528
dash._pages.CONFIG.clear()
2629
dash._pages.CONFIG.__dict__.clear()

tests/unit/pages/test_pages.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,12 @@ def test_import_layouts_from_pages(
7676

7777
page_entry = list(dash.page_registry.values())[0]
7878
assert page_entry["module"] == expected_module_name
79+
80+
81+
def test_import_layouts_from_package(clear_pages_state):
82+
from . import custom_pages
83+
84+
_ = Dash(__package__, use_pages=True, pages_folder="", pages_package=custom_pages)
85+
page_entries = list(dash.page_registry.values())
86+
assert len(page_entries) == 1
87+
assert page_entries[0]["module"] == "pages.custom_pages.page"

0 commit comments

Comments
 (0)