Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix background callbacks & register page #2226

Merged
merged 7 commits into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- [#2152](https://github.com/plotly/dash/pull/2152) Fix bug [#2128](https://github.com/plotly/dash/issues/2128) preventing rendering of multiple components inside a dictionary.
- [#2187](https://github.com/plotly/dash/pull/2187) Fix confusing error message when trying to use pytest fixtures but `dash[testing]` is not installed.
- [#2202](https://github.com/plotly/dash/pull/2202) Fix bug [#2185](https://github.com/plotly/dash/issues/2185) when you copy text with multiple quotes into a table
- [#2226](https://github.com/plotly/dash/pull/2226) Fix [#2219](https://github.com/plotly/dash/issues/2219) pages register & background callbacks.

## [2.6.1] - 2022-08-01

Expand Down
1 change: 1 addition & 0 deletions dash/_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ def add_context(*args, **kwargs):
input_values=callback_ctx.input_values,
state_values=callback_ctx.state_values,
triggered_inputs=callback_ctx.triggered_inputs,
ignore_register_page=True,
),
)

Expand Down
4 changes: 4 additions & 0 deletions dash/_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from . import _validate
from ._utils import AttributeDict
from ._get_paths import get_relative_path
from ._callback_context import context_value


CONFIG = AttributeDict()
Expand Down Expand Up @@ -250,6 +251,9 @@ def register_page(
])
```
"""
if context_value.get().get("ignore_register_page"):
return

_validate.validate_use_pages(CONFIG)

page = dict(
Expand Down
5 changes: 3 additions & 2 deletions dash/_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .development.base_component import Component
from . import exceptions
from ._utils import patch_collections_abc, stringify_id, to_json, coerce_to_list
from .exceptions import PageError


def validate_callback(outputs, inputs, state, extra_args, types):
Expand Down Expand Up @@ -462,10 +463,10 @@ def validate_pages_layout(module, page):

def validate_use_pages(config):
if not config.get("assets_folder", None):
raise Exception("`dash.register_page()` must be called after app instantiation")
raise PageError("`dash.register_page()` must be called after app instantiation")

if flask.has_request_context():
raise Exception(
raise PageError(
"""
dash.register_page() can’t be called within a callback as it updates dash.page_registry, which is a global variable.
For more details, see https://dash.plotly.com/sharing-data-between-callbacks#why-global-variables-will-break-your-app
Expand Down
2 changes: 2 additions & 0 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,8 @@ def dispatch(self):
cb = self.callback_map[output]
func = cb["callback"]

g.ignore_register_page = cb.get("long", False)
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved

# Add args_grouping
inputs_state_indices = cb["inputs_state_indices"]
inputs_state = inputs + state
Expand Down
4 changes: 4 additions & 0 deletions dash/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,7 @@ class LongCallbackError(DashException):

class MissingLongCallbackManagerError(DashException):
pass


class PageError(DashException):
pass
4 changes: 3 additions & 1 deletion dash/long_callback/managers/celery_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ def _set_progress(progress_value):
ctx = copy_context()

def run():
context_value.set(AttributeDict(**context))
c = AttributeDict(**context)
c.ignore_register_page = False
context_value.set(c)
try:
if isinstance(user_callback_args, dict):
user_callback_output = fn(*maybe_progress, **user_callback_args)
Expand Down
56 changes: 34 additions & 22 deletions dash/long_callback/managers/diskcache_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import traceback
from contextvars import copy_context
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved

from . import BaseLongCallbackManager
from ..._callback_context import context_value
from ..._utils import AttributeDict
from ...exceptions import PreventUpdate

_pending_value = "__$pending__"
Expand Down Expand Up @@ -110,12 +113,13 @@ def make_job_fn(self, fn, progress):
def clear_cache_entry(self, key):
self.handle.delete(key)

# noinspection PyUnresolvedReferences
def call_job_fn(self, key, job_fn, args, context):
# pylint: disable-next=import-outside-toplevel,no-name-in-module,import-error
from multiprocess import Process

# pylint: disable-next=not-callable
proc = Process(target=job_fn, args=(key, self._make_progress_key(key), args))
proc = Process(target=job_fn, args=(key, self._make_progress_key(key), args, context))
proc.start()
return proc.pid

Expand Down Expand Up @@ -147,7 +151,7 @@ def get_result(self, key, job):


def _make_job_fn(fn, cache, progress):
def job_fn(result_key, progress_key, user_callback_args):
def job_fn(result_key, progress_key, user_callback_args, context):
def _set_progress(progress_value):
if not isinstance(progress_value, (list, tuple)):
progress_value = [progress_value]
Expand All @@ -156,27 +160,35 @@ def _set_progress(progress_value):

maybe_progress = [_set_progress] if progress else []

try:
if isinstance(user_callback_args, dict):
user_callback_output = fn(*maybe_progress, **user_callback_args)
elif isinstance(user_callback_args, (list, tuple)):
user_callback_output = fn(*maybe_progress, *user_callback_args)
ctx = copy_context()

def run():
c = AttributeDict(**context)
c.ignore_register_page = False
context_value.set(c)
try:
if isinstance(user_callback_args, dict):
user_callback_output = fn(*maybe_progress, **user_callback_args)
elif isinstance(user_callback_args, (list, tuple)):
user_callback_output = fn(*maybe_progress, *user_callback_args)
else:
user_callback_output = fn(*maybe_progress, user_callback_args)
except PreventUpdate:
cache.set(result_key, {"_dash_no_update": "_dash_no_update"})
except Exception as err: # pylint: disable=broad-except
cache.set(
result_key,
{
"long_callback_error": {
"msg": str(err),
"tb": traceback.format_exc(),
}
},
)
else:
user_callback_output = fn(*maybe_progress, user_callback_args)
except PreventUpdate:
cache.set(result_key, {"_dash_no_update": "_dash_no_update"})
except Exception as err: # pylint: disable=broad-except
cache.set(
result_key,
{
"long_callback_error": {
"msg": str(err),
"tb": traceback.format_exc(),
}
},
)
else:
cache.set(result_key, user_callback_output)
cache.set(result_key, user_callback_output)

ctx.run(run)

return job_fn

Expand Down
17 changes: 17 additions & 0 deletions dash/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from contextlib import contextmanager

from .._callback_context import context_value as _ctx
from .._utils import AttributeDict as _AD


@contextmanager
def ignore_register_page():
previous = _ctx.get()
copied = _AD(previous)
copied.ignore_register_page = True
_ctx.set(copied)

try:
yield
finally:
_ctx.set(previous)
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions tests/unit/test_testing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dash.testing import ignore_register_page
from dash import register_page


def test_tst001_ignore_register_page():
with ignore_register_page():
register_page("/")