Skip to content

Commit a5ff987

Browse files
authored
Merge pull request #3077 from CNFeffery/dev
Add new parameter assets_path_ignore for dash.Dash()
2 parents f4dda00 + b6fd63c commit a5ff987

File tree

7 files changed

+100
-14
lines changed

7 files changed

+100
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1313

1414
## Added
1515
- [#3294](https://github.com/plotly/dash/pull/3294) Added the ability to pass `allow_optional` to Input and State to allow callbacks to work even if these components are not in the dash layout.
16+
- [#3077](https://github.com/plotly/dash/pull/3077) Add new parameter `assets_path_ignore` to `dash.Dash()`. Closes [#3076](https://github.com/plotly/dash/issues/3076)
1617

1718
## [3.0.4] - 2025-04-24
1819

dash/dash.py

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import base64
1919
import traceback
2020
from urllib.parse import urlparse
21-
from typing import Any, Callable, Dict, Optional, Union, Sequence, Literal
21+
from typing import Any, Callable, Dict, Optional, Union, Sequence, Literal, List
2222

2323
import flask
2424

@@ -262,6 +262,12 @@ class Dash(ObsoleteChecker):
262262
to sensitive files.
263263
:type assets_ignore: string
264264
265+
:param assets_path_ignore: A list of regex, each regex as a string to pass to ``re.compile``, for
266+
assets path to omit from immediate loading. The files in these ignored paths will still be
267+
served if specifically requested. You cannot use this to prevent access
268+
to sensitive files.
269+
:type assets_path_ignore: list of strings
270+
265271
:param assets_external_path: an absolute URL from which to load assets.
266272
Use with ``serve_locally=False``. assets_external_path is joined
267273
with assets_url_path to determine the absolute url to the
@@ -408,6 +414,7 @@ def __init__( # pylint: disable=too-many-statements
408414
use_pages: Optional[bool] = None,
409415
assets_url_path: str = "assets",
410416
assets_ignore: str = "",
417+
assets_path_ignore: List[str] = None,
411418
assets_external_path: Optional[str] = None,
412419
eager_loading: bool = False,
413420
include_assets_files: bool = True,
@@ -464,6 +471,7 @@ def __init__( # pylint: disable=too-many-statements
464471
), # type: ignore
465472
assets_url_path=assets_url_path,
466473
assets_ignore=assets_ignore,
474+
assets_path_ignore=assets_path_ignore,
467475
assets_external_path=get_combined_config(
468476
"assets_external_path", assets_external_path, ""
469477
),
@@ -764,7 +772,6 @@ def layout(self, value: Any):
764772
and not self.validation_layout
765773
and not self.config.suppress_callback_exceptions
766774
):
767-
768775
layout_value = self._layout_value()
769776
_validate.validate_layout(value, layout_value)
770777
self.validation_layout = layout_value
@@ -1505,11 +1512,18 @@ def _walk_assets_directory(self):
15051512
walk_dir = self.config.assets_folder
15061513
slash_splitter = re.compile(r"[\\/]+")
15071514
ignore_str = self.config.assets_ignore
1515+
ignore_path_list = self.config.assets_path_ignore
15081516
ignore_filter = re.compile(ignore_str) if ignore_str else None
1517+
ignore_path_filters = [
1518+
re.compile(ignore_path)
1519+
for ignore_path in (ignore_path_list or [])
1520+
if ignore_path
1521+
]
15091522

15101523
for current, _, files in sorted(os.walk(walk_dir)):
15111524
if current == walk_dir:
15121525
base = ""
1526+
s = ""
15131527
else:
15141528
s = current.replace(walk_dir, "").lstrip("\\").lstrip("/")
15151529
splitted = slash_splitter.split(s)
@@ -1518,22 +1532,32 @@ def _walk_assets_directory(self):
15181532
else:
15191533
base = splitted[0]
15201534

1521-
if ignore_filter:
1522-
files_gen = (x for x in files if not ignore_filter.search(x))
1535+
# Check if any level of current path matches ignore path
1536+
if s and any(
1537+
ignore_path_filter.search(x)
1538+
for ignore_path_filter in ignore_path_filters
1539+
for x in s.split(os.path.sep)
1540+
):
1541+
pass
15231542
else:
1524-
files_gen = files
1543+
if ignore_filter:
1544+
files_gen = (x for x in files if not ignore_filter.search(x))
1545+
else:
1546+
files_gen = files
15251547

1526-
for f in sorted(files_gen):
1527-
path = "/".join([base, f]) if base else f
1548+
for f in sorted(files_gen):
1549+
path = "/".join([base, f]) if base else f
15281550

1529-
full = os.path.join(current, f)
1551+
full = os.path.join(current, f)
15301552

1531-
if f.endswith("js"):
1532-
self.scripts.append_script(self._add_assets_resource(path, full))
1533-
elif f.endswith("css"):
1534-
self.css.append_css(self._add_assets_resource(path, full)) # type: ignore[reportArgumentType]
1535-
elif f == "favicon.ico":
1536-
self._favicon = path
1553+
if f.endswith("js"):
1554+
self.scripts.append_script(
1555+
self._add_assets_resource(path, full)
1556+
)
1557+
elif f.endswith("css"):
1558+
self.css.append_css(self._add_assets_resource(path, full)) # type: ignore[reportArgumentType]
1559+
elif f == "favicon.ico":
1560+
self._favicon = path
15371561

15381562
@staticmethod
15391563
def _invalid_resources_handler(err):
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from dash import Dash, html
2+
3+
4+
def test_api001_assets_path_ignore(dash_duo):
5+
app = Dash(
6+
__name__,
7+
assets_folder="test_assets_path_ignore_assets",
8+
assets_path_ignore=["should_be_ignored"],
9+
)
10+
app.index_string = """<!DOCTYPE html>
11+
<html>
12+
<head>
13+
{%metas%}
14+
<title>{%title%}</title>
15+
{%css%}
16+
</head>
17+
<body>
18+
<div id="normal-test-target"></div>
19+
<div id="ignored-test-target"></div>
20+
{%app_entry%}
21+
<footer>
22+
{%config%}
23+
{%scripts%}
24+
{%renderer%}
25+
</footer>
26+
</body>
27+
</html>"""
28+
29+
app.layout = html.Div()
30+
31+
dash_duo.start_server(app)
32+
33+
assert (
34+
dash_duo.find_element("#normal-test-target").value_of_css_property(
35+
"background-color"
36+
)
37+
== "rgba(255, 0, 0, 1)"
38+
)
39+
40+
assert (
41+
dash_duo.find_element("#ignored-test-target").value_of_css_property(
42+
"background-color"
43+
)
44+
!= "rgba(255, 0, 0, 1)"
45+
)
46+
47+
normal_target_content = dash_duo.find_element("#normal-test-target").text
48+
ignored_target_content = dash_duo.find_element("#ignored-test-target").text
49+
50+
assert normal_target_content == "loaded"
51+
assert ignored_target_content != "loaded"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#normal-test-target {
2+
background-color: rgba(255, 0, 0, 1);
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const normalTarget = document.getElementById('normal-test-target');
2+
normalTarget.innerHTML = 'loaded';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ignored-test-target {
2+
background-color: rgba(255, 0, 0, 1);
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const ignoredTarget = document.getElementById('ignored-test-target');
2+
ignoredTarget.innerHTML = 'loaded';

0 commit comments

Comments
 (0)