Skip to content

Test WebSocket callbacks before the first HTTP request - #4085

Merged
camdecoster merged 4 commits into
plotly:devfrom
wleeaf:fix-fastapi-websocket-setup
Oct 27, 2026
Merged

camdecoster merged 4 commits into
plotly:devfrom
wleeaf:fix-fastapi-websocket-setup

Conversation

@wleeaf

@wleeaf wleeaf commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

#4082 reported that with the FastAPI backend, a WebSocket callback request sent to a fresh process before any HTTP request was rejected with "does not have websocket=True". Callbacks registered with the global @callback are only copied into app.callback_map by _setup_server(), a before_request hook, and on 4.5.0rc1 the middleware did not run the hooks for WebSocket scopes.

That bug is already fixed on dev by #4075 (in 4.5.0rc2): DashMiddleware._gate_websocket runs the before_request hooks on the WebSocket handshake, so _setup_server() runs before the request is validated. The reproducer from the issue now passes.

This PR only adds a regression test for the path the issue hit: a global @callback(..., websocket=True) and a WebSocket callback_request sent before any HTTP request. The existing tests do not cover it. test_fabr003 checks that the hooks gate the handshake, and tests/websocket/test_ws_stream.py calls app._setup_server() by hand before connecting, which is the workaround from the issue. @app.callback writes into app.callback_map directly, so only the global @callback shows the problem.

The new test is test_fabr006_websocket_callback_before_any_http_request in tests/backend_tests/test_fastapi_before_request.py. It passes on dev. With the websocket branch in DashMiddleware.__call__ removed, it fails with the same WebSocketCallbackError as in the issue.

Contributor Checklist

  • I have run the tests locally and they passed. (pytest tests/backend_tests/test_fastapi_before_request.py tests/websocket/test_ws_stream.py -k "not wsst004", plus black and flake8 on the changed file)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

Closes #4082

Copilot AI balanced review requested due to automatic review settings October 23, 2026 22:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

@T4rk1n T4rk1n left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Confirmed the test passes on the rc2 code and fails with the same WebSocketCallbackError from #4082 when the websocket branch in DashMiddleware.__call__ is disabled, so it guards the right path. One small note inline, not blocking. No CHANGELOG entry needed for a test-only change.

from starlette.testclient import TestClient

app = Dash(__name__, backend="fastapi")
app.layout = html.Div([html.Button(id="ws-btn"), html.Div(id="ws-out")])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this test ever regresses, _setup_server() never runs, so ws-out.children stays in _callback.GLOBAL_CALLBACK_MAP and gets pulled into whatever app the next test builds. Can you pop it in a finally (or a small fixture), like tests/unit/test_main_module_alias.py does, so one failure doesn't cascade?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test now removes ws-out.children from GLOBAL_CALLBACK_MAP and GLOBAL_CALLBACK_LIST in a finally, like test_main_module_alias.py. With _setup_server patched to a no-op, only this test fails now, and a test that runs after it sees no leftover callback (0d0fc1d).

@sonarqubecloud

Copy link
Copy Markdown

@camdecoster
camdecoster merged commit cd5403b into plotly:dev Oct 27, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] FastAPI backend: WebSocket callbacks rejected as 'does not have websocket=True' until the first HTTP request runs _setup_server()

4 participants