From dcae82bb1fb202cc06bcdae8f726b9b5b239c594 Mon Sep 17 00:00:00 2001 From: Suliman Abdulrazzaq Date: Wed, 23 Sep 2026 02:38:28 +0300 Subject: [PATCH 1/3] Fix resource paths under Windows extended-length paths Windows uses a path with the \\?\ prefix verbatim, so a "/" inside it is an invalid name (WinError 123). Dash joined "/"-separated package paths, and a nested favicon's asset path, onto a directory and passed the result to os.stat, so every index render failed with a 500 when Dash was imported from such a path, as in JupyterLab Desktop's site-packages. Split those paths on "/" before joining them in _relative_url_path, _get_worker_url and the favicon mtime lookup. Fixes #3002 --- CHANGELOG.md | 1 + dash/dash.py | 8 +++++--- tests/unit/test_resources.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa5d3287e..d13f1e907e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3881](https://github.com/plotly/dash/pull/3881) Fix components rendered as props (eg. `dcc.Dropdown` option labels, `dcc.Tab` labels) crashing or failing to update when the host subtree was replaced by a callback; out-of-tree `ExternalWrapper` components now re-insert themselves and update in place. - [#3929](https://github.com/plotly/dash/issues/3929) Fix components that set their own initial state on mount (eg. `dash-bootstrap-components` `Tabs`) not applying it on first render, because descendant layout hashes were reset on the first fresh render (regression introduced in 4.2.0 by [#3570](https://github.com/plotly/dash/pull/3570)). - [#3948](https://github.com/plotly/dash/issues/3948) Fix page getting progressively slower as callbacks append children +- [#3002](https://github.com/plotly/dash/issues/3002) Fix page loads failing with a 500 error on Windows when Dash is imported from an extended-length (`\\?\`) path, as in JupyterLab Desktop. Package resource paths, and the path of a `favicon.ico` in an assets subfolder, are now joined with the OS separator instead of `/`. ## [4.4.1] - 2026-07-21 diff --git a/dash/dash.py b/dash/dash.py index ed1ed5ebc8..cb0d849938 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -1261,7 +1261,7 @@ def _get_worker_url( # Build fingerprinted URL (same pattern as _collect_and_register_resources) module_path = os.path.join( os.path.dirname(sys.modules[namespace].__file__), # type: ignore - relative_path, + *relative_path.split("/"), ) # Use a fallback if the file doesn't exist yet (during development) @@ -1302,9 +1302,11 @@ def _relative_url_path(relative_package_path="", namespace=""): else: version = importlib.import_module(namespace).__version__ + # Split on "/" so the file path uses the OS separator: Windows + # extended-length paths (\\?\ prefix) reject forward slashes. module_path = os.path.join( # type: ignore[reportCallIssue] os.path.dirname(sys.modules[namespace].__file__), # type: ignore[reportCallIssue] - relative_package_path, + *relative_package_path.split("/"), ) modified = int(os.stat(module_path).st_mtime) @@ -1489,7 +1491,7 @@ def index(self, *_args, **_kwargs): if self._favicon: favicon_mod_time = os.path.getmtime( - os.path.join(self.config.assets_folder, self._favicon) + os.path.join(self.config.assets_folder, *self._favicon.split("/")) ) favicon_url = f"{self.get_asset_url(self._favicon)}?m={favicon_mod_time}" else: diff --git a/tests/unit/test_resources.py b/tests/unit/test_resources.py index 413514de18..cbbf52964b 100644 --- a/tests/unit/test_resources.py +++ b/tests/unit/test_resources.py @@ -1,4 +1,8 @@ +import os +import sys + import mock +import pytest import dash from dash import dcc, html # noqa: F401 @@ -253,3 +257,28 @@ def test_multiple_external_urls_with_attributes(): {"src": "https://example.com/script1.js", "type": "module"}, {"src": "https://example.com/script2.js", "type": "module"}, ] + + +@pytest.mark.skipif( + sys.platform != "win32", reason="extended-length paths only exist on Windows" +) +def test_index_with_windows_extended_length_paths(tmp_path, monkeypatch): + """Dash and the assets folder under extended-length paths (JupyterLab Desktop). + + Windows uses these paths verbatim, so a "/" inside one is an invalid name. + """ + monkeypatch.setattr(dash, "__file__", "\\\\?\\" + os.path.abspath(dash.__file__)) + icons = tmp_path / "assets" / "icons" + icons.mkdir(parents=True) + (icons / "favicon.ico").write_bytes(b"") + + app = dash.Dash(__name__, assets_folder="\\\\?\\" + str(tmp_path / "assets")) + app.layout = html.Div() + + response = app.server.test_client().get("/") + + assert response.status_code == 200 + body = response.get_data(as_text=True) + assert "/_dash-component-suites/dash/deps/polyfill@" in body + assert "dash-stream-worker.v" in body + assert "/assets/icons/favicon.ico?m=" in body From df45f7ea295fcf421470913d014ff333eb154081 Mon Sep 17 00:00:00 2001 From: Suliman Abdulrazzaq Date: Wed, 23 Sep 2026 23:37:16 +0300 Subject: [PATCH 2/3] Run the resource unit tests on Windows CI and narrow the changelog entry The Windows extended-length path test in tests/unit/test_resources.py is skipped on Linux, and the build-windows job only built the project, so nothing ran it. Add a pytest step for that file to build-windows. Assets are still not served when the assets folder itself is under an extended-length path (Flask's static route joins with '/'), so drop the favicon wording from the changelog entry. --- .github/workflows/testing.yml | 3 +++ CHANGELOG.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 12b3bd6a56..52ee14a49b 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -1012,6 +1012,9 @@ jobs: npm ci npm run build + - name: Run Windows-specific unit tests + run: python -m pytest tests/unit/test_resources.py + dcc-lint: name: DCC Lint Tests (Python ${{ matrix.python-version }}) needs: [build, changes_filter] diff --git a/CHANGELOG.md b/CHANGELOG.md index d13f1e907e..7caf957e6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). - [#3881](https://github.com/plotly/dash/pull/3881) Fix components rendered as props (eg. `dcc.Dropdown` option labels, `dcc.Tab` labels) crashing or failing to update when the host subtree was replaced by a callback; out-of-tree `ExternalWrapper` components now re-insert themselves and update in place. - [#3929](https://github.com/plotly/dash/issues/3929) Fix components that set their own initial state on mount (eg. `dash-bootstrap-components` `Tabs`) not applying it on first render, because descendant layout hashes were reset on the first fresh render (regression introduced in 4.2.0 by [#3570](https://github.com/plotly/dash/pull/3570)). - [#3948](https://github.com/plotly/dash/issues/3948) Fix page getting progressively slower as callbacks append children -- [#3002](https://github.com/plotly/dash/issues/3002) Fix page loads failing with a 500 error on Windows when Dash is imported from an extended-length (`\\?\`) path, as in JupyterLab Desktop. Package resource paths, and the path of a `favicon.ico` in an assets subfolder, are now joined with the OS separator instead of `/`. +- [#3002](https://github.com/plotly/dash/issues/3002) Fix page loads failing with a 500 error on Windows when Dash is imported from an extended-length (`\\?\`) path, as in JupyterLab Desktop. Package resource paths are now joined with the OS separator instead of `/`. ## [4.4.1] - 2026-07-21 From da8ef9d041c9b5b029d8f325db6bf33fc260ff1b Mon Sep 17 00:00:00 2001 From: Suliman Abdulrazzaq Date: Thu, 24 Sep 2026 00:00:37 +0300 Subject: [PATCH 3/3] Give the Windows unit test step the built component folders dash/dcc, dash/html and dash/dash_table are build outputs, and build-windows only builds the renderer and html components into their own folders, so 'import dash' fails there. Download the dash-components artifact from the build job before running the tests, as lint-unit does. --- .github/workflows/testing.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 52ee14a49b..1a48a305ed 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -974,6 +974,7 @@ jobs: build-windows: name: Build on Windows runs-on: windows-latest + needs: build timeout-minutes: 30 steps: @@ -1012,6 +1013,12 @@ jobs: npm ci npm run build + - name: Download built component folders + uses: actions/download-artifact@v4 + with: + name: dash-components + path: ${{ github.workspace }}/dash + - name: Run Windows-specific unit tests run: python -m pytest tests/unit/test_resources.py