Skip to content

Commit 0d9cd2c

Browse files
authored
Merge pull request #3051 from plotly/fix/bg-cookies
Add request data to callback_context
2 parents e6a9846 + dd5e4d6 commit 0d9cd2c

File tree

6 files changed

+114
-13
lines changed

6 files changed

+114
-13
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1414
- [#3034](https://github.com/plotly/dash/pull/3034) Remove whitespace from `metadata.json` files to reduce package size.
1515
- [#3009](https://github.com/plotly/dash/pull/3009) Performance improvement on (pattern-matching) callbacks.
1616
- [3028](https://github.com/plotly/dash/pull/3028) Fix jupyterlab v4 support.
17+
- [#3051](https://github.com/plotly/dash/pull/3051) Add missing request data to callback context. Fix [#2235](https://github.com/plotly/dash/issues/2235).
1718

1819
## [2.18.1] - 2024-09-12
1920

dash/_callback.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -409,23 +409,16 @@ def add_context(*args, **kwargs):
409409

410410
job_fn = callback_manager.func_registry.get(long_key)
411411

412+
ctx_value = AttributeDict(**context_value.get())
413+
ctx_value.ignore_register_page = True
414+
ctx_value.pop("background_callback_manager")
415+
ctx_value.pop("dash_response")
416+
412417
job = callback_manager.call_job_fn(
413418
cache_key,
414419
job_fn,
415420
func_args if func_args else func_kwargs,
416-
AttributeDict(
417-
args_grouping=callback_ctx.args_grouping,
418-
using_args_grouping=callback_ctx.using_args_grouping,
419-
outputs_grouping=callback_ctx.outputs_grouping,
420-
using_outputs_grouping=callback_ctx.using_outputs_grouping,
421-
inputs_list=callback_ctx.inputs_list,
422-
states_list=callback_ctx.states_list,
423-
outputs_list=callback_ctx.outputs_list,
424-
input_values=callback_ctx.input_values,
425-
state_values=callback_ctx.state_values,
426-
triggered_inputs=callback_ctx.triggered_inputs,
427-
ignore_register_page=True,
428-
),
421+
ctx_value,
429422
)
430423

431424
data = {

dash/_callback_context.py

+46
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ def _get_context_value():
3030
return context_value.get()
3131

3232

33+
def _get_from_context(key, default):
34+
return getattr(_get_context_value(), key, default)
35+
36+
3337
class FalsyList(list):
3438
def __bool__(self):
3539
# for Python 3
@@ -258,6 +262,48 @@ def set_props(self, component_id: typing.Union[str, dict], props: dict):
258262
else:
259263
ctx_value.updated_props[_id] = props
260264

265+
@property
266+
@has_context
267+
def cookies(self):
268+
"""
269+
Get the cookies for the current callback.
270+
Works with background callbacks.
271+
"""
272+
return _get_from_context("cookies", {})
273+
274+
@property
275+
@has_context
276+
def headers(self):
277+
"""
278+
Get the original headers for the current callback.
279+
Works with background callbacks.
280+
"""
281+
return _get_from_context("headers", {})
282+
283+
@property
284+
@has_context
285+
def path(self):
286+
"""
287+
Path of the callback request with the query parameters.
288+
"""
289+
return _get_from_context("path", "")
290+
291+
@property
292+
@has_context
293+
def remote(self):
294+
"""
295+
Remote addr of the callback request.
296+
"""
297+
return _get_from_context("remote", "")
298+
299+
@property
300+
@has_context
301+
def origin(self):
302+
"""
303+
Origin of the callback request.
304+
"""
305+
return _get_from_context("origin", "")
306+
261307

262308
callback_context = CallbackContext()
263309

dash/dash.py

+6
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,12 @@ def dispatch(self):
13601360
g.using_outputs_grouping = []
13611361
g.updated_props = {}
13621362

1363+
g.cookies = dict(**flask.request.cookies)
1364+
g.headers = dict(**flask.request.headers)
1365+
g.path = flask.request.full_path
1366+
g.remote = flask.request.remote_addr
1367+
g.origin = flask.request.origin
1368+
13631369
except KeyError as missing_callback_function:
13641370
msg = f"Callback function not found for output '{output}', perhaps you forgot to prepend the '@'?"
13651371
raise KeyError(msg) from missing_callback_function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from dash import Dash, Input, Output, html, callback, ctx
2+
3+
from tests.integration.long_callback.utils import get_long_callback_manager
4+
5+
long_callback_manager = get_long_callback_manager()
6+
handle = long_callback_manager.handle
7+
8+
app = Dash(__name__, background_callback_manager=long_callback_manager)
9+
10+
app.layout = html.Div(
11+
[
12+
html.Button("set-cookies", id="set-cookies"),
13+
html.Button("use-cookies", id="use-cookies"),
14+
html.Div(id="intermediate"),
15+
html.Div("output", id="output"),
16+
]
17+
)
18+
app.test_lock = lock = long_callback_manager.test_lock
19+
20+
21+
@callback(
22+
Output("intermediate", "children"),
23+
Input("set-cookies", "n_clicks"),
24+
prevent_initial_call=True,
25+
)
26+
def set_cookies(_):
27+
ctx.response.set_cookie("bg-cookie", "cookie-value")
28+
return "ok"
29+
30+
31+
@callback(
32+
Output("output", "children"),
33+
Input("use-cookies", "n_clicks"),
34+
prevent_initial_call=True,
35+
background=True,
36+
)
37+
def use_cookies(_):
38+
value = ctx.cookies.get("bg-cookie")
39+
return value
40+
41+
42+
if __name__ == "__main__":
43+
app.run(debug=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from tests.integration.long_callback.utils import setup_long_callback_app
2+
3+
4+
def test_lcbc019_ctx_cookies(dash_duo, manager):
5+
with setup_long_callback_app(manager, "app_ctx_cookies") as app:
6+
dash_duo.start_server(app)
7+
8+
dash_duo.find_element("#set-cookies").click()
9+
dash_duo.wait_for_contains_text("#intermediate", "ok")
10+
11+
dash_duo.find_element("#use-cookies").click()
12+
dash_duo.wait_for_contains_text("#output", "cookie-value")

0 commit comments

Comments
 (0)