From b9fad5f44726a4bb8689ef3b0a0190dbc6fe5cc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:48:37 +0000 Subject: [PATCH 1/3] Bump the pip-dependencies group across 1 directory with 4 updates Updates the requirements on [selenium](https://github.com/SeleniumHQ/Selenium), [black](https://github.com/psf/black), [flake8](https://github.com/pycqa/flake8) and [pylint](https://github.com/pylint-dev/pylint) to permit the latest version. Updates `selenium` to 4.51.0 - [Release notes](https://github.com/SeleniumHQ/Selenium/releases) - [Commits](https://github.com/SeleniumHQ/Selenium/compare/selenium-4.11.0...selenium-4.51.0) Updates `black` from 22.3.0 to 25.11.0 - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/22.3.0...25.11.0) Updates `flake8` from 7.3.0 to 7.4.1 - [Commits](https://github.com/pycqa/flake8/compare/7.3.0...7.4.1) Updates `pylint` from 3.0.3 to 3.3.9 - [Release notes](https://github.com/pylint-dev/pylint/releases) - [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.3...v3.3.9) --- updated-dependencies: - dependency-name: selenium dependency-version: 4.51.0 dependency-type: direct:production dependency-group: pip-dependencies - dependency-name: black dependency-version: 25.11.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: pip-dependencies - dependency-name: flake8 dependency-version: 7.4.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-dependencies - dependency-name: pylint dependency-version: 3.3.9 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-dependencies ... Signed-off-by: dependabot[bot] --- requirements/ci.txt | 6 +++--- requirements/testing.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/ci.txt b/requirements/ci.txt index af242627b2..c6fe9aaf93 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,6 +1,6 @@ # Dependencies used by CI on github.com/plotly/dash -black==22.3.0 -flake8==7.3.0 +black==25.11.0 +flake8==7.4.1 flaky==3.8.1 flask-talisman==1.1.0 httpx # fastapi.testclient (tests/websocket/test_ws_stream.py) @@ -13,7 +13,7 @@ orjson>=3.10.11 openpyxl pandas>=1.4.0 pyarrow -pylint==3.0.3 +pylint==3.3.9 pytest-mock pytest-split pytest-sugar==1.1.1 diff --git a/requirements/testing.txt b/requirements/testing.txt index 65f2866db0..4cc843fe18 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -5,7 +5,7 @@ lxml>=4.6.2 percy-python-selenium>=1.0.0 pytest>=6.0.2 requests[security]>=2.21.0 -selenium>=4.11.0,<=4.50.0 +selenium>=4.11.0,<=4.51.0 waitress>=1.4.4 multiprocess>=0.70.12 psutil>=5.8.0 From 9371473bc18c06953b62d09b89a0732734cc1326 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 27 Oct 2026 11:35:37 -0400 Subject: [PATCH 2/3] Reformat with black 25.11.0, fix pylint 3.3.9 findings, hold flake8 at 7.3.0 flake8 7.4 requires Python 3.10. black 24+ puts stub bodies on one line, so E704 is ignored per black's flake8 guidance. R0917 is disabled since making those signatures keyword-only would break public APIs. E0606 caught a real crash in generate_rpkg when rpkg_data lacks pkg_help_title or pkg_help_description. --- .flake8 | 2 +- .pylintrc | 3 +- .../graph/test_graph_responsive.py | 8 +-- .../tests/integration/misc/test_platter.py | 4 +- components/dash-table/.flake8 | 2 +- .../dash-table/dash_table_base/Format.py | 4 +- .../dash-table/tests/selenium/conftest.py | 4 +- .../tests/selenium/test_basic_copy_paste.py | 2 +- .../dash-table/tests/selenium/test_filter.py | 60 +++++++++++-------- .../dash-table/tests/selenium/test_header.py | 8 ++- .../tests/selenium/test_navigation.py | 10 ++-- components/dash-table/tests/selenium/utils.py | 10 ++-- dash/_callback.py | 8 ++- dash/_callback_context.py | 6 +- dash/_grouping.py | 1 + dash/_jupyter.py | 1 - dash/_pages.py | 8 +-- dash/_shared_storage/_persistence.py | 6 +- dash/_shared_storage/_polling.py | 1 + dash/_shared_storage/base.py | 15 ++--- dash/_shared_storage/diskcache.py | 1 + dash/_shared_storage/redis.py | 1 + dash/_utils.py | 3 +- dash/backends/_fastapi.py | 6 +- dash/backends/_quart.py | 6 +- dash/backends/base_server.py | 1 + dash/backends/ws.py | 1 + dash/dash.py | 20 ++++--- dash/development/_r_components_generation.py | 7 +-- dash/development/base_component.py | 14 +++-- dash/testing/application_runners.py | 8 ++- requirements/ci.txt | 2 +- .../backend_tests/test_dispatch_view_name.py | 1 + .../test_fastapi_before_request.py | 17 ++++-- tests/backend_tests/test_hook_routes.py | 1 + .../test_quart_before_request.py | 9 ++- tests/compliance/test_callback_typing.py | 1 + .../callbacks/test_callback_decorator.py | 1 + .../renderer/test_due_diligence.py | 8 ++- tests/integration/renderer/test_iframe.py | 6 +- .../renderer/test_race_conditions.py | 2 +- tests/shared_storage/test_dash_integration.py | 1 + .../shared_storage/test_diskcache_backend.py | 1 + tests/shared_storage/test_engine.py | 1 + tests/shared_storage/test_env_config.py | 1 + .../test_local_cross_process.py | 1 + tests/shared_storage/test_persistence.py | 1 + tests/shared_storage/test_redis_backend.py | 1 + tests/shared_storage/test_stream_hub.py | 1 + tests/shared_storage/test_topic_ttl.py | 1 + tests/streaming/conftest.py | 1 + tests/streaming/test_stream_asgi_redis.py | 1 + .../test_stream_callbacks_integration.py | 1 + tests/streaming/test_stream_callbacks_unit.py | 1 + tests/streaming/test_stream_shutdown.py | 1 + tests/streaming/test_stream_transport.py | 1 + tests/streaming/test_stream_wsgi.py | 1 + .../unit/development/test_r_component_gen.py | 26 +++++++- .../test_async_background_callback_job_fn.py | 1 + .../unit/test_background_callback_signing.py | 1 + tests/unit/test_callback_unit.py | 1 + tests/unit/test_main_module_alias.py | 1 + tests/unit/test_setup_server_race.py | 1 + tests/websocket/test_ws_stream.py | 1 + 64 files changed, 202 insertions(+), 125 deletions(-) diff --git a/.flake8 b/.flake8 index 7222e3624e..39734ed12e 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -ignore = C901, E203, E266, E501, E731, W503 +ignore = C901, E203, E266, E501, E704, E731, W503 select = B,C,E,F,W,T4 exclude = node_modules,metadata_test.py per-file-ignores = diff --git a/.pylintrc b/.pylintrc index 39e142048d..2f68173b20 100644 --- a/.pylintrc +++ b/.pylintrc @@ -76,7 +76,8 @@ disable=fixme, unnecessary-lambda-assignment, broad-exception-raised, consider-using-generator, - too-many-ancestors + too-many-ancestors, + too-many-positional-arguments # Enable the message, report, category or checker with the given id(s). You can diff --git a/components/dash-core-components/tests/integration/graph/test_graph_responsive.py b/components/dash-core-components/tests/integration/graph/test_graph_responsive.py index f81dc1fb7c..0ef8592422 100644 --- a/components/dash-core-components/tests/integration/graph/test_graph_responsive.py +++ b/components/dash-core-components/tests/integration/graph/test_graph_responsive.py @@ -125,17 +125,13 @@ def resize(n_clicks, style): initial_height = ( 360 if (initial_responsive and (height is None or is_responsive is True)) - else 450 - if height is None - else height + else 450 if height is None else height ) resize_height = ( 260 if (resize_responsive and (height is None or is_responsive is True)) - else initial_height - if height is None - else height + else initial_height if height is None else height ) # 500px card minus (100px header + 20px padding) minus (20px padding on container) -> 360px left diff --git a/components/dash-core-components/tests/integration/misc/test_platter.py b/components/dash-core-components/tests/integration/misc/test_platter.py index a199240ad2..9e67a864cd 100644 --- a/components/dash-core-components/tests/integration/misc/test_platter.py +++ b/components/dash-core-components/tests/integration/misc/test_platter.py @@ -17,7 +17,9 @@ def test_mspl001_dcc_components_platter(platter_app, dash_dcc): dash_dcc.percy_snapshot("gallery") dash_dcc.find_element("#dropdown").click() - dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search").send_keys("北") + dash_dcc.find_element(".dash-dropdown-content .dash-dropdown-search").send_keys( + "北" + ) dash_dcc.percy_snapshot("gallery - chinese character") text_input = dash_dcc.find_element("#textinput") diff --git a/components/dash-table/.flake8 b/components/dash-table/.flake8 index 4447108e6c..0479daa69a 100644 --- a/components/dash-table/.flake8 +++ b/components/dash-table/.flake8 @@ -1,5 +1,5 @@ [flake8] -ignore = C901, E203, E231, E266, E501, E731, W503 +ignore = C901, E203, E231, E266, E501, E704, E731, W503 select = B,C,E,F,W,T4 per-file-ignores = tests/*: E722, F811 \ No newline at end of file diff --git a/components/dash-table/dash_table_base/Format.py b/components/dash-table/dash_table_base/Format.py index 39c39d933a..691780f732 100644 --- a/components/dash-table/dash_table_base/Format.py +++ b/components/dash-table/dash_table_base/Format.py @@ -231,9 +231,7 @@ def groups(self, groups): groups = ( groups if isinstance(groups, list) - else [groups] - if isinstance(groups, int) - else None + else [groups] if isinstance(groups, int) else None ) if not isinstance(groups, list): diff --git a/components/dash-table/tests/selenium/conftest.py b/components/dash-table/tests/selenium/conftest.py index d3cd726120..2af3215f99 100644 --- a/components/dash-table/tests/selenium/conftest.py +++ b/components/dash-table/tests/selenium/conftest.py @@ -55,7 +55,7 @@ def null_decorator(f): def decorate(f): fspec = inspect.getfullargspec(f) - for (appargs, closureargs, p) in precinfo: + for appargs, closureargs, p in precinfo: for apparg in appargs: if apparg not in fspec.args: raise PreconditionError( @@ -78,7 +78,7 @@ def decorate(f): @wraps(f) def g(*a, **kw): args = inspect.getcallargs(f, *a, **kw) - for (appargs, _, p) in precinfo: + for appargs, _, p in precinfo: if not p(*[args[aa] for aa in appargs]): raise PreconditionError( "Precondition failed in call {!r}{}:\n {!s}\n".format( diff --git a/components/dash-table/tests/selenium/test_basic_copy_paste.py b/components/dash-table/tests/selenium/test_basic_copy_paste.py index 10076cc97e..326846a25a 100644 --- a/components/dash-table/tests/selenium/test_basic_copy_paste.py +++ b/components/dash-table/tests/selenium/test_basic_copy_paste.py @@ -81,7 +81,7 @@ def update_data(timestamp, current, previous): modified = False if len(current) == len(previous): - for (i, datum) in enumerate(current): + for i, datum in enumerate(current): previous_datum = previous[i] if datum["Unnamed: 0"] != previous_datum["Unnamed: 0"]: diff --git a/components/dash-table/tests/selenium/test_filter.py b/components/dash-table/tests/selenium/test_filter.py index ac6ec1eb48..3718bd1b05 100644 --- a/components/dash-table/tests/selenium/test_filter.py +++ b/components/dash-table/tests/selenium/test_filter.py @@ -99,32 +99,38 @@ def test_filt002_sensitivity(test, filter_case_options, column_case_filter_optio dict( id="a", name="a", - filter_options=dict(case=column_case_filter_options) - if column_case_filter_options is not None - else None, + filter_options=( + dict(case=column_case_filter_options) + if column_case_filter_options is not None + else None + ), type="any", ), dict( id="b", name="b", - filter_options=dict(case=column_case_filter_options) - if column_case_filter_options is not None - else None, + filter_options=( + dict(case=column_case_filter_options) + if column_case_filter_options is not None + else None + ), type="text", ), dict( id="c", name="c", - filter_options=dict(case=column_case_filter_options) - if column_case_filter_options is not None - else None, + filter_options=( + dict(case=column_case_filter_options) + if column_case_filter_options is not None + else None + ), type="numeric", ), ], filter_action="native", - filter_options=dict(case=filter_case_options) - if filter_case_options is not None - else None, + filter_options=( + dict(case=filter_case_options) if filter_case_options is not None else None + ), style_cell=dict(width=100, min_width=100, max_width=100), ) @@ -221,9 +227,11 @@ def test_filt003_sensitivity(test, filter_case_options, column_case_filter_optio dict( id="a", name="a", - filter_options=dict(case=column_case_filter_options) - if column_case_filter_options is not None - else None, + filter_options=( + dict(case=column_case_filter_options) + if column_case_filter_options is not None + else None + ), type="any", ), dict( @@ -235,16 +243,18 @@ def test_filt003_sensitivity(test, filter_case_options, column_case_filter_optio dict( id="c", name="c", - filter_options=dict(case=column_case_filter_options) - if column_case_filter_options is not None - else None, + filter_options=( + dict(case=column_case_filter_options) + if column_case_filter_options is not None + else None + ), type="numeric", ), ], filter_action="native", - filter_options=dict(case=filter_case_options) - if filter_case_options is not None - else None, + filter_options=( + dict(case=filter_case_options) if filter_case_options is not None else None + ), style_cell=dict(width=100, min_width=100, max_width=100), ) @@ -341,9 +351,11 @@ def test_filt004_placeholder( ), ], filter_action="native", - filter_options=dict(placeholder_text=table_placeholder_setting) - if table_placeholder_setting is not None - else None, + filter_options=( + dict(placeholder_text=table_placeholder_setting) + if table_placeholder_setting is not None + else None + ), ) test.start_server(get_app(props)) diff --git a/components/dash-table/tests/selenium/test_header.py b/components/dash-table/tests/selenium/test_header.py index beabceff82..4990f2633d 100644 --- a/components/dash-table/tests/selenium/test_header.py +++ b/components/dash-table/tests/selenium/test_header.py @@ -60,9 +60,11 @@ def test_head001_renames_only_row(test, row): for i in range(3): wait.until( - lambda: target.column("rows").get_text(i) == "modified" - if row == i - else title[i], + lambda: ( + target.column("rows").get_text(i) == "modified" + if row == i + else title[i] + ), 3, ) diff --git a/components/dash-table/tests/selenium/test_navigation.py b/components/dash-table/tests/selenium/test_navigation.py index 78c587d5e3..2e2c553826 100644 --- a/components/dash-table/tests/selenium/test_navigation.py +++ b/components/dash-table/tests/selenium/test_navigation.py @@ -21,13 +21,15 @@ def get_mixed_markdown_table(): data = [ { "not-markdown-column": "this is not a markdown cell", - "markdown-column": """```javascript + "markdown-column": ( + """```javascript console.warn("this is a markdown cell")```""" - if i % 2 == 0 - else """```javascript + if i % 2 == 0 + else """```javascript console.log("logging things") console.warn("this is a markdown cell") -```""", +```""" + ), "also-not-markdown-column": str(i), "also-also-not-markdown-column": "this is also also not a markdown cell", } diff --git a/components/dash-table/tests/selenium/utils.py b/components/dash-table/tests/selenium/utils.py index f3ed3bc194..ea9d0290b8 100644 --- a/components/dash-table/tests/selenium/utils.py +++ b/components/dash-table/tests/selenium/utils.py @@ -87,14 +87,16 @@ def generate_mixed_markdown_data(rows=100): data=[ { "not-markdown-column": "this is not a markdown cell", - "markdown-column": """```javascript + "markdown-column": ( + """```javascript console.warn("this is a markdown cell") ```""" - if i % 2 == 0 - else """```javascript + if i % 2 == 0 + else """```javascript console.log("logging things") console.warn("this is a markdown cell") -```""", +```""" + ), "also-not-markdown-column": i, "also-also-not-markdown-column": "this is also also not a markdown cell", } diff --git a/dash/_callback.py b/dash/_callback.py index f07741e5f1..bcbe966284 100644 --- a/dash/_callback.py +++ b/dash/_callback.py @@ -585,9 +585,11 @@ def _setup_background_callback( func, func_args if func_args else func_kwargs, background.get("cache_args_to_ignore", []), - None - if cache_ignore_triggered - else callback_ctx.get("triggered_inputs", []), + ( + None + if cache_ignore_triggered + else callback_ctx.get("triggered_inputs", []) + ), callback_key=background_key, ) job_fn = callback_manager.func_registry.get(background_key) diff --git a/dash/_callback_context.py b/dash/_callback_context.py index 430d04591a..95049f55dc 100644 --- a/dash/_callback_context.py +++ b/dash/_callback_context.py @@ -10,9 +10,9 @@ from ._get_app import get_app from ._utils import AttributeDict, stringify_id -context_value: contextvars.ContextVar[ - typing.Dict[str, typing.Any] -] = contextvars.ContextVar("callback_context") +context_value: contextvars.ContextVar[typing.Dict[str, typing.Any]] = ( + contextvars.ContextVar("callback_context") +) context_value.set({}) diff --git a/dash/_grouping.py b/dash/_grouping.py index 7271d973bb..17577ac64c 100644 --- a/dash/_grouping.py +++ b/dash/_grouping.py @@ -13,6 +13,7 @@ structure """ + from .exceptions import InvalidCallbackReturnValue from ._utils import AttributeDict, stringify_id diff --git a/dash/_jupyter.py b/dash/_jupyter.py index 9ae361a0d2..b567e2af35 100644 --- a/dash/_jupyter.py +++ b/dash/_jupyter.py @@ -115,7 +115,6 @@ def _custom_formatargvalues( formatvarkw=lambda name: "**" + name, formatvalue=lambda value: "=" + repr(value), ): - """Copied from inspect.formatargvalues, modified to place function arguments on separate lines""" diff --git a/dash/_pages.py b/dash/_pages.py index 22b7cecb4a..903cae5776 100644 --- a/dash/_pages.py +++ b/dash/_pages.py @@ -314,18 +314,14 @@ def register_page( title=( title if title is not None - else CONFIG.title - if CONFIG.title != "Dash" - else page["name"] + else CONFIG.title if CONFIG.title != "Dash" else page["name"] ), ) page.update( description=( description if description - else CONFIG.description - if CONFIG.description - else "" + else CONFIG.description if CONFIG.description else "" ), order=order, supplied_order=order, diff --git a/dash/_shared_storage/_persistence.py b/dash/_shared_storage/_persistence.py index 1912a9bd08..bae9c2539f 100644 --- a/dash/_shared_storage/_persistence.py +++ b/dash/_shared_storage/_persistence.py @@ -87,9 +87,9 @@ def __init__(self, directory: str, chunk_size: int = _CHUNK_SIZE): self._chunk_size = chunk_size self._lock = threading.RLock() self._keys: Dict[str, int] = {} # key -> chunk_num - self._chunks: Dict[ - int, Dict[str, Any] - ] = {} # chunk_num -> {key: [value, expire_at]} + self._chunks: Dict[int, Dict[str, Any]] = ( + {} + ) # chunk_num -> {key: [value, expire_at]} self._loaded = False os.makedirs(self._dir, exist_ok=True) diff --git a/dash/_shared_storage/_polling.py b/dash/_shared_storage/_polling.py index e5b254b5bc..5082237740 100644 --- a/dash/_shared_storage/_polling.py +++ b/dash/_shared_storage/_polling.py @@ -12,6 +12,7 @@ ``run_in_executor`` call in the process. It polls without waiting and sleeps on the loop between polls instead. """ + import asyncio import threading from typing import Callable diff --git a/dash/_shared_storage/base.py b/dash/_shared_storage/base.py index 61cd6c50d2..50e1bf2a39 100644 --- a/dash/_shared_storage/base.py +++ b/dash/_shared_storage/base.py @@ -75,16 +75,13 @@ def poll(self, timeout: float = 0.0) -> List[Tuple[int, Any]]: at least one. Raises ``SharedStorageGap`` if the buffer overran.""" @abc.abstractmethod - def iter_with_seq(self) -> Iterator[Any]: - ... + def iter_with_seq(self) -> Iterator[Any]: ... @abc.abstractmethod - def aiter_with_seq(self) -> AsyncIterator[Any]: - ... + def aiter_with_seq(self) -> AsyncIterator[Any]: ... @abc.abstractmethod - def close(self) -> None: - ... + def close(self) -> None: ... def __iter__(self) -> Iterator[Any]: for _seq, message in self.iter_with_seq(): @@ -128,8 +125,7 @@ def close(self) -> None: """Release this worker's handle on the backend. Idempotent.""" @abc.abstractmethod - def get(self, key: str, default: Any = None) -> Any: - ... + def get(self, key: str, default: Any = None) -> Any: ... @abc.abstractmethod def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None: @@ -142,8 +138,7 @@ def set(self, key: str, value: Any, ttl: Optional[float] = None) -> None: """ @abc.abstractmethod - def delete(self, key: str) -> None: - ... + def delete(self, key: str) -> None: ... @abc.abstractmethod def publish(self, topic: str, message: Any, ttl: Optional[float] = None) -> None: diff --git a/dash/_shared_storage/diskcache.py b/dash/_shared_storage/diskcache.py index a232dc37ca..c91826df60 100644 --- a/dash/_shared_storage/diskcache.py +++ b/dash/_shared_storage/diskcache.py @@ -17,6 +17,7 @@ and every key of the topic expires together once nobody has published to or polled it for that long, so abandoned topics leave the cache. """ + import time from typing import Any, Optional diff --git a/dash/_shared_storage/redis.py b/dash/_shared_storage/redis.py index 1fdcbefb05..75e517bdd4 100644 --- a/dash/_shared_storage/redis.py +++ b/dash/_shared_storage/redis.py @@ -20,6 +20,7 @@ Instead each loop gets one reader task that serves all of its subscriptions with a single multi-stream XREAD, and renews the lifetime of every topic it reads. """ + import asyncio import os import secrets diff --git a/dash/_utils.py b/dash/_utils.py index 3a3f6ab4d6..8c85398b01 100644 --- a/dash/_utils.py +++ b/dash/_utils.py @@ -305,8 +305,7 @@ def __len__(self): return len(self._data) def __iter__(self): - for i in self._data: - yield i + yield from self._data def coerce_to_list(obj): diff --git a/dash/backends/_fastapi.py b/dash/backends/_fastapi.py index ec1514a405..1da3ad4027 100644 --- a/dash/backends/_fastapi.py +++ b/dash/backends/_fastapi.py @@ -964,9 +964,9 @@ async def websocket_handler(websocket: WebSocket): ) # Track pending callbacks: concurrent.futures.Future (sync/threadpool) # or asyncio.Task (async/event-loop). - pending_callbacks: Dict[ - str, concurrent.futures.Future | asyncio.Future - ] = {} + pending_callbacks: Dict[str, concurrent.futures.Future | asyncio.Future] = ( + {} + ) # Start sender task to drain outbound queue (sends pre-serialized text) # pylint: disable=protected-access diff --git a/dash/backends/_quart.py b/dash/backends/_quart.py index ac58e1acf5..26b75093f1 100644 --- a/dash/backends/_quart.py +++ b/dash/backends/_quart.py @@ -689,9 +689,9 @@ async def websocket_handler(): # pylint: disable=too-many-branches ) # Track pending callbacks: concurrent.futures.Future (sync/threadpool) # or asyncio.Task (async/event-loop). - pending_callbacks: Dict[ - str, concurrent.futures.Future | asyncio.Future - ] = {} + pending_callbacks: Dict[str, concurrent.futures.Future | asyncio.Future] = ( + {} + ) # Start sender task to drain outbound queue (sends pre-serialized text) # pylint: disable=protected-access diff --git a/dash/backends/base_server.py b/dash/backends/base_server.py index 57034da23d..d51cb71c0e 100644 --- a/dash/backends/base_server.py +++ b/dash/backends/base_server.py @@ -3,6 +3,7 @@ This module provides abstract base classes and protocols that define the interface for different web server backends (Flask, Quart, FastAPI, etc.) to integrate with Dash. """ + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/dash/backends/ws.py b/dash/backends/ws.py index 2c72bcdf79..feb0550611 100644 --- a/dash/backends/ws.py +++ b/dash/backends/ws.py @@ -3,6 +3,7 @@ This module provides the WebSocket callback infrastructure for real-time bidirectional communication between Dash backends and the renderer. """ + from __future__ import annotations import asyncio diff --git a/dash/dash.py b/dash/dash.py index 72a1d7b604..3f48cfcc5e 100644 --- a/dash/dash.py +++ b/dash/dash.py @@ -2449,9 +2449,7 @@ def enable_dev_tools( # pylint: disable=too-many-branches pkg_dir = ( package.submodule_search_locations[0] if package.submodule_search_locations - else os.path.dirname(package.origin) - if package.origin - else None + else os.path.dirname(package.origin) if package.origin else None ) if pkg_dir and "dash/dash" in pkg_dir: component_packages_dist[i : i + 1] = [ @@ -2886,9 +2884,11 @@ async def update(pathname_, search_, **states): async def get_layouts(): return [ - await execute_async_function(page["layout"]) - if callable(page["layout"]) - else page["layout"] + ( + await execute_async_function(page["layout"]) + if callable(page["layout"]) + else page["layout"] + ) for page in _pages.PAGE_REGISTRY.values() ] @@ -2978,9 +2978,11 @@ def update(pathname_, search_, **states): ] self.validation_layout = html.Div( [ - page["layout"]() - if callable(page["layout"]) - else page["layout"] + ( + page["layout"]() + if callable(page["layout"]) + else page["layout"] + ) for page in _pages.PAGE_REGISTRY.values() ] + layout diff --git a/dash/development/_r_components_generation.py b/dash/development/_r_components_generation.py index 54d7b04dc2..8b1cb8a2f3 100644 --- a/dash/development/_r_components_generation.py +++ b/dash/development/_r_components_generation.py @@ -574,6 +574,9 @@ def generate_rpkg( package_copyright = "" package_rauthors = "" lib_name = pkg_data.get("name") + # fall back to using description in package.json, if present + package_title = pkg_data.get("description", "") + package_description = pkg_data.get("description", "") if rpkg_data is not None: if rpkg_data.get("pkg_help_title"): @@ -588,10 +591,6 @@ def generate_rpkg( package_copyright = "\nCopyright: {}".format( rpkg_data.get("pkg_copyright", "") ) - else: - # fall back to using description in package.json, if present - package_title = pkg_data.get("description", "") - package_description = pkg_data.get("description", "") package_version = pkg_data.get("version", "0.0.1") diff --git a/dash/development/base_component.py b/dash/development/base_component.py index b6798064e1..d6c42ff5d8 100644 --- a/dash/development/base_component.py +++ b/dash/development/base_component.py @@ -39,9 +39,9 @@ class ComponentRegistry: """Holds a registry of the namespaces used by components.""" registry = OrderedSet() - children_props: typing.DefaultDict[ - str, typing.Dict[str, typing.Any] - ] = collections.defaultdict(dict) + children_props: typing.DefaultDict[str, typing.Dict[str, typing.Any]] = ( + collections.defaultdict(dict) + ) namespace_to_package: typing.Dict[str, str] = {} @classmethod @@ -130,9 +130,11 @@ def __get_pydantic_json_schema__(cls, _schema, _handler): "type": "object", "properties": { "type": {"type": "string"}, - "namespace": {"type": "string", "enum": namespaces} - if namespaces - else {"type": "string"}, + "namespace": ( + {"type": "string", "enum": namespaces} + if namespaces + else {"type": "string"} + ), "props": {"type": "object"}, }, } diff --git a/dash/testing/application_runners.py b/dash/testing/application_runners.py index 0b86cee7d0..485d056fee 100644 --- a/dash/testing/application_runners.py +++ b/dash/testing/application_runners.py @@ -359,9 +359,11 @@ def start( return self.port = port args = shlex.split( - raw_command - if raw_command - else f"waitress-serve --listen=0.0.0.0:{port} {app_module}:{application_name}.server", + ( + raw_command + if raw_command + else f"waitress-serve --listen=0.0.0.0:{port} {app_module}:{application_name}.server" + ), posix=not self.is_windows, ) diff --git a/requirements/ci.txt b/requirements/ci.txt index c6fe9aaf93..da9717fd8a 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,6 +1,6 @@ # Dependencies used by CI on github.com/plotly/dash black==25.11.0 -flake8==7.4.1 +flake8==7.3.0 flaky==3.8.1 flask-talisman==1.1.0 httpx # fastapi.testclient (tests/websocket/test_ws_stream.py) diff --git a/tests/backend_tests/test_dispatch_view_name.py b/tests/backend_tests/test_dispatch_view_name.py index 81013aea88..5404881d29 100644 --- a/tests/backend_tests/test_dispatch_view_name.py +++ b/tests/backend_tests/test_dispatch_view_name.py @@ -8,6 +8,7 @@ See https://github.com/plotly/dash/issues/3827 """ + import pytest from dash import Dash, html diff --git a/tests/backend_tests/test_fastapi_before_request.py b/tests/backend_tests/test_fastapi_before_request.py index b094bbcd7f..3d5612fe96 100644 --- a/tests/backend_tests/test_fastapi_before_request.py +++ b/tests/backend_tests/test_fastapi_before_request.py @@ -1,5 +1,6 @@ """before_request hooks on the FastAPI backend gate every route, as on Flask and Quart.""" + import pytest from dash import Dash, Input, Output, _get_app, hooks, html @@ -109,9 +110,11 @@ def test_fabr002_hook_gates_mounted_app(): ) app.layout = html.Div("layout") app.backend.before_request( - lambda: None - if app.backend.request_adapter().headers.get("authorization") == "good" - else ("denied", 401) + lambda: ( + None + if app.backend.request_adapter().headers.get("authorization") == "good" + else ("denied", 401) + ) ) parent = FastAPI() parent.mount("/dash", app.server) @@ -130,9 +133,11 @@ def test_fabr003_hook_gates_websocket_handshake(): app = Dash(__name__, backend="fastapi", websocket_callbacks=True) app.layout = html.Div("layout") app.backend.before_request( - lambda: None - if app.backend.request_adapter().headers.get("authorization") == "good" - else ("denied", 401) + lambda: ( + None + if app.backend.request_adapter().headers.get("authorization") == "good" + else ("denied", 401) + ) ) origin = {"origin": "http://testserver"} diff --git a/tests/backend_tests/test_hook_routes.py b/tests/backend_tests/test_hook_routes.py index 813af55468..21b783f416 100644 --- a/tests/backend_tests/test_hook_routes.py +++ b/tests/backend_tests/test_hook_routes.py @@ -1,6 +1,7 @@ """Hook routes on every backend: sync and async views reading the request through the backend's request adapter, and FastAPI handlers reading the body themselves after the Dash middleware parsed it.""" + import asyncio import inspect diff --git a/tests/backend_tests/test_quart_before_request.py b/tests/backend_tests/test_quart_before_request.py index a8ae31fdb7..f63d1a530a 100644 --- a/tests/backend_tests/test_quart_before_request.py +++ b/tests/backend_tests/test_quart_before_request.py @@ -1,4 +1,5 @@ """before_request hooks on the Quart backend also gate the WebSocket handshake.""" + import asyncio import pytest @@ -14,9 +15,11 @@ def test_qbr001_hook_gates_websocket_handshake(): app = Dash(__name__, backend="quart", websocket_callbacks=True) app.layout = html.Div("layout") app.backend.before_request( - lambda: None - if app.backend.request_adapter().headers.get("authorization") == "good" - else ("denied", 401) + lambda: ( + None + if app.backend.request_adapter().headers.get("authorization") == "good" + else ("denied", 401) + ) ) origin = {"origin": "http://localhost"} diff --git a/tests/compliance/test_callback_typing.py b/tests/compliance/test_callback_typing.py index ebdf6cc590..d6f1299500 100644 --- a/tests/compliance/test_callback_typing.py +++ b/tests/compliance/test_callback_typing.py @@ -1,4 +1,5 @@ """Type compliance tests for callback with strict mypy/pyright settings.""" + import os import sys diff --git a/tests/integration/callbacks/test_callback_decorator.py b/tests/integration/callbacks/test_callback_decorator.py index fd81eafe1e..334d9a5436 100644 --- a/tests/integration/callbacks/test_callback_decorator.py +++ b/tests/integration/callbacks/test_callback_decorator.py @@ -1,4 +1,5 @@ """Integration tests for callback decorator behavior.""" + import dash from dash import Input, Output, State, callback, dcc, html diff --git a/tests/integration/renderer/test_due_diligence.py b/tests/integration/renderer/test_due_diligence.py index 9ff689a4f6..84f3a7f99c 100644 --- a/tests/integration/renderer/test_due_diligence.py +++ b/tests/integration/renderer/test_due_diligence.py @@ -85,9 +85,11 @@ def test_rddd001_initial_state(dash_duo): assert paths["strs"] == { abbr: ["components"] + [ - int(token) - if token in string.digits - else token.replace("p", "props").replace("c", "children") + ( + int(token) + if token in string.digits + else token.replace("p", "props").replace("c", "children") + ) for token in abbr.split(".") ] for abbr in ( diff --git a/tests/integration/renderer/test_iframe.py b/tests/integration/renderer/test_iframe.py index 20e733a3c1..43f36f3d9a 100644 --- a/tests/integration/renderer/test_iframe.py +++ b/tests/integration/renderer/test_iframe.py @@ -40,9 +40,9 @@ def iframe_wrapper(): @app.server.after_request def apply_cors(response): response.headers["Access-Control-Allow-Origin"] = "*" - response.headers[ - "Access-Control-Allow-Headers" - ] = "Origin, X-Requested-With, Content-Type, Accept, Authorization" + response.headers["Access-Control-Allow-Headers"] = ( + "Origin, X-Requested-With, Content-Type, Accept, Authorization" + ) return response dash_duo.start_server(app) diff --git a/tests/integration/renderer/test_race_conditions.py b/tests/integration/renderer/test_race_conditions.py index 9de9d56f33..b8092b54b5 100644 --- a/tests/integration/renderer/test_race_conditions.py +++ b/tests/integration/renderer/test_race_conditions.py @@ -11,7 +11,7 @@ "layout", "dependencies", "update-component", - "_config" + "_config", # routes and component-suites # are other endpoints but are excluded to speed up tests ] diff --git a/tests/shared_storage/test_dash_integration.py b/tests/shared_storage/test_dash_integration.py index d915085ccd..cebc66d82c 100644 --- a/tests/shared_storage/test_dash_integration.py +++ b/tests/shared_storage/test_dash_integration.py @@ -6,6 +6,7 @@ scenario runs on each backend (Local, Diskcache, Redis-when-reachable) to prove the wiring end to end. """ + import os import uuid diff --git a/tests/shared_storage/test_diskcache_backend.py b/tests/shared_storage/test_diskcache_backend.py index 272b2caeb1..2743af42b4 100644 --- a/tests/shared_storage/test_diskcache_backend.py +++ b/tests/shared_storage/test_diskcache_backend.py @@ -4,6 +4,7 @@ and cross-process visibility over a shared cache directory. Requires the diskcache extra, which the shared-storage CI job installs. """ + import asyncio import multiprocessing as mp import threading diff --git a/tests/shared_storage/test_engine.py b/tests/shared_storage/test_engine.py index 208e28f5cf..8be162997e 100644 --- a/tests/shared_storage/test_engine.py +++ b/tests/shared_storage/test_engine.py @@ -1,4 +1,5 @@ """Unit tests for the in-memory StoreEngine (KV + sequenced pub/sub).""" + import threading import time diff --git a/tests/shared_storage/test_env_config.py b/tests/shared_storage/test_env_config.py index 68535f1065..370dfed5ed 100644 --- a/tests/shared_storage/test_env_config.py +++ b/tests/shared_storage/test_env_config.py @@ -1,4 +1,5 @@ """DASH_SHARED_STORAGE picks the backend when the app does not pass one.""" + # pylint: disable=protected-access import sys diff --git a/tests/shared_storage/test_local_cross_process.py b/tests/shared_storage/test_local_cross_process.py index cff38d98db..74ea8d005f 100644 --- a/tests/shared_storage/test_local_cross_process.py +++ b/tests/shared_storage/test_local_cross_process.py @@ -5,6 +5,7 @@ client reconnect-with-replay across a forced socket drop -- all over the real socket transport, not the in-process fast path. """ + import multiprocessing as mp import threading import time diff --git a/tests/shared_storage/test_persistence.py b/tests/shared_storage/test_persistence.py index c2a953d827..1bf84d1c9c 100644 --- a/tests/shared_storage/test_persistence.py +++ b/tests/shared_storage/test_persistence.py @@ -4,6 +4,7 @@ StoreEngine. Every test writes to a ``tmp_path`` so the real user cache dir is never touched. """ + import threading import time import uuid diff --git a/tests/shared_storage/test_redis_backend.py b/tests/shared_storage/test_redis_backend.py index b51c032034..4e2c138e58 100644 --- a/tests/shared_storage/test_redis_backend.py +++ b/tests/shared_storage/test_redis_backend.py @@ -4,6 +4,7 @@ tests skip when none is available. The CI shared-storage job provides one. Each test uses a unique key prefix so a shared Redis stays isolated. """ + import asyncio import os import threading diff --git a/tests/shared_storage/test_stream_hub.py b/tests/shared_storage/test_stream_hub.py index 894a425a5a..abd3b1a799 100644 --- a/tests/shared_storage/test_stream_hub.py +++ b/tests/shared_storage/test_stream_hub.py @@ -1,4 +1,5 @@ """The stream hub: streaming frames multiplexed over shared-storage pub/sub.""" + import asyncio import threading import time diff --git a/tests/shared_storage/test_topic_ttl.py b/tests/shared_storage/test_topic_ttl.py index fba268ab39..1f61f9612d 100644 --- a/tests/shared_storage/test_topic_ttl.py +++ b/tests/shared_storage/test_topic_ttl.py @@ -1,6 +1,7 @@ """Topic ttl contract, run against every backend so they expire topics the same way: a topic goes as a whole once nobody publishes to or reads it, never message by message while it is in use.""" + import os import threading import time diff --git a/tests/streaming/conftest.py b/tests/streaming/conftest.py index 94467a0d00..c332932c21 100644 --- a/tests/streaming/conftest.py +++ b/tests/streaming/conftest.py @@ -6,6 +6,7 @@ other's connections at teardown. Patching the default namespace per test keeps each app its own owner. """ + import uuid import pytest diff --git a/tests/streaming/test_stream_asgi_redis.py b/tests/streaming/test_stream_asgi_redis.py index cc37e765dc..c561e9ee13 100644 --- a/tests/streaming/test_stream_asgi_redis.py +++ b/tests/streaming/test_stream_asgi_redis.py @@ -5,6 +5,7 @@ of them starve the worker, and every publish and store call behind them waits out the read's block timeout. Frames then arrive seconds late, or not at all. """ + import os import re import signal diff --git a/tests/streaming/test_stream_callbacks_integration.py b/tests/streaming/test_stream_callbacks_integration.py index 1643c1e40c..2cccdd0780 100644 --- a/tests/streaming/test_stream_callbacks_integration.py +++ b/tests/streaming/test_stream_callbacks_integration.py @@ -1,4 +1,5 @@ """Browser integration tests for streaming callbacks over HTTP (NDJSON).""" + import asyncio import time diff --git a/tests/streaming/test_stream_callbacks_unit.py b/tests/streaming/test_stream_callbacks_unit.py index 2078acdec9..535b4fdd1e 100644 --- a/tests/streaming/test_stream_callbacks_unit.py +++ b/tests/streaming/test_stream_callbacks_unit.py @@ -1,4 +1,5 @@ """Unit tests for streaming (generator) callbacks - no browser required.""" + import asyncio import contextvars import json diff --git a/tests/streaming/test_stream_shutdown.py b/tests/streaming/test_stream_shutdown.py index ac1f5a7b1b..0049bde91d 100644 --- a/tests/streaming/test_stream_shutdown.py +++ b/tests/streaming/test_stream_shutdown.py @@ -5,6 +5,7 @@ responses, so if the streaming shutdown hook is not wired in, the process ignores SIGINT until the generator ends. """ + import os import signal import socket diff --git a/tests/streaming/test_stream_transport.py b/tests/streaming/test_stream_transport.py index b3d04fb468..15e90f98e9 100644 --- a/tests/streaming/test_stream_transport.py +++ b/tests/streaming/test_stream_transport.py @@ -5,6 +5,7 @@ which the client's single downlink relays them). Exercised over the real HTTP dispatch on all three backends (Flask WSGI, Quart + FastAPI ASGI). """ + import asyncio import contextlib import json diff --git a/tests/streaming/test_stream_wsgi.py b/tests/streaming/test_stream_wsgi.py index 1d51f0c630..2f48e6e47d 100644 --- a/tests/streaming/test_stream_wsgi.py +++ b/tests/streaming/test_stream_wsgi.py @@ -8,6 +8,7 @@ so they need a shared signing secret (``server.secret_key`` or ``DASH_SECRET_KEY``). """ + import contextlib import json import os diff --git a/tests/unit/development/test_r_component_gen.py b/tests/unit/development/test_r_component_gen.py index ae16fb68f1..843f603036 100644 --- a/tests/unit/development/test_r_component_gen.py +++ b/tests/unit/development/test_r_component_gen.py @@ -5,7 +5,10 @@ import pytest -from dash.development._r_components_generation import make_namespace_exports +from dash.development._r_components_generation import ( + generate_rpkg, + make_namespace_exports, +) @pytest.fixture @@ -85,3 +88,24 @@ def test_r_exports(make_r_dir): matches = re.findall(r"export\(([^()]+)\)", exports.replace("\n", " ")) assert matches == expected_exports + + +def test_r_pkg_without_help_title(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + (tmp_path / "mypkg.py").write_text( + "_js_dist = [{'relative_package_path': 'mypkg.min.js'}]\n" + ) + pkg_data = { + "name": "my-pkg", + "version": "1.0.0", + "description": "My package", + "author": "Jane Doe ", + "license": "MIT", + } + rpkg_data = {"pkg_copyright": "Plotly"} + + generate_rpkg(pkg_data, rpkg_data, "mypkg", "", "", "", "", False) + + description = (tmp_path / "DESCRIPTION").read_text() + assert "Title: My package" in description + assert "Description: My package" in description diff --git a/tests/unit/test_async_background_callback_job_fn.py b/tests/unit/test_async_background_callback_job_fn.py index 04ffa8d8e7..deb188ef21 100644 --- a/tests/unit/test_async_background_callback_job_fn.py +++ b/tests/unit/test_async_background_callback_job_fn.py @@ -6,6 +6,7 @@ function directly, so they require neither a browser nor a running Celery or Diskcache backend. """ + import json import pytest diff --git a/tests/unit/test_background_callback_signing.py b/tests/unit/test_background_callback_signing.py index efff782480..a420d114d8 100644 --- a/tests/unit/test_background_callback_signing.py +++ b/tests/unit/test_background_callback_signing.py @@ -7,6 +7,7 @@ ``cancelJob`` PID. 2. Arbitrary result-cache read/delete via a client-supplied ``cacheKey``. """ + import re import subprocess import sys diff --git a/tests/unit/test_callback_unit.py b/tests/unit/test_callback_unit.py index 4f22657fae..148c337667 100644 --- a/tests/unit/test_callback_unit.py +++ b/tests/unit/test_callback_unit.py @@ -1,4 +1,5 @@ """Unit tests for callback decorator behavior - no browser required.""" + import inspect import json diff --git a/tests/unit/test_main_module_alias.py b/tests/unit/test_main_module_alias.py index 4f166a0575..ef4ab3fa3e 100644 --- a/tests/unit/test_main_module_alias.py +++ b/tests/unit/test_main_module_alias.py @@ -11,6 +11,7 @@ under its canonical import name so the second import resolves to the module already executed instead of re-executing the file. """ + import importlib import sys import types diff --git a/tests/unit/test_setup_server_race.py b/tests/unit/test_setup_server_race.py index 997b0b7253..73c3bc8c29 100644 --- a/tests/unit/test_setup_server_race.py +++ b/tests/unit/test_setup_server_race.py @@ -13,6 +13,7 @@ populated, because a return from ``_setup_server`` is meant to guarantee the setup work is done. """ + import threading import time diff --git a/tests/websocket/test_ws_stream.py b/tests/websocket/test_ws_stream.py index 262cdd3d79..5b0dc0f582 100644 --- a/tests/websocket/test_ws_stream.py +++ b/tests/websocket/test_ws_stream.py @@ -5,6 +5,7 @@ ``stream: true`` followed by a terminal done frame, plus browser tests for the full renderer round-trip. """ + import asyncio import json From ae59a5d54bc7d7af6d5ba07d7e263d1c68aedbdb Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 27 Oct 2026 11:37:29 -0400 Subject: [PATCH 3/3] Ignore E704 in dcc flake8 config, isolate R package test imports --- components/dash-core-components/.flake8 | 2 +- tests/unit/development/test_r_component_gen.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/components/dash-core-components/.flake8 b/components/dash-core-components/.flake8 index 4447108e6c..0479daa69a 100644 --- a/components/dash-core-components/.flake8 +++ b/components/dash-core-components/.flake8 @@ -1,5 +1,5 @@ [flake8] -ignore = C901, E203, E231, E266, E501, E731, W503 +ignore = C901, E203, E231, E266, E501, E704, E731, W503 select = B,C,E,F,W,T4 per-file-ignores = tests/*: E722, F811 \ No newline at end of file diff --git a/tests/unit/development/test_r_component_gen.py b/tests/unit/development/test_r_component_gen.py index 843f603036..c9532e143a 100644 --- a/tests/unit/development/test_r_component_gen.py +++ b/tests/unit/development/test_r_component_gen.py @@ -1,6 +1,7 @@ import os import shutil import re +import sys from textwrap import dedent import pytest @@ -92,6 +93,8 @@ def test_r_exports(make_r_dir): def test_r_pkg_without_help_title(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) + # generate_rpkg puts the cwd on sys.path and imports the package + monkeypatch.setattr(sys, "path", list(sys.path)) (tmp_path / "mypkg.py").write_text( "_js_dist = [{'relative_package_path': 'mypkg.min.js'}]\n" ) @@ -104,7 +107,10 @@ def test_r_pkg_without_help_title(tmp_path, monkeypatch): } rpkg_data = {"pkg_copyright": "Plotly"} - generate_rpkg(pkg_data, rpkg_data, "mypkg", "", "", "", "", False) + try: + generate_rpkg(pkg_data, rpkg_data, "mypkg", "", "", "", "", False) + finally: + sys.modules.pop("mypkg", None) description = (tmp_path / "DESCRIPTION").read_text() assert "Title: My package" in description