[cherry-pick to master] Add rudimentary mypy type checking (#5583)

[cherry-pick to master] Add rudimentary mypy type checking
This commit is contained in:
Bruno Oliveira 2019-07-10 07:52:32 -03:00 committed by GitHub
commit 2c402f4bd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 104 additions and 45 deletions

1
.gitignore vendored
View File

@ -35,6 +35,7 @@ env/
.tox .tox
.cache .cache
.pytest_cache .pytest_cache
.mypy_cache
.coverage .coverage
.coverage.* .coverage.*
coverage.xml coverage.xml

View File

@ -28,6 +28,7 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
language_version: python3 language_version: python3
additional_dependencies: [flake8-typing-imports]
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
rev: v1.4.0 rev: v1.4.0
hooks: hooks:
@ -42,6 +43,17 @@ repos:
rev: v1.4.0 rev: v1.4.0
hooks: hooks:
- id: rst-backticks - id: rst-backticks
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.711
hooks:
- id: mypy
name: mypy (src)
files: ^src/
args: []
- id: mypy
name: mypy (testing)
files: ^testing/
args: []
- repo: local - repo: local
hooks: hooks:
- id: rst - id: rst

View File

@ -6,7 +6,7 @@ if __name__ == "__main__":
import pstats import pstats
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"] script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof") cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
p = pstats.Stats("prof") p = pstats.Stats("prof")
p.strip_dirs() p.strip_dirs()
p.sort_stats("cumulative") p.sort_stats("cumulative")

View File

@ -61,3 +61,11 @@ ignore =
[devpi:upload] [devpi:upload]
formats = sdist.tgz,bdist_wheel formats = sdist.tgz,bdist_wheel
[mypy]
ignore_missing_imports = True
no_implicit_optional = True
strict_equality = True
warn_redundant_casts = True
warn_return_any = True
warn_unused_configs = True

View File

@ -56,6 +56,7 @@ If things do not work right away:
import os import os
import sys import sys
from glob import glob from glob import glob
from typing import Optional
class FastFilesCompleter: class FastFilesCompleter:
@ -91,7 +92,7 @@ if os.environ.get("_ARGCOMPLETE"):
import argcomplete.completers import argcomplete.completers
except ImportError: except ImportError:
sys.exit(-1) sys.exit(-1)
filescompleter = FastFilesCompleter() filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
def try_argcomplete(parser): def try_argcomplete(parser):
argcomplete.autocomplete(parser, always_complete_options=False) argcomplete.autocomplete(parser, always_complete_options=False)

View File

@ -33,7 +33,8 @@ class Code:
def __eq__(self, other): def __eq__(self, other):
return self.raw == other.raw return self.raw == other.raw
__hash__ = None # Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
@ -188,11 +189,11 @@ class TracebackEntry:
""" path to the source code """ """ path to the source code """
return self.frame.code.path return self.frame.code.path
def getlocals(self): @property
def locals(self):
""" locals of underlaying frame """
return self.frame.f_locals return self.frame.f_locals
locals = property(getlocals, None, None, "locals of underlaying frame")
def getfirstlinesource(self): def getfirstlinesource(self):
return self.frame.code.firstlineno return self.frame.code.firstlineno
@ -255,11 +256,11 @@ class TracebackEntry:
line = "???" line = "???"
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line) return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
@property
def name(self): def name(self):
""" co_name of underlaying code """
return self.frame.code.raw.co_name return self.frame.code.raw.co_name
name = property(name, None, None, "co_name of underlaying code")
class Traceback(list): class Traceback(list):
""" Traceback objects encapsulate and offer higher level """ Traceback objects encapsulate and offer higher level

View File

@ -44,7 +44,8 @@ class Source:
return str(self) == other return str(self) == other
return False return False
__hash__ = None # Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, int): if isinstance(key, int):

View File

@ -12,6 +12,10 @@ import struct
import sys import sys
import tokenize import tokenize
import types import types
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
import atomicwrites import atomicwrites
@ -459,17 +463,18 @@ def set_location(node, lineno, col_offset):
return node return node
def _get_assertion_exprs(src: bytes): # -> Dict[int, str] def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
"""Returns a mapping from {lineno: "assertion test expression"}""" """Returns a mapping from {lineno: "assertion test expression"}"""
ret = {} ret = {} # type: Dict[int, str]
depth = 0 depth = 0
lines = [] lines = [] # type: List[str]
assert_lineno = None assert_lineno = None # type: Optional[int]
seen_lines = set() seen_lines = set() # type: Set[int]
def _write_and_reset() -> None: def _write_and_reset() -> None:
nonlocal depth, lines, assert_lineno, seen_lines nonlocal depth, lines, assert_lineno, seen_lines
assert assert_lineno is not None
ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\")
depth = 0 depth = 0
lines = [] lines = []
@ -477,21 +482,21 @@ def _get_assertion_exprs(src: bytes): # -> Dict[int, str]
seen_lines = set() seen_lines = set()
tokens = tokenize.tokenize(io.BytesIO(src).readline) tokens = tokenize.tokenize(io.BytesIO(src).readline)
for tp, src, (lineno, offset), _, line in tokens: for tp, source, (lineno, offset), _, line in tokens:
if tp == tokenize.NAME and src == "assert": if tp == tokenize.NAME and source == "assert":
assert_lineno = lineno assert_lineno = lineno
elif assert_lineno is not None: elif assert_lineno is not None:
# keep track of depth for the assert-message `,` lookup # keep track of depth for the assert-message `,` lookup
if tp == tokenize.OP and src in "([{": if tp == tokenize.OP and source in "([{":
depth += 1 depth += 1
elif tp == tokenize.OP and src in ")]}": elif tp == tokenize.OP and source in ")]}":
depth -= 1 depth -= 1
if not lines: if not lines:
lines.append(line[offset:]) lines.append(line[offset:])
seen_lines.add(lineno) seen_lines.add(lineno)
# a non-nested comma separates the expression from the message # a non-nested comma separates the expression from the message
elif depth == 0 and tp == tokenize.OP and src == ",": elif depth == 0 and tp == tokenize.OP and source == ",":
# one line assert with message # one line assert with message
if lineno in seen_lines and len(lines) == 1: if lineno in seen_lines and len(lines) == 1:
offset_in_trimmed = offset + len(lines[-1]) - len(line) offset_in_trimmed = offset + len(lines[-1]) - len(line)

View File

@ -547,6 +547,8 @@ class FDCaptureBinary:
self.start = lambda: None self.start = lambda: None
self.done = lambda: None self.done = lambda: None
else: else:
self.start = self._start
self.done = self._done
if targetfd == 0: if targetfd == 0:
assert not tmpfile, "cannot set tmpfile with stdin" assert not tmpfile, "cannot set tmpfile with stdin"
tmpfile = open(os.devnull, "r") tmpfile = open(os.devnull, "r")
@ -568,7 +570,7 @@ class FDCaptureBinary:
self.targetfd, getattr(self, "targetfd_save", None), self._state self.targetfd, getattr(self, "targetfd_save", None), self._state
) )
def start(self): def _start(self):
""" Start capturing on targetfd using memorized tmpfile. """ """ Start capturing on targetfd using memorized tmpfile. """
try: try:
os.fstat(self.targetfd_save) os.fstat(self.targetfd_save)
@ -585,7 +587,7 @@ class FDCaptureBinary:
self.tmpfile.truncate() self.tmpfile.truncate()
return res return res
def done(self): def _done(self):
""" stop capturing, restore streams, return original capture file, """ stop capturing, restore streams, return original capture file,
seeked to position zero. """ seeked to position zero. """
targetfd_save = self.__dict__.pop("targetfd_save") targetfd_save = self.__dict__.pop("targetfd_save")
@ -618,7 +620,8 @@ class FDCapture(FDCaptureBinary):
snap() produces text snap() produces text
""" """
EMPTY_BUFFER = str() # Ignore type because it doesn't match the type in the superclass (bytes).
EMPTY_BUFFER = str() # type: ignore
def snap(self): def snap(self):
res = super().snap() res = super().snap()
@ -679,7 +682,8 @@ class SysCapture:
class SysCaptureBinary(SysCapture): class SysCaptureBinary(SysCapture):
EMPTY_BUFFER = b"" # Ignore type because it doesn't match the type in the superclass (str).
EMPTY_BUFFER = b"" # type: ignore
def snap(self): def snap(self):
res = self.tmpfile.buffer.getvalue() res = self.tmpfile.buffer.getvalue()

View File

@ -74,7 +74,7 @@ class pytestPDB:
_pluginmanager = None _pluginmanager = None
_config = None _config = None
_saved = [] _saved = [] # type: list
_recursive_debug = 0 _recursive_debug = 0
_wrapped_pdb_cls = None _wrapped_pdb_cls = None

View File

@ -6,6 +6,8 @@ import warnings
from collections import defaultdict from collections import defaultdict
from collections import deque from collections import deque
from collections import OrderedDict from collections import OrderedDict
from typing import Dict
from typing import Tuple
import attr import attr
import py import py
@ -31,6 +33,9 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME from _pytest.outcomes import TEST_OUTCOME
if False: # TYPE_CHECKING
from typing import Type
@attr.s(frozen=True) @attr.s(frozen=True)
class PseudoFixtureDef: class PseudoFixtureDef:
@ -54,10 +59,10 @@ def pytest_sessionstart(session):
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
scopename2class = {} scopename2class = {} # type: Dict[str, Type[nodes.Node]]
scope2props = dict(session=()) scope2props = dict(session=()) # type: Dict[str, Tuple[str, ...]]
scope2props["package"] = ("fspath",) scope2props["package"] = ("fspath",)
scope2props["module"] = ("fspath", "module") scope2props["module"] = ("fspath", "module")
scope2props["class"] = scope2props["module"] + ("cls",) scope2props["class"] = scope2props["module"] + ("cls",)
@ -960,7 +965,8 @@ class FixtureFunctionMarker:
scope = attr.ib() scope = attr.ib()
params = attr.ib(converter=attr.converters.optional(tuple)) params = attr.ib(converter=attr.converters.optional(tuple))
autouse = attr.ib(default=False) autouse = attr.ib(default=False)
ids = attr.ib(default=None, converter=_ensure_immutable_ids) # Ignore type because of https://github.com/python/mypy/issues/6172.
ids = attr.ib(default=None, converter=_ensure_immutable_ids) # type: ignore
name = attr.ib(default=None) name = attr.ib(default=None)
def __call__(self, function): def __call__(self, function):

View File

@ -91,7 +91,8 @@ def pytest_cmdline_main(config):
return 0 return 0
pytest_cmdline_main.tryfirst = True # Ignore type because of https://github.com/python/mypy/issues/2087.
pytest_cmdline_main.tryfirst = True # type: ignore
def deselect_by_keyword(items, config): def deselect_by_keyword(items, config):

View File

@ -3,6 +3,7 @@ import warnings
from collections import namedtuple from collections import namedtuple
from collections.abc import MutableMapping from collections.abc import MutableMapping
from operator import attrgetter from operator import attrgetter
from typing import Set
import attr import attr
@ -298,7 +299,7 @@ class MarkGenerator:
on the ``test_function`` object. """ on the ``test_function`` object. """
_config = None _config = None
_markers = set() _markers = set() # type: Set[str]
def __getattr__(self, name): def __getattr__(self, name):
if name[0] == "_": if name[0] == "_":

View File

@ -280,7 +280,8 @@ class Node:
truncate_locals=truncate_locals, truncate_locals=truncate_locals,
) )
repr_failure = _repr_failure_py def repr_failure(self, excinfo, style=None):
return self._repr_failure_py(excinfo, style)
def get_fslocation_from_item(item): def get_fslocation_from_item(item):

View File

@ -73,7 +73,8 @@ def exit(msg, returncode=None):
raise Exit(msg, returncode) raise Exit(msg, returncode)
exit.Exception = Exit # Ignore type because of https://github.com/python/mypy/issues/2087.
exit.Exception = Exit # type: ignore
def skip(msg="", *, allow_module_level=False): def skip(msg="", *, allow_module_level=False):
@ -99,7 +100,8 @@ def skip(msg="", *, allow_module_level=False):
raise Skipped(msg=msg, allow_module_level=allow_module_level) raise Skipped(msg=msg, allow_module_level=allow_module_level)
skip.Exception = Skipped # Ignore type because of https://github.com/python/mypy/issues/2087.
skip.Exception = Skipped # type: ignore
def fail(msg="", pytrace=True): def fail(msg="", pytrace=True):
@ -114,7 +116,8 @@ def fail(msg="", pytrace=True):
raise Failed(msg=msg, pytrace=pytrace) raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed # Ignore type because of https://github.com/python/mypy/issues/2087.
fail.Exception = Failed # type: ignore
class XFailed(Failed): class XFailed(Failed):
@ -135,7 +138,8 @@ def xfail(reason=""):
raise XFailed(reason) raise XFailed(reason)
xfail.Exception = XFailed # Ignore type because of https://github.com/python/mypy/issues/2087.
xfail.Exception = XFailed # type: ignore
def importorskip(modname, minversion=None, reason=None): def importorskip(modname, minversion=None, reason=None):

View File

@ -9,6 +9,7 @@ from collections.abc import Sized
from decimal import Decimal from decimal import Decimal
from itertools import filterfalse from itertools import filterfalse
from numbers import Number from numbers import Number
from typing import Union
from more_itertools.more import always_iterable from more_itertools.more import always_iterable
@ -58,7 +59,8 @@ class ApproxBase:
a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual)
) )
__hash__ = None # Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
def __ne__(self, actual): def __ne__(self, actual):
return not (actual == self) return not (actual == self)
@ -202,8 +204,10 @@ class ApproxScalar(ApproxBase):
Perform approximate comparisons where the expected value is a single number. Perform approximate comparisons where the expected value is a single number.
""" """
DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 # Using Real should be better than this Union, but not possible yet:
DEFAULT_RELATIVE_TOLERANCE = 1e-6 # https://github.com/python/typeshed/pull/3108
DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 # type: Union[float, Decimal]
DEFAULT_RELATIVE_TOLERANCE = 1e-6 # type: Union[float, Decimal]
def __repr__(self): def __repr__(self):
""" """
@ -261,7 +265,8 @@ class ApproxScalar(ApproxBase):
# Return true if the two numbers are within the tolerance. # Return true if the two numbers are within the tolerance.
return abs(self.expected - actual) <= self.tolerance return abs(self.expected - actual) <= self.tolerance
__hash__ = None # Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
@property @property
def tolerance(self): def tolerance(self):
@ -691,7 +696,7 @@ def raises(expected_exception, *args, **kwargs):
fail(message) fail(message)
raises.Exception = fail.Exception raises.Exception = fail.Exception # type: ignore
class RaisesContext: class RaisesContext:

View File

@ -1,4 +1,5 @@
from pprint import pprint from pprint import pprint
from typing import Optional
import py import py
@ -28,7 +29,7 @@ def getslaveinfoline(node):
class BaseReport: class BaseReport:
when = None when = None # type: Optional[str]
location = None location = None
def __init__(self, **kw): def __init__(self, **kw):

View File

@ -26,7 +26,10 @@ class TempPathFactory:
# using os.path.abspath() to get absolute path instead of resolve() as it # using os.path.abspath() to get absolute path instead of resolve() as it
# does not work the same in all platforms (see #4427) # does not work the same in all platforms (see #4427)
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012) # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
converter=attr.converters.optional(lambda p: Path(os.path.abspath(str(p)))) # Ignore type because of https://github.com/python/mypy/issues/6172.
converter=attr.converters.optional(
lambda p: Path(os.path.abspath(str(p))) # type: ignore
)
) )
_trace = attr.ib() _trace = attr.ib()
_basetemp = attr.ib(default=None) _basetemp = attr.ib(default=None)

View File

@ -589,7 +589,8 @@ raise ValueError()
def test_repr_local_with_exception_in_class_property(self): def test_repr_local_with_exception_in_class_property(self):
class ExceptionWithBrokenClass(Exception): class ExceptionWithBrokenClass(Exception):
@property # Type ignored because it's bypassed intentionally.
@property # type: ignore
def __class__(self): def __class__(self):
raise TypeError("boom!") raise TypeError("boom!")

View File

@ -265,7 +265,8 @@ class TestRaises:
"""Test current behavior with regard to exceptions via __class__ (#4284).""" """Test current behavior with regard to exceptions via __class__ (#4284)."""
class CrappyClass(Exception): class CrappyClass(Exception):
@property # Type ignored because it's bypassed intentionally.
@property # type: ignore
def __class__(self): def __class__(self):
assert False, "via __class__" assert False, "via __class__"

View File

@ -141,7 +141,8 @@ def test_safe_isclass():
assert safe_isclass(type) is True assert safe_isclass(type) is True
class CrappyClass(Exception): class CrappyClass(Exception):
@property # Type ignored because it's bypassed intentionally.
@property # type: ignore
def __class__(self): def __class__(self):
assert False, "Should be ignored" assert False, "Should be ignored"

View File

@ -6,7 +6,8 @@ import pytest
from _pytest.debugging import _validate_usepdb_cls from _pytest.debugging import _validate_usepdb_cls
try: try:
breakpoint # Type ignored for Python <= 3.6.
breakpoint # type: ignore
except NameError: except NameError:
SUPPORTS_BREAKPOINT_BUILTIN = False SUPPORTS_BREAKPOINT_BUILTIN = False
else: else: