I’m one of those project maintainers that doesn’t want to use typing. But I am OK with putting some type hints on a package’s public API – particularly return types so that fancy IDEs give good completions on the outputs. When I do, I keep the bar low, only type hinting things that seem simple (numbers, strings) and leave anything non-trivial (numpy array-like types, large unions, anything protocol related) untyped.
Despite sticking to what I assumed were the simple and obvious types, I still make mistakes and find people having to # type: ignore my counterproductive attempts at being helpful. So I’m looking for as low-noise a way of checking only my own types as possible.
What I thought would do that really effectively is to run a type checker on my test suite. Since the tests exercise all intended usages of the API, they should fail if its type hints are invalid or block a legitimate usage.
A reduced example of what I want to catch and not catch:
# foo.py
import os
import numbers
# Type reasignment upsets type checkers but I consider it OK.
# I want type checkers to ignore this.
_seed = os.environ.get("FOO_HASH_SEED", "")
if _seed:
_seed = int(_seed)
else:
_seed = int.from_bytes(os.urandom(4), "little")
# numbers.Number isn't a valid type hint. Tests that call this function should
# fail type checking.
def add_1(x: numbers.Number):
return x + 1
# test_foo.py
import foo
def test_add_1():
assert foo.add_1(2) == 3 # <-- should fail
But I can’t get the filtering right. mypy’s filtering rules are apparently either:
- Recurse imports to fetch type information from those module AND type check them.
- Treat the whole imported module as a
typing.Anyfree for all.
Really I want a middle ground where type information is fetched but type errors within those imports are ignored.
# --exclude doesn't exclude errors from foo.py. Checker is too fussy
> mypy --disable-error-code=import-untyped --exclude=foo.py test_foo.py
foo.py:8: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
foo.py:10: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
foo.py:15: error: Unsupported operand types for + ("Number" and "int") [operator]
Found 3 errors in 1 file (checked 1 source file)
# --skip skips too much. Checker ignores what I want it to find
> mypy --disable-error-code=import-untyped --follow-imports=skip test_foo.py
Success: no issues found in 1 source file
Is anyone able to set me on the right path here? I’m surely not the only person who’s wanted to (partially) support typing externally without having to engage with it everywhere else. I know I could # type: ignore all over the (to me) false positives in my source code I’d sooner ditch my type hints entirely.