From 7967b2e710b5d35a56bb9198410a3987c7f9bb59 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jul 2023 00:43:45 +0300 Subject: [PATCH 1/7] fixtures: change a lambda to partial It makes for a more debuggable repr. Before: . at 0x7fe4ae32d440> After: functools.partial(>, request=>) --- src/_pytest/fixtures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 6e1134928..319c2a595 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -698,7 +698,8 @@ class FixtureRequest: self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" ) -> None: # If fixture function failed it might have registered finalizers. - subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest)) + finalizer = functools.partial(fixturedef.finish, request=subrequest) + subrequest.node.addfinalizer(finalizer) def _check_scope( self, From 2c80de532f6501090ccd08a71dbc504af51f3991 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jul 2023 01:20:01 +0300 Subject: [PATCH 2/7] fixtures: replace a `startswith("conftest.py")` with `== "conftest.py"` I can't imagine why we would want to test for a prefix here. --- 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 319c2a595..04c4c85d3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1471,7 +1471,7 @@ class FixtureManager: # Construct the base nodeid which is later used to check # what fixtures are visible for particular tests (as denoted # by their test id). - if p.name.startswith("conftest.py"): + if p.name == "conftest.py": try: nodeid = str(p.parent.relative_to(self.config.rootpath)) except ValueError: From ecfab4dc8b6ea33fef7a8f4880cf8e745679bc15 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jul 2023 23:55:00 +0300 Subject: [PATCH 3/7] fixtures: fix a typing ignore TODO From understanding the code better I see this is the correct fix. The fixturedefs can be None if `request.getfixturevalue("doesnotexist")` is used. In practice there is no change in behavior because this mapping is used as `self._arg2fixturedefs.get(argname, None)` which ends up the same. --- 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 04c4c85d3..41a6504cc 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -464,12 +464,13 @@ class FixtureRequest: assert self._pyfuncitem.parent is not None parentid = self._pyfuncitem.parent.nodeid fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) - # TODO: Fix this type ignore. Either add assert or adjust types. - # Can this be None here? - self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] + if fixturedefs is not None: + self._arg2fixturedefs[argname] = fixturedefs + if fixturedefs is None: + raise FixtureLookupError(argname, self) # fixturedefs list is immutable so we maintain a decreasing index. index = self._arg2index.get(argname, 0) - 1 - if fixturedefs is None or (-index > len(fixturedefs)): + if -index > len(fixturedefs): raise FixtureLookupError(argname, self) self._arg2index[argname] = index return fixturedefs[index] From 01f38aca440f0fd2a0526c08441e419ee8f34880 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jul 2023 14:50:25 +0300 Subject: [PATCH 4/7] fixtures: expand comments and annotations on fixture internals --- src/_pytest/compat.py | 2 +- src/_pytest/doctest.py | 2 +- src/_pytest/fixtures.py | 106 +++++++++++++++++++++++++------------ testing/python/fixtures.py | 9 ++++ testing/test_legacypath.py | 1 + 5 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 998f540f3..47ffe47e8 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -68,7 +68,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: str | None = None) -> str: +def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 455ad62cc..a37f1d2ac 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -582,7 +582,7 @@ def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] node=doctest_item, func=func, cls=None, funcargs=False ) - fixture_request = FixtureRequest(doctest_item, _ispytest=True) + fixture_request = FixtureRequest(doctest_item, _ispytest=True) # type: ignore[arg-type] fixture_request._fillfixtures() return fixture_request diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 41a6504cc..3ffbc752a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -13,6 +13,7 @@ from typing import Any from typing import Callable from typing import cast from typing import Dict +from typing import Final from typing import final from typing import Generator from typing import Generic @@ -73,6 +74,7 @@ if TYPE_CHECKING: from _pytest.scope import _ScopeName from _pytest.main import Session from _pytest.python import CallSpec2 + from _pytest.python import Function from _pytest.python import Metafunc @@ -352,17 +354,24 @@ def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: return request.param -@dataclasses.dataclass +@dataclasses.dataclass(frozen=True) class FuncFixtureInfo: __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") - # Original function argument names. + # Original function argument names, i.e. fixture names that the function + # requests directly. argnames: Tuple[str, ...] - # Argnames that function immediately requires. These include argnames + - # fixture names specified via usefixtures and via autouse=True in fixture - # definitions. + # Fixture names that the function immediately requires. These include + # argnames + fixture names specified via usefixtures and via autouse=True in + # fixture definitions. initialnames: Tuple[str, ...] + # The transitive closure of the fixture names that the function requires. + # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). names_closure: List[str] + # A map from a fixture name in the transitive closure to the FixtureDefs + # matching the name which are applicable to this function. + # There may be multiple overriding fixtures with the same name. The + # sequence is ordered from furthest to closes to the function. name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] def prune_dependency_tree(self) -> None: @@ -401,17 +410,31 @@ class FixtureRequest: indirectly. """ - def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: + def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._pyfuncitem = pyfuncitem #: Fixture for which this request is being performed. self.fixturename: Optional[str] = None + self._pyfuncitem = pyfuncitem + self._fixturemanager = pyfuncitem.session._fixturemanager self._scope = Scope.Function - self._fixture_defs: Dict[str, FixtureDef[Any]] = {} - fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo - self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() + # The FixtureDefs for each fixture name requested by this item. + # Starts from the statically-known fixturedefs resolved during + # collection. Dynamically requested fixtures (using + # `request.getfixturevalue("foo")`) are added dynamically. + self._arg2fixturedefs = pyfuncitem._fixtureinfo.name2fixturedefs.copy() + # A fixture may override another fixture with the same name, e.g. a fixture + # in a module can override a fixture in a conftest, a fixture in a class can + # override a fixture in the module, and so on. + # An overriding fixture can request its own name; in this case it gets + # the value of the fixture it overrides, one level up. + # The _arg2index state keeps the current depth in the overriding chain. + # The fixturedefs list in _arg2fixturedefs for a given name is ordered from + # furthest to closest, so we use negative indexing -1, -2, ... to go from + # last to first. self._arg2index: Dict[str, int] = {} - self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager + # The evaluated argnames so far, mapping to the FixtureDef they resolved + # to. + self._fixture_defs: Dict[str, FixtureDef[Any]] = {} # Notes on the type of `param`: # -`request.param` is only defined in parametrized fixtures, and will raise # AttributeError otherwise. Python typing has no notion of "undefined", so @@ -466,10 +489,14 @@ class FixtureRequest: fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) if fixturedefs is not None: self._arg2fixturedefs[argname] = fixturedefs + # No fixtures defined with this name. if fixturedefs is None: raise FixtureLookupError(argname, self) - # fixturedefs list is immutable so we maintain a decreasing index. + # The are no fixtures with this name applicable for the function. + if not fixturedefs: + raise FixtureLookupError(argname, self) index = self._arg2index.get(argname, 0) - 1 + # The fixture requested its own name, but no remaining to override. if -index > len(fixturedefs): raise FixtureLookupError(argname, self) self._arg2index[argname] = index @@ -503,7 +530,7 @@ class FixtureRequest: """Instance (can be None) on which test function was collected.""" # unittest support hack, see _pytest.unittest.TestCaseFunction. try: - return self._pyfuncitem._testcase + return self._pyfuncitem._testcase # type: ignore[attr-defined] except AttributeError: function = getattr(self, "function", None) return getattr(function, "__self__", None) @@ -513,7 +540,9 @@ class FixtureRequest: """Python module object where the test function was collected.""" if self.scope not in ("function", "class", "module"): raise AttributeError(f"module not available in {self.scope}-scoped context") - return self._pyfuncitem.getparent(_pytest.python.Module).obj + mod = self._pyfuncitem.getparent(_pytest.python.Module) + assert mod is not None + return mod.obj @property def path(self) -> Path: @@ -829,7 +858,9 @@ class FixtureLookupError(LookupError): if msg is None: fm = self.request._fixturemanager available = set() - parentid = self.request._pyfuncitem.parent.nodeid + parent = self.request._pyfuncitem.parent + assert parent is not None + parentid = parent.nodeid for name, fixturedefs in fm._arg2fixturedefs.items(): faclist = list(fm._matchfactories(fixturedefs, parentid)) if faclist: @@ -976,15 +1007,15 @@ class FixtureDef(Generic[FixtureValue]): # directory path relative to the rootdir. # # For other plugins, the baseid is the empty string (always matches). - self.baseid = baseid or "" + self.baseid: Final = baseid or "" # Whether the fixture was found from a node or a conftest in the # collection tree. Will be false for fixtures defined in non-conftest # plugins. - self.has_location = baseid is not None + self.has_location: Final = baseid is not None # The fixture factory function. - self.func = func + self.func: Final = func # The name by which the fixture may be requested. - self.argname = argname + self.argname: Final = argname if scope is None: scope = Scope.Function elif callable(scope): @@ -993,23 +1024,23 @@ class FixtureDef(Generic[FixtureValue]): scope = Scope.from_user( scope, descr=f"Fixture '{func.__name__}'", where=baseid ) - self._scope = scope + self._scope: Final = scope # If the fixture is directly parametrized, the parameter values. - self.params: Optional[Sequence[object]] = params + self.params: Final = params # If the fixture is directly parametrized, a tuple of explicit IDs to # assign to the parameter values, or a callable to generate an ID given # a parameter value. - self.ids = ids + self.ids: Final = ids # The names requested by the fixtures. - self.argnames = getfuncargnames(func, name=argname, is_method=unittest) + self.argnames: Final = getfuncargnames(func, name=argname, is_method=unittest) # Whether the fixture was collected from a unittest TestCase class. # Note that it really only makes sense to define autouse fixtures in # unittest TestCases. - self.unittest = unittest + self.unittest: Final = unittest # If the fixture was executed, the current value of the fixture. # Can change if the fixture is executed with different parameters. self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: List[Callable[[], object]] = [] + self._finalizers: Final[List[Callable[[], object]]] = [] @property def scope(self) -> "_ScopeName": @@ -1040,7 +1071,7 @@ class FixtureDef(Generic[FixtureValue]): # value and remove all finalizers because they may be bound methods # which will keep instances alive. self.cached_result = None - self._finalizers = [] + self._finalizers.clear() def execute(self, request: SubRequest) -> FixtureValue: # Get required arguments and register our own finish() @@ -1417,10 +1448,14 @@ class FixtureManager: def __init__(self, session: "Session") -> None: self.session = session self.config: Config = session.config - self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {} - self._holderobjseen: Set[object] = set() + # Maps a fixture name (argname) to all of the FixtureDefs in the test + # suite/plugins defined with this name. Populated by parsefactories(). + # TODO: The order of the FixtureDefs list of each arg is significant, + # explain. + self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[Set[object]] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Dict[str, List[str]] = { + self._nodeid_autousenames: Final[Dict[str, List[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") @@ -1699,11 +1734,16 @@ class FixtureManager: def getfixturedefs( self, argname: str, nodeid: str ) -> Optional[Sequence[FixtureDef[Any]]]: - """Get a list of fixtures which are applicable to the given node id. + """Get FixtureDefs for a fixture name which are applicable + to a given node. - :param str argname: Name of the fixture to search for. - :param str nodeid: Full node id of the requesting test. - :rtype: Sequence[FixtureDef] + Returns None if there are no fixtures at all defined with the given + name. (This is different from the case in which there are fixtures + with the given name, but none applicable to the node. In this case, + an empty result is returned). + + :param argname: Name of the fixture to search for. + :param nodeid: Full node id of the requesting test. """ try: fixturedefs = self._arg2fixturedefs[argname] diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index e62db8c26..63c18456a 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -699,6 +699,7 @@ class TestRequestBasic: """ ) (item1,) = pytester.genitems([modcol]) + assert isinstance(item1, Function) assert item1.name == "test_method" arg2fixturedefs = fixtures.FixtureRequest( item1, _ispytest=True @@ -967,6 +968,7 @@ class TestRequestBasic: def test_request_getmodulepath(self, pytester: Pytester) -> None: modcol = pytester.getmodulecol("def test_somefunc(): pass") (item,) = pytester.genitems([modcol]) + assert isinstance(item, Function) req = fixtures.FixtureRequest(item, _ispytest=True) assert req.path == modcol.path @@ -1125,6 +1127,7 @@ class TestRequestMarking: pass """ ) + assert isinstance(item1, Function) req1 = fixtures.FixtureRequest(item1, _ispytest=True) assert "xfail" not in item1.keywords req1.applymarker(pytest.mark.xfail) @@ -4009,6 +4012,7 @@ class TestScopeOrdering: """ ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() @@ -4057,6 +4061,7 @@ class TestScopeOrdering: """ ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) # order of fixtures based on their scope and position in the parameter list assert ( @@ -4084,6 +4089,7 @@ class TestScopeOrdering: """ ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "m1 f1".split() @@ -4117,6 +4123,7 @@ class TestScopeOrdering: """ ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 m1 c1 f2 f1".split() @@ -4159,6 +4166,7 @@ class TestScopeOrdering: } ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split() @@ -4203,6 +4211,7 @@ class TestScopeOrdering: """ ) items, _ = pytester.inline_genitems() + assert isinstance(items[0], Function) request = FixtureRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index fbfd88b73..b32036d14 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -90,6 +90,7 @@ def test_cache_makedir(cache: pytest.Cache) -> None: def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None: modcol = pytester.getmodulecol("def test_somefunc(): pass") (item,) = pytester.genitems([modcol]) + assert isinstance(item, pytest.Function) req = pytest.FixtureRequest(item, _ispytest=True) assert req.path == modcol.path assert req.fspath == modcol.fspath # type: ignore[attr-defined] From 9d0ddb462517fc6c4f6dc7e0f1e14af2e08aeed9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 14 Jul 2023 10:35:18 +0300 Subject: [PATCH 5/7] fixtures: change `FixtureDef.cached_result[2]` from exception triplet to exception Fix #11208. --- changelog/11208.trivial.rst | 2 ++ src/_pytest/fixtures.py | 27 +++++++++++---------------- 2 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 changelog/11208.trivial.rst diff --git a/changelog/11208.trivial.rst b/changelog/11208.trivial.rst new file mode 100644 index 000000000..fced57b20 --- /dev/null +++ b/changelog/11208.trivial.rst @@ -0,0 +1,2 @@ +The (internal) ``FixtureDef.cached_result`` type has changed. +Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 3ffbc752a..6a2638669 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -2,13 +2,11 @@ import dataclasses import functools import inspect import os -import sys import warnings from collections import defaultdict from collections import deque from contextlib import suppress from pathlib import Path -from types import TracebackType from typing import Any from typing import Callable from typing import cast @@ -27,7 +25,6 @@ from typing import overload from typing import Sequence from typing import Set from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -99,8 +96,8 @@ _FixtureCachedResult = Union[ None, # Cache key. object, - # Exc info if raised. - Tuple[Type[BaseException], BaseException, TracebackType], + # Exception if raised. + BaseException, ], ] @@ -1085,13 +1082,13 @@ class FixtureDef(Generic[FixtureValue]): my_cache_key = self.cache_key(request) if self.cached_result is not None: + cache_key = self.cached_result[1] # note: comparison with `==` can fail (or be expensive) for e.g. # numpy arrays (#6497). - cache_key = self.cached_result[1] if my_cache_key is cache_key: if self.cached_result[2] is not None: - _, val, tb = self.cached_result[2] - raise val.with_traceback(tb) + exc = self.cached_result[2] + raise exc else: result = self.cached_result[0] return result @@ -1156,14 +1153,12 @@ def pytest_fixture_setup( my_cache_key = fixturedef.cache_key(request) try: result = call_fixture_func(fixturefunc, request, kwargs) - except TEST_OUTCOME: - exc_info = sys.exc_info() - assert exc_info[0] is not None - if isinstance( - exc_info[1], skip.Exception - ) and not fixturefunc.__name__.startswith("xunit_setup"): - exc_info[1]._use_item_location = True # type: ignore[attr-defined] - fixturedef.cached_result = (None, my_cache_key, exc_info) + except TEST_OUTCOME as e: + if isinstance(e, skip.Exception) and not fixturefunc.__name__.startswith( + "xunit_setup" + ): + e._use_item_location = True + fixturedef.cached_result = (None, my_cache_key, e) raise fixturedef.cached_result = (result, my_cache_key, None) return result From fb55615d5e999dd44306596f340036c195428ef1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 14 Jul 2023 10:59:00 +0300 Subject: [PATCH 6/7] Export `FixtureDef` FixtureDef is used in the `pytest_fixture_setup` hook so needs to be public. However since its current internals are quite dubious (and not all marked with `_` prefix) I've added an explicit note that only documented fields/methods are considered public. Refs #7469. --- changelog/7469.feature.rst | 1 + doc/en/reference/reference.rst | 2 +- src/_pytest/fixtures.py | 11 ++++++++++- src/pytest/__init__.py | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 changelog/7469.feature.rst diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst new file mode 100644 index 000000000..8e9df7269 --- /dev/null +++ b/changelog/7469.feature.rst @@ -0,0 +1 @@ +:class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index cf83ae05f..ea008110e 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -910,7 +910,7 @@ ExitCode FixtureDef ~~~~~~~~~~ -.. autoclass:: _pytest.fixtures.FixtureDef() +.. autoclass:: pytest.FixtureDef() :members: :show-inheritance: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 6a2638669..13efca2ad 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -216,6 +216,7 @@ def add_funcarg_pseudo_fixture_def( params=valuelist, unittest=False, ids=None, + _ispytest=True, ) arg2fixturedefs[argname] = [fixturedef] if name2pseudofixturedef is not None: @@ -974,7 +975,11 @@ def _eval_scope_callable( @final class FixtureDef(Generic[FixtureValue]): - """A container for a fixture definition.""" + """A container for a fixture definition. + + Note: At this time, only explicitly documented fields and methods are + considered public stable API. + """ def __init__( self, @@ -988,7 +993,10 @@ class FixtureDef(Generic[FixtureValue]): ids: Optional[ Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] ] = None, + *, + _ispytest: bool = False, ) -> None: + check_ispytest(_ispytest) self._fixturemanager = fixturemanager # The "base" node ID for the fixture. # @@ -1708,6 +1716,7 @@ class FixtureManager: params=marker.params, unittest=unittest, ids=marker.ids, + _ispytest=True, ) faclist = self._arg2fixturedefs.setdefault(name, []) diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 58ddb3288..831ede1fa 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -20,6 +20,7 @@ from _pytest.config.argparsing import Parser from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.doctest import DoctestItem from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureLookupError from _pytest.fixtures import FixtureRequest from _pytest.fixtures import yield_fixture @@ -102,6 +103,7 @@ __all__ = [ "fail", "File", "fixture", + "FixtureDef", "FixtureLookupError", "FixtureRequest", "freeze_includes", From 40ed678885a0be2267f0719bb40b185ee94ba353 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 14 Jul 2023 11:07:42 +0300 Subject: [PATCH 7/7] fixtures: remove two unused functions Not used since 310b67b2271cb05f575054c1cdd2ece2412c89a2. --- src/_pytest/fixtures.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 13efca2ad..1e472cda6 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1172,22 +1172,6 @@ def pytest_fixture_setup( return result -def _ensure_immutable_ids( - ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]] -) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]: - if ids is None: - return None - if callable(ids): - return ids - return tuple(ids) - - -def _params_converter( - params: Optional[Iterable[object]], -) -> Optional[Tuple[object, ...]]: - return tuple(params) if params is not None else None - - def wrap_function_to_error_out_if_called_directly( function: FixtureFunction, fixture_marker: "FixtureFunctionMarker",