Skip to content

Commit a13849b

Browse files
committed
Add typing compliance tests with mypy & pyright
1 parent 3349174 commit a13849b

File tree

2 files changed

+99
-13
lines changed

2 files changed

+99
-13
lines changed

requirements/ci.txt

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ xlrd>=2.0.1
1919
pytest-rerunfailures
2020
jupyterlab<4.0.0
2121
pyright==1.1.398;python_version>="3.7"
22+
mypy==1.15.0

tests/integration/test_typing.py

+98-13
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,55 @@
1111
t = TypeScriptComponent({0})
1212
"""
1313

14+
basic_app_template = """
15+
from dash import Dash, html, dcc, callback, Input, Output
1416
15-
def run_pyright(codefile: str):
17+
app = Dash()
18+
19+
{0}
20+
app.layout = {1}
21+
22+
@callback(Output("out", "children"), Input("btn", "n_clicks"))
23+
def on_click() -> html.Div:
24+
return {2}
25+
"""
26+
27+
valid_layout = """html.Div([
28+
html.H2('Valid'),
29+
'String in middle',
30+
123,
31+
404.4,
32+
dcc.Input(value='', id='in')
33+
])
34+
"""
35+
valid_layout_list = """[
36+
html.H2('Valid'),
37+
'String in middle',
38+
123,
39+
404.4,
40+
dcc.Input(value='', id='in')
41+
]
42+
"""
43+
valid_layout_function = """
44+
def layout() -> html.Div:
45+
return html.Div(["hello layout"])
46+
47+
"""
48+
49+
invalid_layout = """html.Div([
50+
{"invalid": "dictionary in children"}
51+
])
52+
"""
53+
# There is not invalid layout for function & list as explicitly typed as Any to avoid special cases.
54+
55+
valid_callback = "html.Div('Valid')"
56+
invalid_callback = "[]"
57+
58+
59+
def run_module(codefile: str, module: str, extra: str = ""):
1660

1761
cmd = shlex.split(
18-
f"pyright {codefile}",
62+
f"{sys.executable} -m {module} {codefile}{extra}",
1963
posix=sys.platform != "win32",
2064
comments=True,
2165
)
@@ -32,17 +76,39 @@ def run_pyright(codefile: str):
3276
return out.decode(), err.decode(), proc.poll()
3377

3478

35-
def assert_pyright_output(
36-
codefile: str, expected_outputs=tuple(), expected_errors=tuple(), expected_status=0
79+
def assert_output(
80+
codefile: str,
81+
code: str,
82+
expected_outputs=tuple(),
83+
expected_errors=tuple(),
84+
expected_status=0,
85+
module="pyright",
3786
):
38-
output, error, status = run_pyright(codefile)
87+
output, error, status = run_module(codefile, module)
3988
assert (
4089
status == expected_status
41-
), f"Status: {status}\nOutput: {output}\nError: {error}"
90+
), f"Status: {status}\nOutput: {output}\nError: {error}\nCode: {code}"
4291
for ex_out in expected_outputs:
43-
assert ex_out in output, f"Invalid output:\n {output}"
44-
for ex_err in expected_errors:
45-
assert ex_err in error
92+
assert ex_out in output, f"Invalid output:\n {output}\n\nCode: {code}"
93+
94+
95+
def format_template_and_save(template, filename, *args):
96+
formatted = template.format(*args)
97+
with open(filename, "w") as f:
98+
f.write(formatted)
99+
return formatted
100+
101+
102+
def expect(status=None, outputs=None, modular=False):
103+
data = {}
104+
if status is not None:
105+
data["expected_status"] = status
106+
if outputs is not None:
107+
data["expected_outputs"] = outputs
108+
if modular:
109+
# The expectations are per module.
110+
data["modular"] = modular
111+
return data
46112

47113

48114
@pytest.mark.parametrize(
@@ -247,9 +313,28 @@ def assert_pyright_output(
247313
),
248314
],
249315
)
250-
def test_component_typing(arguments, assertions, tmp_path):
316+
def test_typi001_component_typing(arguments, assertions, tmp_path):
251317
codefile = os.path.join(tmp_path, "code.py")
252-
with open(codefile, "w") as f:
253-
f.write(component_template.format(arguments))
318+
code = format_template_and_save(component_template, codefile, arguments)
319+
assert_output(codefile, code, module="pyright", **assertions)
320+
254321

255-
assert_pyright_output(codefile, **assertions)
322+
@pytest.mark.parametrize("typing_module", ["pyright", "mypy"])
323+
@pytest.mark.parametrize(
324+
"prelayout, layout, callback_return, assertions",
325+
[
326+
("", valid_layout, valid_callback, expect(status=0)),
327+
("", valid_layout_list, valid_callback, expect(status=0)),
328+
(valid_layout_function, "layout", valid_callback, expect(status=0)),
329+
("", valid_layout, invalid_callback, expect(status=1)),
330+
("", invalid_layout, valid_callback, expect(status=1)),
331+
],
332+
)
333+
def test_typi002_typing_compliance(
334+
typing_module, prelayout, layout, callback_return, assertions, tmp_path
335+
):
336+
codefile = os.path.join(tmp_path, "code.py")
337+
code = format_template_and_save(
338+
basic_app_template, codefile, prelayout, layout, callback_return
339+
)
340+
assert_output(codefile, code, module=typing_module, **assertions)

0 commit comments

Comments
 (0)