diff options
author | Ran Benita <ran@unusedvar.com> | 2020-05-01 14:40:16 +0300 |
---|---|---|
committer | Ran Benita <ran@unusedvar.com> | 2020-06-05 11:34:20 +0300 |
commit | 71dfdca4df6961460653c265026e194fbcaebef2 (patch) | |
tree | c6a88af4e3c29addc5fd95fd01ae36a3ef5fbfb4 | |
parent | 848ab00663c9daf8cd27ee92dec1005cd9633152 (diff) | |
download | pytest-71dfdca4df6961460653c265026e194fbcaebef2.tar.gz |
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.
-rw-r--r-- | setup.cfg | 3 | ||||
-rw-r--r-- | src/_pytest/capture.py | 8 | ||||
-rw-r--r-- | src/_pytest/config/__init__.py | 6 | ||||
-rw-r--r-- | src/_pytest/fixtures.py | 6 | ||||
-rw-r--r-- | src/_pytest/nodes.py | 36 | ||||
-rw-r--r-- | src/_pytest/pytester.py | 2 | ||||
-rw-r--r-- | src/_pytest/python.py | 26 | ||||
-rw-r--r-- | src/_pytest/python_api.py | 4 | ||||
-rw-r--r-- | src/_pytest/recwarn.py | 5 |
9 files changed, 65 insertions, 31 deletions
@@ -98,3 +98,6 @@ strict_equality = True warn_redundant_casts = True warn_return_any = True warn_unused_configs = True + +[mypy-_pytest.*] +check_untyped_defs = True diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index bcc16ceb6..98ba878b3 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -519,10 +519,11 @@ class MultiCapture: def pop_outerr_to_orig(self): """ pop current snapshot out/err capture and flush to orig streams. """ out, err = self.readouterr() + # TODO: Fix type ignores. if out: - self.out.writeorg(out) + self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821 if err: - self.err.writeorg(err) + self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821 return out, err def suspend_capturing(self, in_: bool = False) -> None: @@ -542,7 +543,8 @@ class MultiCapture: if self.err: self.err.resume() if self._in_suspended: - self.in_.resume() + # TODO: Fix type ignore. + self.in_.resume() # type: ignore[union-attr] # noqa: F821 self._in_suspended = False def stop_capturing(self) -> None: diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index ff6aee744..27083900d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -974,7 +974,7 @@ class Config: self._mark_plugins_for_rewrite(hook) _warn_about_missing_assertion(mode) - def _mark_plugins_for_rewrite(self, hook): + def _mark_plugins_for_rewrite(self, hook) -> None: """ Given an importhook, mark for rewrite any top-level modules or packages in the distribution package for @@ -989,7 +989,9 @@ class Config: package_files = ( str(file) for dist in importlib_metadata.distributions() - if any(ep.group == "pytest11" for ep in dist.entry_points) + # Type ignored due to missing stub: + # https://github.com/python/typeshed/pull/3795 + if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore for file in dist.files or [] ) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 8aa5d73a8..fa7e3e1df 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -721,7 +721,9 @@ class FixtureRequest: # this might also be a non-function Item despite its attribute name return self._pyfuncitem if scope == "package": - node = get_scope_package(self._pyfuncitem, self._fixturedef) + # FIXME: _fixturedef is not defined on FixtureRequest (this class), + # but on FixtureRequest (a subclass). + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] # noqa: F821 else: node = get_scope_node(self._pyfuncitem, scope) if node is None and scope == "class": @@ -1158,7 +1160,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker): # keep reference to the original function in our own custom attribute so we don't unwrap # further than this point and lose useful wrappings like @mock.patch (#3774) - result.__pytest_wrapped__ = _PytestWrapper(function) + result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] # noqa: F821 return result diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index eaa48e5de..15f91343f 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -42,6 +42,8 @@ if TYPE_CHECKING: # Imported here due to circular import. from _pytest.main import Session + from _pytest.warning_types import PytestWarning + SEP = "/" @@ -118,9 +120,9 @@ class Node(metaclass=NodeMeta): def __init__( self, name: str, - parent: Optional["Node"] = None, + parent: "Optional[Node]" = None, config: Optional[Config] = None, - session: Optional["Session"] = None, + session: "Optional[Session]" = None, fspath: Optional[py.path.local] = None, nodeid: Optional[str] = None, ) -> None: @@ -201,7 +203,7 @@ class Node(metaclass=NodeMeta): def __repr__(self) -> str: return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) - def warn(self, warning): + def warn(self, warning: "PytestWarning") -> None: """Issue a warning for this item. Warnings will be displayed after the test session, unless explicitly suppressed @@ -226,11 +228,9 @@ class Node(metaclass=NodeMeta): ) ) path, lineno = get_fslocation_from_item(self) + assert lineno is not None warnings.warn_explicit( - warning, - category=None, - filename=str(path), - lineno=lineno + 1 if lineno is not None else None, + warning, category=None, filename=str(path), lineno=lineno + 1, ) # methods for ordering nodes @@ -417,24 +417,26 @@ class Node(metaclass=NodeMeta): def get_fslocation_from_item( - item: "Item", + node: "Node", ) -> Tuple[Union[str, py.path.local], Optional[int]]: - """Tries to extract the actual location from an item, depending on available attributes: + """Tries to extract the actual location from a node, depending on available attributes: - * "fslocation": a pair (path, lineno) - * "obj": a Python object that the item wraps. + * "location": a pair (path, lineno) + * "obj": a Python object that the node wraps. * "fspath": just a path :rtype: a tuple of (str|LocalPath, int) with filename and line number. """ - try: - return item.location[:2] - except AttributeError: - pass - obj = getattr(item, "obj", None) + # See Item.location. + location = getattr( + node, "location", None + ) # type: Optional[Tuple[str, Optional[int], str]] + if location is not None: + return location[:2] + obj = getattr(node, "obj", None) if obj is not None: return getfslineno(obj) - return getattr(item, "fspath", "unknown location"), -1 + return getattr(node, "fspath", "unknown location"), -1 class Collector(Node): diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 60df17b90..754ecc10f 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1169,8 +1169,10 @@ class Testdir: popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) if stdin is Testdir.CLOSE_STDIN: + assert popen.stdin is not None popen.stdin.close() elif isinstance(stdin, bytes): + assert popen.stdin is not None popen.stdin.write(stdin) return popen diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 55ed2b164..41dd8b292 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -64,6 +64,7 @@ from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning if TYPE_CHECKING: + from typing import Type from typing_extensions import Literal from _pytest.fixtures import _Scope @@ -256,6 +257,18 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj): class PyobjMixin: _ALLOW_MARKERS = True + # Function and attributes that the mixin needs (for type-checking only). + if TYPE_CHECKING: + name = "" # type: str + parent = None # type: Optional[nodes.Node] + own_markers = [] # type: List[Mark] + + def getparent(self, cls: Type[nodes._NodeType]) -> Optional[nodes._NodeType]: + ... + + def listchain(self) -> List[nodes.Node]: + ... + @property def module(self): """Python module object this node was collected from (can be None).""" @@ -292,7 +305,10 @@ class PyobjMixin: def _getobj(self): """Gets the underlying Python object. May be overwritten by subclasses.""" - return getattr(self.parent.obj, self.name) + # TODO: Improve the type of `parent` such that assert/ignore aren't needed. + assert self.parent is not None + obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821 + return getattr(obj, self.name) def getmodpath(self, stopatmodule=True, includemodule=False): """ return python path relative to the containing module. """ @@ -772,7 +788,10 @@ class Instance(PyCollector): # can be removed at node structure reorganization time def _getobj(self): - return self.parent.obj() + # TODO: Improve the type of `parent` such that assert/ignore aren't needed. + assert self.parent is not None + obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821 + return obj() def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: self.session._fixturemanager.parsefactories(self) @@ -1527,7 +1546,8 @@ class Function(PyobjMixin, nodes.Item): return getimfunc(self.obj) def _getobj(self): - return getattr(self.parent.obj, self.originalname) + assert self.parent is not None + return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] @property def _pyfuncitem(self): diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 29c8af7e2..abace3196 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -508,7 +508,7 @@ def approx(expected, rel=None, abs=None, nan_ok=False): __tracebackhide__ = True if isinstance(expected, Decimal): - cls = ApproxDecimal + cls = ApproxDecimal # type: Type[ApproxBase] elif isinstance(expected, Number): cls = ApproxScalar elif isinstance(expected, Mapping): @@ -534,7 +534,7 @@ def _is_numpy_array(obj): """ import sys - np = sys.modules.get("numpy") + np = sys.modules.get("numpy") # type: Any if np is not None: return isinstance(obj, np.ndarray) return False diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 58b6fbab9..57034be2a 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -136,8 +136,9 @@ class WarningsRecorder(warnings.catch_warnings): Adapted from `warnings.catch_warnings`. """ - def __init__(self): - super().__init__(record=True) + def __init__(self) -> None: + # Type ignored due to the way typeshed handles warnings.catch_warnings. + super().__init__(record=True) # type: ignore[call-arg] # noqa: F821 self._entered = False self._list = [] # type: List[warnings.WarningMessage] |