Skip to content

Commit 5581e77

Browse files
authored
Merge pull request #666 from bluetech/pytest81-fix
Adapt to `getfixturedefs` change in pytest 8.1
2 parents 8a694ff + 32a19ce commit 5581e77

File tree

6 files changed

+83
-9
lines changed

6 files changed

+83
-9
lines changed

CHANGES.rst

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Changelog
33

44
Unreleased
55
----------
6+
- Address compatibility issue with pytest 8.1. `#666 <https://github.com/pytest-dev/pytest-bdd/pull/666>`_
67

78
7.0.1
89
-----

poetry.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ parse = "*"
4040
parse-type = "*"
4141
pytest = ">=6.2.0"
4242
typing-extensions = "*"
43+
packaging = "*"
4344

4445
[tool.poetry.group.dev.dependencies]
4546
tox = ">=4.11.3"

src/pytest_bdd/compat.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Sequence
4+
from importlib.metadata import version
5+
6+
from _pytest.fixtures import FixtureDef, FixtureManager
7+
from _pytest.nodes import Node
8+
from packaging.version import Version
9+
from packaging.version import parse as parse_version
10+
11+
pytest_version = parse_version(version("pytest"))
12+
13+
14+
if pytest_version >= Version("8.1"):
15+
16+
def getfixturedefs(fixturemanager: FixtureManager, fixturename: str, node: Node) -> Sequence[FixtureDef] | None:
17+
return fixturemanager.getfixturedefs(fixturename, node)
18+
19+
else:
20+
21+
def getfixturedefs(fixturemanager: FixtureManager, fixturename: str, node: Node) -> Sequence[FixtureDef] | None:
22+
return fixturemanager.getfixturedefs(fixturename, node.nodeid)

src/pytest_bdd/generation.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from _pytest._io import TerminalWriter
99
from mako.lookup import TemplateLookup
1010

11+
from .compat import getfixturedefs
1112
from .feature import get_features
1213
from .scenario import inject_fixturedefs_for_step, make_python_docstring, make_python_name, make_string_literal
1314
from .steps import get_step_fixture_name
@@ -127,9 +128,9 @@ def _find_step_fixturedef(
127128
fixturemanager: FixtureManager, item: Function, step: Step
128129
) -> Sequence[FixtureDef[Any]] | None:
129130
"""Find step fixturedef."""
130-
with inject_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, nodeid=item.nodeid):
131+
with inject_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, node=item):
131132
bdd_name = get_step_fixture_name(step=step)
132-
return fixturemanager.getfixturedefs(bdd_name, item.nodeid)
133+
return getfixturedefs(fixturemanager, bdd_name, item)
133134

134135

135136
def parse_feature_files(paths: list[str], **kwargs: Any) -> tuple[list[Feature], list[ScenarioTemplate], list[Step]]:

src/pytest_bdd/scenario.py

+55-6
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@
2020

2121
import pytest
2222
from _pytest.fixtures import FixtureDef, FixtureManager, FixtureRequest, call_fixture_func
23-
from _pytest.nodes import iterparentnodeids
2423
from typing_extensions import ParamSpec
2524

2625
from . import exceptions
26+
from .compat import getfixturedefs
2727
from .feature import get_feature, get_features
2828
from .steps import StepFunctionContext, get_step_fixture_name, inject_fixture
2929
from .utils import CONFIG_STACK, get_args, get_caller_module_locals, get_caller_module_path
3030

3131
if TYPE_CHECKING:
3232
from _pytest.mark.structures import ParameterSet
33+
from _pytest.nodes import Node
3334

3435
from .parser import Feature, Scenario, ScenarioTemplate, Step
3536

@@ -43,7 +44,7 @@
4344
ALPHA_REGEX = re.compile(r"^\d+_*")
4445

4546

46-
def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid: str) -> Iterable[FixtureDef[Any]]:
47+
def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node: Node) -> Iterable[FixtureDef[Any]]:
4748
"""Find the fixture defs that can parse a step."""
4849
# happens to be that _arg2fixturedefs is changed during the iteration so we use a copy
4950
fixture_def_by_name = list(fixturemanager._arg2fixturedefs.items())
@@ -60,14 +61,62 @@ def find_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid
6061
if not match:
6162
continue
6263

63-
if fixturedef not in (fixturemanager.getfixturedefs(fixturename, nodeid) or []):
64+
fixturedefs = getfixturedefs(fixturemanager, fixturename, node)
65+
if fixturedef not in (fixturedefs or []):
6466
continue
6567

6668
yield fixturedef
6769

6870

71+
# Function copied from pytest 8.0 (removed in later versions).
72+
def iterparentnodeids(nodeid: str) -> Iterator[str]:
73+
"""Return the parent node IDs of a given node ID, inclusive.
74+
75+
For the node ID
76+
77+
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
78+
79+
the result would be
80+
81+
""
82+
"testing"
83+
"testing/code"
84+
"testing/code/test_excinfo.py"
85+
"testing/code/test_excinfo.py::TestFormattedExcinfo"
86+
"testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
87+
88+
Note that / components are only considered until the first ::.
89+
"""
90+
SEP = "/"
91+
pos = 0
92+
first_colons: Optional[int] = nodeid.find("::")
93+
if first_colons == -1:
94+
first_colons = None
95+
# The root Session node - always present.
96+
yield ""
97+
# Eagerly consume SEP parts until first colons.
98+
while True:
99+
at = nodeid.find(SEP, pos, first_colons)
100+
if at == -1:
101+
break
102+
if at > 0:
103+
yield nodeid[:at]
104+
pos = at + len(SEP)
105+
# Eagerly consume :: parts.
106+
while True:
107+
at = nodeid.find("::", pos)
108+
if at == -1:
109+
break
110+
if at > 0:
111+
yield nodeid[:at]
112+
pos = at + len("::")
113+
# The node ID itself.
114+
if nodeid:
115+
yield nodeid
116+
117+
69118
@contextlib.contextmanager
70-
def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, nodeid: str) -> Iterator[None]:
119+
def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node: Node) -> Iterator[None]:
71120
"""Inject fixture definitions that can parse a step.
72121
73122
We fist iterate over all the fixturedefs that can parse the step.
@@ -78,7 +127,7 @@ def inject_fixturedefs_for_step(step: Step, fixturemanager: FixtureManager, node
78127
"""
79128
bdd_name = get_step_fixture_name(step=step)
80129

81-
fixturedefs = list(find_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, nodeid=nodeid))
130+
fixturedefs = list(find_fixturedefs_for_step(step=step, fixturemanager=fixturemanager, node=node))
82131

83132
# Sort the fixture definitions by their "path", so that the `bdd_name` fixture will
84133
# respect the fixture scope
@@ -114,7 +163,7 @@ def get_step_function(request, step: Step) -> StepFunctionContext | None:
114163
__tracebackhide__ = True
115164
bdd_name = get_step_fixture_name(step=step)
116165

117-
with inject_fixturedefs_for_step(step=step, fixturemanager=request._fixturemanager, nodeid=request.node.nodeid):
166+
with inject_fixturedefs_for_step(step=step, fixturemanager=request._fixturemanager, node=request.node):
118167
try:
119168
return cast(StepFunctionContext, request.getfixturevalue(bdd_name))
120169
except pytest.FixtureLookupError:

0 commit comments

Comments
 (0)