8000 Add rudimentary mypy type checking · pytest-dev/pytest@ee614dd · GitHub
[go: up one dir, main page]

Skip to content

Commit ee614dd

Browse files
committed
Add rudimentary mypy type checking
Add a very lax mypy configuration, add it to tox -e linting, and fix/ignore the few errors that come up. The idea is to get it running before diving in too much. This enables: - Progressively adding type annotations and enabling more strict options, which will improve the codebase (IMO). - Annotating the public API in-line, and eventually exposing it to library users who use type checkers (with a py.typed file). Though, none of this is done yet. Refs #3342.
1 parent 60a358f commit ee614dd

File tree

31 files changed

+102
-45
lines changed

31 files changed

+102
-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: 10 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,15 @@ 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+
- id: mypy
53+
name: mypy (testing)
54+
files: ^testing/
4555
- repo: local
4656
hooks:
4757
- 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

0 commit comments

Comments
 (0)
0