Skip to content

Commit 1078647

Browse files
committed
typing: Make use of ParamSpec
So we can catch type issues like the below: def foo(a: str, b: int | None = None): ... class FooTest(testtools.TestCase): def test_foo(self): self.assertRaises(Exception, foo, 123, b='abc') We don't currently have type checking enabled for the tests directory, but adding a test like the above proves this works. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent 8bd225c commit 1078647

1 file changed

Lines changed: 20 additions & 17 deletions

File tree

testtools/testcase.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import types
2424
import unittest
2525
from collections.abc import Callable, Iterator
26-
from typing import TYPE_CHECKING, TypeVar, cast, overload
26+
from typing import TYPE_CHECKING, ParamSpec, TypeVar, cast, overload
2727
from unittest.case import SkipTest
2828

2929
T = TypeVar("T")
@@ -88,6 +88,8 @@ class _ExpectedFailure(Exception):
8888

8989

9090
# TypeVar for decorators
91+
_P = ParamSpec("_P")
92+
_R = TypeVar("_R")
9193
_F = TypeVar("_F", bound=Callable[..., object])
9294

9395

@@ -390,10 +392,10 @@ def _formatTypes(
390392

391393
def addCleanup(
392394
self,
393-
function: Callable[..., object],
395+
function: Callable[_P, _R],
394396
/,
395-
*arguments: object,
396-
**keywordArguments: object,
397+
*args: _P.args,
398+
**kwargs: _P.kwargs,
397399
) -> None:
398400
"""Add a cleanup function to be called after tearDown.
399401
@@ -407,7 +409,7 @@ def addCleanup(
407409
Cleanup functions are always called before a test finishes running,
408410
even if setUp is aborted by an exception.
409411
"""
410-
self._cleanups.append((function, arguments, keywordArguments))
412+
self._cleanups.append((function, args, kwargs))
411413

412414
def addOnException(self, handler: "Callable[[ExcInfo], None]") -> None:
413415
"""Add a handler to be called when an exception occurs in test code.
@@ -503,26 +505,24 @@ def assertIsInstance( # type: ignore[override]
503505
def assertRaises(
504506
self,
505507
expected_exception: type[BaseException] | tuple[type[BaseException]],
506-
callable: Callable[..., object],
507-
*args: object,
508-
**kwargs: object,
508+
callable: Callable[_P, _R],
509+
*args: _P.args,
510+
**kwargs: _P.kwargs,
509511
) -> BaseException: ...
510512

511513
@overload # type: ignore[override]
512514
def assertRaises(
513515
self,
514516
expected_exception: type[BaseException] | tuple[type[BaseException]],
515517
callable: None = ...,
516-
*args: object,
517-
**kwargs: object,
518518
) -> "_AssertRaisesContext": ...
519519

520520
def assertRaises( # type: ignore[override]
521521
self,
522522
expected_exception: type[BaseException] | tuple[type[BaseException]],
523-
callable: Callable[..., object] | None = None,
524-
*args: object,
525-
**kwargs: object,
523+
callable: Callable[_P, _R] | None = None,
524+
*args: _P.args,
525+
**kwargs: _P.kwargs,
526526
) -> "_AssertRaisesContext | BaseException":
527527
"""Fail unless an exception of class expected_exception is thrown
528528
by callable when invoked with arguments args and keyword
@@ -678,9 +678,9 @@ def defaultTestResult(self) -> TestResult:
678678
def expectFailure(
679679
self,
680680
reason: str,
681-
predicate: Callable[..., object],
682-
*args: object,
683-
**kwargs: object,
681+
predicate: Callable[_P, _R],
682+
*args: _P.args,
683+
**kwargs: _P.kwargs,
684684
) -> None:
685685
"""Check that a test fails in a particular way.
686686
@@ -1349,7 +1349,10 @@ class Nullary:
13491349
"""
13501350

13511351
def __init__(
1352-
self, callable_object: Callable[..., object], *args: object, **kwargs: object
1352+
self,
1353+
callable_object: Callable[_P, _R],
1354+
*args: _P.args,
1355+
**kwargs: _P.kwargs,
13531356
) -> None:
13541357
self._callable_object = callable_object
13551358
self._args = args

0 commit comments

Comments
 (0)