From 1070fa3d677fdec35792cb045e677e3e038a855c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 07:16:42 +0200 Subject: [PATCH 01/12] Turn `own_markers` into a property This makes it a bit more easier to follow, especially with the removed hack of setting it only based on `_ALLOW_MARKERS` property. This was triggered based on wondering why setting `_obj` directly would not set the marks. --- src/_pytest/mark/structures.py | 2 +- src/_pytest/nodes.py | 13 ++++++++---- src/_pytest/python.py | 37 +++++++++++++++++++--------------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 40dfe01ea61..dbb35e5e1c0 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -272,7 +272,7 @@ def __call__(self, *args, **kwargs): return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj): +def get_unpacked_marks(obj) -> List[Mark]: """ obtain the unpacked marks that are stored on an object """ diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index c452e63c48e..67b667335e4 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -129,8 +129,8 @@ def __init__( #: keywords/markers collected from all scopes self.keywords = NodeKeywords(self) - #: the marker objects belonging to this node - self.own_markers = [] # type: List[Mark] + #: The (manually added) marks belonging to this node (start, end). + self._own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] #: allow adding of extra keywords to use for matching self.extra_keyword_matches = set() # type: Set[str] @@ -173,6 +173,11 @@ def ihook(self): """ fspath sensitive hook proxy used to call pytest hooks""" return self.session.gethookproxy(self.fspath) + @property + def own_markers(self) -> List[Mark]: + """The marker objects belonging to this node.""" + return self._own_markers[0] + self._own_markers[1] + def __repr__(self): return "<{} nodeid={!r}>".format( self.__class__.__name__, getattr(self, "nodeid", None) @@ -255,9 +260,9 @@ def add_marker( raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker_.name] = marker if append: - self.own_markers.append(marker_.mark) + self._own_markers[1].append(marker_.mark) else: - self.own_markers.insert(0, marker_.mark) + self._own_markers[0].insert(0, marker_.mark) def iter_markers(self, name=None): """ diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4989d6b3984..8312fb37fea 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -262,8 +262,8 @@ class PyobjContext: instance = pyobj_property("Instance") -class PyobjMixin(PyobjContext): - _ALLOW_MARKERS = True +class PyobjMixin(PyobjContext, nodes.Node): + _obj_markers = None @property def obj(self): @@ -271,10 +271,6 @@ def obj(self): obj = getattr(self, "_obj", None) if obj is None: self._obj = obj = self._getobj() - # XXX evil hack - # used to avoid Instance collector marker duplication - if self._ALLOW_MARKERS: - self.own_markers.extend(get_unpacked_marks(self.obj)) return obj @obj.setter @@ -285,6 +281,12 @@ def _getobj(self): """Gets the underlying Python object. May be overwritten by subclasses.""" return getattr(self.parent.obj, self.name) + @property + def own_markers(self) -> List[Mark]: + if self._obj_markers is None: + self._obj_markers = get_unpacked_marks(self.obj) + return self._own_markers[0] + self._obj_markers + self._own_markers[1] + def getmodpath(self, stopatmodule=True, includemodule=False): """ return python path relative to the containing module. """ chain = self.listchain() @@ -751,14 +753,14 @@ def xunit_setup_method_fixture(self, request): class Instance(PyCollector): - _ALLOW_MARKERS = False # hack, destroy later - # instances share the object with their parents in a way - # that duplicates markers instances if not taken out - # can be removed at node structure reorganization time - def _getobj(self): return self.parent.obj() + @property + def own_markers(self) -> List[Mark]: + # Do not include markers from obj, coming from Class already. + return self._own_markers[0] + self._own_markers[1] + def collect(self): self.session._fixturemanager.parsefactories(self) return super().collect() @@ -1395,9 +1397,6 @@ class Function(PyobjMixin, nodes.Item): Python test function. """ - # disable since functions handle it themselves - _ALLOW_MARKERS = False - def __init__( self, name, @@ -1417,7 +1416,6 @@ def __init__( self.obj = callobj self.keywords.update(self.obj.__dict__) - self.own_markers.extend(get_unpacked_marks(self.obj)) if callspec: self.callspec = callspec # this is total hostile and a mess @@ -1427,7 +1425,6 @@ def __init__( # feel free to cry, this was broken for years before # and keywords cant fix it per design self.keywords[mark.name] = mark - self.own_markers.extend(normalize_mark_list(callspec.marks)) if keywords: self.keywords.update(keywords) @@ -1472,6 +1469,14 @@ def function(self): "underlying python 'function' object" return getimfunc(self.obj) + @property + def own_markers(self) -> List[Mark]: + if self._obj_markers is None: + self._obj_markers = get_unpacked_marks(self.obj) + if hasattr(self, "callspec"): + self._obj_markers += normalize_mark_list(self.callspec.marks) + return self._own_markers[0] + self._obj_markers + self._own_markers[1] + def _getobj(self): name = self.name i = name.find("[") # parametrization From 211f480e832e5e69b0e063e1d2aee25734c6ecdf Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 08:14:13 +0200 Subject: [PATCH 02/12] Turn `keywords` into a property This improves collection time when keywords are not used, and helps by factoring out the code in general. With `PYTEST_REORDER_TESTS=0` (since the re-ordering triggers getting keywords to add marks to it...): Before: raw times: 1.58 sec, 1.56 sec, 1.57 sec, 1.57 sec, 1.57 sec 1 loop, best of 5: 1.56 sec per loop After: raw times: 1.49 sec, 1.48 sec, 1.48 sec, 1.5 sec, 1.47 sec 1 loop, best of 5: 1.47 sec per loop --- src/_pytest/nodes.py | 12 ++++++--- src/_pytest/python.py | 63 ++++++++++++++++++++++++++----------------- testing/test_mark.py | 55 ++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 67b667335e4..278fbc698a7 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -92,6 +92,8 @@ class Node(metaclass=NodeMeta): """ base class for Collector and Item the test collection tree. Collector subclasses have children, Items are terminal nodes.""" + _keywords = None + def __init__( self, name: str, @@ -126,9 +128,6 @@ def __init__( #: filesystem path where this node was collected from (can be None) self.fspath = fspath or getattr(parent, "fspath", None) # type: py.path.local - #: keywords/markers collected from all scopes - self.keywords = NodeKeywords(self) - #: The (manually added) marks belonging to this node (start, end). self._own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @@ -178,6 +177,13 @@ def own_markers(self) -> List[Mark]: """The marker objects belonging to this node.""" return self._own_markers[0] + self._own_markers[1] + @property + def keywords(self) -> NodeKeywords: + """keywords/markers collected from all scopes.""" + if self._keywords is None: + self._keywords = NodeKeywords(self) + return self._keywords + def __repr__(self): return "<{} nodeid={!r}>".format( self.__class__.__name__, getattr(self, "nodeid", None) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 8312fb37fea..d2002ce3f81 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -51,6 +51,7 @@ from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import Mark from _pytest.mark.structures import normalize_mark_list +from _pytest.nodes import NodeKeywords from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.pathlib import parts @@ -276,6 +277,8 @@ def obj(self): @obj.setter def obj(self, value): self._obj = value + self._obj_markers = None + self._keywords = None def _getobj(self): """Gets the underlying Python object. May be overwritten by subclasses.""" @@ -1415,29 +1418,11 @@ def __init__( if callobj is not NOTSET: self.obj = callobj - self.keywords.update(self.obj.__dict__) + self._callspec = callspec if callspec: - self.callspec = callspec - # this is total hostile and a mess - # keywords are broken by design by now - # this will be redeemed later - for mark in callspec.marks: - # feel free to cry, this was broken for years before - # and keywords cant fix it per design - self.keywords[mark.name] = mark - if keywords: - self.keywords.update(keywords) - - # todo: this is a hell of a hack - # https://github.com/pytest-dev/pytest/issues/4569 - - self.keywords.update( - { - mark.name: True - for mark in self.iter_markers() - if mark.name not in self.keywords - } - ) + # XXX: only set for existing hasattr checks..! + self.callspec = self._callspec + self._keywords_arg = keywords if fixtureinfo is None: fixtureinfo = self.session._fixturemanager.getfixtureinfo( @@ -1473,10 +1458,40 @@ def function(self): def own_markers(self) -> List[Mark]: if self._obj_markers is None: self._obj_markers = get_unpacked_marks(self.obj) - if hasattr(self, "callspec"): - self._obj_markers += normalize_mark_list(self.callspec.marks) + if self._callspec: + self._obj_markers += normalize_mark_list(self._callspec.marks) return self._own_markers[0] + self._obj_markers + self._own_markers[1] + @property + def keywords(self) -> NodeKeywords: + if self._keywords is not None: + return self._keywords + + keywords = super().keywords + keywords.update(self.obj.__dict__) + if self._callspec: + # this is total hostile and a mess + # keywords are broken by design by now + # this will be redeemed later + for mark in self._callspec.marks: + # feel free to cry, this was broken for years before + # and keywords cant fix it per design + self.keywords[mark.name] = mark + if self._keywords_arg: + keywords.update(self._keywords_arg) + + # todo: this is a hell of a hack + # https://github.com/pytest-dev/pytest/issues/4569 + keywords.update( + { + mark.name: True + for mark in self.iter_markers() + if mark.name not in self.keywords + } + ) + self._keywords = keywords + return self._keywords + def _getobj(self): name = self.name i = name.find("[") # parametrization diff --git a/testing/test_mark.py b/testing/test_mark.py index 09b1ab26421..7c0283c35dd 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -4,11 +4,14 @@ import pytest from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.config import ExitCode from _pytest.mark import EMPTY_PARAMETERSET_OPTION from _pytest.mark import MarkGenerator as Mark +from _pytest.mark.structures import NodeKeywords from _pytest.nodes import Collector from _pytest.nodes import Node +from _pytest.python import Function if TYPE_CHECKING: from _pytest.pytester import Testdir @@ -1019,7 +1022,7 @@ def test_3(): assert reprec.countoutcomes() == [3, 0, 0] -def test_addmarker_order(): +def test_addmarker_order(pytestconfig: Config, monkeypatch) -> None: session = mock.Mock() session.own_markers = [] session.parent = None @@ -1032,6 +1035,56 @@ def test_addmarker_order(): extracted = [x.name for x in node.iter_markers()] assert extracted == ["baz", "foo", "bar"] + # Check marks/keywords with Function. + session.name = "session" + session.keywords = NodeKeywords(session) + + # Register markers for `--strict-markers`. + added_markers = pytestconfig._inicache["markers"] + [ + "funcmark", + "prepended", + "funcmark2", + ] + monkeypatch.setitem(pytestconfig._inicache, "markers", added_markers) + + @pytest.mark.funcmark + def f1(): + assert False, "don't call me" + + func = Function.from_parent(node, name="func", callobj=f1) + expected_marks = ["funcmark", "baz", "foo", "bar"] + assert [x.name for x in func.iter_markers()] == expected_marks + func.add_marker("prepended", append=False) + assert [x.name for x in func.iter_markers()] == ["prepended"] + expected_marks + assert set(func.keywords) == { + "Test", + "bar", + "baz", + "foo", + "func", + "funcmark", + "prepended", + "pytestmark", + "session", + } + + # Changing the "obj" updates marks and keywords (lazily). + @pytest.mark.funcmark2 + def f2(): + assert False, "don't call me" + + func.obj = f2 + assert [x.name for x in func.iter_markers()] == [ + "prepended", + "funcmark2", + "baz", + "foo", + "bar", + ] + keywords = set(func.keywords) + assert "funcmark2" in keywords + assert "funcmark" not in keywords + @pytest.mark.filterwarnings("ignore") def test_markers_from_parametrize(testdir): From cbf93a2271410a4a6c8d0a0043c0dc37846074e4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 16:15:02 +0200 Subject: [PATCH 03/12] fixup! Turn `own_markers` into a property --- src/_pytest/python.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d2002ce3f81..2898922ac79 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -263,8 +263,14 @@ class PyobjContext: instance = pyobj_property("Instance") -class PyobjMixin(PyobjContext, nodes.Node): - _obj_markers = None +class PyobjMixin(PyobjContext): + _obj_markers = None # type: Optional[List[Mark]] + + # Function and attributes that the mixin needs (for type-checking only). + if TYPE_CHECKING: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @property def obj(self): @@ -275,7 +281,7 @@ def obj(self): return obj @obj.setter - def obj(self, value): + def obj(self, value) -> None: self._obj = value self._obj_markers = None self._keywords = None From 38736e3e8d48832c10279eb892c504523bb9dc60 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 16:15:19 +0200 Subject: [PATCH 04/12] fixup! Turn `keywords` into a property --- src/_pytest/nodes.py | 2 +- src/_pytest/python.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 278fbc698a7..3b87872ba3d 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -92,7 +92,7 @@ class Node(metaclass=NodeMeta): """ base class for Collector and Item the test collection tree. Collector subclasses have children, Items are terminal nodes.""" - _keywords = None + _keywords = None # type: Optional[NodeKeywords] def __init__( self, diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2898922ac79..e04652709a6 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -268,6 +268,8 @@ class PyobjMixin(PyobjContext): # Function and attributes that the mixin needs (for type-checking only). if TYPE_CHECKING: + _keywords = None # type: Optional[NodeKeywords] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] From 76bee7384841fe6393773c0320c3920091105773 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 16:20:35 +0200 Subject: [PATCH 05/12] fixup! fixup! Turn `own_markers` into a property --- src/_pytest/python.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e04652709a6..f2c4680f626 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -269,10 +269,7 @@ class PyobjMixin(PyobjContext): # Function and attributes that the mixin needs (for type-checking only). if TYPE_CHECKING: _keywords = None # type: Optional[NodeKeywords] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] + _own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @property def obj(self): From 8c495a56f840be271dc67d246658fabe0b11903e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 19:50:03 +0200 Subject: [PATCH 06/12] typing for diff --- src/_pytest/mark/structures.py | 8 ++++++-- src/_pytest/nodes.py | 3 ++- src/_pytest/python.py | 12 ++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index dbb35e5e1c0..cd705cb7d03 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -24,6 +24,10 @@ from typing import Tuple +if TYPE_CHECKING: + from .. import nodes + + def istestfunc(func): return ( hasattr(func, "__call__") @@ -272,7 +276,7 @@ def __call__(self, *args, **kwargs): return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj) -> List[Mark]: +def get_unpacked_marks(obj: object) -> List[Mark]: """ obtain the unpacked marks that are stored on an object """ @@ -368,7 +372,7 @@ def __getattr__(self, name: str) -> MarkDecorator: class NodeKeywords(MutableMapping): - def __init__(self, node): + def __init__(self, node: "nodes.Node") -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 3b87872ba3d..8fc60bc1144 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -3,6 +3,7 @@ from functools import lru_cache from typing import Any from typing import Dict +from typing import Iterator from typing import List from typing import Optional from typing import Set @@ -270,7 +271,7 @@ def add_marker( else: self._own_markers[0].insert(0, marker_.mark) - def iter_markers(self, name=None): + def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: """ :param name: if given, filter the results by the name attribute diff --git a/src/_pytest/python.py b/src/_pytest/python.py index f2c4680f626..b2c30d827f5 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -272,7 +272,7 @@ class PyobjMixin(PyobjContext): _own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @property - def obj(self): + def obj(self) -> object: """Underlying Python object.""" obj = getattr(self, "_obj", None) if obj is None: @@ -280,12 +280,12 @@ def obj(self): return obj @obj.setter - def obj(self, value) -> None: + def obj(self, value: object) -> None: self._obj = value self._obj_markers = None self._keywords = None - def _getobj(self): + def _getobj(self) -> object: """Gets the underlying Python object. May be overwritten by subclasses.""" return getattr(self.parent.obj, self.name) @@ -791,13 +791,13 @@ def hasnew(obj): class CallSpec2: - def __init__(self, metafunc): + def __init__(self, metafunc: "Metafunc") -> None: self.metafunc = metafunc self.funcargs = {} self._idlist = [] self.params = {} self._arg2scopenum = {} # used for sorting parametrized resources - self.marks = [] + self.marks = [] # type: List[Mark] self.indices = {} def copy(self): @@ -1413,7 +1413,7 @@ def __init__( config=None, callspec: Optional[CallSpec2] = None, callobj=NOTSET, - keywords=None, + keywords: Optional[Iterable[str]] = None, session=None, fixtureinfo: Optional[FuncFixtureInfo] = None, originalname=None, From e33227b80812c577bd8d01e5fc3e8abfc023822e Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 22:15:04 +0200 Subject: [PATCH 07/12] back out typing for obj --- src/_pytest/python.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b2c30d827f5..f47247b0a1c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -272,7 +272,7 @@ class PyobjMixin(PyobjContext): _own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @property - def obj(self) -> object: + def obj(self): """Underlying Python object.""" obj = getattr(self, "_obj", None) if obj is None: @@ -280,12 +280,12 @@ def obj(self) -> object: return obj @obj.setter - def obj(self, value: object) -> None: + def obj(self, value) -> None: self._obj = value self._obj_markers = None self._keywords = None - def _getobj(self) -> object: + def _getobj(self): """Gets the underlying Python object. May be overwritten by subclasses.""" return getattr(self.parent.obj, self.name) From 7d3db1378a7811cb3a16f96431de9021ebedc167 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 22:16:51 +0200 Subject: [PATCH 08/12] typing: CallSpec2.__init__ --- src/_pytest/python.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index f47247b0a1c..a4421f8a108 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -793,12 +793,13 @@ def hasnew(obj): class CallSpec2: def __init__(self, metafunc: "Metafunc") -> None: self.metafunc = metafunc - self.funcargs = {} - self._idlist = [] - self.params = {} - self._arg2scopenum = {} # used for sorting parametrized resources + self.funcargs = {} # type: Dict[str, object] + self._idlist = [] # type: List[str] + self.params = {} # type: Dict[str, object] + # Used for sorting parametrized resources. + self._arg2scopenum = {} # type: Dict[str, int] self.marks = [] # type: List[Mark] - self.indices = {} + self.indices = {} # type: Dict[str, int] def copy(self): cs = CallSpec2(self.metafunc) From 74aefd8a6cbe61050415afc94bd8a8a06b7b7aa4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Thu, 9 Apr 2020 22:19:16 +0200 Subject: [PATCH 09/12] fixup! typing for diff --- src/_pytest/python.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index a4421f8a108..45e078aac08 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -12,11 +12,13 @@ from collections.abc import Sequence from functools import partial from types import ModuleType +from typing import Any from typing import Callable from typing import Dict from typing import Iterable from typing import List from typing import Optional +from typing import Mapping from typing import Tuple from typing import Union @@ -1414,7 +1416,7 @@ def __init__( config=None, callspec: Optional[CallSpec2] = None, callobj=NOTSET, - keywords: Optional[Iterable[str]] = None, + keywords: Optional[Mapping[str, Any]] = None, session=None, fixtureinfo: Optional[FuncFixtureInfo] = None, originalname=None, From 87fcf789a8d9f5c8b9c1f491f262b411eed8a6c2 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 10 Apr 2020 08:14:08 +0200 Subject: [PATCH 10/12] typing: python: pytest_pyfunc_call --- src/_pytest/python.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 45e078aac08..6ff314839e7 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -61,6 +61,8 @@ from _pytest.warning_types import PytestUnhandledCoroutineWarning if TYPE_CHECKING: + from typing_extensions import Literal + from _pytest._io import TerminalWriter @@ -183,7 +185,7 @@ def async_warn(pyfuncitem: "Function") -> None: @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: "Function"): +def pytest_pyfunc_call(pyfuncitem: "Function") -> "Literal[True]": testfunction = pyfuncitem.obj if iscoroutinefunction(testfunction) or ( sys.version_info >= (3, 6) and inspect.isasyncgenfunction(testfunction) From d3405e218db2457e87efabeeeb05384728c99409 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 10 Apr 2020 09:10:46 +0200 Subject: [PATCH 11/12] fixup! Turn `keywords` into a property --- src/_pytest/python.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 6ff314839e7..b0bde32f0f3 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -53,7 +53,6 @@ from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import Mark from _pytest.mark.structures import normalize_mark_list -from _pytest.nodes import NodeKeywords from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.pathlib import parts @@ -272,7 +271,7 @@ class PyobjMixin(PyobjContext): # Function and attributes that the mixin needs (for type-checking only). if TYPE_CHECKING: - _keywords = None # type: Optional[NodeKeywords] + _keywords = None # type: Optional[nodes.NodeKeywords] _own_markers = ([], []) # type: Tuple[List[Mark], List[Mark]] @property @@ -1473,7 +1472,7 @@ def own_markers(self) -> List[Mark]: return self._own_markers[0] + self._obj_markers + self._own_markers[1] @property - def keywords(self) -> NodeKeywords: + def keywords(self) -> "nodes.NodeKeywords": if self._keywords is not None: return self._keywords From fce7031ce5949714811a33dca76bb62d18ac785a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Tue, 30 Jun 2020 22:49:15 +0200 Subject: [PATCH 12/12] fixup! fixup! typing for diff --- src/_pytest/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b0bde32f0f3..84af0f44cd7 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -17,8 +17,8 @@ from typing import Dict from typing import Iterable from typing import List -from typing import Optional from typing import Mapping +from typing import Optional from typing import Tuple from typing import Union