Skip to content

Commit 282db18

Browse files
committed
Add type annotations to assertions, compat, run, and other modules
Remove testtools.assertions, testtools.compat, testtools.run, testtools.matchers.*, testtools.monkey, and testtools.runtest from mypy strict override list as they now have full type annotations.
1 parent a7b2c4e commit 282db18

File tree

16 files changed

+404
-274
lines changed

16 files changed

+404
-274
lines changed

pyproject.toml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,6 @@ ignore_missing_imports = true
7979
module = [
8080
# FIXME(stephenfin): We would like to remove all modules from this list
8181
# except tests (we're not sadists)
82-
"testtools.assertions",
83-
"testtools.compat",
84-
"testtools.matchers.*",
85-
"testtools.monkey",
86-
"testtools.run",
87-
"testtools.runtest",
8882
"testtools.twistedsupport.*",
8983
"tests.*",
9084
]

testtools/assertions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22

33
"""Assertion helpers."""
44

5+
from typing import TypeVar
6+
57
from testtools.matchers import (
68
Annotate,
9+
Matcher,
710
MismatchError,
811
)
912

13+
T = TypeVar("T")
14+
1015

11-
def assert_that(matchee, matcher, message="", verbose=False):
16+
def assert_that(
17+
matchee: T, matcher: Matcher[T], message: str = "", verbose: bool = False
18+
) -> None:
1219
"""Assert that matchee is matched by matcher.
1320
1421
This should only be used when you need to use a function based

testtools/compat.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
import sys
1515
import unicodedata
1616
from io import BytesIO, StringIO # for backwards-compat
17+
from typing import IO
1718

1819

19-
def _slow_escape(text):
20+
def _slow_escape(text: str) -> str:
2021
"""Escape unicode ``text`` leaving printable characters unmodified
2122
2223
The behaviour emulates the Python 3 implementation of repr, see
@@ -26,7 +27,7 @@ def _slow_escape(text):
2627
does not handle astral characters correctly on Python builds with 16 bit
2728
rather than 32 bit unicode type.
2829
"""
29-
output = []
30+
output: list[str | bytes] = []
3031
for c in text:
3132
o = ord(c)
3233
if o < 256:
@@ -43,14 +44,14 @@ def _slow_escape(text):
4344
output.append(c.encode("unicode-escape"))
4445
else:
4546
output.append(c)
46-
return "".join(output)
47+
return "".join(output) # type: ignore[arg-type]
4748

4849

49-
def text_repr(text, multiline=None):
50+
def text_repr(text: str | bytes, multiline: bool | None = None) -> str:
5051
"""Rich repr for ``text`` returning unicode, triple quoted if ``multiline``."""
5152
nl = (isinstance(text, bytes) and bytes((0xA,))) or "\n"
5253
if multiline is None:
53-
multiline = nl in text
54+
multiline = nl in text # type: ignore[operator]
5455
if not multiline:
5556
# Use normal repr for single line of unicode
5657
return repr(text)
@@ -60,7 +61,7 @@ def text_repr(text, multiline=None):
6061
# making sure that quotes are not escaped.
6162
offset = len(prefix) + 1
6263
lines = []
63-
for line in text.split(nl):
64+
for line in text.split(nl): # type: ignore[arg-type]
6465
r = repr(line)
6566
q = r[-1]
6667
lines.append(r[offset:-1].replace("\\" + q, q))
@@ -87,7 +88,7 @@ def text_repr(text, multiline=None):
8788
return "".join([prefix, quote, escaped_text, quote])
8889

8990

90-
def unicode_output_stream(stream):
91+
def unicode_output_stream(stream: IO[str]) -> IO[str]:
9192
"""Get wrapper for given stream that writes any unicode without exception
9293
9394
Characters that can't be coerced to the encoding of the stream, or 'ascii'
@@ -103,21 +104,21 @@ def unicode_output_stream(stream):
103104
# attribute).
104105
return stream
105106
try:
106-
writer = codecs.getwriter(stream.encoding or "")
107+
writer = codecs.getwriter(stream.encoding or "") # type: ignore[attr-defined]
107108
except (AttributeError, LookupError):
108-
return codecs.getwriter("ascii")(stream, "replace")
109+
return codecs.getwriter("ascii")(stream, "replace") # type: ignore[arg-type, return-value]
109110
if writer.__module__.rsplit(".", 1)[1].startswith("utf"):
110111
# The current stream has a unicode encoding so no error handler is needed
111112
return stream
112113
# Python 3 doesn't seem to make this easy, handle a common case
113114
try:
114-
return stream.__class__(
115-
stream.buffer,
116-
stream.encoding,
115+
return stream.__class__( # type: ignore[call-arg, return-value]
116+
stream.buffer, # type: ignore[attr-defined]
117+
stream.encoding, # type: ignore[attr-defined]
117118
"replace",
118-
stream.newlines,
119-
stream.line_buffering,
119+
stream.newlines, # type: ignore[attr-defined]
120+
stream.line_buffering, # type: ignore[attr-defined]
120121
)
121122
except AttributeError:
122123
pass
123-
return writer(stream, "replace")
124+
return writer(stream, "replace") # type: ignore[arg-type, return-value]

testtools/matchers/_basic.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def _describe_set_difference(self) -> str:
135135
return "\n".join(lines)
136136

137137

138-
class Equals(_BinaryComparison):
138+
class Equals(_BinaryComparison[T]):
139139
"""Matches if the items are equal."""
140140

141141
comparator = operator.eq
@@ -165,7 +165,7 @@ def match(self, other: T) -> Mismatch | None:
165165
return _BinaryMismatch(other, "!=", self._expected, False)
166166

167167

168-
class NotEquals(_BinaryComparison):
168+
class NotEquals(_BinaryComparison[T]):
169169
"""Matches if the items are not equal.
170170
171171
In most cases, this is equivalent to ``Not(Equals(foo))``. The difference
@@ -176,38 +176,38 @@ class NotEquals(_BinaryComparison):
176176
mismatch_string = "=="
177177

178178

179-
class Is(_BinaryComparison):
179+
class Is(_BinaryComparison[T]):
180180
"""Matches if the items are identical."""
181181

182182
comparator = operator.is_
183183
mismatch_string = "is not"
184184

185185

186-
class LessThan(_BinaryComparison):
186+
class LessThan(_BinaryComparison[T]):
187187
"""Matches if the item is less than the matchers reference object."""
188188

189189
comparator = operator.lt
190190
mismatch_string = ">="
191191

192192

193-
class GreaterThan(_BinaryComparison):
193+
class GreaterThan(_BinaryComparison[T]):
194194
"""Matches if the item is greater than the matchers reference object."""
195195

196196
comparator = operator.gt
197197
mismatch_string = "<="
198198

199199

200-
class _NotNearlyEqual(Mismatch):
200+
class _NotNearlyEqual(Mismatch, Generic[T]):
201201
"""Mismatch for Nearly matcher."""
202202

203-
def __init__(self, actual, expected, delta):
203+
def __init__(self, actual: T, expected: T, delta: Any) -> None:
204204
self.actual = actual
205205
self.expected = expected
206206
self.delta = delta
207207

208-
def describe(self):
208+
def describe(self) -> str:
209209
try:
210-
diff = abs(self.actual - self.expected)
210+
diff = abs(self.actual - self.expected) # type: ignore[operator]
211211
return (
212212
f"{self.actual!r} is not nearly equal to {self.expected!r}: "
213213
f"difference {diff!r} exceeds tolerance {self.delta!r}"
@@ -219,7 +219,7 @@ def describe(self):
219219
)
220220

221221

222-
class Nearly(Matcher):
222+
class Nearly(Matcher[T]):
223223
"""Matches if a value is nearly equal to the expected value.
224224
225225
This matcher is useful for comparing floating point values where exact
@@ -232,7 +232,7 @@ class Nearly(Matcher):
232232
operations (e.g., integers, floats, Decimal, etc.).
233233
"""
234234

235-
def __init__(self, expected, delta=0.001):
235+
def __init__(self, expected: T, delta: Any = 0.001) -> None:
236236
"""Create a Nearly matcher.
237237
238238
:param expected: The expected value to compare against.
@@ -242,12 +242,12 @@ def __init__(self, expected, delta=0.001):
242242
self.expected = expected
243243
self.delta = delta
244244

245-
def __str__(self):
245+
def __str__(self) -> str:
246246
return f"Nearly({self.expected!r}, delta={self.delta!r})"
247247

248-
def match(self, actual):
248+
def match(self, actual: T) -> Mismatch | None:
249249
try:
250-
diff = abs(actual - self.expected)
250+
diff = abs(actual - self.expected) # type: ignore[operator]
251251
if diff <= self.delta:
252252
return None
253253
except (TypeError, AttributeError):
@@ -256,25 +256,25 @@ def match(self, actual):
256256
return _NotNearlyEqual(actual, self.expected, self.delta)
257257

258258

259-
class SameMembers(Matcher):
259+
class SameMembers(Matcher[list[T]]):
260260
"""Matches if two iterators have the same members.
261261
262262
This is not the same as set equivalence. The two iterators must be of the
263263
same length and have the same repetitions.
264264
"""
265265

266-
def __init__(self, expected):
266+
def __init__(self, expected: list[T]) -> None:
267267
super().__init__()
268268
self.expected = expected
269269

270-
def __str__(self):
270+
def __str__(self) -> str:
271271
return f"{self.__class__.__name__}({self.expected!r})"
272272

273-
def match(self, observed):
273+
def match(self, observed: list[T]) -> Mismatch | None:
274274
expected_only = list_subtract(self.expected, observed)
275275
observed_only = list_subtract(observed, self.expected)
276276
if expected_only == observed_only == []:
277-
return
277+
return None
278278
return PostfixedMismatch(
279279
(
280280
f"\nmissing: {_format(expected_only)}\n"
@@ -285,7 +285,7 @@ def match(self, observed):
285285

286286

287287
class DoesNotStartWith(Mismatch):
288-
def __init__(self, matchee, expected):
288+
def __init__(self, matchee: str | bytes, expected: str | bytes) -> None:
289289
"""Create a DoesNotStartWith Mismatch.
290290
291291
:param matchee: the string that did not match.
@@ -294,33 +294,33 @@ def __init__(self, matchee, expected):
294294
self.matchee = matchee
295295
self.expected = expected
296296

297-
def describe(self):
297+
def describe(self) -> str:
298298
return (
299299
f"{text_repr(self.matchee)} does not start with {text_repr(self.expected)}."
300300
)
301301

302302

303-
class StartsWith(Matcher):
303+
class StartsWith(Matcher[str | bytes]):
304304
"""Checks whether one string starts with another."""
305305

306-
def __init__(self, expected):
306+
def __init__(self, expected: str | bytes) -> None:
307307
"""Create a StartsWith Matcher.
308308
309309
:param expected: the string that matchees should start with.
310310
"""
311311
self.expected = expected
312312

313-
def __str__(self):
313+
def __str__(self) -> str:
314314
return f"StartsWith({self.expected!r})"
315315

316-
def match(self, matchee):
317-
if not matchee.startswith(self.expected):
316+
def match(self, matchee: str | bytes) -> Mismatch | None:
317+
if not matchee.startswith(self.expected): # type: ignore[arg-type]
318318
return DoesNotStartWith(matchee, self.expected)
319319
return None
320320

321321

322322
class DoesNotEndWith(Mismatch):
323-
def __init__(self, matchee, expected):
323+
def __init__(self, matchee: str | bytes, expected: str | bytes) -> None:
324324
"""Create a DoesNotEndWith Mismatch.
325325
326326
:param matchee: the string that did not match.
@@ -329,27 +329,27 @@ def __init__(self, matchee, expected):
329329
self.matchee = matchee
330330
self.expected = expected
331331

332-
def describe(self):
332+
def describe(self) -> str:
333333
return (
334334
f"{text_repr(self.matchee)} does not end with {text_repr(self.expected)}."
335335
)
336336

337337

338-
class EndsWith(Matcher):
338+
class EndsWith(Matcher[str | bytes]):
339339
"""Checks whether one string ends with another."""
340340

341-
def __init__(self, expected):
341+
def __init__(self, expected: str | bytes) -> None:
342342
"""Create a EndsWith Matcher.
343343
344344
:param expected: the string that matchees should end with.
345345
"""
346346
self.expected = expected
347347

348-
def __str__(self):
348+
def __str__(self) -> str:
349349
return f"EndsWith({self.expected!r})"
350350

351-
def match(self, matchee):
352-
if not matchee.endswith(self.expected):
351+
def match(self, matchee: str | bytes) -> Mismatch | None:
352+
if not matchee.endswith(self.expected): # type: ignore[arg-type]
353353
return DoesNotEndWith(matchee, self.expected)
354354
return None
355355

@@ -459,7 +459,7 @@ def match(self, value: str) -> Mismatch | None:
459459
return None
460460

461461

462-
def has_len(x, y):
462+
def has_len(x: Any, y: int) -> bool:
463463
return len(x) == y
464464

465465

testtools/matchers/_const.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
"Never",
66
]
77

8-
from ._impl import Mismatch
98

9+
from ._impl import Matcher, Mismatch
1010

11-
class _Always:
11+
12+
class _Always(Matcher[object]):
1213
"""Always matches."""
1314

14-
def __str__(self):
15+
def __str__(self) -> str:
1516
return "Always()"
1617

17-
def match(self, value):
18+
def match(self, value: object) -> None:
1819
return None
1920

2021

21-
def Always():
22+
def Always() -> _Always:
2223
"""Always match.
2324
2425
That is::
@@ -32,17 +33,17 @@ def Always():
3233
return _Always()
3334

3435

35-
class _Never:
36+
class _Never(Matcher[object]):
3637
"""Never matches."""
3738

38-
def __str__(self):
39+
def __str__(self) -> str:
3940
return "Never()"
4041

41-
def match(self, value):
42+
def match(self, value: object) -> Mismatch:
4243
return Mismatch(f"Inevitable mismatch on {value!r}")
4344

4445

45-
def Never():
46+
def Never() -> _Never:
4647
"""Never match.
4748
4849
That is::

0 commit comments

Comments
 (0)