[BUG] Callbacks still double-register with uvicorn `--reload` when an explicit `name` is passed to `Dash()` (incomplete fix for #3818 / #3883)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 55/100
Hướng nghiên cứu
Bắt đầu tại dash/_utils.py, ở alias_main_module, và tái hiện vấn đề với app được cung cấp có tên tường minh cùng cấu hình reload của uvicorn. Theo dõi cách reload worker import server và đăng ký các callback; hoàn tất khi output dependency chứa mỗi callback đúng một lần đối với các tên tường minh và các mẫu app-factory, mà không làm hỏng hành vi main hiện có.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
Thanks for the fix! This is a follow up issue from #3818 . PR #3883 (released in 4.4.1) fixes the double-registration reported in #3818 but only for the exact idiom app = Dash(__name__) written at the top level of the entry-point file.
The fix (alias_main_module) keys entirely on the app's resolved name being "__main__" or "__mp_main__". As soon as Dash() receives any other name — a literal string, or Dash(__name__) when the app is built inside a package / app-factory (where __name__ is the module name) — the alias no-ops, the module is executed twice in the reload worker, and every callback is registered twice. The renderer then throws:
Duplicate callback outputs
Output 0 (output.children) is already in use.
Environment
- dash 4.4.1 (
dash[fastapi]) - uvicorn (standard), reload=True
- Python 3.10, Linux
Minimal reproduction
# repro.py
from dash import Dash, html, dcc, callback, Output, Input
# BUG TRIGGER: any explicit name that is not "__main__"/"__mp_main__".
# Using Dash(__name__) here (top level) works; Dash("myapp") does not.
app = Dash("myapp", backend="fastapi")
app.layout = html.Div([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("repro:server", host="127.0.0.1", port=8050, reload=True)
python3 repro.py
curl -s http://127.0.0.1:8050/_dash-dependencies | grep -o '"output.children"' | wc -l
# -> 2 (expected 1)
Open the page in a browser and the console shows Duplicate callback outputs.
Constructing the app inside a package with Dash(__name__) also reproduces it, because __name__ is the package name, not __main__, in this case -- __name__ == "app_factory":
- app_factory.py:
from dash import Dash, html, dcc, callback, Output, Input
class AppBuilder:
def __init__(self):
self.app = Dash(__name__, backend="fastapi")
self.app.layout = html.Div([
html.H1("Reload double-register demo (factory)"),
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}"
def setup(self):
self.app.enable_dev_tools(debug=True, dev_tools_hot_reload=True)
return self.app.server
- main.py:
if __name__ != "__main__":
from app_factory import AppBuilder
server = AppBuilder().setup()
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:server", host="127.0.0.1", port=8050, reload=True)
Root Vause
dash/_utils.py:
def alias_main_module(caller_name: str) -> None:
if caller_name not in ("__main__", "__mp_main__"):
return
...
alias_main_module only aliases the running module into sys.modules when caller_name is exactly "__main__"/"__mp_main__". Passing an explicit name (or building the app anywhere other than the entry module's top level) makes caller_name something else, so the alias is skipped, the "main:server" import string re-executes the file, and the callbacks register a second time.
This means the fix effectively only covers the single-file app = Dash(__name__) tutorial layout, and misses the very common app-factory / class-based / explicitly-named app patterns.
Current Workaround
# Skip the reload worker's __mp_main__ spawn re-exec so callbacks are registered exactly once.
if __name__ not in ("__main__", "__mp_main__"):
server = build_app()
Works temporarily but idk if that will break in any future releases.
- Ngôn ngữ chính
- Python
- Star
- 24.4k
- Fork
- 2.3k
- Merge trung bình
- 1 ngày 21 giờ
- Pull request đã merge (30 ngày)
- 19
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của plotly/dash
-
good first issue P3 size: 1 task
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
-
enhancement P2 size: 5
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 45/100
-
enhancement P3 size: 10+
-
P2 size: 1 task
-
enhancement P3 size: 1
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 72/100
Issue tương tự
-
essnmx good first issue
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 95/100
-
[Feature] 奇物选择添加优先级 Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
syfoud/Simulated_Scepter#174 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Giskard-AI/giskard-oss#2840 · 1 bình luận ·
-
A claim comment carrying the issue number is silently declined while the workflow reports success Đang mởarea: repo bug perceived difficulty: 2
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
yeti-platform/yeti#1380 ·