From fb2640b82fdf6789d36a8204066695c2cc14ff6a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Jun 2020 18:34:32 +0300 Subject: [PATCH 01/15] Stop using ExceptionInfo.errisinstance internally It does the same as a simple isinstance check, but adds a little layer of obscurity on top, which the type checker can't penetrate. --- src/_pytest/_code/code.py | 7 +++++-- src/_pytest/main.py | 2 +- src/_pytest/reports.py | 2 +- src/_pytest/runner.py | 21 ++++++++++++++------- src/_pytest/unittest.py | 4 +--- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index e548bceb7..ab38b204f 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -558,7 +558,10 @@ class ExceptionInfo(Generic[_E]): def errisinstance( self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]] ) -> bool: - """ return True if the exception is an instance of exc """ + """Return True if the exception is an instance of exc. + + Consider using ``isinstance(excinfo.value, exc)`` instead. + """ return isinstance(self.value, exc) def _getreprcrash(self) -> "ReprFileLocation": @@ -804,7 +807,7 @@ class FormattedExcinfo: if self.tbfilter: traceback = traceback.filter() - if excinfo.errisinstance(RecursionError): + if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 98dabaf87..7e6816506 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -265,7 +265,7 @@ def wrap_session( session.exitstatus = exc.returncode sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc)) else: - if excinfo.errisinstance(SystemExit): + if isinstance(excinfo.value, SystemExit): sys.stderr.write("mainloop: caught unexpected SystemExit!\n") finally: diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 7aba0b024..186c53ed3 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -308,7 +308,7 @@ class TestReport(BaseReport): if not isinstance(excinfo, ExceptionInfo): outcome = "failed" longrepr = excinfo - elif excinfo.errisinstance(skip.Exception): + elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() longrepr = (str(r.path), r.lineno, r.message) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 8b23cb49e..702380a5b 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -215,11 +215,18 @@ def call_and_report( def check_interactive_exception(call: "CallInfo", report: BaseReport) -> bool: - return call.excinfo is not None and not ( - hasattr(report, "wasxfail") - or call.excinfo.errisinstance(Skipped) - or call.excinfo.errisinstance(bdb.BdbQuit) - ) + """Check whether the call raised an exception that should be reported as + interactive.""" + if call.excinfo is None: + # Didn't raise. + return False + if hasattr(report, "wasxfail"): + # Exception was expected. + return False + if isinstance(call.excinfo.value, (Skipped, bdb.BdbQuit)): + # Special control flow exception. + return False + return True def call_runtest_hook( @@ -287,7 +294,7 @@ class CallInfo(Generic[_T]): result = func() # type: Optional[_T] except BaseException: excinfo = ExceptionInfo.from_current() - if reraise is not None and excinfo.errisinstance(reraise): + if reraise is not None and isinstance(excinfo.value, reraise): raise result = None # use the perf counter @@ -325,7 +332,7 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: if unittest is not None: # Type ignored because unittest is loaded dynamically. skip_exceptions.append(unittest.SkipTest) # type: ignore - if call.excinfo.errisinstance(tuple(skip_exceptions)): + if isinstance(call.excinfo.value, tuple(skip_exceptions)): outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") assert isinstance(r_, ExceptionChainRepr), repr(r_) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 0e4a31311..bd61726c7 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -302,9 +302,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: if ( unittest and call.excinfo - and call.excinfo.errisinstance( - unittest.SkipTest # type: ignore[attr-defined] # noqa: F821 - ) + and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] ): excinfo = call.excinfo # let's substitute the excinfo with a pytest.skip one From e079ebbd57534e1d0041e22f091054e4cd6f2ecb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Jun 2020 19:31:35 +0300 Subject: [PATCH 02/15] python: more type annotations --- src/_pytest/python.py | 106 +++++++++++++++++++------------------ testing/python/metafunc.py | 18 ++++--- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b41a234f4..2c9f383e4 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -13,7 +13,9 @@ from collections.abc import Sequence from functools import partial from typing import Callable from typing import Dict +from typing import Generator from typing import Iterable +from typing import Iterator from typing import List from typing import Mapping from typing import Optional @@ -196,8 +198,8 @@ def pytest_collect_file(path: py.path.local, parent) -> Optional["Module"]: return None -def path_matches_patterns(path, patterns): - """Returns True if the given py.path.local matches one of the patterns in the list of globs given""" +def path_matches_patterns(path: py.path.local, patterns: Iterable[str]) -> bool: + """Returns True if path matches any of the patterns in the list of globs given.""" return any(path.fnmatch(pattern) for pattern in patterns) @@ -300,7 +302,7 @@ class PyobjMixin: obj = self.parent.obj # type: ignore[attr-defined] # noqa: F821 return getattr(obj, self.name) - def getmodpath(self, stopatmodule=True, includemodule=False): + def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """ return python path relative to the containing module. """ chain = self.listchain() chain.reverse() @@ -338,10 +340,10 @@ class PyobjMixin: class PyCollector(PyobjMixin, nodes.Collector): - def funcnamefilter(self, name): + def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) - def isnosetest(self, obj): + def isnosetest(self, obj: object) -> bool: """ Look for the __test__ attribute, which is applied by the @nose.tools.istest decorator """ @@ -350,10 +352,10 @@ class PyCollector(PyobjMixin, nodes.Collector): # function) as test classes. return safe_getattr(obj, "__test__", False) is True - def classnamefilter(self, name): + def classnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_classes", name) - def istestfunction(self, obj, name): + def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): if isinstance(obj, staticmethod): # static methods need to be unwrapped @@ -365,10 +367,10 @@ class PyCollector(PyobjMixin, nodes.Collector): else: return False - def istestclass(self, obj, name): + def istestclass(self, obj: object, name: str) -> bool: return self.classnamefilter(name) or self.isnosetest(obj) - def _matches_prefix_or_glob_option(self, option_name, name): + def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: """ checks if the given name matches the prefix or glob-pattern defined in ini configuration. @@ -428,7 +430,7 @@ class PyCollector(PyobjMixin, nodes.Collector): ) # type: Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]] return item - def _genfunctions(self, name, funcobj): + def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: modulecol = self.getparent(Module) assert modulecol is not None module = modulecol.obj @@ -486,7 +488,7 @@ class Module(nodes.File, PyCollector): self.session._fixturemanager.parsefactories(self) return super().collect() - def _inject_setup_module_fixture(self): + def _inject_setup_module_fixture(self) -> None: """Injects a hidden autouse, module scoped fixture into the collected module object that invokes setUpModule/tearDownModule if either or both are available. @@ -504,7 +506,7 @@ class Module(nodes.File, PyCollector): return @fixtures.fixture(autouse=True, scope="module") - def xunit_setup_module_fixture(request): + def xunit_setup_module_fixture(request) -> Generator[None, None, None]: if setup_module is not None: _call_with_optional_argument(setup_module, request.module) yield @@ -513,7 +515,7 @@ class Module(nodes.File, PyCollector): self.obj.__pytest_setup_module = xunit_setup_module_fixture - def _inject_setup_function_fixture(self): + def _inject_setup_function_fixture(self) -> None: """Injects a hidden autouse, function scoped fixture into the collected module object that invokes setup_function/teardown_function if either or both are available. @@ -528,7 +530,7 @@ class Module(nodes.File, PyCollector): return @fixtures.fixture(autouse=True, scope="function") - def xunit_setup_function_fixture(request): + def xunit_setup_function_fixture(request) -> Generator[None, None, None]: if request.instance is not None: # in this case we are bound to an instance, so we need to let # setup_method handle this @@ -608,7 +610,7 @@ class Package(Module): ) self.name = os.path.basename(str(fspath.dirname)) - def setup(self): + def setup(self) -> None: # not using fixtures to call setup_module here because autouse fixtures # from packages are not called automatically (#4085) setup_module = _get_first_non_fixture_func( @@ -661,7 +663,7 @@ class Package(Module): pkg_prefixes.add(path) -def _call_with_optional_argument(func, arg): +def _call_with_optional_argument(func, arg) -> None: """Call the given function with the given argument if func accepts one argument, otherwise calls func without arguments""" arg_count = func.__code__.co_argcount @@ -673,7 +675,7 @@ def _call_with_optional_argument(func, arg): func() -def _get_first_non_fixture_func(obj, names): +def _get_first_non_fixture_func(obj: object, names: Iterable[str]): """Return the attribute from the given object to be used as a setup/teardown xunit-style function, but only if not marked as a fixture to avoid calling it twice. @@ -723,7 +725,7 @@ class Class(PyCollector): return [Instance.from_parent(self, name="()")] - def _inject_setup_class_fixture(self): + def _inject_setup_class_fixture(self) -> None: """Injects a hidden autouse, class scoped fixture into the collected class object that invokes setup_class/teardown_class if either or both are available. @@ -736,7 +738,7 @@ class Class(PyCollector): return @fixtures.fixture(autouse=True, scope="class") - def xunit_setup_class_fixture(cls): + def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: if setup_class is not None: func = getimfunc(setup_class) _call_with_optional_argument(func, self.obj) @@ -747,7 +749,7 @@ class Class(PyCollector): self.obj.__pytest_setup_class = xunit_setup_class_fixture - def _inject_setup_method_fixture(self): + def _inject_setup_method_fixture(self) -> None: """Injects a hidden autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. @@ -760,7 +762,7 @@ class Class(PyCollector): return @fixtures.fixture(autouse=True, scope="function") - def xunit_setup_method_fixture(self, request): + def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: method = request.function if setup_method is not None: func = getattr(self, "setup_method") @@ -794,16 +796,18 @@ class Instance(PyCollector): return self.obj -def hasinit(obj): - init = getattr(obj, "__init__", None) +def hasinit(obj: object) -> bool: + init = getattr(obj, "__init__", None) # type: object if init: return init != object.__init__ + return False -def hasnew(obj): - new = getattr(obj, "__new__", None) +def hasnew(obj: object) -> bool: + new = getattr(obj, "__new__", None) # type: object if new: return new != object.__new__ + return False class CallSpec2: @@ -843,7 +847,7 @@ class CallSpec2: def setmulti2( self, - valtypes: "Mapping[str, Literal['params', 'funcargs']]", + valtypes: Mapping[str, "Literal['params', 'funcargs']"], argnames: typing.Sequence[str], valset: Iterable[object], id: str, @@ -903,7 +907,7 @@ class Metafunc: self._arg2fixturedefs = fixtureinfo.name2fixturedefs @property - def funcargnames(self): + def funcargnames(self) -> List[str]: """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" warnings.warn(FUNCARGNAMES, stacklevel=2) return self.fixturenames @@ -1170,7 +1174,11 @@ class Metafunc: ) -def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): +def _find_parametrized_scope( + argnames: typing.Sequence[str], + arg2fixturedefs: Mapping[str, typing.Sequence[fixtures.FixtureDef]], + indirect: Union[bool, typing.Sequence[str]], +) -> "fixtures._Scope": """Find the most appropriate scope for a parametrized call based on its arguments. When there's at least one direct argument, always use "function" scope. @@ -1180,9 +1188,7 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): Related to issue #1832, based on code posted by @Kingdread. """ - from _pytest.fixtures import scopes - - if isinstance(indirect, (list, tuple)): + if isinstance(indirect, Sequence): all_arguments_are_fixtures = len(indirect) == len(argnames) else: all_arguments_are_fixtures = bool(indirect) @@ -1196,7 +1202,7 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): ] if used_scopes: # Takes the most narrow scope from used fixtures - for scope in reversed(scopes): + for scope in reversed(fixtures.scopes): if scope in used_scopes: return scope @@ -1264,7 +1270,7 @@ def _idvalset( ids: Optional[List[Union[None, str]]], nodeid: Optional[str], config: Optional[Config], -): +) -> str: if parameterset.id is not None: return parameterset.id id = None if ids is None or idx >= len(ids) else ids[idx] @@ -1318,7 +1324,7 @@ def show_fixtures_per_test(config): return wrap_session(config, _show_fixtures_per_test) -def _show_fixtures_per_test(config, session): +def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1330,7 +1336,7 @@ def _show_fixtures_per_test(config, session): loc = getlocation(func, curdir) return curdir.bestrelpath(py.path.local(loc)) - def write_fixture(fixture_def): + def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: argname = fixture_def.argname if verbose <= 0 and argname.startswith("_"): return @@ -1346,18 +1352,16 @@ def _show_fixtures_per_test(config, session): else: tw.line(" no docstring available", red=True) - def write_item(item): - try: - info = item._fixtureinfo - except AttributeError: - # doctests items have no _fixtureinfo attribute - return - if not info.name2fixturedefs: - # this test item does not use any fixtures + def write_item(item: nodes.Item) -> None: + # Not all items have _fixtureinfo attribute. + info = getattr(item, "_fixtureinfo", None) # type: Optional[FuncFixtureInfo] + if info is None or not info.name2fixturedefs: + # This test item does not use any fixtures. return tw.line() tw.sep("-", "fixtures used by {}".format(item.name)) - tw.sep("-", "({})".format(get_best_relpath(item.function))) + # TODO: Fix this type ignore. + tw.sep("-", "({})".format(get_best_relpath(item.function))) # type: ignore[attr-defined] # dict key not used in loop but needed for sorting for _, fixturedefs in sorted(info.name2fixturedefs.items()): assert fixturedefs is not None @@ -1448,15 +1452,15 @@ class Function(PyobjMixin, nodes.Item): def __init__( self, - name, + name: str, parent, - config=None, + config: Optional[Config] = None, callspec: Optional[CallSpec2] = None, callobj=NOTSET, keywords=None, - session=None, + session: Optional[Session] = None, fixtureinfo: Optional[FuncFixtureInfo] = None, - originalname=None, + originalname: Optional[str] = None, ) -> None: """ param name: the full function name, including any decorations like those @@ -1533,8 +1537,8 @@ class Function(PyobjMixin, nodes.Item): """ return super().from_parent(parent=parent, **kw) - def _initrequest(self): - self.funcargs = {} + def _initrequest(self) -> None: + self.funcargs = {} # type: Dict[str, object] self._request = fixtures.FixtureRequest(self) @property @@ -1552,7 +1556,7 @@ class Function(PyobjMixin, nodes.Item): return self @property - def funcargnames(self): + def funcargnames(self) -> List[str]: """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" warnings.warn(FUNCARGNAMES, stacklevel=2) return self.fixturenames diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 1f110d419..6a841a346 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -3,9 +3,12 @@ import re import sys import textwrap from typing import Any +from typing import cast +from typing import Dict from typing import Iterator from typing import List from typing import Optional +from typing import Sequence from typing import Tuple from typing import Union @@ -138,12 +141,15 @@ class TestMetafunc: class DummyFixtureDef: scope = attr.ib() - fixtures_defs = dict( - session_fix=[DummyFixtureDef("session")], - package_fix=[DummyFixtureDef("package")], - module_fix=[DummyFixtureDef("module")], - class_fix=[DummyFixtureDef("class")], - func_fix=[DummyFixtureDef("function")], + fixtures_defs = cast( + Dict[str, Sequence[fixtures.FixtureDef]], + dict( + session_fix=[DummyFixtureDef("session")], + package_fix=[DummyFixtureDef("package")], + module_fix=[DummyFixtureDef("module")], + class_fix=[DummyFixtureDef("class")], + func_fix=[DummyFixtureDef("function")], + ), ) # use arguments to determine narrow scope; the cause of the bug is that it would look on all From 5da4a1d84f1d5c49ce9d6e8c63ce7df7f7a92b24 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 9 Jul 2020 23:23:30 +0300 Subject: [PATCH 03/15] capture: type annotate return value of fixtures --- src/_pytest/capture.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index daded6395..3f9c60fb9 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -821,7 +821,7 @@ class CaptureFixture: @pytest.fixture -def capsys(request: SubRequest): +def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]: """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method @@ -838,7 +838,7 @@ def capsys(request: SubRequest): @pytest.fixture -def capsysbinary(request: SubRequest): +def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]: """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` @@ -855,7 +855,7 @@ def capsysbinary(request: SubRequest): @pytest.fixture -def capfd(request: SubRequest): +def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]: """Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -872,7 +872,7 @@ def capfd(request: SubRequest): @pytest.fixture -def capfdbinary(request: SubRequest): +def capfdbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]: """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method From c7a1db5d01596e38154d7b29f14df130c49505cb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 9 Jul 2020 23:30:04 +0300 Subject: [PATCH 04/15] junitxml: few typing fixes & additions --- src/_pytest/junitxml.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 71a31b85b..4873e722d 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -14,6 +14,7 @@ import platform import re import sys from datetime import datetime +from typing import Callable from typing import Dict from typing import List from typing import Match @@ -70,7 +71,7 @@ del _legal_xml_re _py_ext_re = re.compile(r"\.py$") -def bin_xml_escape(arg: str) -> py.xml.raw: +def bin_xml_escape(arg: object) -> py.xml.raw: def repl(matchobj: Match[str]) -> str: i = ord(matchobj.group()) if i <= 0xFF: @@ -78,7 +79,7 @@ def bin_xml_escape(arg: str) -> py.xml.raw: else: return "#x%04X" % i - return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) + return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(str(arg)))) def merge_family(left, right) -> None: @@ -118,10 +119,10 @@ class _NodeReporter: self.xml.add_stats(type(node).__name__) self.nodes.append(node) - def add_property(self, name: str, value: str) -> None: + def add_property(self, name: str, value: object) -> None: self.properties.append((str(name), bin_xml_escape(value))) - def add_attribute(self, name: str, value: str) -> None: + def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) def make_properties_node(self) -> Union[py.xml.Tag, str]: @@ -301,12 +302,14 @@ def _warn_incompatibility_with_xunit2( @pytest.fixture -def record_property(request: FixtureRequest): - """Add an extra properties the calling test. +def record_property(request: FixtureRequest) -> Callable[[str, object], None]: + """Add extra properties to the calling test. + User properties become part of the test report and are available to the configured reporters, like JUnit XML. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded. + + The fixture is callable with ``name, value``. The value is automatically + XML-encoded. Example:: @@ -322,10 +325,11 @@ def record_property(request: FixtureRequest): @pytest.fixture -def record_xml_attribute(request: FixtureRequest): +def record_xml_attribute(request: FixtureRequest) -> Callable[[str, object], None]: """Add extra xml attributes to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being - automatically xml-encoded + + The fixture is callable with ``name, value``. The value is + automatically XML-encoded. """ from _pytest.warning_types import PytestExperimentalApiWarning @@ -336,7 +340,7 @@ def record_xml_attribute(request: FixtureRequest): _warn_incompatibility_with_xunit2(request, "record_xml_attribute") # Declare noop - def add_attr_noop(name: str, value: str) -> None: + def add_attr_noop(name: str, value: object) -> None: pass attr_func = add_attr_noop @@ -359,7 +363,7 @@ def _check_record_param_type(param: str, v: str) -> None: @pytest.fixture(scope="session") -def record_testsuite_property(request: FixtureRequest): +def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object], None]: """ Records a new ```` tag as child of the root ````. This is suitable to writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. @@ -377,7 +381,7 @@ def record_testsuite_property(request: FixtureRequest): __tracebackhide__ = True - def record_func(name: str, value: str): + def record_func(name: str, value: object) -> None: """noop function in case --junitxml was not passed in the command-line""" __tracebackhide__ = True _check_record_param_type("name", name) @@ -693,7 +697,7 @@ class LogXML: def pytest_terminal_summary(self, terminalreporter: TerminalReporter) -> None: terminalreporter.write_sep("-", "generated xml file: {}".format(self.logfile)) - def add_global_property(self, name: str, value: str) -> None: + def add_global_property(self, name: str, value: object) -> None: __tracebackhide__ = True _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) From bcff02c4c608ed6123e23bbada08cb404abd0354 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 9 Jul 2020 23:50:15 +0300 Subject: [PATCH 05/15] pytester: some type annotations --- src/_pytest/pytester.py | 57 ++++++++++++++++++++------------------- testing/python/collect.py | 6 ++++- testing/test_nodes.py | 2 +- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 45f6f008a..594abee90 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -32,6 +32,7 @@ from _pytest.compat import TYPE_CHECKING from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode +from _pytest.config import PytestPluginManager from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest from _pytest.main import Session @@ -210,7 +211,7 @@ class HookRecorder: """ - def __init__(self, pluginmanager) -> None: + def __init__(self, pluginmanager: PytestPluginManager) -> None: self._pluginmanager = pluginmanager self.calls = [] # type: List[ParsedCall] @@ -376,7 +377,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> "Type[LineMatcher]": @pytest.fixture -def testdir(request: FixtureRequest, tmpdir_factory) -> "Testdir": +def testdir(request: FixtureRequest, tmpdir_factory: TempdirFactory) -> "Testdir": """ A :class: `TestDir` instance, that can be used to run and test pytest itself. @@ -388,7 +389,7 @@ def testdir(request: FixtureRequest, tmpdir_factory) -> "Testdir": @pytest.fixture -def _sys_snapshot(): +def _sys_snapshot() -> Generator[None, None, None]: snappaths = SysPathsSnapshot() snapmods = SysModulesSnapshot() yield @@ -526,7 +527,7 @@ class CwdSnapshot: class SysModulesSnapshot: - def __init__(self, preserve: Optional[Callable[[str], bool]] = None): + def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -605,13 +606,13 @@ class Testdir: # Do not use colors for inner runs by default. mp.setenv("PY_COLORS", "0") - def __repr__(self): + def __repr__(self) -> str: return "".format(self.tmpdir) - def __str__(self): + def __str__(self) -> str: return str(self.tmpdir) - def finalize(self): + def finalize(self) -> None: """Clean up global state artifacts. Some methods modify the global interpreter state and this tries to @@ -624,7 +625,7 @@ class Testdir: self._cwd_snapshot.restore() self.monkeypatch.undo() - def __take_sys_modules_snapshot(self): + def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: # some zope modules used by twisted-related tests keep internal state # and can't be deleted; we had some trouble in the past with # `zope.interface` for example @@ -633,13 +634,13 @@ class Testdir: return SysModulesSnapshot(preserve=preserve_module) - def make_hook_recorder(self, pluginmanager): + def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: """Create a new :py:class:`HookRecorder` for a PluginManager.""" pluginmanager.reprec = reprec = HookRecorder(pluginmanager) self.request.addfinalizer(reprec.finish_recording) return reprec - def chdir(self): + def chdir(self) -> None: """Cd into the temporary directory. This is done automatically upon instantiation. @@ -647,7 +648,7 @@ class Testdir: """ self.tmpdir.chdir() - def _makefile(self, ext, lines, files, encoding="utf-8"): + def _makefile(self, ext: str, lines, files, encoding: str = "utf-8"): items = list(files.items()) def to_text(s): @@ -669,7 +670,7 @@ class Testdir: ret = p return ret - def makefile(self, ext, *args, **kwargs): + def makefile(self, ext: str, *args: str, **kwargs): r"""Create new file(s) in the testdir. :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`. @@ -698,7 +699,7 @@ class Testdir: """Write a tox.ini file with 'source' as contents.""" return self.makefile(".ini", tox=source) - def getinicfg(self, source): + def getinicfg(self, source) -> IniConfig: """Return the pytest section from the tox.ini config file.""" p = self.makeini(source) return IniConfig(p)["pytest"] @@ -748,7 +749,7 @@ class Testdir: """ return self._makefile(".txt", args, kwargs) - def syspathinsert(self, path=None): + def syspathinsert(self, path=None) -> None: """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`. This is undone automatically when this object dies at the end of each @@ -759,11 +760,11 @@ class Testdir: self.monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name): + def mkdir(self, name) -> py.path.local: """Create a new (sub)directory.""" return self.tmpdir.mkdir(name) - def mkpydir(self, name): + def mkpydir(self, name) -> py.path.local: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -774,7 +775,7 @@ class Testdir: p.ensure("__init__.py") return p - def copy_example(self, name=None): + def copy_example(self, name=None) -> py.path.local: """Copy file from project's directory into the testdir. :param str name: The name of the file to copy. @@ -826,7 +827,7 @@ class Testdir: Session = Session - def getnode(self, config, arg): + def getnode(self, config: Config, arg): """Return the collection node of a file. :param config: :py:class:`_pytest.config.Config` instance, see @@ -861,7 +862,7 @@ class Testdir: config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the @@ -974,7 +975,7 @@ class Testdir: class reprec: # type: ignore pass - reprec.ret = ret + reprec.ret = ret # type: ignore[attr-defined] # typically we reraise keyboard interrupts from the child run # because it's our user requesting interruption of the testing @@ -1083,7 +1084,7 @@ class Testdir: config._do_configure() return config - def getitem(self, source, funcname="test_func"): + def getitem(self, source, funcname: str = "test_func") -> Item: """Return the test item for a test function. This writes the source to a python file and runs pytest's collection on @@ -1104,7 +1105,7 @@ class Testdir: funcname, source, items ) - def getitems(self, source): + def getitems(self, source) -> List[Item]: """Return all test items collected from the module. This writes the source to a python file and runs pytest's collection on @@ -1114,7 +1115,7 @@ class Testdir: modcol = self.getmodulecol(source) return self.genitems([modcol]) - def getmodulecol(self, source, configargs=(), withinit=False): + def getmodulecol(self, source, configargs=(), withinit: bool = False): """Return the module collection node for ``source``. This writes ``source`` to a file using :py:meth:`makepyfile` and then @@ -1199,7 +1200,9 @@ class Testdir: return popen - def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: + def run( + self, *cmdargs, timeout: Optional[float] = None, stdin=CLOSE_STDIN + ) -> RunResult: """Run a command with arguments. Run a process using subprocess.Popen saving the stdout and stderr. @@ -1238,7 +1241,7 @@ class Testdir: if isinstance(stdin, bytes): popen.stdin.close() - def handle_timeout(): + def handle_timeout() -> None: __tracebackhide__ = True timeout_message = ( @@ -1283,7 +1286,7 @@ class Testdir: except UnicodeEncodeError: print("couldn't print to {} because of encoding".format(fp)) - def _getpytestargs(self): + def _getpytestargs(self) -> Tuple[str, ...]: return sys.executable, "-mpytest" def runpython(self, script) -> RunResult: @@ -1298,7 +1301,7 @@ class Testdir: """Run python -c "command", return a :py:class:`RunResult`.""" return self.run(sys.executable, "-c", command) - def runpytest_subprocess(self, *args, timeout=None) -> RunResult: + def runpytest_subprocess(self, *args, timeout: Optional[float] = None) -> RunResult: """Run pytest as a subprocess with given arguments. Any plugins added to the :py:attr:`plugins` list will be added using the diff --git a/testing/python/collect.py b/testing/python/collect.py index e98a21f1c..691380a0b 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -675,7 +675,11 @@ class TestFunction: pass """ ) - assert [x.originalname for x in items] == [ + originalnames = [] + for x in items: + assert isinstance(x, pytest.Function) + originalnames.append(x.originalname) + assert originalnames == [ "test_func", "test_func", "test_no_param", diff --git a/testing/test_nodes.py b/testing/test_nodes.py index e5d8ffd71..cc6e562a5 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -38,7 +38,7 @@ def test_std_warn_not_pytestwarning(testdir: Testdir) -> None: """ ) with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): - items[0].warn(UserWarning("some warning")) + items[0].warn(UserWarning("some warning")) # type: ignore[arg-type] def test__check_initialpaths_for_relpath() -> None: From 8e8d63927691f0abd814be252b6e9bbb4adec7c3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 00:27:47 +0300 Subject: [PATCH 06/15] tmpdir: type annotations --- src/_pytest/tmpdir.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index f6d1799ad..58dd65908 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -46,7 +46,7 @@ class TempPathFactory: given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir") ) - def _ensure_relative_to_basetemp(self, basename: str): + def _ensure_relative_to_basetemp(self, basename: str) -> str: basename = os.path.normpath(basename) if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp(): raise ValueError( @@ -119,7 +119,7 @@ class TempdirFactory: """ return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve()) - def getbasetemp(self): + def getbasetemp(self) -> py.path.local: """backward compat wrapper for ``_tmppath_factory.getbasetemp``""" return py.path.local(self._tmppath_factory.getbasetemp().resolve()) @@ -176,7 +176,7 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @pytest.fixture -def tmpdir(tmp_path): +def tmpdir(tmp_path: Path) -> py.path.local: """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary From 168d9adefc21de98e91e06199a27cac205765c06 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 00:41:43 +0300 Subject: [PATCH 07/15] hookspec: change Node -> Union[Item, Collector] to avoid exposing Node We don't really want `Node` itself as a public API, only its two subclasses. --- src/_pytest/hookspec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 8c88b66cb..cf3da400a 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -32,7 +32,6 @@ if TYPE_CHECKING: from _pytest.main import Session from _pytest.nodes import Collector from _pytest.nodes import Item - from _pytest.nodes import Node from _pytest.outcomes import Exit from _pytest.python import Function from _pytest.python import Metafunc @@ -827,7 +826,7 @@ def pytest_keyboard_interrupt( def pytest_exception_interact( - node: "Node", + node: Union["Item", "Collector"], call: "CallInfo[object]", report: Union["CollectReport", "TestReport"], ) -> None: From fc702ab7e41a583b0b0658aa2bf7c962a19d3fe7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 00:50:10 +0300 Subject: [PATCH 08/15] fixtures: some type annotations --- src/_pytest/fixtures.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index cedcd4625..52dc78b63 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -809,7 +809,9 @@ def scope2index(scope: str, descr: str, where: Optional[str] = None) -> int: class FixtureLookupError(LookupError): """ could not return a requested Fixture (missing or invalid). """ - def __init__(self, argname, request, msg: Optional[str] = None) -> None: + def __init__( + self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() @@ -861,7 +863,14 @@ class FixtureLookupError(LookupError): class FixtureLookupErrorRepr(TerminalRepr): - def __init__(self, filename, firstlineno, tblines, errorstring, argname): + def __init__( + self, + filename: Union[str, py.path.local], + firstlineno: int, + tblines: Sequence[str], + errorstring: str, + argname: Optional[str], + ) -> None: self.tblines = tblines self.errorstring = errorstring self.filename = filename From a2f021b6f3618f30d0cd5604797ce5069353d864 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 09:44:14 +0300 Subject: [PATCH 09/15] Remove no longer needed `noqa: F821` uses Not needed since pyflakes 2.2.0. --- src/_pytest/_io/saferepr.py | 4 ++-- src/_pytest/debugging.py | 4 ++-- src/_pytest/doctest.py | 10 ++++----- src/_pytest/fixtures.py | 22 +++++++++---------- src/_pytest/junitxml.py | 2 +- src/_pytest/logging.py | 4 ++-- src/_pytest/mark/structures.py | 4 ++-- src/_pytest/nodes.py | 4 ++-- src/_pytest/python.py | 6 ++--- src/_pytest/recwarn.py | 2 +- src/_pytest/runner.py | 12 +++++----- src/_pytest/setuponly.py | 4 ++-- src/_pytest/skipping.py | 2 +- src/_pytest/terminal.py | 2 +- src/_pytest/unittest.py | 12 +++++----- testing/code/test_excinfo.py | 2 +- .../test_compare_two_different_dataclasses.py | 2 +- testing/io/test_saferepr.py | 8 +++---- testing/python/collect.py | 6 ++--- testing/python/fixtures.py | 10 ++++----- testing/python/metafunc.py | 10 ++++----- testing/python/raises.py | 12 +++++----- testing/test_assertrewrite.py | 10 ++++----- testing/test_capture.py | 2 +- testing/test_collection.py | 6 ++--- testing/test_config.py | 2 +- testing/test_doctest.py | 2 +- testing/test_junitxml.py | 2 +- testing/test_mark.py | 8 +++---- testing/test_nodes.py | 4 ++-- testing/test_pytester.py | 14 ++++++------ testing/test_runner.py | 4 ++-- testing/test_store.py | 2 +- testing/test_terminal.py | 2 +- 34 files changed, 101 insertions(+), 101 deletions(-) diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 6b9f353a2..823b8d719 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -98,12 +98,12 @@ class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): level: int, ) -> None: # Type ignored because _dispatch is private. - p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] # noqa: F821 + p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] objid = id(object) if objid in context or p is None: # Type ignored because _format is private. - super()._format( # type: ignore[misc] # noqa: F821 + super()._format( # type: ignore[misc] object, stream, indent, allowance, context, level, ) return diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 63126cbe0..3677d3bf9 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -146,7 +146,7 @@ class pytestPDB: # Type ignored because mypy doesn't support "dynamic" # inheritance like this. - class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] # noqa: F821 + class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] _pytest_capman = capman _continued = False @@ -349,7 +349,7 @@ def _enter_pdb( rep.toterminal(tw) tw.sep(">", "entering PDB") tb = _postmortem_traceback(excinfo) - rep._pdbshown = True # type: ignore[attr-defined] # noqa: F821 + rep._pdbshown = True # type: ignore[attr-defined] post_mortem(tb) return rep diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 181c66b95..ebf0d584c 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -284,7 +284,7 @@ class DoctestItem(pytest.Item): failures = [] # type: List[doctest.DocTestFailure] # Type ignored because we change the type of `out` from what # doctest expects. - self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] # noqa: F821 + self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] if failures: raise MultipleDoctestFailures(failures) @@ -302,7 +302,7 @@ class DoctestItem(pytest.Item): sys.stderr.write(err) # TODO: Type ignored -- breaks Liskov Substitution. - def repr_failure( # type: ignore[override] # noqa: F821 + def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], ) -> Union[str, TerminalRepr]: import doctest @@ -329,7 +329,7 @@ class DoctestItem(pytest.Item): lineno = test.lineno + example.lineno + 1 message = type(failure).__name__ # TODO: ReprFileLocation doesn't expect a None lineno. - reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] # noqa: F821 + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] checker = _get_checker() report_choice = _get_report_choice( self.config.getoption("doctestreport") @@ -567,9 +567,9 @@ def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: def func() -> None: pass - doctest_item.funcargs = {} # type: ignore[attr-defined] # noqa: F821 + doctest_item.funcargs = {} # type: ignore[attr-defined] fm = doctest_item.session._fixturemanager - doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] # noqa: F821 + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] node=doctest_item, func=func, cls=None, funcargs=False ) fixture_request = FixtureRequest(doctest_item) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 52dc78b63..aef28a1ca 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -248,7 +248,7 @@ def get_parametrized_fixture_keys(item: "nodes.Item", scopenum: int) -> Iterator the specified scope. """ assert scopenum < scopenum_function # function try: - callspec = item.callspec # type: ignore[attr-defined] # noqa: F821 + callspec = item.callspec # type: ignore[attr-defined] except AttributeError: pass else: @@ -266,7 +266,7 @@ def get_parametrized_fixture_keys(item: "nodes.Item", scopenum: int) -> Iterator elif scopenum == 2: # module key = (argname, param_index, item.fspath) elif scopenum == 3: # class - item_cls = item.cls # type: ignore[attr-defined] # noqa: F821 + item_cls = item.cls # type: ignore[attr-defined] key = (argname, param_index, item.fspath, item_cls) yield key @@ -477,7 +477,7 @@ class FixtureRequest: fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) # TODO: Fix this type ignore. Either add assert or adjust types. # Can this be None here? - self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] # noqa: F821 + self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] # fixturedefs list is immutable so we maintain a decreasing index index = self._arg2index.get(argname, 0) - 1 if fixturedefs is None or (-index > len(fixturedefs)): @@ -723,7 +723,7 @@ class FixtureRequest: if scope == "package": # 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 + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] else: node = get_scope_node(self._pyfuncitem, scope) if node is None and scope == "class": @@ -944,7 +944,7 @@ def _eval_scope_callable( try: # Type ignored because there is no typing mechanism to specify # keyword arguments, currently. - result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] # noqa: F821 + result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] except Exception as e: raise TypeError( "Error evaluating {} while defining fixture '{}'.\n" @@ -1081,7 +1081,7 @@ def resolve_fixture_function( if fixturedef.unittest: if request.instance is not None: # bind the unbound method to the TestCase instance - fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] # noqa: F821 + fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] else: # the fixture function needs to be bound to the actual # request.instance so that code working with "fixturedef" behaves @@ -1090,12 +1090,12 @@ def resolve_fixture_function( # handle the case where fixture is defined not in a test class, but some other class # (for example a plugin class with a fixture), see #2270 if hasattr(fixturefunc, "__self__") and not isinstance( - request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] # noqa: F821 + request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] ): return fixturefunc fixturefunc = getimfunc(fixturedef.func) if fixturefunc != fixturedef.func: - fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] # noqa: F821 + fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] return fixturefunc @@ -1167,7 +1167,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) # type: ignore[attr-defined] # noqa: F821 + result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] return result @@ -1209,7 +1209,7 @@ class FixtureFunctionMarker: ) # Type ignored because https://github.com/python/mypy/issues/2087. - function._pytestfixturefunction = self # type: ignore[attr-defined] # noqa: F821 + function._pytestfixturefunction = self # type: ignore[attr-defined] return function @@ -1503,7 +1503,7 @@ class FixtureManager: def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: nodeid = None try: - p = py.path.local(plugin.__file__) # type: ignore[attr-defined] # noqa: F821 + p = py.path.local(plugin.__file__) # type: ignore[attr-defined] except AttributeError: pass else: diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 4873e722d..8c68d196a 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -281,7 +281,7 @@ class _NodeReporter: self.__dict__.clear() # Type ignored becuase mypy doesn't like overriding a method. # Also the return value doesn't match... - self.to_xml = lambda: py.xml.raw(data) # type: ignore # noqa: F821 + self.to_xml = lambda: py.xml.raw(data) # type: ignore def _warn_incompatibility_with_xunit2( diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 52d75e66d..1df0643a6 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -165,7 +165,7 @@ class PercentStyleMultiline(logging.PercentStyle): if "\n" in record.message: if hasattr(record, "auto_indent"): # passed in from the "extra={}" kwarg on the call to logging.log() - auto_indent = self._get_auto_indent(record.auto_indent) # type: ignore[attr-defined] # noqa: F821 + auto_indent = self._get_auto_indent(record.auto_indent) # type: ignore[attr-defined] else: auto_indent = self._auto_indent @@ -755,7 +755,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): :param _pytest.terminal.TerminalReporter terminal_reporter: :param _pytest.capture.CaptureManager capture_manager: """ - logging.StreamHandler.__init__(self, stream=terminal_reporter) # type: ignore[arg-type] # noqa: F821 + logging.StreamHandler.__init__(self, stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager self.reset() self.set_when(None) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 0c9344f3f..38369388c 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -124,7 +124,7 @@ class ParameterSet( # # @pytest.mark.parametrize(('x', 'y'), [1, 2]) # def test_foo(x, y): pass - return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] # noqa: F821 + return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] @staticmethod def _parse_parametrize_args( @@ -320,7 +320,7 @@ class MarkDecorator: # return type. Not much we can do about that. Thankfully mypy picks # the first match so it works out even if we break the rules. @overload - def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] # noqa: F821 + def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] raise NotImplementedError() @overload # noqa: F811 diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 24e466586..560548aea 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -457,7 +457,7 @@ class Collector(Node): raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. - def repr_failure( # type: ignore[override] # noqa: F821 + def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException] ) -> Union[str, TerminalRepr]: """ @@ -600,7 +600,7 @@ class FSCollector(Collector): else: duplicate_paths.add(path) - return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] # noqa: F723 + return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] class File(FSCollector): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2c9f383e4..7209bf1ed 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -299,7 +299,7 @@ class PyobjMixin: """Gets the underlying Python object. May be overwritten by subclasses.""" # 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 + obj = self.parent.obj # type: ignore[attr-defined] return getattr(obj, self.name) def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: @@ -784,7 +784,7 @@ class Instance(PyCollector): def _getobj(self): # 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 + obj = self.parent.obj # type: ignore[attr-defined] return obj() def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: @@ -1593,7 +1593,7 @@ class Function(PyobjMixin, nodes.Item): entry.set_repr_style("short") # TODO: Type ignored -- breaks Liskov Substitution. - def repr_failure( # type: ignore[override] # noqa: F821 + def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], ) -> Union[str, TerminalRepr]: style = self.config.getoption("tbstyle", "auto") diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 49bb909cc..11ca571aa 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -165,7 +165,7 @@ class WarningsRecorder(warnings.catch_warnings): 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 + super().__init__(record=True) # type: ignore[call-arg] self._entered = False self._list = [] # type: List[warnings.WarningMessage] diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 702380a5b..69754ad5e 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -106,8 +106,8 @@ def runtestprotocol( item: Item, log: bool = True, nextitem: Optional[Item] = None ) -> List[TestReport]: hasrequest = hasattr(item, "_request") - if hasrequest and not item._request: # type: ignore[attr-defined] # noqa: F821 - item._initrequest() # type: ignore[attr-defined] # noqa: F821 + if hasrequest and not item._request: # type: ignore[attr-defined] + item._initrequest() # type: ignore[attr-defined] rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: @@ -119,8 +119,8 @@ def runtestprotocol( # after all teardown hooks have been called # want funcargs and request info to go away if hasrequest: - item._request = False # type: ignore[attr-defined] # noqa: F821 - item.funcargs = None # type: ignore[attr-defined] # noqa: F821 + item._request = False # type: ignore[attr-defined] + item.funcargs = None # type: ignore[attr-defined] return reports @@ -422,7 +422,7 @@ class SetupState: # check if the last collection node has raised an error for col in self.stack: if hasattr(col, "_prepare_exc"): - exc = col._prepare_exc # type: ignore[attr-defined] # noqa: F821 + exc = col._prepare_exc # type: ignore[attr-defined] raise exc needed_collectors = colitem.listchain() @@ -431,7 +431,7 @@ class SetupState: try: col.setup() except TEST_OUTCOME as e: - col._prepare_exc = e # type: ignore[attr-defined] # noqa: F821 + col._prepare_exc = e # type: ignore[attr-defined] raise e diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 932d0c279..dfd01cc76 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -43,7 +43,7 @@ def pytest_fixture_setup( param = fixturedef.ids[request.param_index] else: param = request.param - fixturedef.cached_param = param # type: ignore[attr-defined] # noqa: F821 + fixturedef.cached_param = param # type: ignore[attr-defined] _show_fixture_action(fixturedef, "SETUP") @@ -53,7 +53,7 @@ def pytest_fixture_post_finalizer(fixturedef: FixtureDef) -> None: if config.option.setupshow: _show_fixture_action(fixturedef, "TEARDOWN") if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param # type: ignore[attr-defined] # noqa: F821 + del fixturedef.cached_param # type: ignore[attr-defined] def _show_fixture_action(fixturedef: FixtureDef, msg: str) -> None: diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index dca2466c4..335e10996 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -56,7 +56,7 @@ def pytest_configure(config: Config) -> None: def nop(*args, **kwargs): pass - nop.Exception = xfail.Exception # type: ignore[attr-defined] # noqa: F821 + nop.Exception = xfail.Exception # type: ignore[attr-defined] setattr(pytest, "xfail", nop) config.addinivalue_line( diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 6c19e56dd..ef9da50f3 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1219,7 +1219,7 @@ def _get_line_with_reprcrash_message( try: # Type ignored intentionally -- possible AttributeError expected. - msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] # noqa: F821 + msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] except AttributeError: pass else: diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index bd61726c7..782a5c369 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -102,13 +102,13 @@ class UnitTestCase(Class): cls, "setUpClass", "tearDownClass", scope="class", pass_self=False ) if class_fixture: - cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] # noqa: F821 + cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] method_fixture = _make_xunit_fixture( cls, "setup_method", "teardown_method", scope="function", pass_self=True ) if method_fixture: - cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] # noqa: F821 + cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] def _make_xunit_fixture( @@ -148,7 +148,7 @@ class TestCaseFunction(Function): # a bound method to be called during teardown() if set (see 'runtest()') self._explicit_tearDown = None # type: Optional[Callable[[], None]] assert self.parent is not None - self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] # noqa: F821 + self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] self._obj = getattr(self._testcase, self.name) if hasattr(self, "_request"): self._request._fillfixtures() @@ -167,7 +167,7 @@ class TestCaseFunction(Function): # unwrap potential exception info (see twisted trial support below) rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: - excinfo = _pytest._code.ExceptionInfo(rawexcinfo) # type: ignore[arg-type] # noqa: F821 + excinfo = _pytest._code.ExceptionInfo(rawexcinfo) # type: ignore[arg-type] # invoke the attributes to trigger storing the traceback # trial causes some issue there excinfo.value @@ -259,7 +259,7 @@ class TestCaseFunction(Function): # let the unittest framework handle async functions if is_async_function(self.obj): # Type ignored because self acts as the TestResult, but is not actually one. - self._testcase(result=self) # type: ignore[arg-type] # noqa: F821 + self._testcase(result=self) # type: ignore[arg-type] else: # when --pdb is given, we want to postpone calling tearDown() otherwise # when entering the pdb prompt, tearDown() would have probably cleaned up @@ -275,7 +275,7 @@ class TestCaseFunction(Function): # wrap_pytest_function_for_tracing replaces self.obj by a wrapper setattr(self._testcase, self.name, self.obj) try: - self._testcase(result=self) # type: ignore[arg-type] # noqa: F821 + self._testcase(result=self) # type: ignore[arg-type] finally: delattr(self._testcase, self.name) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 6ee848e54..060f52cc7 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -752,7 +752,7 @@ raise ValueError() from _pytest._code.code import Code monkeypatch.setattr(Code, "path", "bogus") - excinfo.traceback[0].frame.code.path = "bogus" # type: ignore[misc] # noqa: F821 + excinfo.traceback[0].frame.code.path = "bogus" # type: ignore[misc] p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index 22e981e33..0a4820c69 100644 --- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -16,4 +16,4 @@ def test_comparing_two_different_data_classes() -> None: left = SimpleDataObjectOne(1, "b") right = SimpleDataObjectTwo(1, "c") - assert left != right # type: ignore[comparison-overlap] # noqa: F821 + assert left != right # type: ignore[comparison-overlap] diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index 6912a113f..7a97cf424 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -34,8 +34,8 @@ def test_exceptions() -> None: raise self.ex class BrokenReprException(Exception): - __str__ = None # type: ignore[assignment] # noqa: F821 - __repr__ = None # type: ignore[assignment] # noqa: F821 + __str__ = None # type: ignore[assignment] + __repr__ = None # type: ignore[assignment] assert "Exception" in saferepr(BrokenRepr(Exception("broken"))) s = saferepr(BrokenReprException("really broken")) @@ -44,7 +44,7 @@ def test_exceptions() -> None: none = None try: - none() # type: ignore[misc] # noqa: F821 + none() # type: ignore[misc] except BaseException as exc: exp_exc = repr(exc) obj = BrokenRepr(BrokenReprException("omg even worse")) @@ -139,7 +139,7 @@ def test_big_repr(): def test_repr_on_newstyle() -> None: class Function: def __repr__(self): - return "<%s>" % (self.name) # type: ignore[attr-defined] # noqa: F821 + return "<%s>" % (self.name) # type: ignore[attr-defined] assert saferepr(Function()) diff --git a/testing/python/collect.py b/testing/python/collect.py index 691380a0b..80c962d70 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -724,10 +724,10 @@ class TestSorting: assert fn1 != fn3 for fn in fn1, fn2, fn3: - assert fn != 3 # type: ignore[comparison-overlap] # noqa: F821 + assert fn != 3 # type: ignore[comparison-overlap] assert fn != modcol - assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821 - assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821 + assert fn != [1, 2, 3] # type: ignore[comparison-overlap] + assert [1, 2, 3] != fn # type: ignore[comparison-overlap] assert modcol != fn def test_allow_sane_sorting_for_decorators(self, testdir): diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 3efbbe107..ca3408ece 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3850,7 +3850,7 @@ class TestScopeOrdering: ) testdir.runpytest() # actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir") - FIXTURE_ORDER = pytest.FIXTURE_ORDER # type: ignore[attr-defined] # noqa: F821 + FIXTURE_ORDER = pytest.FIXTURE_ORDER # type: ignore[attr-defined] assert FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split() def test_func_closure_module(self, testdir): @@ -4159,7 +4159,7 @@ def test_fixture_duplicated_arguments() -> None: """Raise error if there are positional and keyword arguments for the same parameter (#1682).""" with pytest.raises(TypeError) as excinfo: - @pytest.fixture("session", scope="session") # type: ignore[call-overload] # noqa: F821 + @pytest.fixture("session", scope="session") # type: ignore[call-overload] def arg(arg): pass @@ -4171,7 +4171,7 @@ def test_fixture_duplicated_arguments() -> None: with pytest.raises(TypeError) as excinfo: - @pytest.fixture( # type: ignore[call-overload] # noqa: F821 + @pytest.fixture( # type: ignore[call-overload] "function", ["p1"], True, @@ -4199,7 +4199,7 @@ def test_fixture_with_positionals() -> None: with pytest.warns(pytest.PytestDeprecationWarning) as warnings: - @pytest.fixture("function", [0], True) # type: ignore[call-overload] # noqa: F821 + @pytest.fixture("function", [0], True) # type: ignore[call-overload] def fixture_with_positionals(): pass @@ -4213,7 +4213,7 @@ def test_fixture_with_positionals() -> None: def test_fixture_with_too_many_positionals() -> None: with pytest.raises(TypeError) as excinfo: - @pytest.fixture("function", [0], True, ["id"], "name", "extra") # type: ignore[call-overload] # noqa: F821 + @pytest.fixture("function", [0], True, ["id"], "name", "extra") # type: ignore[call-overload] def fixture_with_positionals(): pass diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 6a841a346..4e6cfaf91 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -77,7 +77,7 @@ class TestMetafunc: pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6])) with pytest.raises(TypeError, match="^ids must be a callable or an iterable$"): - metafunc.parametrize("y", [5, 6], ids=42) # type: ignore[arg-type] # noqa: F821 + metafunc.parametrize("y", [5, 6], ids=42) # type: ignore[arg-type] def test_parametrize_error_iterator(self) -> None: def func(x): @@ -95,7 +95,7 @@ class TestMetafunc: metafunc = self.Metafunc(func) # When the input is an iterator, only len(args) are taken, # so the bad Exc isn't reached. - metafunc.parametrize("x", [1, 2], ids=gen()) # type: ignore[arg-type] # noqa: F821 + metafunc.parametrize("x", [1, 2], ids=gen()) # type: ignore[arg-type] assert [(x.funcargs, x.id) for x in metafunc._calls] == [ ({"x": 1}, "0"), ({"x": 2}, "2"), @@ -107,7 +107,7 @@ class TestMetafunc: r" Exc\(from_gen\) \(type: \) at index 2" ), ): - metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type] # noqa: F821 + metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type] def test_parametrize_bad_scope(self) -> None: def func(x): @@ -118,7 +118,7 @@ class TestMetafunc: fail.Exception, match=r"parametrize\(\) call in func got an unexpected scope value 'doggy'", ): - metafunc.parametrize("x", [1], scope="doggy") # type: ignore[arg-type] # noqa: F821 + metafunc.parametrize("x", [1], scope="doggy") # type: ignore[arg-type] def test_parametrize_request_name(self, testdir: Testdir) -> None: """Show proper error when 'request' is used as a parameter name in parametrize (#6183)""" @@ -675,7 +675,7 @@ class TestMetafunc: fail.Exception, match="In func: expected Sequence or boolean for indirect, got dict", ): - metafunc.parametrize("x, y", [("a", "b")], indirect={}) # type: ignore[arg-type] # noqa: F821 + metafunc.parametrize("x, y", [("a", "b")], indirect={}) # type: ignore[arg-type] def test_parametrize_indirect_list_functional(self, testdir: Testdir) -> None: """ diff --git a/testing/python/raises.py b/testing/python/raises.py index e55eb6f54..3f378d015 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -8,7 +8,7 @@ from _pytest.outcomes import Failed class TestRaises: def test_check_callable(self) -> None: with pytest.raises(TypeError, match=r".* must be callable"): - pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload] # noqa: F821 + pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload] def test_raises(self): excinfo = pytest.raises(ValueError, int, "qwe") @@ -30,7 +30,7 @@ class TestRaises: def test_raises_falsey_type_error(self) -> None: with pytest.raises(TypeError): - with pytest.raises(AssertionError, match=0): # type: ignore[call-overload] # noqa: F821 + with pytest.raises(AssertionError, match=0): # type: ignore[call-overload] raise AssertionError("ohai") def test_raises_repr_inflight(self): @@ -128,11 +128,11 @@ class TestRaises: def test_noclass(self) -> None: with pytest.raises(TypeError): - pytest.raises("wrong", lambda: None) # type: ignore[call-overload] # noqa: F821 + pytest.raises("wrong", lambda: None) # type: ignore[call-overload] def test_invalid_arguments_to_raises(self) -> None: with pytest.raises(TypeError, match="unknown"): - with pytest.raises(TypeError, unknown="bogus"): # type: ignore[call-overload] # noqa: F821 + with pytest.raises(TypeError, unknown="bogus"): # type: ignore[call-overload] raise ValueError() def test_tuple(self): @@ -262,12 +262,12 @@ class TestRaises: assert False, "via __class__" with pytest.raises(AssertionError) as excinfo: - with pytest.raises(CrappyClass()): # type: ignore[call-overload] # noqa: F821 + with pytest.raises(CrappyClass()): # type: ignore[call-overload] pass assert "via __class__" in excinfo.value.args[0] def test_raises_context_manager_with_kwargs(self): with pytest.raises(TypeError) as excinfo: - with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] # noqa: F821 + with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] pass assert "Unexpected keyword arguments" in str(excinfo.value) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 38893deb9..e403bb2ec 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -51,7 +51,7 @@ def getmsg( exec(code, ns) func = ns[f.__name__] try: - func() # type: ignore[operator] # noqa: F821 + func() # type: ignore[operator] except AssertionError: if must_pass: pytest.fail("shouldn't have raised") @@ -174,7 +174,7 @@ class TestAssertionRewrite: assert getmsg(f3, {"a_global": False}) == "assert False" def f4() -> None: - assert sys == 42 # type: ignore[comparison-overlap] # noqa: F821 + assert sys == 42 # type: ignore[comparison-overlap] verbose = request.config.getoption("verbose") msg = getmsg(f4, {"sys": sys}) @@ -188,7 +188,7 @@ class TestAssertionRewrite: assert msg == "assert sys == 42" def f5() -> None: - assert cls == 42 # type: ignore[name-defined] # noqa: F821 + assert cls == 42 # type: ignore[name-defined] # noqa: F821 class X: pass @@ -684,7 +684,7 @@ class TestAssertionRewrite: def test_formatchar(self) -> None: def f() -> None: - assert "%test" == "test" # type: ignore[comparison-overlap] # noqa: F821 + assert "%test" == "test" # type: ignore[comparison-overlap] msg = getmsg(f) assert msg is not None @@ -1264,7 +1264,7 @@ class TestEarlyRewriteBailout: # use default patterns, otherwise we inherit pytest's testing config hook.fnpats[:] = ["test_*.py", "*_test.py"] monkeypatch.setattr(hook, "_find_spec", spy_find_spec) - hook.set_session(StubSession()) # type: ignore[arg-type] # noqa: F821 + hook.set_session(StubSession()) # type: ignore[arg-type] testdir.syspathinsert() return hook diff --git a/testing/test_capture.py b/testing/test_capture.py index 9e5036a66..a3bd4b623 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1537,7 +1537,7 @@ def test_encodedfile_writelines(tmpfile: BinaryIO) -> None: ef = capture.EncodedFile(tmpfile, encoding="utf-8") with pytest.raises(TypeError): ef.writelines([b"line1", b"line2"]) - assert ef.writelines(["line3", "line4"]) is None # type: ignore[func-returns-value] # noqa: F821 + assert ef.writelines(["line3", "line4"]) is None # type: ignore[func-returns-value] ef.flush() tmpfile.seek(0) assert tmpfile.read() == b"line3line4" diff --git a/testing/test_collection.py b/testing/test_collection.py index d7a9b0439..6bab509d0 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -41,10 +41,10 @@ class TestCollector: for fn in fn1, fn2, fn3: assert isinstance(fn, pytest.Function) - assert fn != 3 # type: ignore[comparison-overlap] # noqa: F821 + assert fn != 3 # type: ignore[comparison-overlap] assert fn != modcol - assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821 - assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821 + assert fn != [1, 2, 3] # type: ignore[comparison-overlap] + assert [1, 2, 3] != fn # type: ignore[comparison-overlap] assert modcol != fn assert testdir.collect_by_name(modcol, "doesnotexist") is None diff --git a/testing/test_config.py b/testing/test_config.py index 94cb3db5e..9b1c11d5e 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1602,7 +1602,7 @@ def test_invocation_args(testdir): # args cannot be None with pytest.raises(TypeError): - Config.InvocationParams(args=None, plugins=None, dir=Path()) # type: ignore[arg-type] # noqa: F821 + Config.InvocationParams(args=None, plugins=None, dir=Path()) # type: ignore[arg-type] @pytest.mark.parametrize( diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 9ef9417cd..965dba6c1 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1490,7 +1490,7 @@ def test_warning_on_unwrap_of_broken_object( pytest.PytestWarning, match="^Got KeyError.* when unwrapping" ): with pytest.raises(KeyError): - inspect.unwrap(bad_instance, stop=stop) # type: ignore[arg-type] # noqa: F821 + inspect.unwrap(bad_instance, stop=stop) # type: ignore[arg-type] assert inspect.unwrap.__module__ == "inspect" diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 5e5826b23..eb8475ca5 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1124,7 +1124,7 @@ def test_unicode_issue368(testdir) -> None: node_reporter.append_skipped(test_report) test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" node_reporter.append_skipped(test_report) - test_report.wasxfail = ustr # type: ignore[attr-defined] # noqa: F821 + test_report.wasxfail = ustr # type: ignore[attr-defined] node_reporter.append_skipped(test_report) log.pytest_sessionfinish() diff --git a/testing/test_mark.py b/testing/test_mark.py index f261c8922..f35660093 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -20,7 +20,7 @@ class TestMark: def test_pytest_mark_notcallable(self) -> None: mark = Mark() with pytest.raises(TypeError): - mark() # type: ignore[operator] # noqa: F821 + mark() # type: ignore[operator] def test_mark_with_param(self): def some_function(abc): @@ -31,10 +31,10 @@ class TestMark: assert pytest.mark.foo(some_function) is some_function marked_with_args = pytest.mark.foo.with_args(some_function) - assert marked_with_args is not some_function # type: ignore[comparison-overlap] # noqa: F821 + assert marked_with_args is not some_function # type: ignore[comparison-overlap] assert pytest.mark.foo(SomeClass) is SomeClass - assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] # noqa: F821 + assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] def test_pytest_mark_name_starts_with_underscore(self): mark = Mark() @@ -1077,7 +1077,7 @@ def test_markers_from_parametrize(testdir): def test_pytest_param_id_requires_string() -> None: with pytest.raises(TypeError) as excinfo: - pytest.param(id=True) # type: ignore[arg-type] # noqa: F821 + pytest.param(id=True) # type: ignore[arg-type] (msg,) = excinfo.value.args assert msg == "Expected id to be a string, got : True" diff --git a/testing/test_nodes.py b/testing/test_nodes.py index cc6e562a5..f9026ec61 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -25,9 +25,9 @@ def test_ischildnode(baseid: str, nodeid: str, expected: bool) -> None: def test_node_from_parent_disallowed_arguments() -> None: with pytest.raises(TypeError, match="session is"): - nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] # noqa: F821 + nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] with pytest.raises(TypeError, match="config is"): - nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] # noqa: F821 + nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] def test_std_warn_not_pytestwarning(testdir: Testdir) -> None: diff --git a/testing/test_pytester.py b/testing/test_pytester.py index d0afb40b0..46f3e1cab 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -484,20 +484,20 @@ def test_linematcher_with_nonlist() -> None: lm = LineMatcher([]) with pytest.raises(TypeError, match="invalid type for lines2: set"): - lm.fnmatch_lines(set()) # type: ignore[arg-type] # noqa: F821 + lm.fnmatch_lines(set()) # type: ignore[arg-type] with pytest.raises(TypeError, match="invalid type for lines2: dict"): - lm.fnmatch_lines({}) # type: ignore[arg-type] # noqa: F821 + lm.fnmatch_lines({}) # type: ignore[arg-type] with pytest.raises(TypeError, match="invalid type for lines2: set"): - lm.re_match_lines(set()) # type: ignore[arg-type] # noqa: F821 + lm.re_match_lines(set()) # type: ignore[arg-type] with pytest.raises(TypeError, match="invalid type for lines2: dict"): - lm.re_match_lines({}) # type: ignore[arg-type] # noqa: F821 + lm.re_match_lines({}) # type: ignore[arg-type] with pytest.raises(TypeError, match="invalid type for lines2: Source"): - lm.fnmatch_lines(Source()) # type: ignore[arg-type] # noqa: F821 + lm.fnmatch_lines(Source()) # type: ignore[arg-type] lm.fnmatch_lines([]) lm.fnmatch_lines(()) lm.fnmatch_lines("") - assert lm._getlines({}) == {} # type: ignore[arg-type,comparison-overlap] # noqa: F821 - assert lm._getlines(set()) == set() # type: ignore[arg-type,comparison-overlap] # noqa: F821 + assert lm._getlines({}) == {} # type: ignore[arg-type,comparison-overlap] + assert lm._getlines(set()) == set() # type: ignore[arg-type,comparison-overlap] assert lm._getlines(Source()) == [] assert lm._getlines(Source("pass\npass")) == ["pass", "pass"] diff --git a/testing/test_runner.py b/testing/test_runner.py index 474ff4df8..def3f910d 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -884,7 +884,7 @@ def test_store_except_info_on_error() -> None: raise IndexError("TEST") try: - runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] # noqa: F821 + runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] except IndexError: pass # Check that exception info is stored on sys @@ -895,7 +895,7 @@ def test_store_except_info_on_error() -> None: # The next run should clear the exception info stored by the previous run ItemMightRaise.raise_error = False - runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] # noqa: F821 + runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] assert not hasattr(sys, "last_type") assert not hasattr(sys, "last_value") assert not hasattr(sys, "last_traceback") diff --git a/testing/test_store.py b/testing/test_store.py index 98014887e..b6d4208a0 100644 --- a/testing/test_store.py +++ b/testing/test_store.py @@ -47,7 +47,7 @@ def test_store() -> None: # Can't accidentally add attributes to store object itself. with pytest.raises(AttributeError): - store.foo = "nope" # type: ignore[attr-defined] # noqa: F821 + store.foo = "nope" # type: ignore[attr-defined] # No interaction with anoter store. store2 = Store() diff --git a/testing/test_terminal.py b/testing/test_terminal.py index f1481dce5..19aff9954 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1699,7 +1699,7 @@ def test_summary_stats( class fake_session: testscollected = 0 - tr._session = fake_session # type: ignore[assignment] # noqa: F821 + tr._session = fake_session # type: ignore[assignment] assert tr._is_last_item # Reset cache. From 087b047426415ed46bdd5eb2d5c7c8f00294468b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 10:08:16 +0300 Subject: [PATCH 10/15] cacheprovider: type annotations --- src/_pytest/cacheprovider.py | 10 +++++----- src/_pytest/pathlib.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 00f62b60c..de7ee9149 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -80,7 +80,7 @@ class Cache: rm_rf(d) @staticmethod - def cache_dir_from_config(config: Config): + def cache_dir_from_config(config: Config) -> Path: return resolve_from_str(config.getini("cache_dir"), config.rootdir) def warn(self, fmt: str, **args: object) -> None: @@ -113,7 +113,7 @@ class Cache: def _getvaluepath(self, key: str) -> Path: return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) - def get(self, key, default): + def get(self, key: str, default): """ return cached value for the given key. If no value was yet cached or the value cannot be read, the specified default is returned. @@ -131,7 +131,7 @@ class Cache: except (ValueError, OSError): return default - def set(self, key, value) -> None: + def set(self, key: str, value: object) -> None: """ save value for the given key. :param key: must be a ``/`` separated value. Usually the first @@ -522,7 +522,7 @@ def cacheshow(config: Config, session: Session) -> int: vdir = basedir / Cache._CACHE_PREFIX_VALUES tw.sep("-", "cache values for %r" % glob) for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): - key = valpath.relative_to(vdir) + key = str(valpath.relative_to(vdir)) val = config.cache.get(key, dummy) if val is dummy: tw.line("%s contains unreadable content, will be ignored" % key) @@ -539,6 +539,6 @@ def cacheshow(config: Config, session: Session) -> int: # if p.check(dir=1): # print("%s/" % p.relto(basedir)) if p.is_file(): - key = p.relative_to(basedir) + key = str(p.relative_to(basedir)) tw.line("{} is a file of length {:d}".format(key, p.stat().st_size)) return 0 diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index dd7443f07..6a0bf7f6f 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -363,15 +363,15 @@ def make_numbered_dir_with_cleanup( raise e -def resolve_from_str(input: str, root): +def resolve_from_str(input: str, root: py.path.local) -> Path: assert not isinstance(input, Path), "would break on py2" - root = Path(root) + rootpath = Path(root) input = expanduser(input) input = expandvars(input) if isabs(input): return Path(input) else: - return root.joinpath(input) + return rootpath.joinpath(input) def fnmatch_ex(pattern: str, path) -> bool: From 77f3cb4baade9cf0a02bd779dde9b11766bda0d4 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 11:50:36 +0300 Subject: [PATCH 11/15] code/code: type annotations & doc cleanups --- src/_pytest/_code/code.py | 180 ++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 95 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index ab38b204f..aa28fea18 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -51,7 +51,7 @@ if TYPE_CHECKING: class Code: - """ wrapper around Python code objects """ + """Wrapper around Python code objects.""" def __init__(self, rawcode) -> None: if not hasattr(rawcode, "co_filename"): @@ -74,7 +74,7 @@ class Code: @property def path(self) -> Union[py.path.local, str]: - """ return a path object pointing to source code (or a str in case + """Return a path object pointing to source code (or a str in case of OSError / non-existing file). """ if not self.raw.co_filename: @@ -92,24 +92,22 @@ class Code: @property def fullsource(self) -> Optional["Source"]: - """ return a _pytest._code.Source object for the full source file of the code - """ + """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) return full def source(self) -> "Source": - """ return a _pytest._code.Source object for the code object's source only - """ + """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code return Source(self.raw) def getargs(self, var: bool = False) -> Tuple[str, ...]: - """ return a tuple with the argument names for the code object + """Return a tuple with the argument names for the code object. - if 'var' is set True also return the names of the variable and - keyword arguments when present + If 'var' is set True also return the names of the variable and + keyword arguments when present. """ - # handfull shortcut for getting args + # Handy shortcut for getting args. raw = self.raw argcount = raw.co_argcount if var: @@ -131,44 +129,43 @@ class Frame: @property def statement(self) -> "Source": - """ statement this frame is at """ + """Statement this frame is at.""" if self.code.fullsource is None: return Source("") return self.code.fullsource.getstatement(self.lineno) def eval(self, code, **vars): - """ evaluate 'code' in the frame + """Evaluate 'code' in the frame. - 'vars' are optional additional local variables + 'vars' are optional additional local variables. - returns the result of the evaluation + Returns the result of the evaluation. """ f_locals = self.f_locals.copy() f_locals.update(vars) return eval(code, self.f_globals, f_locals) def exec_(self, code, **vars) -> None: - """ exec 'code' in the frame + """Exec 'code' in the frame. - 'vars' are optional; additional local variables + 'vars' are optional; additional local variables. """ f_locals = self.f_locals.copy() f_locals.update(vars) exec(code, self.f_globals, f_locals) def repr(self, object: object) -> str: - """ return a 'safe' (non-recursive, one-line) string repr for 'object' - """ + """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" return saferepr(object) def is_true(self, object): return object def getargs(self, var: bool = False): - """ return a list of tuples (name, value) for all arguments + """Return a list of tuples (name, value) for all arguments. - if 'var' is set True also include the variable and keyword - arguments when present + If 'var' is set True, also include the variable and keyword arguments + when present. """ retval = [] for arg in self.code.getargs(var): @@ -180,12 +177,16 @@ class Frame: class TracebackEntry: - """ a single entry in a traceback """ + """A single entry in a Traceback.""" _repr_style = None # type: Optional[Literal["short", "long"]] exprinfo = None - def __init__(self, rawentry: TracebackType, excinfo=None) -> None: + def __init__( + self, + rawentry: TracebackType, + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + ) -> None: self._excinfo = excinfo self._rawentry = rawentry self.lineno = rawentry.tb_lineno - 1 @@ -207,26 +208,26 @@ class TracebackEntry: @property def statement(self) -> "Source": - """ _pytest._code.Source object for the current statement """ + """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource assert source is not None return source.getstatement(self.lineno) @property def path(self) -> Union[py.path.local, str]: - """ path to the source code """ + """Path to the source code.""" return self.frame.code.path @property def locals(self) -> Dict[str, Any]: - """ locals of underlying frame """ + """Locals of underlying frame.""" return self.frame.f_locals def getfirstlinesource(self) -> int: return self.frame.code.firstlineno def getsource(self, astcache=None) -> Optional["Source"]: - """ return failing source code. """ + """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing source = self.frame.code.fullsource @@ -251,19 +252,19 @@ class TracebackEntry: source = property(getsource) - def ishidden(self): - """ return True if the current frame has a var __tracebackhide__ - resolving to True. + def ishidden(self) -> bool: + """Return True if the current frame has a var __tracebackhide__ + resolving to True. - If __tracebackhide__ is a callable, it gets called with the - ExceptionInfo instance and can decide whether to hide the traceback. + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. - mostly for internal use + Mostly for internal use. """ f = self.frame tbh = f.f_locals.get( "__tracebackhide__", f.f_globals.get("__tracebackhide__", False) - ) + ) # type: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] if tbh and callable(tbh): return tbh(None if self._excinfo is None else self._excinfo()) return tbh @@ -280,21 +281,19 @@ class TracebackEntry: @property def name(self) -> str: - """ co_name of underlying code """ + """co_name of underlying code.""" return self.frame.code.raw.co_name class Traceback(List[TracebackEntry]): - """ Traceback objects encapsulate and offer higher level - access to Traceback entries. - """ + """Traceback objects encapsulate and offer higher level access to Traceback entries.""" def __init__( self, tb: Union[TracebackType, Iterable[TracebackEntry]], - excinfo: Optional["ReferenceType[ExceptionInfo]"] = None, + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, ) -> None: - """ initialize from given python traceback object and ExceptionInfo """ + """Initialize from given python traceback object and ExceptionInfo.""" self._excinfo = excinfo if isinstance(tb, TracebackType): @@ -313,16 +312,16 @@ class Traceback(List[TracebackEntry]): path=None, lineno: Optional[int] = None, firstlineno: Optional[int] = None, - excludepath=None, + excludepath: Optional[py.path.local] = None, ) -> "Traceback": - """ return a Traceback instance wrapping part of this Traceback + """Return a Traceback instance wrapping part of this Traceback. - by providing any combination of path, lineno and firstlineno, the - first frame to start the to-be-returned traceback is determined + By providing any combination of path, lineno and firstlineno, the + first frame to start the to-be-returned traceback is determined. - this allows cutting the first part of a Traceback instance e.g. - for formatting reasons (removing some uninteresting bits that deal - with handling of the exception/traceback) + This allows cutting the first part of a Traceback instance e.g. + for formatting reasons (removing some uninteresting bits that deal + with handling of the exception/traceback). """ for x in self: code = x.frame.code @@ -359,21 +358,19 @@ class Traceback(List[TracebackEntry]): def filter( self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() ) -> "Traceback": - """ return a Traceback instance with certain items removed + """Return a Traceback instance with certain items removed - fn is a function that gets a single argument, a TracebackEntry - instance, and should return True when the item should be added - to the Traceback, False when not + fn is a function that gets a single argument, a TracebackEntry + instance, and should return True when the item should be added + to the Traceback, False when not. - by default this removes all the TracebackEntries which are hidden - (see ishidden() above) + By default this removes all the TracebackEntries which are hidden + (see ishidden() above). """ return Traceback(filter(fn, self), self._excinfo) def getcrashentry(self) -> TracebackEntry: - """ return last non-hidden traceback entry that lead - to the exception of a traceback. - """ + """Return last non-hidden traceback entry that lead to the exception of a traceback.""" for i in range(-1, -len(self) - 1, -1): entry = self[i] if not entry.ishidden(): @@ -381,9 +378,8 @@ class Traceback(List[TracebackEntry]): return self[-1] def recursionindex(self) -> Optional[int]: - """ return the index of the frame/TracebackEntry where recursion - originates if appropriate, None if no recursion occurred - """ + """Return the index of the frame/TracebackEntry where recursion originates if + appropriate, None if no recursion occurred.""" cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] for i, entry in enumerate(self): # id for the code.raw is needed to work around @@ -414,14 +410,12 @@ co_equal = compile( ) -_E = TypeVar("_E", bound=BaseException) +_E = TypeVar("_E", bound=BaseException, covariant=True) @attr.s(repr=False) class ExceptionInfo(Generic[_E]): - """ wraps sys.exc_info() objects and offers - help for navigating the traceback. - """ + """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" _assert_start_repr = "AssertionError('assert " @@ -435,13 +429,12 @@ class ExceptionInfo(Generic[_E]): exc_info: Tuple["Type[_E]", "_E", TracebackType], exprinfo: Optional[str] = None, ) -> "ExceptionInfo[_E]": - """returns an ExceptionInfo for an existing exc_info tuple. + """Returns an ExceptionInfo for an existing exc_info tuple. .. warning:: Experimental API - :param exprinfo: a text string helping to determine if we should strip ``AssertionError`` from the output, defaults to the exception message/``__str__()`` @@ -460,13 +453,12 @@ class ExceptionInfo(Generic[_E]): def from_current( cls, exprinfo: Optional[str] = None ) -> "ExceptionInfo[BaseException]": - """returns an ExceptionInfo matching the current traceback + """Returns an ExceptionInfo matching the current traceback. .. warning:: Experimental API - :param exprinfo: a text string helping to determine if we should strip ``AssertionError`` from the output, defaults to the exception message/``__str__()`` @@ -480,8 +472,7 @@ class ExceptionInfo(Generic[_E]): @classmethod def for_later(cls) -> "ExceptionInfo[_E]": - """return an unfilled ExceptionInfo - """ + """Return an unfilled ExceptionInfo.""" return cls(None) def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None: @@ -491,7 +482,7 @@ class ExceptionInfo(Generic[_E]): @property def type(self) -> "Type[_E]": - """the exception class""" + """The exception class.""" assert ( self._excinfo is not None ), ".type can only be used after the context manager exits" @@ -499,7 +490,7 @@ class ExceptionInfo(Generic[_E]): @property def value(self) -> _E: - """the exception value""" + """The exception value.""" assert ( self._excinfo is not None ), ".value can only be used after the context manager exits" @@ -507,7 +498,7 @@ class ExceptionInfo(Generic[_E]): @property def tb(self) -> TracebackType: - """the exception raw traceback""" + """The exception raw traceback.""" assert ( self._excinfo is not None ), ".tb can only be used after the context manager exits" @@ -515,7 +506,7 @@ class ExceptionInfo(Generic[_E]): @property def typename(self) -> str: - """the type name of the exception""" + """The type name of the exception.""" assert ( self._excinfo is not None ), ".typename can only be used after the context manager exits" @@ -523,7 +514,7 @@ class ExceptionInfo(Generic[_E]): @property def traceback(self) -> Traceback: - """the traceback""" + """The traceback.""" if self._traceback is None: self._traceback = Traceback(self.tb, excinfo=ref(self)) return self._traceback @@ -540,12 +531,12 @@ class ExceptionInfo(Generic[_E]): ) def exconly(self, tryshort: bool = False) -> str: - """ return the exception as a string + """Return the exception as a string. - when 'tryshort' resolves to True, and the exception is a - _pytest._code._AssertionError, only the actual exception part of - the exception representation is returned (so 'AssertionError: ' is - removed from the beginning) + When 'tryshort' resolves to True, and the exception is a + _pytest._code._AssertionError, only the actual exception part of + the exception representation is returned (so 'AssertionError: ' is + removed from the beginning). """ lines = format_exception_only(self.type, self.value) text = "".join(lines) @@ -580,8 +571,7 @@ class ExceptionInfo(Generic[_E]): truncate_locals: bool = True, chain: bool = True, ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: - """ - Return str()able representation of this exception info. + """Return str()able representation of this exception info. :param bool showlocals: Show locals per traceback entry. @@ -630,11 +620,10 @@ class ExceptionInfo(Generic[_E]): return fmt.repr_excinfo(self) def match(self, regexp: "Union[str, Pattern]") -> "Literal[True]": - """ - Check whether the regular expression `regexp` matches the string + """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. - If it matches `True` is returned. - If it doesn't match an `AssertionError` is raised. + + If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True assert re.search( @@ -646,7 +635,7 @@ class ExceptionInfo(Generic[_E]): @attr.s class FormattedExcinfo: - """ presenting information about failing Functions and Generators. """ + """Presenting information about failing Functions and Generators.""" # for traceback entries flow_marker = ">" @@ -697,7 +686,7 @@ class FormattedExcinfo: excinfo: Optional[ExceptionInfo] = None, short: bool = False, ) -> List[str]: - """ return formatted and marked up source lines. """ + """Return formatted and marked up source lines.""" lines = [] if source is None or line_index >= len(source.lines): source = Source("???") @@ -938,7 +927,7 @@ class ExceptionRepr(TerminalRepr): reprcrash = None # type: Optional[ReprFileLocation] reprtraceback = None # type: ReprTraceback - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: self.sections = [] # type: List[Tuple[str, str, str]] def addsection(self, name: str, content: str, sep: str = "-") -> None: @@ -958,7 +947,7 @@ class ExceptionChainRepr(ExceptionRepr): ] ) - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: super().__attrs_post_init__() # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain @@ -1160,8 +1149,9 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]: - """ Return source location (path, lineno) for the given object. +def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: + """Return source location (path, lineno) for the given object. + If the source cannot be determined return ("", -1). The line number is 0-based. @@ -1171,13 +1161,13 @@ def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]: # in 6ec13a2b9. It ("place_as") appears to be something very custom. obj = get_real_func(obj) if hasattr(obj, "place_as"): - obj = obj.place_as + obj = obj.place_as # type: ignore[attr-defined] try: code = Code(obj) except TypeError: try: - fn = inspect.getsourcefile(obj) or inspect.getfile(obj) + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] except TypeError: return "", -1 @@ -1189,8 +1179,8 @@ def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]: except OSError: pass return fspath, lineno - else: - return code.path, code.firstlineno + + return code.path, code.firstlineno # relative paths that we use to filter traceback entries from appearing to the user; From c3864bc12b1652c9afd50a7ae059a2fc1ea193be Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 11:51:50 +0300 Subject: [PATCH 12/15] code/code: remove Frame.is_true() method Really odd one, let's just inline it. --- src/_pytest/_code/code.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index aa28fea18..9c724c8b6 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -158,9 +158,6 @@ class Frame: """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" return saferepr(object) - def is_true(self, object): - return object - def getargs(self, var: bool = False): """Return a list of tuples (name, value) for all arguments. @@ -393,12 +390,10 @@ class Traceback(List[TracebackEntry]): f = entry.frame loc = f.f_locals for otherloc in values: - if f.is_true( - f.eval( - co_equal, - __recursioncache_locals_1=loc, - __recursioncache_locals_2=otherloc, - ) + if f.eval( + co_equal, + __recursioncache_locals_1=loc, + __recursioncache_locals_2=otherloc, ): return i values.append(entry.frame.f_locals) From 85ef2bf698aa0f3732d161e99104f9df40a632bc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 12:00:14 +0300 Subject: [PATCH 13/15] code/code: remove Frame.exec_() method Not used. --- src/_pytest/_code/code.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 9c724c8b6..c1e6f49f3 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -145,15 +145,6 @@ class Frame: f_locals.update(vars) return eval(code, self.f_globals, f_locals) - def exec_(self, code, **vars) -> None: - """Exec 'code' in the frame. - - 'vars' are optional; additional local variables. - """ - f_locals = self.f_locals.copy() - f_locals.update(vars) - exec(code, self.f_globals, f_locals) - def repr(self, object: object) -> str: """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" return saferepr(object) From c8676002a7e11c7d4dcc39abdeb907826b244129 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 12:05:15 +0300 Subject: [PATCH 14/15] code/code: remove redundant __ne__ implementation This implementation is the default when __eq__ is implemented. --- src/_pytest/_code/code.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index c1e6f49f3..eb85d941c 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -69,9 +69,6 @@ class Code: # Ignore type because of https://github.com/python/mypy/issues/4266. __hash__ = None # type: ignore - def __ne__(self, other): - return not self == other - @property def path(self) -> Union[py.path.local, str]: """Return a path object pointing to source code (or a str in case From 7934ac280f2ddd956086ad5c078dfe901c94d67e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 10 Jul 2020 13:02:00 +0300 Subject: [PATCH 15/15] Add changelog entry for Frame removals --- changelog/7472.breaking.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/7472.breaking.rst diff --git a/changelog/7472.breaking.rst b/changelog/7472.breaking.rst new file mode 100644 index 000000000..b76874e37 --- /dev/null +++ b/changelog/7472.breaking.rst @@ -0,0 +1 @@ +The ``exec_()`` and ``is_true()`` methods of ``_pytest._code.Frame`` have been removed.