Type annotate pytest.fixture and more improvements to _pytest.fixtures
This commit is contained in:
parent
8bcf1d6de1
commit
2833884688
|
@ -10,6 +10,8 @@ from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from typing import Generator
|
||||||
|
from typing import Generic
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
|
@ -17,6 +19,7 @@ from typing import Optional
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
@ -37,6 +40,7 @@ from _pytest.compat import getlocation
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import order_preserving_dict
|
from _pytest.compat import order_preserving_dict
|
||||||
|
from _pytest.compat import overload
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.config import _PluggyPlugin
|
from _pytest.config import _PluggyPlugin
|
||||||
|
@ -64,13 +68,30 @@ if TYPE_CHECKING:
|
||||||
_Scope = Literal["session", "package", "module", "class", "function"]
|
_Scope = Literal["session", "package", "module", "class", "function"]
|
||||||
|
|
||||||
|
|
||||||
_FixtureCachedResult = Tuple[
|
# The value of the fixture -- return/yield of the fixture function (type variable).
|
||||||
|
_FixtureValue = TypeVar("_FixtureValue")
|
||||||
|
# The type of the fixture function (type variable).
|
||||||
|
_FixtureFunction = TypeVar("_FixtureFunction", bound=Callable[..., object])
|
||||||
|
# The type of a fixture function (type alias generic in fixture value).
|
||||||
|
_FixtureFunc = Union[
|
||||||
|
Callable[..., _FixtureValue], Callable[..., Generator[_FixtureValue, None, None]]
|
||||||
|
]
|
||||||
|
# The type of FixtureDef.cached_result (type alias generic in fixture value).
|
||||||
|
_FixtureCachedResult = Union[
|
||||||
|
Tuple[
|
||||||
# The result.
|
# The result.
|
||||||
Optional[object],
|
_FixtureValue,
|
||||||
|
# Cache key.
|
||||||
|
object,
|
||||||
|
None,
|
||||||
|
],
|
||||||
|
Tuple[
|
||||||
|
None,
|
||||||
# Cache key.
|
# Cache key.
|
||||||
object,
|
object,
|
||||||
# Exc info if raised.
|
# Exc info if raised.
|
||||||
Optional[Tuple["Type[BaseException]", BaseException, TracebackType]],
|
Tuple["Type[BaseException]", BaseException, TracebackType],
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -871,9 +892,13 @@ def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":
|
||||||
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
|
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
|
||||||
|
|
||||||
|
|
||||||
def call_fixture_func(fixturefunc, request: FixtureRequest, kwargs):
|
def call_fixture_func(
|
||||||
yieldctx = is_generator(fixturefunc)
|
fixturefunc: "_FixtureFunc[_FixtureValue]", request: FixtureRequest, kwargs
|
||||||
if yieldctx:
|
) -> _FixtureValue:
|
||||||
|
if is_generator(fixturefunc):
|
||||||
|
fixturefunc = cast(
|
||||||
|
Callable[..., Generator[_FixtureValue, None, None]], fixturefunc
|
||||||
|
)
|
||||||
generator = fixturefunc(**kwargs)
|
generator = fixturefunc(**kwargs)
|
||||||
try:
|
try:
|
||||||
fixture_result = next(generator)
|
fixture_result = next(generator)
|
||||||
|
@ -884,6 +909,7 @@ def call_fixture_func(fixturefunc, request: FixtureRequest, kwargs):
|
||||||
finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
|
finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
|
||||||
request.addfinalizer(finalizer)
|
request.addfinalizer(finalizer)
|
||||||
else:
|
else:
|
||||||
|
fixturefunc = cast(Callable[..., _FixtureValue], fixturefunc)
|
||||||
fixture_result = fixturefunc(**kwargs)
|
fixture_result = fixturefunc(**kwargs)
|
||||||
return fixture_result
|
return fixture_result
|
||||||
|
|
||||||
|
@ -926,7 +952,7 @@ def _eval_scope_callable(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class FixtureDef:
|
class FixtureDef(Generic[_FixtureValue]):
|
||||||
""" A container for a factory definition. """
|
""" A container for a factory definition. """
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -934,7 +960,7 @@ class FixtureDef:
|
||||||
fixturemanager: "FixtureManager",
|
fixturemanager: "FixtureManager",
|
||||||
baseid,
|
baseid,
|
||||||
argname: str,
|
argname: str,
|
||||||
func,
|
func: "_FixtureFunc[_FixtureValue]",
|
||||||
scope: "Union[_Scope, Callable[[str, Config], _Scope]]",
|
scope: "Union[_Scope, Callable[[str, Config], _Scope]]",
|
||||||
params: Optional[Sequence[object]],
|
params: Optional[Sequence[object]],
|
||||||
unittest: bool = False,
|
unittest: bool = False,
|
||||||
|
@ -966,7 +992,7 @@ class FixtureDef:
|
||||||
) # type: Tuple[str, ...]
|
) # type: Tuple[str, ...]
|
||||||
self.unittest = unittest
|
self.unittest = unittest
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
self.cached_result = None # type: Optional[_FixtureCachedResult]
|
self.cached_result = None # type: Optional[_FixtureCachedResult[_FixtureValue]]
|
||||||
self._finalizers = [] # type: List[Callable[[], object]]
|
self._finalizers = [] # type: List[Callable[[], object]]
|
||||||
|
|
||||||
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||||
|
@ -996,7 +1022,7 @@ class FixtureDef:
|
||||||
self.cached_result = None
|
self.cached_result = None
|
||||||
self._finalizers = []
|
self._finalizers = []
|
||||||
|
|
||||||
def execute(self, request: SubRequest):
|
def execute(self, request: SubRequest) -> _FixtureValue:
|
||||||
# get required arguments and register our own finish()
|
# get required arguments and register our own finish()
|
||||||
# with their finalization
|
# with their finalization
|
||||||
for argname in self.argnames:
|
for argname in self.argnames:
|
||||||
|
@ -1008,14 +1034,15 @@ class FixtureDef:
|
||||||
|
|
||||||
my_cache_key = self.cache_key(request)
|
my_cache_key = self.cache_key(request)
|
||||||
if self.cached_result is not None:
|
if self.cached_result is not None:
|
||||||
result, cache_key, err = self.cached_result
|
|
||||||
# note: comparison with `==` can fail (or be expensive) for e.g.
|
# note: comparison with `==` can fail (or be expensive) for e.g.
|
||||||
# numpy arrays (#6497)
|
# numpy arrays (#6497)
|
||||||
|
cache_key = self.cached_result[1]
|
||||||
if my_cache_key is cache_key:
|
if my_cache_key is cache_key:
|
||||||
if err is not None:
|
if self.cached_result[2] is not None:
|
||||||
_, val, tb = err
|
_, val, tb = self.cached_result[2]
|
||||||
raise val.with_traceback(tb)
|
raise val.with_traceback(tb)
|
||||||
else:
|
else:
|
||||||
|
result = self.cached_result[0]
|
||||||
return result
|
return result
|
||||||
# we have a previous but differently parametrized fixture instance
|
# we have a previous but differently parametrized fixture instance
|
||||||
# so we need to tear it down before creating a new one
|
# so we need to tear it down before creating a new one
|
||||||
|
@ -1023,7 +1050,8 @@ class FixtureDef:
|
||||||
assert self.cached_result is None
|
assert self.cached_result is None
|
||||||
|
|
||||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
result = hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||||
|
return result
|
||||||
|
|
||||||
def cache_key(self, request: SubRequest) -> object:
|
def cache_key(self, request: SubRequest) -> object:
|
||||||
return request.param_index if not hasattr(request, "param") else request.param
|
return request.param_index if not hasattr(request, "param") else request.param
|
||||||
|
@ -1034,7 +1062,9 @@ class FixtureDef:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def resolve_fixture_function(fixturedef: FixtureDef, request: FixtureRequest):
|
def resolve_fixture_function(
|
||||||
|
fixturedef: FixtureDef[_FixtureValue], request: FixtureRequest
|
||||||
|
) -> "_FixtureFunc[_FixtureValue]":
|
||||||
"""Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
|
"""Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific
|
||||||
instances and bound methods.
|
instances and bound methods.
|
||||||
"""
|
"""
|
||||||
|
@ -1042,7 +1072,7 @@ def resolve_fixture_function(fixturedef: FixtureDef, request: FixtureRequest):
|
||||||
if fixturedef.unittest:
|
if fixturedef.unittest:
|
||||||
if request.instance is not None:
|
if request.instance is not None:
|
||||||
# bind the unbound method to the TestCase instance
|
# bind the unbound method to the TestCase instance
|
||||||
fixturefunc = fixturedef.func.__get__(request.instance)
|
fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] # noqa: F821
|
||||||
else:
|
else:
|
||||||
# the fixture function needs to be bound to the actual
|
# the fixture function needs to be bound to the actual
|
||||||
# request.instance so that code working with "fixturedef" behaves
|
# request.instance so that code working with "fixturedef" behaves
|
||||||
|
@ -1051,16 +1081,18 @@ def resolve_fixture_function(fixturedef: FixtureDef, request: FixtureRequest):
|
||||||
# handle the case where fixture is defined not in a test class, but some other class
|
# 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
|
# (for example a plugin class with a fixture), see #2270
|
||||||
if hasattr(fixturefunc, "__self__") and not isinstance(
|
if hasattr(fixturefunc, "__self__") and not isinstance(
|
||||||
request.instance, fixturefunc.__self__.__class__
|
request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] # noqa: F821
|
||||||
):
|
):
|
||||||
return fixturefunc
|
return fixturefunc
|
||||||
fixturefunc = getimfunc(fixturedef.func)
|
fixturefunc = getimfunc(fixturedef.func)
|
||||||
if fixturefunc != fixturedef.func:
|
if fixturefunc != fixturedef.func:
|
||||||
fixturefunc = fixturefunc.__get__(request.instance)
|
fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] # noqa: F821
|
||||||
return fixturefunc
|
return fixturefunc
|
||||||
|
|
||||||
|
|
||||||
def pytest_fixture_setup(fixturedef: FixtureDef, request: SubRequest) -> object:
|
def pytest_fixture_setup(
|
||||||
|
fixturedef: FixtureDef[_FixtureValue], request: SubRequest
|
||||||
|
) -> _FixtureValue:
|
||||||
""" Execution of fixture setup. """
|
""" Execution of fixture setup. """
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
for argname in fixturedef.argnames:
|
for argname in fixturedef.argnames:
|
||||||
|
@ -1146,7 +1178,7 @@ class FixtureFunctionMarker:
|
||||||
)
|
)
|
||||||
name = attr.ib(type=Optional[str], default=None)
|
name = attr.ib(type=Optional[str], default=None)
|
||||||
|
|
||||||
def __call__(self, function):
|
def __call__(self, function: _FixtureFunction) -> _FixtureFunction:
|
||||||
if inspect.isclass(function):
|
if inspect.isclass(function):
|
||||||
raise ValueError("class fixtures not supported (maybe in the future)")
|
raise ValueError("class fixtures not supported (maybe in the future)")
|
||||||
|
|
||||||
|
@ -1166,12 +1198,50 @@ class FixtureFunctionMarker:
|
||||||
),
|
),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
function._pytestfixturefunction = self
|
|
||||||
|
# Type ignored because https://github.com/python/mypy/issues/2087.
|
||||||
|
function._pytestfixturefunction = self # type: ignore[attr-defined] # noqa: F821
|
||||||
return function
|
return function
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
def fixture(
|
def fixture(
|
||||||
fixture_function=None,
|
fixture_function: _FixtureFunction,
|
||||||
|
*,
|
||||||
|
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,
|
||||||
|
params: Optional[Iterable[object]] = ...,
|
||||||
|
autouse: bool = ...,
|
||||||
|
ids: Optional[
|
||||||
|
Union[
|
||||||
|
Iterable[Union[None, str, float, int, bool]],
|
||||||
|
Callable[[object], Optional[object]],
|
||||||
|
]
|
||||||
|
] = ...,
|
||||||
|
name: Optional[str] = ...
|
||||||
|
) -> _FixtureFunction:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@overload # noqa: F811
|
||||||
|
def fixture( # noqa: F811
|
||||||
|
fixture_function: None = ...,
|
||||||
|
*,
|
||||||
|
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,
|
||||||
|
params: Optional[Iterable[object]] = ...,
|
||||||
|
autouse: bool = ...,
|
||||||
|
ids: Optional[
|
||||||
|
Union[
|
||||||
|
Iterable[Union[None, str, float, int, bool]],
|
||||||
|
Callable[[object], Optional[object]],
|
||||||
|
]
|
||||||
|
] = ...,
|
||||||
|
name: Optional[str] = None
|
||||||
|
) -> FixtureFunctionMarker:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
def fixture( # noqa: F811
|
||||||
|
fixture_function: Optional[_FixtureFunction] = None,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",
|
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",
|
||||||
params: Optional[Iterable[object]] = None,
|
params: Optional[Iterable[object]] = None,
|
||||||
|
@ -1183,7 +1253,7 @@ def fixture(
|
||||||
]
|
]
|
||||||
] = None,
|
] = None,
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
):
|
) -> Union[FixtureFunctionMarker, _FixtureFunction]:
|
||||||
"""Decorator to mark a fixture factory function.
|
"""Decorator to mark a fixture factory function.
|
||||||
|
|
||||||
This decorator can be used, with or without parameters, to define a
|
This decorator can be used, with or without parameters, to define a
|
||||||
|
@ -1317,7 +1387,7 @@ def yield_fixture(
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
def pytestconfig(request: FixtureRequest):
|
def pytestconfig(request: FixtureRequest) -> Config:
|
||||||
"""Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
|
"""Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
|
@ -3799,7 +3799,7 @@ class TestScopeOrdering:
|
||||||
request = FixtureRequest(items[0])
|
request = FixtureRequest(items[0])
|
||||||
assert request.fixturenames == "m1 f1".split()
|
assert request.fixturenames == "m1 f1".split()
|
||||||
|
|
||||||
def test_func_closure_with_native_fixtures(self, testdir, monkeypatch):
|
def test_func_closure_with_native_fixtures(self, testdir, monkeypatch) -> None:
|
||||||
"""Sanity check that verifies the order returned by the closures and the actual fixture execution order:
|
"""Sanity check that verifies the order returned by the closures and the actual fixture execution order:
|
||||||
The execution order may differ because of fixture inter-dependencies.
|
The execution order may differ because of fixture inter-dependencies.
|
||||||
"""
|
"""
|
||||||
|
@ -3849,9 +3849,8 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
testdir.runpytest()
|
testdir.runpytest()
|
||||||
# actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir")
|
# actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir")
|
||||||
assert (
|
FIXTURE_ORDER = pytest.FIXTURE_ORDER # type: ignore[attr-defined] # noqa: F821
|
||||||
pytest.FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split()
|
assert FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split()
|
||||||
)
|
|
||||||
|
|
||||||
def test_func_closure_module(self, testdir):
|
def test_func_closure_module(self, testdir):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
|
@ -4159,7 +4158,7 @@ def test_fixture_duplicated_arguments() -> None:
|
||||||
"""Raise error if there are positional and keyword arguments for the same parameter (#1682)."""
|
"""Raise error if there are positional and keyword arguments for the same parameter (#1682)."""
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
|
||||||
@pytest.fixture("session", scope="session")
|
@pytest.fixture("session", scope="session") # type: ignore[call-overload] # noqa: F821
|
||||||
def arg(arg):
|
def arg(arg):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -4171,7 +4170,7 @@ def test_fixture_duplicated_arguments() -> None:
|
||||||
|
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
|
||||||
@pytest.fixture(
|
@pytest.fixture( # type: ignore[call-overload] # noqa: F821
|
||||||
"function",
|
"function",
|
||||||
["p1"],
|
["p1"],
|
||||||
True,
|
True,
|
||||||
|
@ -4199,7 +4198,7 @@ def test_fixture_with_positionals() -> None:
|
||||||
|
|
||||||
with pytest.warns(pytest.PytestDeprecationWarning) as warnings:
|
with pytest.warns(pytest.PytestDeprecationWarning) as warnings:
|
||||||
|
|
||||||
@pytest.fixture("function", [0], True)
|
@pytest.fixture("function", [0], True) # type: ignore[call-overload] # noqa: F821
|
||||||
def fixture_with_positionals():
|
def fixture_with_positionals():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -4213,7 +4212,7 @@ def test_fixture_with_positionals() -> None:
|
||||||
def test_fixture_with_too_many_positionals() -> None:
|
def test_fixture_with_too_many_positionals() -> None:
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
|
||||||
@pytest.fixture("function", [0], True, ["id"], "name", "extra")
|
@pytest.fixture("function", [0], True, ["id"], "name", "extra") # type: ignore[call-overload] # noqa: F821
|
||||||
def fixture_with_positionals():
|
def fixture_with_positionals():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue