I have following setup:
@pytest.fixture
def check_answer() -> t.Callable[[dict[str, t.Any], dict[str, t.Any], int], None]:
def _check(
first_response: dict[str, t.Any],
second_response: dict[str, t.Any],
named_optional_arg: int = 1,
) -> None:
*assert stuff*
return _check
I then pass check_answer
as a parameter to test_my_answer
and I want to call it as check_answer(ans1, ans2, named_optional_arg=5)
. The optional argument is used very rarely, so it is important that the name is present when it is called, however, when I include the name, I get an error from mypy because of an unexpected keyword argument.
The problem is that your type signature is not specific enough to capture the fact that _check
has named_optional_arg
. Callable
s are not sufficient to type keyword arguments, that is where callback protocols must be used:
from collections.abc import Mapping
from typing import Protocol, Any
class Checker(Protocol):
def __call__(
self,
first_response: Mapping[str, Any],
second_response: Mapping[str, Any],
/,
*,
named_optional_arg: int = ...
):
...
You can read What do * (single star) and / (slash) do as independent parameters? for more information on how /
and *
can be used within the parameter list to describe the exact way the function is allowed to be invoked. In this situation, I've made it so the first two arguments must be specified by position, and the third optional one must be specified by name.
Additionally, note that I replaced dict
with Mapping
. This is because checking / asserting requires only reading from, not writing to the responses.
You would then change your declaration to:
@pytest.fixture
def check_answer() -> Checker:
...