From 8666b2751fb613deb980d245604b6612b609ae19 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 13 Jan 2020 03:11:54 +0100 Subject: [PATCH 1/5] typing for AssertionRewritingHook, get_config, get_plugin_manager, _prepareconfig, InvocationParams --- src/_pytest/assertion/rewrite.py | 3 ++- src/_pytest/config/__init__.py | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index ab5e63a1e0c..5312d06e143 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -26,6 +26,7 @@ format_explanation as _format_explanation, ) from _pytest.compat import fspath +from _pytest.config import Config from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import Path from _pytest.pathlib import PurePath @@ -39,7 +40,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder): """PEP302/PEP451 import hook which rewrites asserts.""" - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config try: self.fnpats = config.getini("python_files") diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 3516b333e22..fe5f036b40b 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -172,7 +172,10 @@ def directory_arg(path, optname): builtin_plugins.add("pytester") -def get_config(args=None, plugins=None): +def get_config( + args: Optional[Union[List, Tuple]] = None, + plugins: Optional[Sequence[Union[str, object]]] = None, +) -> "Config": # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() config = Config( @@ -191,7 +194,7 @@ def get_config(args=None, plugins=None): return config -def get_plugin_manager(): +def get_plugin_manager() -> PluginManager: """ Obtain a new instance of the :py:class:`_pytest.config.PytestPluginManager`, with default plugins @@ -203,7 +206,10 @@ def get_plugin_manager(): return get_config().pluginmanager -def _prepareconfig(args=None, plugins=None): +def _prepareconfig( + args: Optional[Union[List, Tuple]] = None, + plugins: Optional[Sequence[Union[str, object]]] = None, +): if args is None: args = sys.argv[1:] elif isinstance(args, py.path.local): @@ -732,7 +738,9 @@ class InvocationParams: plugins = attr.ib() dir = attr.ib(type=Path) - def __init__(self, pluginmanager, *, invocation_params=None): + def __init__( + self, pluginmanager: "PytestPluginManager", *, invocation_params=None + ) -> None: from .argparsing import Parser, FILE_OR_DIR if invocation_params is None: From 604b84fbdde6a4f1c75704fbf9d1ada51f9fcaf7 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 15 Jan 2020 06:10:04 +0100 Subject: [PATCH 2/5] typing: get_parametrized_fixture_keys, Session._fixturemanager --- src/_pytest/fixtures.py | 54 +++++++++++++++++++++++++++-------------- src/_pytest/python.py | 6 +++-- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 464828de4c9..f76ebf2553d 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -7,8 +7,10 @@ from collections import deque from collections import OrderedDict from typing import Dict +from typing import Generator from typing import List from typing import Tuple +from typing import Union import attr import py @@ -38,6 +40,9 @@ from typing import Type from _pytest import nodes + from _pytest.main import Session + from _pytest.python import Class # noqa: F401 + from _pytest.python import Function @attr.s(frozen=True) @@ -46,7 +51,7 @@ class PseudoFixtureDef: scope = attr.ib() -def pytest_sessionstart(session): +def pytest_sessionstart(session: "Session") -> None: import _pytest.python import _pytest.nodes @@ -179,30 +184,43 @@ def getfixturemarker(obj): return None -def get_parametrized_fixture_keys(item, scopenum): +def get_parametrized_fixture_keys( + item: "nodes.Item", # only used for Function (with callspec), kept for B/C. + scopenum: int, +) -> Generator[ + Union[ + Tuple[str, int], + Tuple[str, int, py.path.local], + Tuple[str, int, py.path.local, "Class"], + ], + None, + None, +]: """ return list of keys for all parametrized arguments which match the specified scope. """ - assert scopenum < scopenum_function # function + assert scopenum < scopenum_function, (scopenum, scopenum_function) + try: - cs = item.callspec + cs = item.callspec # type: ignore[attr-defined] except AttributeError: - pass - else: - # cs.indices.items() is random order of argnames. Need to - # sort this so that different calls to - # get_parametrized_fixture_keys will be deterministic. - for argname, param_index in sorted(cs.indices.items()): - if cs._arg2scopenum[argname] != scopenum: - continue + return + if TYPE_CHECKING: + assert isinstance(item, Function) + + # cs.indices.items() is random order of argnames. Need to + # sort this so that different calls to + # get_parametrized_fixture_keys will be deterministic. + for argname, param_index in sorted(cs.indices.items()): + if cs._arg2scopenum[argname] == scopenum: if scopenum == 0: # session - key = (argname, param_index) + yield (argname, param_index) elif scopenum == 1: # package - key = (argname, param_index, item.fspath.dirpath()) + yield (argname, param_index, item.fspath.dirpath()) elif scopenum == 2: # module - key = (argname, param_index, item.fspath) - elif scopenum == 3: # class - key = (argname, param_index, item.fspath, item.cls) - yield key + yield (argname, param_index, item.fspath) + else: # class + assert scopenum == 3, scopenum + yield (argname, param_index, item.fspath, item.cls) # algorithm for sorting on a per-parametrized resource setup basis diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 82dca3bcc9b..34c7931f4a5 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -10,7 +10,9 @@ from collections.abc import Sequence from functools import partial from textwrap import dedent +from typing import Dict from typing import List +from typing import Optional from typing import Tuple from typing import Union @@ -839,7 +841,7 @@ def __init__(self, metafunc): self._globalparam = NOTSET self._arg2scopenum = {} # used for sorting parametrized resources self.marks = [] - self.indices = {} + self.indices = {} # type: Dict[str, int] def copy(self): cs = CallSpec2(self.metafunc) @@ -1368,7 +1370,7 @@ def __init__( parent, args=None, config=None, - callspec=None, + callspec: Optional[CallSpec2] = None, callobj=NOTSET, keywords=None, session=None, From b96077b0be9031b1c37a77e9404cbf2f5453322a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 17 Jan 2020 10:48:27 +0100 Subject: [PATCH 3/5] _PluggyPlugin --- src/_pytest/config/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index fe5f036b40b..2211cc17539 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -47,6 +47,8 @@ if TYPE_CHECKING: from typing import Type + _PluggyPlugin = object + hookimpl = HookimplMarker("pytest") hookspec = HookspecMarker("pytest") @@ -208,7 +210,7 @@ def get_plugin_manager() -> PluginManager: def _prepareconfig( args: Optional[Union[List, Tuple]] = None, - plugins: Optional[Sequence[Union[str, object]]] = None, + plugins: Optional[Sequence[Union[str, "_PluggyPlugin"]]] = None, ): if args is None: args = sys.argv[1:] From b95394b6efbb61b26575c0841f9e7631447dd6ba Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 17 Jan 2020 11:51:07 +0100 Subject: [PATCH 4/5] typing: pytestconfig, request.config --- src/_pytest/fixtures.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index f76ebf2553d..b956fa2a38f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -31,6 +31,7 @@ from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.deprecated import FUNCARGNAMES from _pytest.outcomes import fail @@ -364,7 +365,7 @@ class FixtureRequest: the fixture is parametrized indirectly. """ - def __init__(self, pyfuncitem): + def __init__(self, pyfuncitem: "Function") -> None: self._pyfuncitem = pyfuncitem #: fixture for which this request is being performed self.fixturename = None @@ -373,7 +374,7 @@ def __init__(self, pyfuncitem): self._fixture_defs = {} # type: Dict[str, FixtureDef] fixtureinfo = pyfuncitem._fixtureinfo self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() - self._arg2index = {} + self._arg2index = {} # type: Dict[str, int] self._fixturemanager = pyfuncitem.session._fixturemanager @property @@ -411,7 +412,7 @@ def _getnextfixturedef(self, argname): return fixturedefs[index] @property - def config(self): + def config(self) -> Config: """ the pytest config object associated with this request. """ return self._pyfuncitem.config @@ -1204,7 +1205,7 @@ def yield_fixture( @fixture(scope="session") -def pytestconfig(request): +def pytestconfig(request: FixtureRequest) -> Config: """Session-scoped fixture that returns the :class:`_pytest.config.Config` object. Example:: From 0a8328a11e27dfac86acbb0670b0bd4cf5eba477 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 17 Jan 2020 12:06:16 +0100 Subject: [PATCH 5/5] # noqa: F821 --- src/_pytest/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b956fa2a38f..6c5241db943 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -202,7 +202,7 @@ def get_parametrized_fixture_keys( assert scopenum < scopenum_function, (scopenum, scopenum_function) try: - cs = item.callspec # type: ignore[attr-defined] + cs = item.callspec # type: ignore[attr-defined] # noqa: F821 except AttributeError: return if TYPE_CHECKING: