Skip to content

Commit a81f4e8

Browse files
authored
Merge pull request #3738 from stef41/fix/add-missing-stacklevel-to-warnings
Add missing stacklevel=2 to 9 warnings.warn() calls
2 parents 0d85cad + 334ce0a commit a81f4e8

10 files changed

Lines changed: 153 additions & 53 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Post Test Status
2+
3+
# This workflow runs in the base repo context (with write permissions)
4+
# even for fork PRs, allowing us to post commit statuses and check runs.
5+
#
6+
# Add any status posting or check run creation for fork PRs here to avoid
7+
# "Resource not accessible by integration" errors.
8+
9+
on:
10+
workflow_run:
11+
workflows: ["Dash Testing"]
12+
types:
13+
- completed
14+
15+
jobs:
16+
post-skipped-statuses:
17+
name: Post Statuses for Skipped Jobs
18+
runs-on: ubuntu-latest
19+
if: github.event.workflow_run.event == 'pull_request'
20+
permissions:
21+
statuses: write
22+
actions: read
23+
steps:
24+
- name: Post statuses for skipped jobs
25+
uses: actions/github-script@v7
26+
with:
27+
script: |
28+
const { owner, repo } = context.repo;
29+
const runId = context.payload.workflow_run.id;
30+
const headSha = context.payload.workflow_run.head_sha;
31+
32+
// Define jobs that need a success status posted when skipped
33+
const skippedStatusJobs = [
34+
{
35+
jobName: 'Dash Table Visual Tests',
36+
statusContext: 'percy/dash-table-test',
37+
description: 'Skipped — no dash-table changes'
38+
}
39+
// Add more jobs here as needed
40+
];
41+
42+
// Get all jobs for the workflow run
43+
const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({
44+
owner,
45+
repo,
46+
run_id: runId,
47+
});
48+
49+
// Post status for each skipped job
50+
for (const { jobName, statusContext, description } of skippedStatusJobs) {
51+
const job = jobs.find(j => j.name === jobName);
52+
53+
if (job && job.conclusion === 'skipped') {
54+
await github.rest.repos.createCommitStatus({
55+
owner,
56+
repo,
57+
sha: headSha,
58+
state: 'success',
59+
context: statusContext,
60+
description: description,
61+
});
62+
console.log(`Posted skipped status for ${statusContext}`);
63+
} else {
64+
console.log(`Job "${jobName}" status: ${job?.conclusion ?? 'not found'} - no status posted`);
65+
}
66+
}
67+
68+
test-report:
69+
name: Consolidated Test Report (Fork PR)
70+
runs-on: ubuntu-latest
71+
# Only run for fork PRs (non-fork PRs are handled in the main workflow)
72+
if: |
73+
github.event.workflow_run.event == 'pull_request' &&
74+
github.event.workflow_run.head_repository.full_name != github.repository
75+
permissions:
76+
checks: write
77+
actions: read
78+
steps:
79+
- name: Download test results artifact
80+
uses: actions/download-artifact@v4
81+
with:
82+
pattern: '*-results-*'
83+
path: test-results
84+
merge-multiple: false
85+
github-token: ${{ secrets.GITHUB_TOKEN }}
86+
run-id: ${{ github.event.workflow_run.id }}
87+
88+
- name: List downloaded results
89+
run: find test-results -name "*.xml" -type f 2>/dev/null || echo "No XML files found"
90+
91+
- name: Publish Test Report
92+
uses: dorny/test-reporter@v1
93+
if: always()
94+
with:
95+
name: Test Results Summary
96+
path: 'test-results/**/*.xml'
97+
reporter: java-junit
98+
fail-on-error: false
99+
fail-on-empty: false
100+
list-suites: 'failed'
101+
list-tests: 'failed'
102+
max-annotations: '50'

.github/workflows/testing.yml

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -918,30 +918,6 @@ jobs:
918918
if: env.PERCY_TOKEN == ''
919919
run: echo "::notice::Skipping Percy finalize (no token available - likely a fork PR)"
920920

921-
report-table-percy-skipped:
922-
name: Report Percy Table Skipped
923-
needs: table-visual-test
924-
runs-on: ubuntu-latest
925-
if: |
926-
always() &&
927-
github.event_name == 'pull_request' &&
928-
needs.table-visual-test.result == 'skipped'
929-
permissions:
930-
statuses: write
931-
steps:
932-
- name: Post success status for percy/dash-table-test
933-
uses: actions/github-script@v7
934-
with:
935-
script: |
936-
await github.rest.repos.createCommitStatus({
937-
owner: context.repo.owner,
938-
repo: context.repo.repo,
939-
sha: context.payload.pull_request.head.sha,
940-
state: 'success',
941-
context: 'percy/dash-table-test',
942-
description: 'Skipped — no dash-table changes',
943-
});
944-
945921
test-report:
946922
name: Consolidated Test Report
947923
needs: [lint-unit, test-main, dcc-test, html-test, table-server, background-callbacks, test-typing]
@@ -963,7 +939,8 @@ jobs:
963939

964940
- name: Publish Test Report
965941
uses: dorny/test-reporter@v1
966-
if: always()
942+
# Skip for fork PRs - handled by post-test-status.yml workflow_run
943+
if: always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
967944
with:
968945
name: Test Results Summary
969946
path: 'test-results/**/*.xml'

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1515
## Fixed
1616
- [#3690](https://github.com/plotly/dash/pull/3690) Fixes Input when min or max is set to None
1717
- [#3723](https://github.com/plotly/dash/pull/3723) Fix misaligned `dcc.Slider` marks when some labels are empty strings
18+
- [#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
1819
- [#3740](https://github.com/plotly/dash/pull/3740) Fix cannot tab into dropdowns in Safari
1920
- [#2462](https://github.com/plotly/dash/issues/2462) Allow `MATCH` in `Input`/`State` when the callback's `Output` has no wildcards (fixed-id Output, no Output, or `ALL`-only wildcard Output). `ALLSMALLER` still requires a corresponding `MATCH` in an Output.
2021

dash/_callback_context.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from . import exceptions
1010
from ._utils import AttributeDict, stringify_id
1111

12-
1312
context_value: contextvars.ContextVar[
1413
typing.Dict[str, typing.Any]
1514
] = contextvars.ContextVar("callback_context")
@@ -177,6 +176,7 @@ def outputs_list(self):
177176
warnings.warn(
178177
"outputs_list is deprecated, use outputs_grouping instead",
179178
DeprecationWarning,
179+
stacklevel=2,
180180
)
181181

182182
return getattr(_get_context_value(), "outputs_list", [])
@@ -188,6 +188,7 @@ def inputs_list(self):
188188
warnings.warn(
189189
"inputs_list is deprecated, use args_grouping instead",
190190
DeprecationWarning,
191+
stacklevel=2,
191192
)
192193

193194
return getattr(_get_context_value(), "inputs_list", [])
@@ -199,6 +200,7 @@ def states_list(self):
199200
warnings.warn(
200201
"states_list is deprecated, use args_grouping instead",
201202
DeprecationWarning,
203+
stacklevel=2,
202204
)
203205
return getattr(_get_context_value(), "states_list", [])
204206

dash/dash.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,8 @@ def __init__( # pylint: disable=too-many-statements
663663
if self.__class__.__name__ == "JupyterDash":
664664
warnings.warn(
665665
"JupyterDash is deprecated, use Dash instead.\n"
666-
"See https://dash.plotly.com/dash-in-jupyter for more details."
666+
"See https://dash.plotly.com/dash-in-jupyter for more details.",
667+
stacklevel=2,
667668
)
668669
self.setup_startup_routes()
669670

@@ -1139,9 +1140,11 @@ def _generate_css_dist_html(self):
11391140

11401141
return "\n".join(
11411142
[
1142-
format_tag("link", link, opened=True)
1143-
if isinstance(link, dict)
1144-
else f'<link rel="stylesheet" href="{link}">'
1143+
(
1144+
format_tag("link", link, opened=True)
1145+
if isinstance(link, dict)
1146+
else f'<link rel="stylesheet" href="{link}">'
1147+
)
11451148
for link in (external_links + links)
11461149
]
11471150
)
@@ -1195,9 +1198,11 @@ def _generate_scripts_html(self) -> str:
11951198

11961199
return "\n".join(
11971200
[
1198-
format_tag("script", src)
1199-
if isinstance(src, dict)
1200-
else f'<script src="{src}"></script>'
1201+
(
1202+
format_tag("script", src)
1203+
if isinstance(src, dict)
1204+
else f'<script src="{src}"></script>'
1205+
)
12011206
for src in srcs
12021207
]
12031208
+ [f"<script>{src}</script>" for src in self._inline_scripts]
@@ -1674,9 +1679,11 @@ def _setup_server(self):
16741679
# For each callback function, if the hidden parameter uses the default value None,
16751680
# replace it with the actual value of the self.config.hide_all_callbacks.
16761681
self._callback_list = [
1677-
{**_callback, "hidden": self.config.get("hide_all_callbacks", False)}
1678-
if _callback.get("hidden") is None
1679-
else _callback
1682+
(
1683+
{**_callback, "hidden": self.config.get("hide_all_callbacks", False)}
1684+
if _callback.get("hidden") is None
1685+
else _callback
1686+
)
16801687
for _callback in self._callback_list
16811688
]
16821689

@@ -2636,9 +2643,11 @@ async def update(pathname_, search_, **states):
26362643
if not self.config.suppress_callback_exceptions:
26372644
self.validation_layout = html.Div(
26382645
[
2639-
asyncio.run(execute_async_function(page["layout"]))
2640-
if callable(page["layout"])
2641-
else page["layout"]
2646+
(
2647+
asyncio.run(execute_async_function(page["layout"]))
2648+
if callable(page["layout"])
2649+
else page["layout"]
2650+
)
26422651
for page in _pages.PAGE_REGISTRY.values()
26432652
]
26442653
+ [
@@ -2708,9 +2717,11 @@ def update(pathname_, search_, **states):
27082717
]
27092718
self.validation_layout = html.Div(
27102719
[
2711-
page["layout"]()
2712-
if callable(page["layout"])
2713-
else page["layout"]
2720+
(
2721+
page["layout"]()
2722+
if callable(page["layout"])
2723+
else page["layout"]
2724+
)
27142725
for page in _pages.PAGE_REGISTRY.values()
27152726
]
27162727
+ layout

dash/development/_jl_components_generation.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,11 @@ def nothing_or_string(v):
355355
external_url=nothing_or_string(resource.get("external_url", "")),
356356
dynamic=str(resource.get("dynamic", "nothing")).lower(),
357357
type=metatype,
358-
async_string=":{}".format(str(resource.get("async")).lower())
359-
if "async" in resource.keys()
360-
else "nothing",
358+
async_string=(
359+
":{}".format(str(resource.get("async")).lower())
360+
if "async" in resource.keys()
361+
else "nothing"
362+
),
361363
)
362364
for resource in resources
363365
]
@@ -468,7 +470,8 @@ def generate_class_string(name, props, description, project_shortname, prefix):
468470
(
469471
'WARNING: prop "{}" in component "{}" is a Julia keyword'
470472
" - REMOVED FROM THE JULIA COMPONENT"
471-
).format(item, name)
473+
).format(item, name),
474+
stacklevel=2,
472475
)
473476

474477
default_paramtext += ", ".join(":{}".format(p) for p in prop_keys)

dash/development/_r_components_generation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from ._all_keywords import r_keywords
1212
from ._py_components_generation import reorder_props
1313

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

222222
default_argtext += ", ".join("{}=NULL".format(p) for p in prop_keys)

dash/development/base_component.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,9 @@ def _validate_deprecation(self):
451451
_ns = getattr(self, "_namespace", "")
452452
deprecation_message = _deprecated_components.get(_ns, {}).get(_type)
453453
if deprecation_message:
454-
warnings.warn(DeprecationWarning(textwrap.dedent(deprecation_message)))
454+
warnings.warn(
455+
DeprecationWarning(textwrap.dedent(deprecation_message)), stacklevel=2
456+
)
455457

456458

457459
ComponentSingleType = typing.Union[str, int, float, Component, None]

dash/resources.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from .development.base_component import ComponentRegistry
1010
from . import exceptions
1111

12-
1312
# ResourceType has `async` key, use the init form to be able to provide it.
1413
ResourceType = _tx.TypedDict(
1514
"ResourceType",
@@ -111,7 +110,8 @@ def _filter_resources(
111110
"or `app.css.append_css`, use `external_scripts` "
112111
"or `external_stylesheets` instead.\n"
113112
"See https://dash.plotly.com/external-resources"
114-
)
113+
),
114+
stacklevel=2,
115115
)
116116
continue
117117
else:

dash/testing/browser.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
from dash.testing.errors import DashAppLoadingError, BrowserError, TestingTimeoutError
3333
from dash.testing.consts import SELENIUM_GRID_DEFAULT
3434

35-
3635
logger = logging.getLogger(__name__)
3736

3837

@@ -629,7 +628,10 @@ def get_logs(self):
629628
for entry in self.driver.get_log("browser")
630629
if entry["timestamp"] > self._last_ts
631630
]
632-
warnings.warn("get_logs always return None with webdrivers other than Chrome")
631+
warnings.warn(
632+
"get_logs always return None with webdrivers other than Chrome",
633+
stacklevel=2,
634+
)
633635
return None
634636

635637
def reset_log_timestamp(self):

0 commit comments

Comments
 (0)