Imagine I am writing a little Python library with one (nonsense) function:
def takes_a_str(x: str) -> str:
if x.startswith("."):
raise RuntimeError("Must not start with '.'")
return x + ";"
For runtime tests of the functionality, I can check it behaves as expected under both correct conditions (e.g. assert takes_a_str('x')=='x;') and also error conditions (e.g. with pytest.raises(RuntimeError): takes_a_str('.')).
If I want to check that I have not made a mistake with the type hints, I can also perform positive tests: I can create a little test function in a separate file and run mypy or pyright to see that there are no errors:
def check_types() -> None:
x: str = takes_a_str("")
But I also want to make sure my type hints are not too loose, by checking that some negative cases fail as they ought to:
def should_fail_type_checking() -> None:
x: dict = takes_a_str("")
takes_a_str(2)
I can run mypy on this and observe it has errors where I expected, but this is not an automated solution. For example, if I have 20 cases like this, I cannot instantly see that they have all failed, and also may not notice if other errors are nestled amongst them.
Is there a way to ask the type checker to pass, and ONLY pass, where a type conversion does not match? A sort of analogue of pytest.raises() for type checking?
mypy and pyright both support emitting errors when they detect unnecessary error-suppressing comments. You can utilise this to do an equivalent of
pytest.raises, failing a check when things are type-safe. The static type-checking options that need to be turned on are:warn_unused_ignores = True/--warn-unused-ignores/strict = True/--strictreportUnnecessaryTypeIgnoreComment(see Pyright: Type Check Diagnostics Settings)Demonstration (mypy Playground, Pyright Playground):
Notes:
pytest.raises(BaseException).type: ignore[<mypy error code>]- The comment character sequence# type: ignore...is a Python-typing-compliant code which should be recognised by all type-checkers.