Background callbacks fail to serialise request args on FastAPI/Quart backends (QueryParams is not JSON serializable)
Summary
When a background=True callback is dispatched on a non-Flask backend (e.g. the FastAPI or Quart adapters), Dash attempts to serialise the callback context to the background worker (Celery via kombu, or Diskcache). Serialisation fails because the request args stored on the context is a backend-specific multi-dict starlette.datastructures.QueryParams on FastAPI, which is not JSON serialisable.
The result is a runtime error such as:
TypeError: Object of type QueryParams is not JSON serializable
(or the equivalent kombu/EncodeError wrapping it) whenever a background callback is triggered. The same problem applies to the Quart adapter, whose args is a werkzeug/quart MultiDict.
Environment
- Dash version: 4.2.0
- Backend: FastAPI (also affects Quart)
- Background callback manager: Celery (
CeleryManager); Diskcache is likely affected for the same reason
- Python: 3.12
Steps to reproduce
- Configure a Dash app on the FastAPI backend with a Celery background callback manager.
- Define any callback with
background=True.
- Trigger the callback from the browser.
from dash import Dash, Input, Output, html
from dash.background_callback import CeleryManager
from celery import Celery
celery_app = Celery(__name__, broker=..., backend=...)
manager = CeleryManager(celery_app)
app = Dash(__name__, background_callback_manager=manager) # FastAPI backend
app.layout = html.Div([
html.Button("Run", id="run"),
html.Div(id="out"),
])
@app.callback(
Output("out", "children"),
Input("run", "n_clicks"),
background=True,
prevent_initial_call=True,
)
def slow(n):
return f"done {n}"
Clicking Run dispatches the job and raises the serialisation error.
Expected behaviour
A background callback should be dispatched to the worker successfully, regardless of which backend (Flask, FastAPI, Quart) is in use. The request context handed to the worker must be JSON serialisable.
Actual behaviour
Dispatch fails because the callback context contains a non-serialisable args object.
Root cause analysis
During request setup, Dash._initialize_context stores the request data on the context. Unlike cookies and headers, which are explicitly coerced to plain dicts, args is stored as-is:
dash/dash.py (_initialize_context):
g.cookies = dict(adapter.cookies)
g.headers = dict(adapter.headers)
g.args = adapter.args # <-- not coerced to a plain dict
This is intentional for the synchronous HTTP path: several code paths rely on the multi-dict interface, e.g. callback_context.args.getlist("cancelJob") and adapter.args.getlist("oldJob"), which a plain dict does not provide.
On the FastAPI backend, adapter.args is the raw Starlette query params:
dash/backends/_fastapi.py:
@property
def args(self):
return self._request.query_params # starlette.datastructures.QueryParams
For background=True callbacks, _setup_background_callback (dash/_callback.py) copies the context value and dispatches it to the worker via callback_manager.call_job_fn(...). The background callback manager serialises this context (Celery → kombu → JSON), and serialisation fails on the QueryParams (FastAPI) / MultiDict (Quart) object.
On the Flask backend the issue does not surface in the same way because the worker receives a JSON-serialisable representation.
Background callbacks fail to serialise request
argson FastAPI/Quart backends (QueryParams is not JSON serializable)Summary
When a
background=Truecallback is dispatched on a non-Flask backend (e.g. the FastAPI or Quart adapters), Dash attempts to serialise the callback context to the background worker (Celery via kombu, or Diskcache). Serialisation fails because the requestargsstored on the context is a backend-specific multi-dictstarlette.datastructures.QueryParamson FastAPI, which is not JSON serialisable.The result is a runtime error such as:
(or the equivalent kombu/
EncodeErrorwrapping it) whenever a background callback is triggered. The same problem applies to the Quart adapter, whoseargsis awerkzeug/quartMultiDict.Environment
CeleryManager); Diskcache is likely affected for the same reasonSteps to reproduce
background=True.Clicking Run dispatches the job and raises the serialisation error.
Expected behaviour
A background callback should be dispatched to the worker successfully, regardless of which backend (Flask, FastAPI, Quart) is in use. The request context handed to the worker must be JSON serialisable.
Actual behaviour
Dispatch fails because the callback context contains a non-serialisable
argsobject.Root cause analysis
During request setup,
Dash._initialize_contextstores the request data on the context. Unlikecookiesandheaders, which are explicitly coerced to plain dicts,argsis stored as-is:dash/dash.py(_initialize_context):This is intentional for the synchronous HTTP path: several code paths rely on the multi-dict interface, e.g.
callback_context.args.getlist("cancelJob")andadapter.args.getlist("oldJob"), which a plaindictdoes not provide.On the FastAPI backend,
adapter.argsis the raw Starlette query params:dash/backends/_fastapi.py:For
background=Truecallbacks,_setup_background_callback(dash/_callback.py) copies the context value and dispatches it to the worker viacallback_manager.call_job_fn(...). The background callback manager serialises this context (Celery → kombu → JSON), and serialisation fails on theQueryParams(FastAPI) /MultiDict(Quart) object.On the Flask backend the issue does not surface in the same way because the worker receives a JSON-serialisable representation.