Skip to content

Commit 3eea787

Browse files
committed
Enable check_untyped_defs mypy option for src/
This option checks even functions which are not annotated. It's a good step to ensure that existing type annotation are correct. In a Pareto fashion, the last few holdouts are always the ugliest, beware.
1 parent 93b5324 commit 3eea787

File tree

9 files changed

+68
-34
lines changed

9 files changed

+68
-34
lines changed

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ strict_equality = True
7070
warn_redundant_casts = True
7171
warn_return_any = True
7272
warn_unused_configs = True
73+
74+
[mypy-_pytest.*]
75+
check_untyped_defs = True

src/_pytest/capture.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,11 @@ def start_capturing(self) -> None:
443443
def pop_outerr_to_orig(self):
444444
""" pop current snapshot out/err capture and flush to orig streams. """
445445
out, err = self.readouterr()
446+
# TODO: Fix type ignores.
446447
if out:
447-
self.out.writeorg(out)
448+
self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821
448449
if err:
449-
self.err.writeorg(err)
450+
self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821
450451
return out, err
451452

452453
def suspend_capturing(self, in_: bool = False) -> None:
@@ -459,14 +460,15 @@ def suspend_capturing(self, in_: bool = False) -> None:
459460
self.in_.suspend()
460461
self._in_suspended = True
461462

462-
def resume_capturing(self):
463+
def resume_capturing(self) -> None:
463464
self._state = "resumed"
464465
if self.out:
465466
self.out.resume()
466467
if self.err:
467468
self.err.resume()
468469
if self._in_suspended:
469-
self.in_.resume()
470+
# TODO: Fix type ignore.
471+
self.in_.resume() # type: ignore[union-attr] # noqa: F821
470472
self._in_suspended = False
471473

472474
def stop_capturing(self):

src/_pytest/config/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ def _consider_importhook(self, args: Sequence[str]) -> None:
949949
self._mark_plugins_for_rewrite(hook)
950950
_warn_about_missing_assertion(mode)
951951

952-
def _mark_plugins_for_rewrite(self, hook):
952+
def _mark_plugins_for_rewrite(self, hook) -> None:
953953
"""
954954
Given an importhook, mark for rewrite any top-level
955955
modules or packages in the distribution package for
@@ -964,7 +964,9 @@ def _mark_plugins_for_rewrite(self, hook):
964964
package_files = (
965965
str(file)
966966
for dist in importlib_metadata.distributions()
967-
if any(ep.group == "pytest11" for ep in dist.entry_points)
967+
# Type ignored due to missing stub:
968+
# https://github.com/python/typeshed/pull/3795
969+
if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore
968970
for file in dist.files or []
969971
)
970972

src/_pytest/fixtures.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,9 @@ def _getscopeitem(self, scope):
720720
# this might also be a non-function Item despite its attribute name
721721
return self._pyfuncitem
722722
if scope == "package":
723-
node = get_scope_package(self._pyfuncitem, self._fixturedef)
723+
# FIXME: _fixturedef is not defined on FixtureRequest (this class),
724+
# but on FixtureRequest (a subclass).
725+
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] # noqa: F821
724726
else:
725727
node = get_scope_node(self._pyfuncitem, scope)
726728
if node is None and scope == "class":
@@ -1157,7 +1159,7 @@ def result(*args, **kwargs):
11571159

11581160
# keep reference to the original function in our own custom attribute so we don't unwrap
11591161
# further than this point and lose useful wrappings like @mock.patch (#3774)
1160-
result.__pytest_wrapped__ = _PytestWrapper(function)
1162+
result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] # noqa: F821
11611163

11621164
return result
11631165

src/_pytest/nodes.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
if TYPE_CHECKING:
4040
from typing import Type
4141

42-
# Imported here due to circular import.
43-
from _pytest.main import Session # noqa: F401
42+
from _pytest.main import Session
43+
from _pytest.warning_types import PytestWarning
4444

4545
SEP = "/"
4646

@@ -104,9 +104,9 @@ class Node(metaclass=NodeMeta):
104104
def __init__(
105105
self,
106106
name: str,
107-
parent: Optional["Node"] = None,
107+
parent: "Optional[Node]" = None,
108108
config: Optional[Config] = None,
109-
session: Optional["Session"] = None,
109+
session: "Optional[Session]" = None,
110110
fspath: Optional[py.path.local] = None,
111111
nodeid: Optional[str] = None,
112112
) -> None:
@@ -187,7 +187,7 @@ def ihook(self):
187187
def __repr__(self) -> str:
188188
return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None))
189189

190-
def warn(self, warning):
190+
def warn(self, warning: "PytestWarning") -> None:
191191
"""Issue a warning for this item.
192192
193193
Warnings will be displayed after the test session, unless explicitly suppressed
@@ -212,11 +212,9 @@ def warn(self, warning):
212212
)
213213
)
214214
path, lineno = get_fslocation_from_item(self)
215+
assert lineno is not None
215216
warnings.warn_explicit(
216-
warning,
217-
category=None,
218-
filename=str(path),
219-
lineno=lineno + 1 if lineno is not None else None,
217+
warning, category=None, filename=str(path), lineno=lineno + 1,
220218
)
221219

222220
# methods for ordering nodes
@@ -396,24 +394,26 @@ def repr_failure(
396394

397395

398396
def get_fslocation_from_item(
399-
item: "Item",
397+
node: "Node",
400398
) -> Tuple[Union[str, py.path.local], Optional[int]]:
401-
"""Tries to extract the actual location from an item, depending on available attributes:
399+
"""Tries to extract the actual location from a node, depending on available attributes:
402400
403-
* "fslocation": a pair (path, lineno)
404-
* "obj": a Python object that the item wraps.
401+
* "location": a pair (path, lineno)
402+
* "obj": a Python object that the node wraps.
405403
* "fspath": just a path
406404
407405
:rtype: a tuple of (str|LocalPath, int) with filename and line number.
408406
"""
409-
try:
410-
return item.location[:2]
411-
except AttributeError:
412-
pass
413-
obj = getattr(item, "obj", None)
407+
# See Item.location.
408+
location = getattr(
409+
node, "location", None
410+
) # type: Optional[Tuple[str, Optional[int], str]]
411+
if location is not None:
412+
return location[:2]
413+
obj = getattr(node, "obj", None)
414414
if obj is not None:
415415
return getfslineno(obj)
416-
return getattr(item, "fspath", "unknown location"), -1
416+
return getattr(node, "fspath", "unknown location"), -1
417417

418418

419419
class Collector(Node):

src/_pytest/pytester.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,8 +1138,10 @@ def popen(
11381138

11391139
popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
11401140
if stdin is Testdir.CLOSE_STDIN:
1141+
assert popen.stdin is not None
11411142
popen.stdin.close()
11421143
elif isinstance(stdin, bytes):
1144+
assert popen.stdin is not None
11431145
popen.stdin.write(stdin)
11441146

11451147
return popen

src/_pytest/python.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from _pytest.warning_types import PytestUnhandledCoroutineWarning
6565

6666
if TYPE_CHECKING:
67+
from typing import Type
6768
from typing_extensions import Literal
6869
from _pytest.fixtures import _Scope
6970

@@ -276,6 +277,18 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj):
276277
class PyobjMixin:
277278
_ALLOW_MARKERS = True
278279

280+
# Function and attributes that the mixin needs (for type-checking only).
281+
if TYPE_CHECKING:
282+
name = "" # type: str
283+
parent = None # type: Optional[nodes.Node]
284+
own_markers = [] # type: List[Mark]
285+
286+
def getparent(self, cls: Type[nodes._NodeType]) -> Optional[nodes._NodeType]:
287+
...
288+
289+
def listchain(self) -> List[nodes.Node]:
290+
...
291+
279292
@property
280293
def module(self):
281294
"""Python module object this node was collected from (can be None)."""
@@ -312,7 +325,10 @@ def obj(self, value):
312325

313326
def _getobj(self):
314327
"""Gets the underlying Python object. May be overwritten by subclasses."""
315-
return getattr(self.parent.obj, self.name)
328+
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
329+
assert self.parent is not None
330+
obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821
331+
return getattr(obj, self.name)
316332

317333
def getmodpath(self, stopatmodule=True, includemodule=False):
318334
""" return python path relative to the containing module. """
@@ -791,7 +807,10 @@ class Instance(PyCollector):
791807
# can be removed at node structure reorganization time
792808

793809
def _getobj(self):
794-
return self.parent.obj()
810+
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
811+
assert self.parent is not None
812+
obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821
813+
return obj()
795814

796815
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
797816
self.session._fixturemanager.parsefactories(self)
@@ -1563,7 +1582,10 @@ def _getobj(self):
15631582
i = name.find("[") # parametrization
15641583
if i != -1:
15651584
name = name[:i]
1566-
return getattr(self.parent.obj, name)
1585+
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
1586+
assert self.parent is not None
1587+
obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821
1588+
return getattr(obj, name)
15671589

15681590
@property
15691591
def _pyfuncitem(self):

src/_pytest/python_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
506506
__tracebackhide__ = True
507507

508508
if isinstance(expected, Decimal):
509-
cls = ApproxDecimal
509+
cls = ApproxDecimal # type: Type[ApproxBase]
510510
elif isinstance(expected, Number):
511511
cls = ApproxScalar
512512
elif isinstance(expected, Mapping):
@@ -532,7 +532,7 @@ def _is_numpy_array(obj):
532532
"""
533533
import sys
534534

535-
np = sys.modules.get("numpy")
535+
np = sys.modules.get("numpy") # type: Any
536536
if np is not None:
537537
return isinstance(obj, np.ndarray)
538538
return False

src/_pytest/recwarn.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,9 @@ class WarningsRecorder(warnings.catch_warnings):
136136
Adapted from `warnings.catch_warnings`.
137137
"""
138138

139-
def __init__(self):
140-
super().__init__(record=True)
139+
def __init__(self) -> None:
140+
# Type ignored due to the way typeshed handles warnings.catch_warnings.
141+
super().__init__(record=True) # type: ignore[call-arg] # noqa: F821
141142
self._entered = False
142143
self._list = [] # type: List[warnings.WarningMessage]
143144

0 commit comments

Comments
 (0)