Skip to content

Commit 90f5e2b

Browse files
authored
pytest: typecheck with pyrefly (#5898)
Helps to ensure the generated type stubs are compatible with it
1 parent fea8332 commit 90f5e2b

File tree

9 files changed

+78
-14
lines changed

9 files changed

+78
-14
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -793,9 +793,14 @@ jobs:
793793
- run: python -m pip install --upgrade pip && pip install nox[uv]
794794
- run: nox -s test-introspection
795795

796-
mypy-pytests:
796+
pytests-type-checking:
797797
needs: [fmt]
798798
runs-on: ubuntu-latest
799+
strategy:
800+
# If one platform fails, allow the rest to keep testing if `CI-no-fail-fast` label is present
801+
fail-fast: ${{ !contains(github.event.pull_request.labels.*.name, 'CI-no-fail-fast') }}
802+
matrix:
803+
checker: [mypy, pyrefly]
799804
steps:
800805
- uses: actions/checkout@v6.0.2
801806
- uses: dtolnay/rust-toolchain@stable
@@ -805,7 +810,7 @@ jobs:
805810
with:
806811
python-version: "3.14"
807812
- run: python -m pip install --upgrade pip && pip install nox[uv]
808-
- run: nox -s mypy
813+
- run: nox -s ${{matrix.checker}}
809814
working-directory: pytests
810815

811816
conclusion:

pytests/noxfile.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Iterable
12
import shutil
23
from pathlib import Path
34

@@ -41,19 +42,29 @@ def bench(session: nox.Session):
4142

4243
@nox.session
4344
def mypy(session: nox.Session):
45+
type_checker(
46+
session,
47+
("python", "-m", "mypy", "tests"),
48+
)
49+
# TODO: enable stubtest session.run_always("python", "-m", "mypy.stubtest", "pyo3_pytests")
50+
51+
52+
@nox.session
53+
def pyrefly(session: nox.Session):
54+
type_checker(
55+
session,
56+
("python", "-m", "pyrefly", "check", "tests"),
57+
)
58+
59+
60+
def type_checker(session: nox.Session, command: Iterable[str]):
4461
session.env["MATURIN_PEP517_ARGS"] = "--profile=dev"
4562
try:
4663
# We move the stubs where maturin is expecting them to be
4764
shutil.copytree("stubs", "pyo3_pytests")
4865
(Path("pyo3_pytests") / "py.typed").touch()
4966
session.install(".[dev]")
5067

51-
session.run_always(
52-
"python",
53-
"-m",
54-
"mypy",
55-
"tests",
56-
)
57-
# TODO: enable stubtest when previously listed errors will be fixed session.run_always("python", "-m", "mypy.stubtest", "pyo3_pytests")
68+
session.run_always(*command)
5869
finally:
5970
shutil.rmtree("pyo3_pytests")

pytests/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ classifiers = [
2222
dev = [
2323
"hypothesis>=3.55",
2424
"mypy~=1.0",
25+
"pyrefly~=0.57.0",
2526
"pytest-asyncio>=0.21,<2",
2627
"pytest-benchmark>=3.4",
2728
"pytest>=7",

pytests/src/awaitable.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ pub mod awaitable {
4444
_ => Ok(py.None()),
4545
}
4646
}
47+
48+
fn send(&mut self, value: Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
49+
self.__next__(value.py())
50+
}
51+
52+
#[pyo3(signature = (value, _a = None, _b = None))]
53+
fn throw(
54+
&mut self,
55+
value: Bound<'_, PyAny>,
56+
_a: Option<Bound<'_, PyAny>>,
57+
_b: Option<Bound<'_, PyAny>>,
58+
) -> PyResult<Py<PyAny>> {
59+
self.__next__(value.py())
60+
}
61+
62+
fn close(&self) {}
4763
}
4864

4965
#[pyclass]
@@ -80,5 +96,24 @@ pub mod awaitable {
8096
_ => Ok(pyself),
8197
}
8298
}
99+
100+
fn send<'py>(
101+
pyself: PyRefMut<'py, Self>,
102+
_value: Bound<'py, PyAny>,
103+
) -> PyResult<PyRefMut<'py, Self>> {
104+
Self::__next__(pyself)
105+
}
106+
107+
#[pyo3(signature = (_value, _a = None, _b = None))]
108+
fn throw<'py>(
109+
pyself: PyRefMut<'py, Self>,
110+
_value: Bound<'py, PyAny>,
111+
_a: Option<Bound<'py, PyAny>>,
112+
_b: Option<Bound<'py, PyAny>>,
113+
) -> PyResult<PyRefMut<'py, Self>> {
114+
Self::__next__(pyself)
115+
}
116+
117+
fn close(&self) {}
83118
}
84119
}

pytests/stubs/awaitable.pyi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,20 @@ class FutureAwaitable:
1010
def _asyncio_future_blocking(self, /) -> bool: ...
1111
@_asyncio_future_blocking.setter
1212
def _asyncio_future_blocking(self, /, value: bool) -> None: ...
13+
def close(self, /) -> None: ...
14+
def send(self, /, _value: Any) -> FutureAwaitable: ...
15+
def throw(
16+
self, /, _value: Any, _a: Any | None = None, _b: Any | None = None
17+
) -> FutureAwaitable: ...
1318

1419
@final
1520
class IterAwaitable:
1621
def __await__(self, /) -> IterAwaitable: ...
1722
def __iter__(self, /) -> IterAwaitable: ...
1823
def __new__(cls, /, result: Any) -> IterAwaitable: ...
1924
def __next__(self, /) -> Any: ...
25+
def close(self, /) -> None: ...
26+
def send(self, /, value: Any) -> Any: ...
27+
def throw(
28+
self, /, value: Any, _a: Any | None = None, _b: Any | None = None
29+
) -> Any: ...

pytests/tests/test_datetime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def test_date_from_timestamp(d):
107107
rdt.date_from_timestamp(ts)
108108
assert str(exc_info.value) == str(pdt_fail)
109109
else:
110-
assert rdt.date_from_timestamp(int(ts)) == expected
110+
assert rdt.date_from_timestamp(ts) == expected
111111

112112

113113
@pytest.mark.parametrize(
@@ -233,7 +233,7 @@ def test_invalid_datetime_fails():
233233

234234
def test_datetime_typeerror():
235235
with pytest.raises(TypeError):
236-
rdt.make_datetime("2011", 1, 1, 0, 0, 0, 0)
236+
rdt.make_datetime("2011", 1, 1, 0, 0, 0, 0) # type: ignore[bad-argument-type]
237237

238238

239239
@given(dt=st.datetimes(MIN_DATETIME, MAX_DATETIME))

pytests/tests/test_misc.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import importlib
1+
import importlib.util
22
import platform
33
import sys
44

@@ -22,6 +22,8 @@ def test_issue_219():
2222
)
2323
def test_multiple_imports_same_interpreter_ok():
2424
spec = importlib.util.find_spec("pyo3_pytests.pyo3_pytests")
25+
assert spec is not None
26+
assert spec.loader is not None
2527

2628
module = importlib.util.module_from_spec(spec)
2729
spec.loader.exec_module(module)

pytests/tests/test_path.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_take_invalid_pathlike():
3131

3232
def test_take_invalid():
3333
with pytest.raises(TypeError):
34-
assert rpath.take_pathbuf(3)
34+
assert rpath.take_pathbuf(3) # type: ignore[bad-argument-type]
3535

3636

3737
class PathLike:

pytests/tests/test_pyclasses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def test_dict():
150150
d = ClassWithDict()
151151
assert d.__dict__ == {}
152152

153-
d.foo = 42
153+
d.foo = 42 # type: ignore[missing-attribute]
154154
assert d.__dict__ == {"foo": 42}
155155

156156

0 commit comments

Comments
 (0)