Skip to content

Commit b1dc8f6

Browse files
authored
Split out Mypy-dependent typing examples, add more type checkers (#1474)
* Split out Mypy-dependant typing examples, add more type checkers * Add explanation * link
1 parent c53a611 commit b1dc8f6

File tree

7 files changed

+292
-106
lines changed

7 files changed

+292
-106
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ jobs:
179179

180180
- run: uvx --with=tox-uv tox run -e docs-doctests,changelog
181181

182-
pyright:
183-
name: Check types using pyright
182+
typing:
183+
name: Check types using supported type checkers
184184
runs-on: ubuntu-latest
185185
steps:
186186
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -191,7 +191,7 @@ jobs:
191191
- run: >
192192
uvx --with=tox-uv
193193
--python $(cat .python-version-default)
194-
tox run -e pyright
194+
tox run -f typing
195195
196196
install-dev:
197197
name: Verify dev env
@@ -222,7 +222,7 @@ jobs:
222222
- tests-pypy
223223
- docs
224224
- install-dev
225-
- pyright
225+
- typing
226226

227227
runs-on: ubuntu-latest
228228

pyproject.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ tests = [
5151
]
5252
cov = [{ include-group = "tests" }, "coverage[toml]"]
5353
pyright = ["pyright", { include-group = "tests" }]
54+
ty = ["ty", { include-group = "tests" }]
55+
pyrefly = ["pyrefly", { include-group = "tests" }]
5456
benchmark = [
5557
{ include-group = "tests" },
5658
"pytest-codspeed",
@@ -254,11 +256,12 @@ ignore = [
254256

255257
"src/*/*.pyi" = ["ALL"] # TODO
256258
"tests/test_annotations.py" = ["FA100"]
257-
"tests/typing_example.py" = [
258-
"E741", # ambiguous variable names don't matter in type checks
259-
"B018", # useless expressions aren't useless in type checks
260-
"B015", # pointless comparison in type checks aren't pointless
261-
"UP037", # we test some older syntaxes on purpose
259+
"typing-examples/*" = [
260+
"INP001", # not for imports
261+
"E741", # ambiguous variable names don't matter in type checks
262+
"B018", # useless expressions aren't useless in type checks
263+
"B015", # pointless comparison in type checks aren't pointless
264+
"UP037", # we test some older syntaxes on purpose
262265
]
263266

264267
[tool.ruff.lint.isort]

tox.ini

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
# SPDX-License-Identifier: MIT
2+
13
[tox]
24
min_version = 4
35
env_list =
46
pre-commit,
57
py3{9,10,11,12,13,14}-tests,
68
py3{10,11,12,13,14}-mypy,
79
pypy3-tests,
8-
pyright,
10+
# Mypy needs to run within the respective Python version
11+
typing-{pyright,ty,pyrefly}
912
docs-{sponsors,doctests},
1013
changelog,
1114
coverage-combine,
@@ -26,7 +29,7 @@ dependency_groups =
2629
commands =
2730
tests: pytest {posargs:-n auto}
2831
mypy: pytest -k test_mypy
29-
mypy: mypy tests/typing_example.py
32+
mypy: mypy typing-examples
3033
mypy: mypy src/attrs/__init__.pyi src/attr/__init__.pyi src/attr/_typing_compat.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi
3134

3235
[testenv:pypy3-tests]
@@ -121,9 +124,19 @@ commands =
121124
towncrier build --version main --draft
122125

123126

124-
[testenv:pyright]
127+
[testenv:typing-pyright]
125128
dependency_groups = pyright
126-
commands = pytest tests/test_pyright.py -vv
129+
commands =
130+
pyright typing-examples/baseline_examples.py
131+
pytest tests/test_pyright.py -vv
132+
133+
[testenv:typing-ty]
134+
dependency_groups = ty
135+
commands = ty check typing-examples/baseline_examples.py
136+
137+
[testenv:typing-pyrefly]
138+
dependency_groups = pyrefly
139+
commands = pyrefly check typing-examples/baseline_examples.py
127140

128141

129142
[testenv:docset]

typing-examples/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Typing examples
2+
3+
The files in this directory are **not** meant to be executed.
4+
5+
They are type-checked using various type checkers to ensure our type stubs are correct.
6+
7+
See the environments starting with `typing-` in [`tox.ini`](../tox.ini).
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
"""
4+
Baseline features that should be supported by all type checkers.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from typing import Any
10+
11+
import attrs
12+
13+
14+
@attrs.define(order=True)
15+
class NGClass:
16+
x: int = attrs.field(default=42)
17+
18+
19+
ngc = NGClass(1)
20+
21+
22+
@attrs.mutable(slots=False)
23+
class NGClass2:
24+
x: int
25+
26+
27+
ngc2 = NGClass2(1)
28+
29+
30+
@attrs.frozen(str=True)
31+
class NGFrozen:
32+
x: int
33+
34+
35+
ngf = NGFrozen(1)
36+
37+
attrs.fields(NGFrozen).x.evolve(eq=False)
38+
a = attrs.fields(NGFrozen).x
39+
a.evolve(repr=False)
40+
41+
42+
@attrs.define
43+
class C:
44+
a: int
45+
46+
47+
c = C(1)
48+
c.a
49+
50+
51+
@attrs.frozen
52+
class D:
53+
a: int
54+
55+
56+
D(1).a
57+
58+
59+
@attrs.define
60+
class Derived(C):
61+
b: int
62+
63+
64+
Derived(1, 2).a
65+
Derived(1, 2).b
66+
67+
68+
@attrs.define
69+
class Error(Exception):
70+
x: int
71+
72+
73+
try:
74+
raise Error(1)
75+
except Error as e:
76+
e.x
77+
e.args
78+
str(e)
79+
80+
81+
@attrs.define
82+
class AliasExample:
83+
without_alias: int
84+
_with_alias: int = attrs.field(alias="_with_alias")
85+
86+
87+
attrs.fields(AliasExample).without_alias.alias
88+
attrs.fields(AliasExample)._with_alias.alias
89+
90+
91+
@attrs.define
92+
class Validated:
93+
num: int = attrs.field(validator=attrs.validators.ge(0))
94+
95+
96+
attrs.validators.set_disabled(True)
97+
attrs.validators.set_disabled(False)
98+
99+
100+
with attrs.validators.disabled():
101+
Validated(num=-1)
102+
103+
104+
@attrs.define
105+
class WithCustomRepr:
106+
a: int = attrs.field(repr=True)
107+
b: str = attrs.field(repr=False)
108+
c: str = attrs.field(repr=lambda value: "c is for cookie")
109+
d: bool = attrs.field(repr=str)
110+
111+
112+
@attrs.define(on_setattr=attrs.setters.validate)
113+
class ValidatedSetter2:
114+
a: int
115+
b: str = attrs.field(on_setattr=attrs.setters.NO_OP)
116+
c: bool = attrs.field(on_setattr=attrs.setters.frozen)
117+
d: int = attrs.field(
118+
on_setattr=[attrs.setters.convert, attrs.setters.validate]
119+
)
120+
e: bool = attrs.field(
121+
on_setattr=attrs.setters.pipe(
122+
attrs.setters.convert, attrs.setters.validate
123+
)
124+
)
125+
126+
127+
@attrs.define(eq=True, order=True)
128+
class OrderFlags:
129+
a: int = attrs.field(eq=False, order=False)
130+
b: int = attrs.field(eq=True, order=True)
131+
132+
133+
# field_transformer
134+
def ft_hook2(
135+
cls: type, attribs: list[attrs.Attribute]
136+
) -> list[attrs.Attribute]:
137+
return attribs
138+
139+
140+
@attrs.define(field_transformer=ft_hook2)
141+
class TransformedAttrs2:
142+
x: int
143+
144+
145+
@attrs.define
146+
class FactoryTest:
147+
a: list[int] = attrs.field(default=attrs.Factory(list))
148+
b: list[Any] = attrs.field(default=attrs.Factory(list, False))
149+
c: list[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True))
150+
151+
152+
attrs.asdict(FactoryTest())
153+
attrs.asdict(FactoryTest(), retain_collection_types=False)
154+
155+
156+
@attrs.define(match_args=False)
157+
class MatchArgs2:
158+
a: int
159+
b: int
160+
161+
162+
# NG versions of asdict/astuple
163+
attrs.asdict(MatchArgs2(1, 2))
164+
attrs.astuple(MatchArgs2(1, 2))
165+
166+
167+
def accessing_from_attrs() -> None:
168+
"""
169+
Use a function to keep the ns clean.
170+
"""
171+
attrs.converters.optional
172+
attrs.exceptions.FrozenError
173+
attrs.filters.include
174+
attrs.filters.exclude
175+
attrs.setters.frozen
176+
attrs.validators.and_
177+
attrs.cmp_using

0 commit comments

Comments
 (0)