Skip to content
Open
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 @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## Fixed
- [#3690](https://github.com/plotly/dash/pull/3690) Fixes Input when min or max is set to None
- [#3723](https://github.com/plotly/dash/pull/3723) Fix misaligned `dcc.Slider` marks when some labels are empty strings
- [#3738](https://github.com/plotly/dash/pull/3738) Add missing `stacklevel=2` to `warnings.warn()` calls so warnings report the caller's location instead of internal Dash source lines

## [4.1.0] - 2026-03-23

Expand Down
10 changes: 6 additions & 4 deletions dash/_callback_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
from . import exceptions
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({})


Expand Down Expand Up @@ -177,6 +176,7 @@ def outputs_list(self):
warnings.warn(
"outputs_list is deprecated, use outputs_grouping instead",
DeprecationWarning,
stacklevel=2,
)

return getattr(_get_context_value(), "outputs_list", [])
Expand All @@ -188,6 +188,7 @@ def inputs_list(self):
warnings.warn(
"inputs_list is deprecated, use args_grouping instead",
DeprecationWarning,
stacklevel=2,
)

return getattr(_get_context_value(), "inputs_list", [])
Expand All @@ -199,6 +200,7 @@ def states_list(self):
warnings.warn(
"states_list is deprecated, use args_grouping instead",
DeprecationWarning,
stacklevel=2,
)
return getattr(_get_context_value(), "states_list", [])

Expand Down
61 changes: 32 additions & 29 deletions dash/dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,8 @@ def __init__( # pylint: disable=too-many-statements
if self.__class__.__name__ == "JupyterDash":
warnings.warn(
"JupyterDash is deprecated, use Dash instead.\n"
"See https://dash.plotly.com/dash-in-jupyter for more details."
"See https://dash.plotly.com/dash-in-jupyter for more details.",
stacklevel=2,
)
self.setup_startup_routes()

Expand Down Expand Up @@ -1117,9 +1118,11 @@ def _generate_css_dist_html(self):

return "\n".join(
[
format_tag("link", link, opened=True)
if isinstance(link, dict)
else f'<link rel="stylesheet" href="{link}">'
(
format_tag("link", link, opened=True)
if isinstance(link, dict)
else f'<link rel="stylesheet" href="{link}">'
)
for link in (external_links + links)
]
)
Expand Down Expand Up @@ -1173,9 +1176,11 @@ def _generate_scripts_html(self) -> str:

return "\n".join(
[
format_tag("script", src)
if isinstance(src, dict)
else f'<script src="{src}"></script>'
(
format_tag("script", src)
if isinstance(src, dict)
else f'<script src="{src}"></script>'
)
for src in srcs
]
+ [f"<script>{src}</script>" for src in self._inline_scripts]
Expand Down Expand Up @@ -1652,9 +1657,11 @@ def _setup_server(self):
# For each callback function, if the hidden parameter uses the default value None,
# replace it with the actual value of the self.config.hide_all_callbacks.
self._callback_list = [
{**_callback, "hidden": self.config.get("hide_all_callbacks", False)}
if _callback.get("hidden") is None
else _callback
(
{**_callback, "hidden": self.config.get("hide_all_callbacks", False)}
if _callback.get("hidden") is None
else _callback
)
for _callback in self._callback_list
]

Expand Down Expand Up @@ -2164,9 +2171,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] = [
Expand Down Expand Up @@ -2495,14 +2500,12 @@ def run(

def verify_url_part(served_part, url_part, part_name):
if served_part != url_part:
raise ProxyError(
f"""
raise ProxyError(f"""
{part_name}: {url_part} is incompatible with the proxy:
{proxy}
To see your app at {proxied_url.geturl()},
you must use {part_name}: {served_part}
"""
)
""")

verify_url_part(served_url.scheme, protocol, "protocol")
verify_url_part(served_url.hostname, host, "host")
Expand Down Expand Up @@ -2614,16 +2617,16 @@ async def update(pathname_, search_, **states):
if not self.config.suppress_callback_exceptions:
self.validation_layout = html.Div(
[
asyncio.run(execute_async_function(page["layout"]))
if callable(page["layout"])
else page["layout"]
(
asyncio.run(execute_async_function(page["layout"]))
if callable(page["layout"])
else page["layout"]
)
for page in _pages.PAGE_REGISTRY.values()
]
+ [
# pylint: disable=not-callable
self.layout()
if callable(self.layout)
else self.layout
self.layout() if callable(self.layout) else self.layout
]
)
if _ID_CONTENT not in self.validation_layout:
Expand Down Expand Up @@ -2680,15 +2683,15 @@ def update(pathname_, search_, **states):
if not isinstance(layout, list):
layout = [
# pylint: disable=not-callable
self.layout()
if callable(self.layout)
else self.layout
self.layout() if callable(self.layout) else self.layout
]
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
Expand Down
11 changes: 7 additions & 4 deletions dash/development/_jl_components_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,9 +355,11 @@ def nothing_or_string(v):
external_url=nothing_or_string(resource.get("external_url", "")),
dynamic=str(resource.get("dynamic", "nothing")).lower(),
type=metatype,
async_string=":{}".format(str(resource.get("async")).lower())
if "async" in resource.keys()
else "nothing",
async_string=(
":{}".format(str(resource.get("async")).lower())
if "async" in resource.keys()
else "nothing"
),
)
for resource in resources
]
Expand Down Expand Up @@ -468,7 +470,8 @@ def generate_class_string(name, props, description, project_shortname, prefix):
(
'WARNING: prop "{}" in component "{}" is a Julia keyword'
" - REMOVED FROM THE JULIA COMPONENT"
).format(item, name)
).format(item, name),
stacklevel=2,
)

default_paramtext += ", ".join(":{}".format(p) for p in prop_keys)
Expand Down
4 changes: 2 additions & 2 deletions dash/development/_r_components_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from ._all_keywords import r_keywords
from ._py_components_generation import reorder_props


# Declaring longer string templates as globals to improve
# readability, make method logic clearer to anyone inspecting
# code below
Expand Down Expand Up @@ -216,7 +215,8 @@ def generate_class_string(name, props, project_shortname, prefix):
(
'WARNING: prop "{}" in component "{}" is an R keyword'
" - REMOVED FROM THE R COMPONENT"
).format(item, name)
).format(item, name),
stacklevel=2,
)

default_argtext += ", ".join("{}=NULL".format(p) for p in prop_keys)
Expand Down
38 changes: 14 additions & 24 deletions dash/development/base_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,14 @@
rd = random.Random(0)

_deprecated_components = {
"dash_core_components": {
"LogoutButton": textwrap.dedent(
"""
"dash_core_components": {"LogoutButton": textwrap.dedent("""
The Logout Button is no longer used with Dash Enterprise and can be replaced with a html.Button or html.A.
eg: html.A(href=os.getenv('DASH_LOGOUT_URL'))
"""
)
},
"dash_table": {
"DataTable": textwrap.dedent(
"""
""")},
"dash_table": {"DataTable": textwrap.dedent("""
The dash_table.DataTable will be removed from the builtin dash components in a future major version.
We recommend using dash-ag-grid as a replacement. Install with `pip install dash[ag-grid]`.
"""
)
},
""")},
}


Expand All @@ -39,9 +31,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
Expand Down Expand Up @@ -221,26 +213,22 @@ def _set_random_id(self):
kind = f"`{self._namespace}.{self._type}`" # pylint: disable=no-member

if getattr(self, "persistence", False):
raise RuntimeError(
f"""
raise RuntimeError(f"""
Attempting to use an auto-generated ID with the `persistence` prop.
This is prohibited because persistence is tied to component IDs and
auto-generated IDs can easily change.

Please assign an explicit ID to this {kind} component.
"""
)
""")
if "dash_snapshots" in sys.modules:
raise RuntimeError(
f"""
raise RuntimeError(f"""
Attempting to use an auto-generated ID in an app with `dash_snapshots`.
This is prohibited because snapshots saves the whole app layout,
including component IDs, and auto-generated IDs can easily change.
Callbacks referencing the new IDs will not work with old snapshots.

Please assign an explicit ID to this {kind} component.
"""
)
""")

v = str(uuid.UUID(int=rd.randint(0, 2**128)))
setattr(self, "id", v)
Expand Down Expand Up @@ -451,7 +439,9 @@ def _validate_deprecation(self):
_ns = getattr(self, "_namespace", "")
deprecation_message = _deprecated_components.get(_ns, {}).get(_type)
if deprecation_message:
warnings.warn(DeprecationWarning(textwrap.dedent(deprecation_message)))
warnings.warn(
DeprecationWarning(textwrap.dedent(deprecation_message)), stacklevel=2
)


ComponentSingleType = typing.Union[str, int, float, Component, None]
Expand Down
16 changes: 6 additions & 10 deletions dash/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from .development.base_component import ComponentRegistry
from . import exceptions


# ResourceType has `async` key, use the init form to be able to provide it.
ResourceType = _tx.TypedDict(
"ResourceType",
Expand Down Expand Up @@ -59,12 +58,10 @@ def _filter_resources(
filtered_resource["dynamic"] = s["dynamic"]
if "async" in s:
if "dynamic" in s:
raise exceptions.ResourceException(
f"""
raise exceptions.ResourceException(f"""
Can't have both 'dynamic' and 'async'.
{json.dumps(filtered_resource)}
"""
)
""")

# Async assigns a value dynamically to 'dynamic'
# based on the value of 'async' and config.eager_loading
Expand Down Expand Up @@ -111,16 +108,15 @@ def _filter_resources(
"or `app.css.append_css`, use `external_scripts` "
"or `external_stylesheets` instead.\n"
"See https://dash.plotly.com/external-resources"
)
),
stacklevel=2,
)
continue
else:
raise exceptions.ResourceException(
f"""
raise exceptions.ResourceException(f"""
{json.dumps(filtered_resource)} does not have a
relative_package_path, absolute_path, or an external_url.
"""
)
""")

if valid_resource:
filtered_resources.append(filtered_resource)
Expand Down
Loading
Loading