From 190e01c48b1361c338700f7dc85a9c9f1defb8cc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 13 Feb 2020 13:57:38 +0200 Subject: [PATCH] Type annotate some hookspecs & impls Annotate some "easy" arguments of hooks that repeat in a lot of internal plugins. Not all of the arguments are annotated fully for now. --- src/_pytest/assertion/__init__.py | 5 ++- src/_pytest/cacheprovider.py | 16 ++++--- src/_pytest/capture.py | 3 +- src/_pytest/config/__init__.py | 6 ++- src/_pytest/debugging.py | 11 +++-- src/_pytest/doctest.py | 5 ++- src/_pytest/faulthandler.py | 20 +++++---- src/_pytest/fixtures.py | 5 ++- src/_pytest/helpconfig.py | 14 +++++-- src/_pytest/hookspec.py | 69 +++++++++++++++++++------------ src/_pytest/junitxml.py | 19 +++++---- src/_pytest/logging.py | 18 ++++---- src/_pytest/mark/__init__.py | 20 ++++++--- src/_pytest/mark/structures.py | 3 +- src/_pytest/nodes.py | 2 +- src/_pytest/pastebin.py | 20 +++++---- src/_pytest/pytester.py | 8 ++-- src/_pytest/python.py | 17 +++++--- src/_pytest/resultlog.py | 16 ++++--- src/_pytest/runner.py | 9 ++-- src/_pytest/setuponly.py | 11 ++++- src/_pytest/setupplan.py | 12 +++++- src/_pytest/skipping.py | 8 ++-- src/_pytest/stepwise.py | 11 +++-- src/_pytest/terminal.py | 13 +++--- src/_pytest/warnings.py | 6 ++- 26 files changed, 223 insertions(+), 124 deletions(-) diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index cdb0347034f..2cf8d65b0ac 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -9,12 +9,13 @@ from _pytest.assertion import util from _pytest.compat import TYPE_CHECKING from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser if TYPE_CHECKING: from _pytest.main import Session -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--assert", @@ -162,7 +163,7 @@ def call_assertion_pass_hook(lineno, orig, expl): util._reprcompare, util._assertion_pass = saved_assert_hooks -def pytest_sessionfinish(session): +def pytest_sessionfinish(session: "Session") -> None: assertstate = getattr(session.config, "_assertstate", None) if assertstate: if assertstate.hook is not None: diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index a0f486089ff..51f843a32b0 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -12,6 +12,7 @@ from typing import List from typing import Optional from typing import Set +from typing import Union import attr import py @@ -24,6 +25,8 @@ from _pytest import nodes from _pytest._io import TerminalWriter from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser from _pytest.main import Session from _pytest.python import Module @@ -316,11 +319,12 @@ def pytest_collection_modifyitems(self, session, config, items): else: self._report_status += "not deselecting items." - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self, session: Session) -> None: config = self.config if config.getoption("cacheshow") or hasattr(config, "slaveinput"): return + assert config.cache is not None saved_lastfailed = config.cache.get("cache/lastfailed", {}) if saved_lastfailed != self.lastfailed: config.cache.set("cache/lastfailed", self.lastfailed) @@ -358,7 +362,7 @@ def pytest_collection_modifyitems( def _get_increasing_order(self, items): return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True) - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self, session: Session) -> None: config = self.config if config.getoption("cacheshow") or hasattr(config, "slaveinput"): return @@ -366,7 +370,7 @@ def pytest_sessionfinish(self, session): config.cache.set("cache/nodeids", self.cached_nodeids) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--lf", @@ -424,16 +428,18 @@ def pytest_addoption(parser): ) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.cacheshow: from _pytest.main import wrap_session return wrap_session(config, cacheshow) + return None @pytest.hookimpl(tryfirst=True) def pytest_configure(config: Config) -> None: - config.cache = Cache.for_config(config) + # Type ignored: pending mechanism to store typed objects scoped to config. + config.cache = Cache.for_config(config) # type: ignore # noqa: F821 config.pluginmanager.register(LFPlugin(config), "lfplugin") config.pluginmanager.register(NFPlugin(config), "nfplugin") diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index a1335d654f0..44fa28cec95 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -19,6 +19,7 @@ from _pytest.compat import CaptureIO from _pytest.compat import TYPE_CHECKING from _pytest.config import Config +from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest if TYPE_CHECKING: @@ -29,7 +30,7 @@ patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "--capture", diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index b8e0bad997f..e299ad69f9d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -393,7 +393,7 @@ def hasplugin(self, name): """Return True if the plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config): + def pytest_configure(self, config: "Config") -> None: # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line( @@ -831,7 +831,9 @@ def _ensure_unconfigure(self): def get_terminal_writer(self): return self.pluginmanager.get_plugin("terminalreporter")._tw - def pytest_cmdline_parse(self, pluginmanager, args): + def pytest_cmdline_parse( + self, pluginmanager: PytestPluginManager, args: List[str] + ) -> object: try: self.parse(args) except UsageError: diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 9155d7e98e3..fdbacded891 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -4,7 +4,10 @@ import sys from _pytest import outcomes +from _pytest.config import Config from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager +from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError @@ -19,7 +22,7 @@ def _validate_usepdb_cls(value): return (modname, classname) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "--pdb", @@ -43,7 +46,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: import pdb if config.getvalue("trace"): @@ -73,8 +76,8 @@ def fin(): class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ - _pluginmanager = None - _config = None + _pluginmanager = None # type: PytestPluginManager + _config = None # type: Config _saved = [] # type: list _recursive_debug = 0 _wrapped_pdb_cls = None diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 02108477818..57a097ea97a 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -23,6 +23,7 @@ from _pytest._io import TerminalWriter from _pytest.compat import safe_getattr from _pytest.compat import TYPE_CHECKING +from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest from _pytest.outcomes import OutcomeException from _pytest.python_api import approx @@ -52,7 +53,7 @@ CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]] -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "doctest_optionflags", "option flags for doctests", @@ -102,7 +103,7 @@ def pytest_addoption(parser): ) -def pytest_unconfigure(): +def pytest_unconfigure() -> None: global RUNNER_CLASS RUNNER_CLASS = None diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index ed2dfd02504..398321bf028 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -3,9 +3,11 @@ import sys import pytest +from _pytest.config import Config +from _pytest.config.argparsing import Parser -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: help = ( "Dump the traceback of all threads if a test takes " "more than TIMEOUT seconds to finish.\n" @@ -14,7 +16,7 @@ def pytest_addoption(parser): parser.addini("faulthandler_timeout", help, default=0.0) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: import faulthandler if not faulthandler.is_enabled(): @@ -42,14 +44,16 @@ class FaultHandlerHooks: """Implements hooks that will actually install fault handler before tests execute, as well as correctly handle pdb and internal errors.""" - def pytest_configure(self, config): + def pytest_configure(self, config: Config) -> None: import faulthandler stderr_fd_copy = os.dup(self._get_stderr_fileno()) - config.fault_handler_stderr = os.fdopen(stderr_fd_copy, "w") - faulthandler.enable(file=config.fault_handler_stderr) + fault_handler_stderr = os.fdopen(stderr_fd_copy, "w") + # Type ignored: pending mechanism to store typed objects scoped to config. + config.fault_handler_stderr = fault_handler_stderr # type: ignore # noqa: F821 + faulthandler.enable(file=fault_handler_stderr) - def pytest_unconfigure(self, config): + def pytest_unconfigure(self, config: Config) -> None: import faulthandler faulthandler.disable() @@ -57,8 +61,8 @@ def pytest_unconfigure(self, config): # re-enable the faulthandler, attaching it to the default sys.stderr # so we can see crashes after pytest has finished, usually during # garbage collection during interpreter shutdown - config.fault_handler_stderr.close() - del config.fault_handler_stderr + config.fault_handler_stderr.close() # type: ignore[attr-defined] # noqa: F821 + del config.fault_handler_stderr # type: ignore[attr-defined] # noqa: F821 faulthandler.enable(file=self._get_stderr_fileno()) @staticmethod diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index cdd249d93a6..5357ae46664 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -28,6 +28,7 @@ from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.compat import TYPE_CHECKING +from _pytest.config.argparsing import Parser from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.deprecated import FUNCARGNAMES from _pytest.mark import ParameterSet @@ -47,7 +48,7 @@ class PseudoFixtureDef: scope = attr.ib() -def pytest_sessionstart(session: "Session"): +def pytest_sessionstart(session: "Session") -> None: import _pytest.python import _pytest.nodes @@ -1200,7 +1201,7 @@ def test_foo(pytestconfig): return request.config -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "usefixtures", type="args", diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index ae37fdea45b..d84f48be500 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -2,11 +2,16 @@ import os import sys from argparse import Action +from typing import Optional +from typing import Union import py import pytest +from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import PrintHelp +from _pytest.config.argparsing import Parser class HelpAction(Action): @@ -36,7 +41,7 @@ def __call__(self, parser, namespace, values, option_string=None): raise PrintHelp -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--version", @@ -106,7 +111,7 @@ def pytest_cmdline_parse(): undo_tracing = config.pluginmanager.enable_tracing() sys.stderr.write("writing pytestdebug information to %s\n" % path) - def unset_tracing(): + def unset_tracing() -> None: debugfile.close() sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name) config.trace.root.setwriter(None) @@ -127,7 +132,7 @@ def showversion(config): sys.stderr.write(line + "\n") -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.version: showversion(config) return 0 @@ -136,9 +141,10 @@ def pytest_cmdline_main(config): showhelp(config) config._ensure_unconfigure() return 0 + return None -def showhelp(config): +def showhelp(config: Config) -> None: import textwrap reporter = config.pluginmanager.get_plugin("terminalreporter") diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 62e2155a263..a74eea597ae 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -1,14 +1,21 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ from typing import Any +from typing import List from typing import Optional +from typing import Union from pluggy import HookspecMarker from _pytest.compat import TYPE_CHECKING if TYPE_CHECKING: + from _pytest.config import Config + from _pytest.config import PytestPluginManager + from _pytest.config import _PluggyPlugin + from _pytest.config.argparsing import Parser + from _pytest.config import ExitCode from _pytest.main import Session - + from _pytest.python import Metafunc hookspec = HookspecMarker("pytest") @@ -18,7 +25,7 @@ @hookspec(historic=True) -def pytest_addhooks(pluginmanager): +def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: """called at plugin registration time to allow adding new hooks via a call to ``pluginmanager.add_hookspecs(module_or_class, prefix)``. @@ -31,7 +38,9 @@ def pytest_addhooks(pluginmanager): @hookspec(historic=True) -def pytest_plugin_registered(plugin, manager): +def pytest_plugin_registered( + plugin: "_PluggyPlugin", manager: "PytestPluginManager" +) -> None: """ a new pytest plugin got registered. :param plugin: the plugin module or instance @@ -43,7 +52,7 @@ def pytest_plugin_registered(plugin, manager): @hookspec(historic=True) -def pytest_addoption(parser, pluginmanager): +def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: """register argparse-style options and ini-style config values, called once at the beginning of a test run. @@ -81,7 +90,7 @@ def pytest_addoption(parser, pluginmanager): @hookspec(historic=True) -def pytest_configure(config): +def pytest_configure(config: "Config") -> None: """ Allows plugins and conftest files to perform initial configuration. @@ -105,7 +114,9 @@ def pytest_configure(config): @hookspec(firstresult=True) -def pytest_cmdline_parse(pluginmanager, args): +def pytest_cmdline_parse( + pluginmanager: "PytestPluginManager", args: List[str] +) -> Optional[object]: """return initialized config object, parsing the specified args. Stops at first non-None result, see :ref:`firstresult` @@ -119,7 +130,7 @@ def pytest_cmdline_parse(pluginmanager, args): """ -def pytest_cmdline_preparse(config, args): +def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: """(**Deprecated**) modify command line arguments before option parsing. This hook is considered deprecated and will be removed in a future pytest version. Consider @@ -134,7 +145,7 @@ def pytest_cmdline_preparse(config, args): @hookspec(firstresult=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: "Config") -> "Optional[Union[ExitCode, int]]": """ called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. @@ -147,7 +158,9 @@ def pytest_cmdline_main(config): """ -def pytest_load_initial_conftests(early_config, parser, args): +def pytest_load_initial_conftests( + early_config: "Config", parser: "Parser", args: List[str] +) -> None: """ implements the loading of initial conftest files ahead of command line option parsing. @@ -175,7 +188,7 @@ def pytest_collection(session: "Session") -> Optional[Any]: """ -def pytest_collection_modifyitems(session, config, items): +def pytest_collection_modifyitems(session: "Session", config: "Config", items): """ called after collection has been performed, may filter or re-order the items in-place. @@ -185,7 +198,7 @@ def pytest_collection_modifyitems(session, config, items): """ -def pytest_collection_finish(session): +def pytest_collection_finish(session: "Session"): """ called after collection has been performed and modified. :param _pytest.main.Session session: the pytest session object @@ -193,7 +206,7 @@ def pytest_collection_finish(session): @hookspec(firstresult=True) -def pytest_ignore_collect(path, config): +def pytest_ignore_collect(path, config: "Config"): """ return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. @@ -281,12 +294,12 @@ def pytest_pyfunc_call(pyfuncitem): Stops at first non-None result, see :ref:`firstresult` """ -def pytest_generate_tests(metafunc): +def pytest_generate_tests(metafunc: "Metafunc") -> None: """ generate (multiple) parametrized calls to a test function.""" @hookspec(firstresult=True) -def pytest_make_parametrize_id(config, val, argname): +def pytest_make_parametrize_id(config: "Config", val, argname) -> Optional[str]: """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. The parameter name is available as ``argname``, if required. @@ -305,7 +318,7 @@ def pytest_make_parametrize_id(config, val, argname): @hookspec(firstresult=True) -def pytest_runtestloop(session): +def pytest_runtestloop(session: "Session"): """ called for performing the main runtest loop (after collection finished). @@ -388,7 +401,7 @@ def pytest_runtest_logreport(report): @hookspec(firstresult=True) -def pytest_report_to_serializable(config, report): +def pytest_report_to_serializable(config: "Config", report): """ Serializes the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON. @@ -396,7 +409,7 @@ def pytest_report_to_serializable(config, report): @hookspec(firstresult=True) -def pytest_report_from_serializable(config, data): +def pytest_report_from_serializable(config: "Config", data): """ Restores a report object previously serialized with pytest_report_to_serializable(). """ @@ -433,7 +446,7 @@ def pytest_fixture_post_finalizer(fixturedef, request): # ------------------------------------------------------------------------- -def pytest_sessionstart(session): +def pytest_sessionstart(session: "Session") -> None: """ called after the ``Session`` object has been created and before performing collection and entering the run test loop. @@ -441,7 +454,9 @@ def pytest_sessionstart(session): """ -def pytest_sessionfinish(session, exitstatus): +def pytest_sessionfinish( + session: "Session", exitstatus: "Union[int, ExitCode]" +) -> None: """ called after whole test run finished, right before returning the exit status to the system. :param _pytest.main.Session session: the pytest session object @@ -449,7 +464,7 @@ def pytest_sessionfinish(session, exitstatus): """ -def pytest_unconfigure(config): +def pytest_unconfigure(config: "Config") -> None: """ called before test process is exited. :param _pytest.config.Config config: pytest config object @@ -461,7 +476,7 @@ def pytest_unconfigure(config): # ------------------------------------------------------------------------- -def pytest_assertrepr_compare(config, op, left, right): +def pytest_assertrepr_compare(config: "Config", op, left, right): """return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list @@ -516,7 +531,7 @@ def pytest_assertion_pass(item, lineno, orig, expl): # ------------------------------------------------------------------------- -def pytest_report_header(config, startdir): +def pytest_report_header(config: "Config", startdir): """ return a string or list of strings to be displayed as header info for terminal reporting. :param _pytest.config.Config config: pytest config object @@ -530,7 +545,7 @@ def pytest_report_header(config, startdir): """ -def pytest_report_collectionfinish(config, startdir, items): +def pytest_report_collectionfinish(config: "Config", startdir, items): """ .. versionadded:: 3.2 @@ -545,7 +560,7 @@ def pytest_report_collectionfinish(config, startdir, items): @hookspec(firstresult=True) -def pytest_report_teststatus(report, config): +def pytest_report_teststatus(report, config: "Config"): """ return result-category, shortletter and verbose word for reporting. :param _pytest.config.Config config: pytest config object @@ -553,7 +568,7 @@ def pytest_report_teststatus(report, config): Stops at first non-None result, see :ref:`firstresult` """ -def pytest_terminal_summary(terminalreporter, exitstatus, config): +def pytest_terminal_summary(terminalreporter, exitstatus, config: "Config"): """Add a section to terminal summary reporting. :param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object @@ -627,7 +642,7 @@ def pytest_exception_interact(node, call, report): """ -def pytest_enter_pdb(config, pdb): +def pytest_enter_pdb(config: "Config", pdb): """ called upon pdb.set_trace(), can be used by plugins to take special action just before the python debugger enters in interactive mode. @@ -636,7 +651,7 @@ def pytest_enter_pdb(config, pdb): """ -def pytest_leave_pdb(config, pdb): +def pytest_leave_pdb(config: "Config", pdb): """ called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 6ef5358397f..8538fa4042c 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -21,7 +21,9 @@ import pytest from _pytest import deprecated from _pytest import nodes +from _pytest.config import Config from _pytest.config import filename_arg +from _pytest.config.argparsing import Parser from _pytest.warnings import _issue_warning_captured @@ -359,7 +361,7 @@ def record_func(name, value): return record_func -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") group.addoption( "--junitxml", @@ -404,7 +406,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: xmlpath = config.option.xmlpath # prevent opening xmllog on slave nodes (xdist) if xmlpath and not hasattr(config, "slaveinput"): @@ -412,7 +414,8 @@ def pytest_configure(config): if not junit_family: _issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2) junit_family = "xunit1" - config._xml = LogXML( + # Type ignored: pending mechanism to store typed objects scoped to config. + _xml = config._xml = LogXML( # type: ignore # noqa: F821 xmlpath, config.option.junitprefix, config.getini("junit_suite_name"), @@ -421,13 +424,13 @@ def pytest_configure(config): junit_family, config.getini("junit_log_passing_tests"), ) - config.pluginmanager.register(config._xml) + config.pluginmanager.register(_xml) -def pytest_unconfigure(config): +def pytest_unconfigure(config: Config) -> None: xml = getattr(config, "_xml", None) if xml: - del config._xml + del config._xml # type: ignore[attr-defined] # noqa: F821 config.pluginmanager.unregister(xml) @@ -622,10 +625,10 @@ def pytest_internalerror(self, excrepr): reporter.attrs.update(classname="pytest", name="internal") reporter._add_simple(Junit.error, "internal error", excrepr) - def pytest_sessionstart(self): + def pytest_sessionstart(self) -> None: self.suite_start_time = time.time() - def pytest_sessionfinish(self): + def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) if not os.path.isdir(dirname): os.makedirs(dirname) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 1df7c1691f6..d3d8bda82e9 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -13,7 +13,9 @@ from _pytest import nodes from _pytest.compat import nullcontext from _pytest.config import _strtobool +from _pytest.config import Config from _pytest.config import create_terminal_writer +from _pytest.config.argparsing import Parser from _pytest.pathlib import Path DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" @@ -171,7 +173,7 @@ def get_option_ini(config, *names): return ret -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: """Add options to control log capturing.""" group = parser.getgroup("logging") @@ -470,7 +472,7 @@ def get_actual_log_level(config, *setting_names): # run after terminalreporter/capturemanager are configured @pytest.hookimpl(trylast=True) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") @@ -478,7 +480,7 @@ class LoggingPlugin: """Attaches to the logging module and captures log messages for each test. """ - def __init__(self, config): + def __init__(self, config: Config) -> None: """Creates a new plugin to capture log messages. The formatter can be safely shared across all handlers so @@ -510,13 +512,13 @@ def __init__(self, config): ) log_file = get_option_ini(config, "log_file") - if log_file: + if not log_file: + self.log_file_handler = None + else: self.log_file_handler = logging.FileHandler( log_file, mode="w", encoding="UTF-8" ) self.log_file_handler.setFormatter(self.log_file_formatter) - else: - self.log_file_handler = None self.log_cli_handler = None @@ -683,7 +685,7 @@ def pytest_runtest_logreport(self): yield @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_sessionfinish(self): + def pytest_sessionfinish(self) -> Generator[None, None, None]: with self.live_logs_context(): if self.log_cli_handler: self.log_cli_handler.set_when("sessionfinish") @@ -701,7 +703,7 @@ def pytest_sessionfinish(self): yield @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_sessionstart(self): + def pytest_sessionstart(self) -> Generator[None, None, None]: with self.live_logs_context(): if self.log_cli_handler: self.log_cli_handler.set_when("sessionstart") diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index f493bd839f1..985fc3247e6 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,4 +1,7 @@ """ generic mechanism for marking and selecting python functions. """ +from typing import Optional +from typing import Union + from .legacy import matchkeyword from .legacy import matchmark from .structures import EMPTY_PARAMETERSET_OPTION @@ -8,8 +11,12 @@ from .structures import MarkDecorator from .structures import MarkGenerator from .structures import ParameterSet +from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import UsageError +from _pytest.config.argparsing import Parser + __all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"] @@ -34,7 +41,7 @@ def test_eval(test_input, expected): return ParameterSet.param(*values, **kw) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "-k", @@ -77,7 +84,7 @@ def pytest_addoption(parser): @hookimpl(tryfirst=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: import _pytest.config if config.option.markers: @@ -93,6 +100,8 @@ def pytest_cmdline_main(config): config._ensure_unconfigure() return 0 + return None + def deselect_by_keyword(items, config): keywordexpr = config.option.keyword.lstrip() @@ -144,8 +153,9 @@ def pytest_collection_modifyitems(items, config): deselect_by_mark(items, config) -def pytest_configure(config): - config._old_mark_config = MARK_GEN._config +def pytest_configure(config: Config) -> None: + # Type ignored: pending mechanism to store typed objects scoped to config. + config._old_mark_config = MARK_GEN._config # type: ignore[attr-defined] # noqa: F821 MARK_GEN._config = config empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) @@ -157,5 +167,5 @@ def pytest_configure(config): ) -def pytest_unconfigure(config): +def pytest_unconfigure(config: Config) -> None: MARK_GEN._config = getattr(config, "_old_mark_config", None) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 1ab22b7c758..83f0583e6d7 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -13,6 +13,7 @@ from .._code.source import getfslineno from ..compat import ascii_escaped from ..compat import NOTSET +from _pytest.config import Config from _pytest.outcomes import fail from _pytest.warning_types import PytestUnknownMarkWarning @@ -310,7 +311,7 @@ def test_function(): will set a 'slowtest' :class:`MarkInfo` object on the ``test_function`` object. """ - _config = None + _config = None # type: Optional[Config] _markers = set() # type: Set[str] def __getattr__(self, name: str) -> MarkDecorator: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 133e93df7cc..2f554b93633 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -107,7 +107,7 @@ def __init__( #: the pytest config object if config: - self.config = config + self.config = config # type: Config else: if not parent: raise TypeError("config or parent must be provided") diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 77b4e2621eb..60f335d44e3 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -2,9 +2,11 @@ import tempfile import pytest +from _pytest.config import Config +from _pytest.config.argparsing import Parser -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") group._addoption( "--pastebin", @@ -18,7 +20,7 @@ def pytest_addoption(parser): @pytest.hookimpl(trylast=True) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.option.pastebin == "all": tr = config.pluginmanager.getplugin("terminalreporter") # if no terminal reporter plugin is present, nothing we can do here; @@ -26,7 +28,8 @@ def pytest_configure(config): # when using pytest-xdist, for example if tr is not None: # pastebin file will be utf-8 encoded binary file - config._pastebinfile = tempfile.TemporaryFile("w+b") + # Type ignored: pending mechanism to store typed objects scoped to config. + config._pastebinfile = tempfile.TemporaryFile("w+b") # type: ignore # noqa: F821 oldwrite = tr._tw.write def tee_write(s, **kwargs): @@ -38,13 +41,14 @@ def tee_write(s, **kwargs): tr._tw.write = tee_write -def pytest_unconfigure(config): +def pytest_unconfigure(config: Config) -> None: if hasattr(config, "_pastebinfile"): # get terminal contents and delete file - config._pastebinfile.seek(0) - sessionlog = config._pastebinfile.read() - config._pastebinfile.close() - del config._pastebinfile + pastebinfile = config._pastebinfile # type: ignore[attr-defined] # noqa: F821 + del config._pastebinfile # type: ignore[attr-defined] # noqa: F821 + pastebinfile.seek(0) + sessionlog = pastebinfile.read() + pastebinfile.close() # undo our patching in the terminal reporter tr = config.pluginmanager.getplugin("terminalreporter") del tr._tw.__dict__["write"] diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f383e51f652..a80259d8d9a 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -29,7 +29,9 @@ from _pytest.capture import SysCapture from _pytest.compat import TYPE_CHECKING from _pytest.config import _PluggyPlugin +from _pytest.config import Config from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest from _pytest.main import Session from _pytest.monkeypatch import MonkeyPatch @@ -51,7 +53,7 @@ ] -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addoption( "--lsof", action="store_true", @@ -76,7 +78,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.getvalue("lsof"): checker = LsofFdLeakChecker() if checker.matching_platform(): @@ -887,7 +889,7 @@ def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False): rec = [] class Collect: - def pytest_configure(x, config): + def pytest_configure(x, config: Config) -> None: rec.append(self.make_hook_recorder(config.pluginmanager)) plugins.append(Collect()) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ae19fa568fd..28cc3edfc3e 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -16,6 +16,7 @@ from typing import Iterable from typing import List from typing import Optional +from typing import Set from typing import Tuple from typing import Union @@ -42,9 +43,12 @@ from _pytest.compat import safe_isclass from _pytest.compat import STRING_TYPES from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser from _pytest.deprecated import FUNCARGNAMES from _pytest.fixtures import FuncFixtureInfo +from _pytest.main import Session from _pytest.mark import MARK_GEN from _pytest.mark import ParameterSet from _pytest.mark.structures import get_unpacked_marks @@ -69,7 +73,7 @@ def get(self): return property(get, None, None, doc) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--fixtures", @@ -124,13 +128,14 @@ def pytest_addoption(parser): ) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.showfixtures: showfixtures(config) return 0 if config.option.show_fixtures_per_test: show_fixtures_per_test(config) return 0 + return None def pytest_generate_tests(metafunc: "Metafunc") -> None: @@ -139,7 +144,7 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None: metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) # type: ignore[misc] # noqa: F821 -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.addinivalue_line( "markers", "parametrize(argnames, argvalues): call a test function multiple " @@ -1369,13 +1374,13 @@ def write_item(item): write_item(session_item) -def showfixtures(config): +def showfixtures(config: Config) -> Union[int, ExitCode]: from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) -def _showfixtures_main(config, session): +def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1386,7 +1391,7 @@ def _showfixtures_main(config, session): fm = session._fixturemanager available = [] - seen = set() + seen = set() # type: Set[Tuple[str, str]] for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py index a977b29da43..5c40c03ad7a 100644 --- a/src/_pytest/resultlog.py +++ b/src/_pytest/resultlog.py @@ -5,8 +5,11 @@ import py +from _pytest.config import Config +from _pytest.config.argparsing import Parser -def pytest_addoption(parser): + +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "resultlog plugin options") group.addoption( "--resultlog", @@ -18,7 +21,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: resultlog = config.option.resultlog # prevent opening resultlog on slave nodes (xdist) if resultlog and not hasattr(config, "slaveinput"): @@ -26,8 +29,9 @@ def pytest_configure(config): if not os.path.isdir(dirname): os.makedirs(dirname) logfile = open(resultlog, "w", 1) # line buffered - config._resultlog = ResultLog(config, logfile) - config.pluginmanager.register(config._resultlog) + # Type ignored: pending mechanism to store typed objects scoped to config. + _resultlog = config._resultlog = ResultLog(config, logfile) # type: ignore # noqa: F821 + config.pluginmanager.register(_resultlog) from _pytest.deprecated import RESULT_LOG from _pytest.warnings import _issue_warning_captured @@ -35,11 +39,11 @@ def pytest_configure(config): _issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2) -def pytest_unconfigure(config): +def pytest_unconfigure(config: Config) -> None: resultlog = getattr(config, "_resultlog", None) if resultlog: + del config._resultlog # type: ignore[attr-defined] # noqa: F821 resultlog.logfile.close() - del config._resultlog config.pluginmanager.unregister(resultlog) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e53c72e2c36..28aee4466dc 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -17,6 +17,7 @@ from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr from _pytest.compat import TYPE_CHECKING +from _pytest.config.argparsing import Parser from _pytest.nodes import Collector from _pytest.nodes import Node from _pytest.outcomes import Exit @@ -27,11 +28,13 @@ from typing import Type from typing_extensions import Literal + from _pytest.main import Session + # # pytest plugin hooks -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "reporting", after="general") group.addoption( "--durations", @@ -72,11 +75,11 @@ def pytest_terminal_summary(terminalreporter): tr.write_line("{:02.2f}s {:<8} {}".format(rep.duration, rep.when, rep.nodeid)) -def pytest_sessionstart(session): +def pytest_sessionstart(session: "Session") -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session): +def pytest_sessionfinish(session: "Session") -> None: session._setupstate.teardown_all() diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index aa5a95ff920..f1581fad465 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -1,7 +1,13 @@ +from typing import Optional +from typing import Union + import pytest +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--setuponly", @@ -73,6 +79,7 @@ def _show_fixture_action(fixturedef, msg): @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setuponly: config.option.setupshow = True + return None diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py index 6fdd3aed064..834d4ae2d51 100644 --- a/src/_pytest/setupplan.py +++ b/src/_pytest/setupplan.py @@ -1,7 +1,13 @@ +from typing import Optional +from typing import Union + import pytest +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--setupplan", @@ -19,10 +25,12 @@ def pytest_fixture_setup(fixturedef, request): my_cache_key = fixturedef.cache_key(request) fixturedef.cached_result = (None, my_cache_key, None) return fixturedef.cached_result + return None @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True + return None diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index f70ef7f591c..c784b0b2526 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,12 +1,14 @@ """ support for skip/xfail functions and markers. """ +from _pytest.config import Config from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser from _pytest.mark.evaluate import MarkEvaluator from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--runxfail", @@ -25,7 +27,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.option.runxfail: # yay a hack import pytest @@ -36,7 +38,7 @@ def pytest_configure(config): def nop(*args, **kwargs): pass - nop.Exception = xfail.Exception + nop.Exception = xfail.Exception # type: ignore[attr-defined] # noqa: F821 setattr(pytest, "xfail", nop) config.addinivalue_line( diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 6fa21cd1c65..3cbf0be9fc0 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -1,7 +1,10 @@ import pytest +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.main import Session -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--sw", @@ -19,7 +22,7 @@ def pytest_addoption(parser): @pytest.hookimpl -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") @@ -34,7 +37,7 @@ def __init__(self, config): self.lastfailed = config.cache.get("cache/stepwise", None) self.skip = config.getvalue("stepwise_skip") - def pytest_sessionstart(self, session): + def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems(self, session, config, items): @@ -100,7 +103,7 @@ def pytest_report_collectionfinish(self): if self.active and self.config.getoption("verbose") >= 0 and self.report_status: return "stepwise: %s" % self.report_status - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self, session: Session) -> None: if self.active: self.config.cache.set("cache/stepwise", self.lastfailed) else: diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 4be5b85139a..23bd41788f9 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -17,6 +17,7 @@ from typing import Optional from typing import Set from typing import Tuple +from typing import Union import attr import pluggy @@ -25,8 +26,10 @@ import pytest from _pytest import nodes +from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser from _pytest.main import Session from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -72,7 +75,7 @@ def __call__(self, parser, namespace, values, option_string=None): namespace.quiet = getattr(namespace, "quiet", 0) + 1 -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption( "-v", @@ -407,7 +410,7 @@ def pytest_warning_captured(self, warning_message, item): ) self._add_stats("warnings", [warning_report]) - def pytest_plugin_registered(self, plugin): + def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: if self.config.option.traceconfig: msg = "PLUGIN registered: {}".format(plugin) # XXX this event may happen during setup/teardown time @@ -699,7 +702,7 @@ def _printcollecteditems(self, items): self._tw.line("{}{}".format(indent + " ", line.strip())) @pytest.hookimpl(hookwrapper=True) - def pytest_sessionfinish(self, session: Session, exitstatus: ExitCode): + def pytest_sessionfinish(self, session: Session, exitstatus: Union[int, ExitCode]): outcome = yield outcome.get_result() self._tw.line("") @@ -734,10 +737,10 @@ def pytest_terminal_summary(self): # Display any extra warnings from teardown here (if any). self.summary_warnings() - def pytest_keyboard_interrupt(self, excinfo): + def pytest_keyboard_interrupt(self, excinfo) -> None: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) - def pytest_unconfigure(self): + def pytest_unconfigure(self) -> None: if hasattr(self, "_keyboardinterrupt_memo"): self._report_keyboardinterrupt() diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 2a4d189d573..640794fbffb 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -4,6 +4,8 @@ from typing import Generator import pytest +from _pytest.config import Config +from _pytest.config.argparsing import Parser from _pytest.main import Session @@ -31,7 +33,7 @@ def _setoption(wmod, arg): wmod.filterwarnings(action, message, category, module, lineno) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("pytest-warnings") group.addoption( "-W", @@ -48,7 +50,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.addinivalue_line( "markers", "filterwarnings(warning): add a warning filter to the given test. "