From 5481f09e3987bb01ea5c248ee85a037ed16417d7 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 24 Feb 2026 11:46:54 -0500 Subject: [PATCH] remove global backends.backend --- dash/_callback.py | 15 +++++++-------- dash/_callback_context.py | 6 +++--- dash/_get_app.py | 4 +++- dash/_validate.py | 19 ++++++++++++------- dash/backends/__init__.py | 4 ---- dash/dash.py | 11 ++++------- dash/exceptions.py | 4 ++++ 7 files changed, 33 insertions(+), 30 deletions(-) diff --git a/dash/_callback.py b/dash/_callback.py index 0ce4ee59a4..37a53d7ec5 100644 --- a/dash/_callback.py +++ b/dash/_callback.py @@ -20,7 +20,7 @@ BackgroundCallbackError, ImportedInsideCallbackError, ) - +from ._get_app import get_app from ._grouping import ( flatten_grouping, make_grouping_by_index, @@ -39,7 +39,6 @@ from ._callback_context import context_value from ._no_update import NoUpdate from . import _validate -from . import backends async def _async_invoke_callback( @@ -373,7 +372,7 @@ def _get_callback_manager( " and store results on redis.\n" ) - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() old_job = adapter.args.getlist("oldJob") if hasattr(adapter.args, "getlist") else [] if old_job: @@ -433,7 +432,7 @@ def _setup_background_callback( def _progress_background_callback(response, callback_manager, background): progress_outputs = background.get("progress") - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() cache_key = adapter.args.get("cacheKey") if progress_outputs: @@ -451,7 +450,7 @@ def _update_background_callback( """Set up the background callback and manage jobs.""" callback_manager = _get_callback_manager(kwargs, background) - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() cache_key = adapter.args.get("cacheKey") if adapter else None job_id = adapter.args.get("job") if adapter else None @@ -473,7 +472,7 @@ def _handle_rest_background_callback( multi, has_update=False, ): - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() cache_key = adapter.args.get("cacheKey") if adapter else None job_id = adapter.args.get("job") if adapter else None # Must get job_running after get_result since get_results terminates it. @@ -691,7 +690,7 @@ def add_context(*args, **kwargs): jsonResponse: Optional[str] = None try: if background is not None: - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() if not (adapter and adapter.args.get("cacheKey")): return _setup_background_callback( kwargs, @@ -763,7 +762,7 @@ async def async_add_context(*args, **kwargs): try: if background is not None: - adapter = backends.backend.request_adapter() + adapter = get_app().backend.request_adapter() if not (adapter and adapter.args.get("cacheKey")): return _setup_background_callback( kwargs, diff --git a/dash/_callback_context.py b/dash/_callback_context.py index 10cfb20055..646db990ab 100644 --- a/dash/_callback_context.py +++ b/dash/_callback_context.py @@ -5,7 +5,7 @@ import typing from . import exceptions -from . import backends +from ._get_app import get_app from ._utils import AttributeDict, stringify_id @@ -221,7 +221,7 @@ def record_timing(name, duration, description=None): :param description: A description of the resource. :type description: string or None """ - request = backends.backend.request_adapter() + request = get_app().backend.request_adapter() timing_information = getattr(request.context, "timing_information", {}) if name in timing_information: @@ -252,7 +252,7 @@ def using_outputs_grouping(self): @property @has_context def timing_information(self): - request = backends.backend.request_adapter() + request = get_app().backend.request_adapter() return getattr(request.context, "timing_information", {}) @has_context diff --git a/dash/_get_app.py b/dash/_get_app.py index a64a7450cc..ab0b897f81 100644 --- a/dash/_get_app.py +++ b/dash/_get_app.py @@ -4,6 +4,8 @@ from textwrap import dedent from typing import Any, Optional +from dash.exceptions import AppNotFoundError + APP: Optional[Any] = None app_context: ContextVar[Any] = ContextVar("dash_app_context") @@ -55,7 +57,7 @@ def get_app(): pass if APP is None: - raise Exception( + raise AppNotFoundError( dedent( """ App object is not yet defined. `app = dash.Dash()` needs to be run diff --git a/dash/_validate.py b/dash/_validate.py index bb76f896e1..fb5689f850 100644 --- a/dash/_validate.py +++ b/dash/_validate.py @@ -9,6 +9,7 @@ from .development.base_component import Component from . import backends from . import exceptions +from ._get_app import get_app from ._utils import ( patch_collections_abc, stringify_id, @@ -510,13 +511,17 @@ def validate_use_pages(config): "`dash.register_page()` must be called after app instantiation" ) - if backends.backend.has_request_context(): - raise exceptions.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 - """ - ) + try: + if get_app().backend.has_request_context(): + raise exceptions.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 + """ + ) + except exceptions.AppNotFoundError: + # If the app is not found we can add pages since before instantiation. + pass def validate_module_name(module): diff --git a/dash/backends/__init__.py b/dash/backends/__init__.py index 3a12e7939a..585d34a65c 100644 --- a/dash/backends/__init__.py +++ b/dash/backends/__init__.py @@ -4,9 +4,6 @@ from .base_server import BaseDashServer -backend: BaseDashServer - - _backend_imports = { "flask": ("dash.backends._flask", "FlaskDashServer"), "fastapi": ("dash.backends._fastapi", "FastAPIDashServer"), @@ -74,6 +71,5 @@ def get_server_type(server): __all__ = [ "get_backend", - "backend", "get_server_type", ] diff --git a/dash/dash.py b/dash/dash.py index 71f7f33cb4..a46f3af99a 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -502,14 +502,10 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches self.backend = backend_cls(server) self.server = server - backends.backend = self.backend # type: ignore - backends.request_adapter = self.backend.request_adapter # type: ignore else: # No server instance provided, create backend and let backend create server self.server = backend_cls.create_app(caller_name) # type: ignore self.backend = backend_cls(self.server) - backends.backend = self.backend - backends.request_adapter = self.backend.request_adapter # type: ignore base_prefix, routes_prefix, requests_prefix = pathname_configs( url_base_pathname, routes_pathname_prefix, requests_pathname_prefix @@ -642,6 +638,7 @@ def __init__( # pylint: disable=too-many-statements, too-many-branches self._got_first_request = {"pages": False, "setup_server": False} if server: + print(f"init app from server {server}") self.init_app() self.logger.setLevel(logging.INFO) @@ -1180,7 +1177,7 @@ def index(self, *_args, **_kwargs): renderer = self._generate_renderer() title = self.title # Refactored: direct access to global request adapter - request = backends.backend.request_adapter() + request = self.backend.request_adapter() if self.use_pages and self.config.include_pages_meta and request: metas = _page_meta_tags(self, request) + metas @@ -1396,7 +1393,7 @@ def _inputs_to_vals(self, inputs): # pylint: disable=R0915 def _initialize_context(self, body): """Initialize the global context for the request.""" - adapter = backends.backend.request_adapter() + adapter = self.backend.request_adapter() g = AttributeDict({}) g.inputs_list = body.get("inputs", []) g.states_list = body.get("state", []) @@ -2383,7 +2380,7 @@ def verify_url_part(served_part, url_part, part_name): server_url=jupyter_server_url, ) else: - backends.backend.run( + self.backend.run( dash_app=self, host=host, port=port, debug=debug, **flask_run_options ) diff --git a/dash/exceptions.py b/dash/exceptions.py index 00bd2c1553..019f0d2726 100644 --- a/dash/exceptions.py +++ b/dash/exceptions.py @@ -109,3 +109,7 @@ class ImportedInsideCallbackError(DashException): class HookError(DashException): pass + + +class AppNotFoundError(DashException): + pass