Skip to content

Commit 39a43db

Browse files
authored
Merge pull request #5575 from bluetech/mypy-initial
Add rudimentary mypy type checking
2 parents e33736c + c1167ac commit 39a43db

File tree

30 files changed

+104
-45
lines changed

30 files changed

+104
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ env/
3535
.tox
3636
.cache
3737
.pytest_cache
38+
.mypy_cache
3839
.coverage
3940
.coverage.*
4041
coverage.xml

.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ repos:
2828
hooks:
2929
- id: flake8
3030
language_version: python3
31+
additional_dependencies: [flake8-typing-imports]
3132
- repo: https://github.com/asottile/reorder_python_imports
3233
rev: v1.4.0
3334
hooks:
@@ -42,6 +43,17 @@ repos:
4243
rev: v1.4.0
4344
hooks:
4445
- id: rst-backticks
46+
- repo: https://github.com/pre-commit/mirrors-mypy
47+
rev: v0.711
48+
hooks:
49+
- id: mypy
50+
name: mypy (src)
51+
files: ^src/
52+
args: []
53+
- id: mypy
54+
name: mypy (testing)
55+
files: ^testing/
56+
args: []
4557
- repo: local
4658
hooks:
4759
- id: rst

bench/bench.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pstats
77

88
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
9-
stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
9+
cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
1010
p = pstats.Stats("prof")
1111
p.strip_dirs()
1212
p.sort_stats("cumulative")

setup.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,11 @@ ignore =
6161

6262
[devpi:upload]
6363
formats = sdist.tgz,bdist_wheel
64+
65+
[mypy]
66+
ignore_missing_imports = True
67+
no_implicit_optional = True
68+
strict_equality = True
69+
warn_redundant_casts = True
70+
warn_return_any = True
71+
warn_unused_configs = True

src/_pytest/_argcomplete.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import os
5757
import sys
5858
from glob import glob
59+
from typing import Optional
5960

6061

6162
class FastFilesCompleter:
@@ -91,7 +92,7 @@ def __call__(self, prefix, **kwargs):
9192
import argcomplete.completers
9293
except ImportError:
9394
sys.exit(-1)
94-
filescompleter = FastFilesCompleter()
95+
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
9596

9697
def try_argcomplete(parser):
9798
argcomplete.autocomplete(parser, always_complete_options=False)

src/_pytest/_code/code.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def __init__(self, rawcode):
3333
def __eq__(self, other):
3434
return self.raw == other.raw
3535

36-
__hash__ = None
36+
# Ignore type because of https://github.com/python/mypy/issues/4266.
37+
__hash__ = None # type: ignore
3738

3839
def __ne__(self, other):
3940
return not self == other
@@ -188,11 +189,11 @@ def path(self):
188189
""" path to the source code """
189190
return self.frame.code.path
190191

191-
def getlocals(self):
192+
@property
193+
def locals(self):
194+
""" locals of underlaying frame """
192195
return self.frame.f_locals
193196

194-
locals = property(getlocals, None, None, "locals of underlaying frame")
195-
196197
def getfirstlinesource(self):
197198
return self.frame.code.firstlineno
198199

@@ -255,11 +256,11 @@ def __str__(self):
255256
line = "???"
256257
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
257258

259+
@property
258260
def name(self):
261+
""" co_name of underlaying code """
259262
return self.frame.code.raw.co_name
260263

261-
name = property(name, None, None, "co_name of underlaying code")
262-
263264

264265
class Traceback(list):
265266
""" Traceback objects encapsulate and offer higher level

src/_pytest/_code/source.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ def __eq__(self, other):
4444
return str(self) == other
4545
return False
4646

47-
__hash__ = None
47+
# Ignore type because of https://github.com/python/mypy/issues/4266.
48+
__hash__ = None # type: ignore
4849

4950
def __getitem__(self, key):
5051
if isinstance(key, int):

src/_pytest/assertion/rewrite.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
import sys
1313
import tokenize
1414
import types
15+
from typing import Dict
16+
from typing import List
17+
from typing import Optional
18+
from typing import Set
1519

1620
import atomicwrites
1721

@@ -459,39 +463,40 @@ def _fix(node, lineno, col_offset):
459463
return node
460464

461465

462-
def _get_assertion_exprs(src: bytes): # -> Dict[int, str]
466+
def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
463467
"""Returns a mapping from {lineno: "assertion test expression"}"""
464-
ret = {}
468+
ret = {} # type: Dict[int, str]
465469

466470
depth = 0
467-
lines = []
468-
assert_lineno = None
469-
seen_lines = set()
471+
lines = [] # type: List[str]
472+
assert_lineno = None # type: Optional[int]
473+
seen_lines = set() # type: Set[int]
470474

471475
def _write_and_reset() -> None:
472476
nonlocal depth, lines, assert_lineno, seen_lines
477+
assert assert_lineno is not None
473478
ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\")
474479
depth = 0
475480
lines = []
476481
assert_lineno = None
477482
seen_lines = set()
478483

479484
tokens = tokenize.tokenize(io.BytesIO(src).readline)
480-
for tp, src, (lineno, offset), _, line in tokens:
481-
if tp == tokenize.NAME and src == "assert":
485+
for tp, source, (lineno, offset), _, line in tokens:
486+
if tp == tokenize.NAME and source == "assert":
482487
assert_lineno = lineno
483488
elif assert_lineno is not None:
484489
# keep track of depth for the assert-message `,` lookup
485-
if tp == tokenize.OP and src in "([{":
490+
if tp == tokenize.OP and source in "([{":
486491
depth += 1
487-
elif tp == tokenize.OP and src in ")]}":
492+
elif tp == tokenize.OP and source in ")]}":
488493
depth -= 1
489494

490495
if not lines:
491496
lines.append(line[offset:])
492497
seen_lines.add(lineno)
493498
# a non-nested comma separates the expression from the message
494-
elif depth == 0 and tp == tokenize.OP and src == ",":
499+
elif depth == 0 and tp == tokenize.OP and source == ",":
495500
# one line assert with message
496501
if lineno in seen_lines and len(lines) == 1:
497502
offset_in_trimmed = offset + len(lines[-1]) - len(line)

src/_pytest/capture.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ def __init__(self, targetfd, tmpfile=None):
547547
self.start = lambda: None
548548
self.done = lambda: None
549549
else:
550+
self.start = self._start
551+
self.done = self._done
550552
if targetfd == 0:
551553
assert not tmpfile, "cannot set tmpfile with stdin"
552554
tmpfile = open(os.devnull, "r")
@@ -568,7 +570,7 @@ def __repr__(self):
568570
self.targetfd, getattr(self, "targetfd_save", None), self._state
569571
)
570572

571-
def start(self):
573+
def _start(self):
572574
""" Start capturing on targetfd using memorized tmpfile. """
573575
try:
574576
os.fstat(self.targetfd_save)
@@ -585,7 +587,7 @@ def snap(self):
585587
self.tmpfile.truncate()
586588
return res
587589

588-
def done(self):
590+
def _done(self):
589591
""" stop capturing, restore streams, return original capture file,
590592
seeked to position zero. """
591593
targetfd_save = self.__dict__.pop("targetfd_save")
@@ -618,7 +620,8 @@ class FDCapture(FDCaptureBinary):
618620
snap() produces text
619621
"""
620622

621-
EMPTY_BUFFER = str()
623+
# Ignore type because it doesn't match the type in the superclass (bytes).
624+
EMPTY_BUFFER = str() # type: ignore
622625

623626
def snap(self):
624627
res = super().snap()
@@ -679,7 +682,8 @@ def writeorg(self, data):
679682

680683

681684
class SysCaptureBinary(SysCapture):
682-
EMPTY_BUFFER = b""
685+
# Ignore type because it doesn't match the type in the superclass (str).
686+
EMPTY_BUFFER = b"" # type: ignore
683687

684688
def snap(self):
685689
res = self.tmpfile.buffer.getvalue()

src/_pytest/debugging.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class pytestPDB:
7474

7575
_pluginmanager = None
7676
_config = None
77-
_saved = []
77+
_saved = [] # type: list
7878
_recursive_debug = 0
7979
_wrapped_pdb_cls = None
8080

src/_pytest/fixtures.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from collections import defaultdict
77
from collections import deque
88
from collections import OrderedDict
9+
from typing import Dict
10+
from typing import Tuple
911

1012
import attr
1113
import py
@@ -31,6 +33,9 @@
3133
from _pytest.outcomes import fail
3234
from _pytest.outcomes import TEST_OUTCOME
3335

36+
if False: # TYPE_CHECKING
37+
from typing import Type
38+
3439

3540
@attr.s(frozen=True)
3641
class PseudoFixtureDef:
@@ -54,10 +59,10 @@ def pytest_sessionstart(session):
5459
session._fixturemanager = FixtureManager(session)
5560

5661

57-
scopename2class = {}
62+
scopename2class = {} # type: Dict[str, Type[nodes.Node]]
5863

5964

60-
scope2props = dict(session=())
65+
scope2props = dict(session=()) # type: Dict[str, Tuple[str, ...]]
6166
scope2props["package"] = ("fspath",)
6267
scope2props["module"] = ("fspath", "module")
6368
scope2props["class"] = scope2props["module"] + ("cls",)
@@ -960,7 +965,8 @@ class FixtureFunctionMarker:
960965
scope = attr.ib()
961966
params = attr.ib(converter=attr.converters.optional(tuple))
962967
autouse = attr.ib(default=False)
963-
ids = attr.ib(default=None, converter=_ensure_immutable_ids)
968+
# Ignore type because of https://github.com/python/mypy/issues/6172.
969+
ids = attr.ib(default=None, converter=_ensure_immutable_ids) # type: ignore
964970
name = attr.ib(default=None)
965971

966972
def __call__(self, function):

src/_pytest/mark/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ def pytest_cmdline_main(config):
9191
return 0
9292

9393

94-
pytest_cmdline_main.tryfirst = True
94+
# Ignore type because of https://github.com/python/mypy/issues/2087.
95+
pytest_cmdline_main.tryfirst = True # type: ignore
9596

9697

9798
def deselect_by_keyword(items, config):

src/_pytest/mark/structures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from collections import namedtuple
44
from collections.abc import MutableMapping
55
from operator import attrgetter
6+
from typing import Set
67

78
import attr
89

@@ -298,7 +299,7 @@ def test_function():
298299
on the ``test_function`` object. """
299300

300301
_config = None
301-
_markers = set()
302+
_markers = set() # type: Set[str]
302303

303304
def __getattr__(self, name):
304305
if name[0] == "_":

src/_pytest/nodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ def _repr_failure_py(self, excinfo, style=None):
280280
truncate_locals=truncate_locals,
281281
)
282282

283-
repr_failure = _repr_failure_py
283+
def repr_failure(self, excinfo, style=None):
284+
return self._repr_failure_py(excinfo, style)
284285

285286

286287
def get_fslocation_from_item(item):

src/_pytest/outcomes.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ def exit(msg, returncode=None):
7070
raise Exit(msg, returncode)
7171

7272

73-
exit.Exception = Exit
73+
# Ignore type because of https://github.com/python/mypy/issues/2087.
74+
exit.Exception = Exit # type: ignore
7475

7576

7677
def skip(msg="", *, allow_module_level=False):
@@ -96,7 +97,8 @@ def skip(msg="", *, allow_module_level=False):
9697
raise Skipped(msg=msg, allow_module_level=allow_module_level)
9798

9899

99-
skip.Exception = Skipped
100+
# Ignore type because of https://github.com/python/mypy/issues/2087.
101+
skip.Exception = Skipped # type: ignore
100102

101103

102104
def fail(msg="", pytrace=True):
@@ -111,7 +113,8 @@ def fail(msg="", pytrace=True):
111113
raise Failed(msg=msg, pytrace=pytrace)
112114

113115

114-
fail.Exception = Failed
116+
# Ignore type because of https://github.com/python/mypy/issues/2087.
117+
fail.Exception = Failed # type: ignore
115118

116119

117120
class XFailed(Failed):
@@ -132,7 +135,8 @@ def xfail(reason=""):
132135
raise XFailed(reason)
133136

134137

135-
xfail.Exception = XFailed
138+
# Ignore type because of https://github.com/python/mypy/issues/2087.
139+
xfail.Exception = XFailed # type: ignore
136140

137141

138142
def importorskip(modname, minversion=None, reason=None):

0 commit comments

Comments
 (0)