Context
- Python 3.12.13
- uvicorn 0.49.0
- Linux (Docker:
python:3.12-slim)
- Package version:
Description
When using Dash(backend="fastapi") with uvicorn's --reload flag for development, all callbacks are registered twice after the first file-change reload. This produces Duplicate callback outputs and Overlapping wildcard callback outputs errors.
Root cause: GLOBAL_CALLBACK_LIST and GLOBAL_CALLBACK_MAP in dash._callback are module-level globals that persist across uvicorn's reload cycle. When uvicorn detects a file change, it calls importlib.reload() on the application module within the same process. This re-executes all @callback decorators, appending duplicates to the never-cleared global lists.
This did not happen with the Flask backend because Werkzeug's reloader spawns a fresh subprocess (clean sys.modules, clean globals). Uvicorn's reloader operates in-process via importlib.reload().
Example:
This code below:
import os
from dash import Dash, html, dcc, callback, Output, Input
app = Dash(__name__, backend="fastapi")
app.layout = html.Div([
html.H1("Reload Bug Demo"),
dcc.Input(id="input", value="Hello"),
html.Div(id="output"),
])
@callback(Output("output", "children"), Input("input", "value"))
def update(value):
return f"You typed: {value}"
app.enable_dev_tools(debug=True, dev_tools_hot_reload=True)
server = app.server
if __name__ == "__main__":
import uvicorn
uvicorn.run("app:server", host="0.0.0.0", port=8050, reload=True)
Run with:
pip install dash[fastapi]==4.2.0
python app.py
Will result the following error:
In the callback for output(s):
output.children
Output 0 (output.children) is already in use.
To resolve this, set `allow_duplicate=True` on duplicate outputs, or combine the outputs into one callback function, distinguishing the trigger by using `dash.callback_context` if necessary.
Currently I have to manually clearing the private globals before registering callbacks:
from dash._callback import GLOBAL_CALLBACK_LIST, GLOBAL_CALLBACK_MAP
GLOBAL_CALLBACK_LIST.clear()
GLOBAL_CALLBACK_MAP.clear()
This works but relies on private internals (dash._callback) that could break in any future release.
Context
python:3.12-slim)Description
When using
Dash(backend="fastapi")with uvicorn's--reloadflag for development, all callbacks are registered twice after the first file-change reload. This producesDuplicate callback outputsandOverlapping wildcard callback outputserrors.Root cause:
GLOBAL_CALLBACK_LISTandGLOBAL_CALLBACK_MAPindash._callbackare module-level globals that persist across uvicorn's reload cycle. When uvicorn detects a file change, it callsimportlib.reload()on the application module within the same process. This re-executes all@callbackdecorators, appending duplicates to the never-cleared global lists.This did not happen with the Flask backend because Werkzeug's reloader spawns a fresh subprocess (clean
sys.modules, clean globals). Uvicorn's reloader operates in-process viaimportlib.reload().Example:
This code below:
Run with:
Will result the following error:
Currently I have to manually clearing the private globals before registering callbacks:
This works but relies on private internals (
dash._callback) that could break in any future release.