Skip to content

Commit 4a12177

Browse files
authored
Merge branch 'dev' into improve-layout-error
2 parents 191b054 + 835da28 commit 4a12177

File tree

19 files changed

+287
-236
lines changed

19 files changed

+287
-236
lines changed

.circleci/config.yml

Lines changed: 103 additions & 98 deletions
Large diffs are not rendered by default.

.pylintrc

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# A comma-separated list of package or module names from where C extensions may
44
# be loaded. Extensions are loading into the active Python interpreter and may
55
# run arbitrary code
6-
extension-pkg-whitelist=
6+
extension-pkg-allow-list=
77

88
# Add files or directories to the blacklist. They should be base names, not
99
# paths.
@@ -68,7 +68,13 @@ disable=fixme,
6868
superfluous-parens,
6969
bad-continuation,
7070
line-too-long,
71-
bad-option-value
71+
bad-option-value,
72+
use-dict-literal,
73+
missing-timeout,
74+
unnecessary-dunder-call,
75+
unnecessary-lambda-assignment,
76+
broad-exception-raised,
77+
consider-using-generator,
7278

7379

7480
# Enable the message, report, category or checker with the given id(s). You can
@@ -234,13 +240,6 @@ indent-string=' '
234240
# Maximum number of lines in a module
235241
max-module-lines=1000
236242

237-
# List of optional constructs for which whitespace checking is disabled. `dict-
238-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
239-
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
240-
# `empty-line` allows space-only lines.
241-
no-space-check=trailing-comma,
242-
dict-separator
243-
244243
# Allow the body of a class to be on the same line as the declaration if body
245244
# contains single statement.
246245
single-line-class-stmt=no
@@ -415,7 +414,7 @@ max-bool-expr=5
415414
max-branches=15
416415

417416
# Maximum number of locals for function / method body
418-
max-locals=15
417+
max-locals=18
419418

420419
# Maximum number of parents for a class (see R0901).
421420
max-parents=7
@@ -467,10 +466,3 @@ known-standard-library=
467466

468467
# Force import order to recognize a module as part of a third party library.
469468
known-third-party=enchant
470-
471-
472-
[EXCEPTIONS]
473-
474-
# Exceptions that will emit a warning when being caught. Defaults to
475-
# "Exception"
476-
overgeneral-exceptions=Exception

.pylintrc310 renamed to .pylintrc312

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# A comma-separated list of package or module names from where C extensions may
44
# be loaded. Extensions are loading into the active Python interpreter and may
55
# run arbitrary code.
6-
extension-pkg-whitelist=
6+
extension-pkg-allow-list=
77

88
# Add files or directories to the blacklist. They should be base names, not
99
# paths.
@@ -159,7 +159,13 @@ disable=invalid-name,
159159
line-too-long,
160160
super-with-arguments,
161161
raise-missing-from,
162-
bad-option-value
162+
bad-option-value,
163+
use-dict-literal,
164+
missing-timeout,
165+
unnecessary-dunder-call,
166+
unnecessary-lambda-assignment,
167+
broad-exception-raised,
168+
consider-using-generator,
163169

164170
# Enable the message, report, category or checker with the given id(s). You can
165171
# either give multiple identifier separated by comma (,) or put this option
@@ -332,13 +338,6 @@ indent-string=' '
332338
# Maximum number of lines in a module.
333339
max-module-lines=1000
334340

335-
# List of optional constructs for which whitespace checking is disabled. `dict-
336-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
337-
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
338-
# `empty-line` allows space-only lines.
339-
no-space-check=trailing-comma,
340-
dict-separator
341-
342341
# Allow the body of a class to be on the same line as the declaration if body
343342
# contains single statement.
344343
single-line-class-stmt=no
@@ -516,7 +515,7 @@ max-bool-expr=5
516515
max-branches=15
517516

518517
# Maximum number of locals for function / method body.
519-
max-locals=15
518+
max-locals=18
520519

521520
# Maximum number of parents for a class (see R0901).
522521
max-parents=7
@@ -574,4 +573,4 @@ known-third-party=enchant
574573

575574
# Exceptions that will emit a warning when being caught. Defaults to
576575
# "Exception".
577-
overgeneral-exceptions=Exception
576+
overgeneral-exceptions=builtins.Exception

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
77
## Changed
88

99
- [#2734](https://github.com/plotly/dash/pull/2734) Configure CI for Python 3.10 [#1863](https://github.com/plotly/dash/issues/1863)
10+
- [#2735](https://github.com/plotly/dash/pull/2735) Configure CI for Python 3.8 and 3.12, drop support for Python 3.6 and Python 3.7 [#2736](https://github.com/plotly/dash/issues/2736)
1011

1112
## [2.15.0] - 2024-01-31
1213

components/dash-core-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"private::format.black": "black dash_core_components_base/ tests/ setup.py",
1616
"private::format.eslint": "eslint src --fix",
1717
"private::format.prettier": "prettier --config .prettierrc --write src/**/*.js",
18-
"private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python310') !== 'python36'){process.exit(1)} \" || black --check dash_core_components_base/ tests/ setup.py",
18+
"private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python312') !== 'python38'){process.exit(1)} \" || black --check dash_core_components_base/ tests/ setup.py",
1919
"private::lint.eslint": "eslint src",
2020
"private::lint.flake8": "flake8 --exclude=dash_core_components,node_modules,venv",
2121
"private::lint.prettier": "prettier --config .prettierrc src/**/*.js --list-different",

components/dash-table/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"private::format.black": "black dash_table_base tests",
2424
"private::lint.ts": "eslint ./src ./tests",
2525
"private::lint.flake": "flake8 dash_table_base tests",
26-
"private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python310') !== 'python36'){process.exit(1)} \" || black --check dash_table_base tests",
26+
"private::lint.black": "node -e \"if ((process.env.PYVERSION || 'python312') !== 'python38'){process.exit(1)} \" || black --check dash_table_base tests",
2727
"private::lint.prettier": "prettier --config .prettierrc \"{src,tests,demo}/**/*.{js,ts,tsx}\" --list-different",
2828
"private::test.python": "python -m unittest tests/unit/format_test.py",
2929
"private::test.unit": "karma start karma.conf.js --single-run",

components/dash-table/tests/selenium/conftest.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,102 @@
11
import platform
22
import pytest
3+
from functools import wraps
4+
import inspect
35

46
from dash.testing.browser import Browser
5-
from preconditions import preconditions
67
from selenium.webdriver.common.action_chains import ActionChains
78
from selenium.webdriver.common.by import By
89
from selenium.webdriver.common.keys import Keys
910
from selenium.webdriver.support import expected_conditions as EC
1011
from selenium.webdriver.support.wait import WebDriverWait
1112

13+
14+
# @preconditions decorator, copied from the preconditions PyPI package
15+
# https://pypi.org/project/preconditions/
16+
# https://github.com/nejucomo/preconditions
17+
# and modified to support Python 3.12
18+
19+
20+
class PreconditionError(TypeError):
21+
pass
22+
23+
24+
def preconditions(*precs):
25+
stripped_source = lambda obj: inspect.getsource(obj).strip()
26+
27+
if not precs:
28+
# This edge case makes ``@preconditions()`` efficiently delegate
29+
# to the wrapped function, which I anticipate will be useful
30+
# for stubbing and code consistency in applications:
31+
def null_decorator(f):
32+
f.nopre = f # Meet the .nopre interface requirement.
33+
return f
34+
35+
return null_decorator
36+
37+
precinfo = []
38+
for p in precs:
39+
spec = inspect.getfullargspec(p)
40+
if spec.varargs or spec.varkw:
41+
raise PreconditionError(
42+
(
43+
"Invalid precondition must not accept * nor ** args:\n" + " {!s}\n"
44+
).format(stripped_source(p))
45+
)
46+
47+
i = -len(spec.defaults or ())
48+
if i == 0:
49+
appargs, closureargs = spec.args, []
50+
else:
51+
appargs, closureargs = spec.args[:i], spec.args[i:]
52+
precinfo.append((appargs, closureargs, p))
53+
54+
def decorate(f):
55+
fspec = inspect.getfullargspec(f)
56+
57+
for (appargs, closureargs, p) in precinfo:
58+
for apparg in appargs:
59+
if apparg not in fspec.args:
60+
raise PreconditionError(
61+
(
62+
"Invalid precondition refers to unknown parameter {!r}:\n"
63+
+ " {!s}\n"
64+
+ "Known parameters: {!r}\n"
65+
).format(apparg, stripped_source(p), fspec.args)
66+
)
67+
for carg in closureargs:
68+
if carg in fspec.args:
69+
raise PreconditionError(
70+
(
71+
"Invalid precondition masks parameter {!r}:\n"
72+
+ " {!s}\n"
73+
+ "Known parameters: {!r}\n"
74+
).format(carg, stripped_source(p), fspec.args)
75+
)
76+
77+
@wraps(f)
78+
def g(*a, **kw):
79+
args = inspect.getcallargs(f, *a, **kw)
80+
for (appargs, _, p) in precinfo:
81+
if not p(*[args[aa] for aa in appargs]):
82+
raise PreconditionError(
83+
"Precondition failed in call {!r}{}:\n {!s}\n".format(
84+
g,
85+
inspect.formatargvalues(
86+
fspec.args, fspec.varargs, fspec.varkw, args
87+
),
88+
stripped_source(p),
89+
)
90+
)
91+
92+
return f(*a, **kw)
93+
94+
g.nopre = f
95+
return g
96+
97+
return decorate
98+
99+
12100
_validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or (
13101
isinstance(col, int) and col >= 0
14102
)

dash/_callback.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
from ._callback_context import context_value
3636

3737

38+
def _invoke_callback(func, *args, **kwargs): # used to mark the frame for the debugger
39+
return func(*args, **kwargs) # %% callback invoked %%
40+
41+
3842
class NoUpdate:
3943
def to_plotly_json(self): # pylint: disable=no-self-use
4044
return {"_dash_no_update": "_dash_no_update"}
@@ -438,8 +442,7 @@ def add_context(*args, **kwargs):
438442
if output_value is callback_manager.UNDEFINED:
439443
return to_json(response)
440444
else:
441-
# don't touch the comment on the next line - used by debugger
442-
output_value = func(*func_args, **func_kwargs) # %% callback invoked %%
445+
output_value = _invoke_callback(func, *func_args, **func_kwargs)
443446

444447
if NoUpdate.is_no_update(output_value):
445448
raise PreventUpdate

dash/_jupyter.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import sys
99
import threading
1010
import time
11-
import traceback
1211

1312
from typing_extensions import Literal
1413

@@ -37,12 +36,18 @@
3736

3837

3938
def _get_skip(error: Exception):
40-
tb = traceback.format_exception(type(error), error, error.__traceback__)
41-
skip = 0
42-
for i, line in enumerate(tb):
43-
if "%% callback invoked %%" in line:
44-
skip = i + 1
45-
break
39+
from dash._callback import ( # pylint: disable=import-outside-toplevel
40+
_invoke_callback,
41+
)
42+
43+
tb = error.__traceback__
44+
skip = 1
45+
while tb.tb_next is not None:
46+
skip += 1
47+
tb = tb.tb_next
48+
if tb.tb_frame.f_code is _invoke_callback.__code__:
49+
return skip
50+
4651
return skip
4752

4853

dash/dash.py

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -146,32 +146,44 @@ def _get_traceback(secret, error: Exception):
146146
except ImportError:
147147
tbtools = None
148148

149-
def _get_skip(text, divider=2):
150-
skip = 0
151-
for i, line in enumerate(text):
152-
if "%% callback invoked %%" in line:
153-
skip = int((i + 1) / divider)
154-
break
149+
def _get_skip(error):
150+
from dash._callback import ( # pylint: disable=import-outside-toplevel
151+
_invoke_callback,
152+
)
153+
154+
tb = error.__traceback__
155+
skip = 1
156+
while tb.tb_next is not None:
157+
skip += 1
158+
tb = tb.tb_next
159+
if tb.tb_frame.f_code is _invoke_callback.__code__:
160+
return skip
161+
155162
return skip
156163

164+
def _do_skip(error):
165+
from dash._callback import ( # pylint: disable=import-outside-toplevel
166+
_invoke_callback,
167+
)
168+
169+
tb = error.__traceback__
170+
while tb.tb_next is not None:
171+
if tb.tb_frame.f_code is _invoke_callback.__code__:
172+
return tb.tb_next
173+
tb = tb.tb_next
174+
return error.__traceback__
175+
157176
# werkzeug<2.1.0
158177
if hasattr(tbtools, "get_current_traceback"):
159-
tb = tbtools.get_current_traceback()
160-
skip = _get_skip(tb.plaintext.splitlines())
161-
return tbtools.get_current_traceback(skip=skip).render_full()
178+
return tbtools.get_current_traceback(skip=_get_skip(error)).render_full()
162179

163180
if hasattr(tbtools, "DebugTraceback"):
164-
tb = tbtools.DebugTraceback(error) # pylint: disable=no-member
165-
skip = _get_skip(tb.render_traceback_text().splitlines())
166-
167181
# pylint: disable=no-member
168-
return tbtools.DebugTraceback(error, skip=skip).render_debugger_html(
169-
True, secret, True
170-
)
182+
return tbtools.DebugTraceback(
183+
error, skip=_get_skip(error)
184+
).render_debugger_html(True, secret, True)
171185

172-
tb = traceback.format_exception(type(error), error, error.__traceback__)
173-
skip = _get_skip(tb, 1)
174-
return tb[0] + "".join(tb[skip:])
186+
return "".join(traceback.format_exception(type(error), error, _do_skip(error)))
175187

176188

177189
# Singleton signal to not update an output, alternative to PreventUpdate

0 commit comments

Comments
 (0)