Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,7 @@ jobs:
build-windows:
name: Build on Windows
runs-on: windows-latest
needs: build
timeout-minutes: 30

steps:
Expand Down Expand Up @@ -1012,6 +1013,15 @@ 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

dcc-lint:
name: DCC Lint Tests (Python ${{ matrix.python-version }})
needs: [build, changes_filter]
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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 are now joined with the OS separator instead of `/`.

## [4.4.1] - 2026-07-21

Expand Down
8 changes: 5 additions & 3 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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("/"))
Comment thread
SulimanAbdulrazzaq marked this conversation as resolved.
)
favicon_url = f"{self.get_asset_url(self._favicon)}?m={favicon_mod_time}"
else:
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/test_resources.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import os
import sys

import mock
import pytest
import dash
from dash import dcc, html # noqa: F401

Expand Down Expand Up @@ -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(
Comment thread
SulimanAbdulrazzaq marked this conversation as resolved.
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
Loading