Skip to content

Commit 4a809d9

Browse files
authored
Merge pull request #14568 from pytest-dev/register-fixture
Add `pytest.register_fixture`
2 parents 5dfa385 + f52ff0c commit 4a809d9

8 files changed

Lines changed: 87 additions & 13 deletions

File tree

changelog/12376.feature.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Added :func:`pytest.register_fixture()` to register fixtures using an imperative interface.
2+
3+
This is an advanced function intended for use by plugins.
4+
5+
Normally, fixtures should be registered declaratively using the :func:`@pytest.fixture <pytest.fixture>` decorator.
6+
Pytest looks for these fixture definitions during the collection phase and registers them automatically.
7+
For some plugin usecases the declarative interface can be cumbersome or unviable, in which case this imperative interface can be used.

doc/en/deprecations.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ node-based matching instead of fragile string prefix matching.
3737
3838
# Use instead
3939
fixture_manager.parsefactories(holder=plugin_obj, node=directory_node)
40-
fixture_manager._register_fixture(name="fix", func=func, node=directory_node)
40+
pytest.register_fixture(name="fix", func=func, node=directory_node)
4141
4242
The equivalent of passing ``nodeid=None`` (global visibility) is ``node=session``.
4343

doc/en/reference/reference.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ pytest.register_assert_rewrite
139139

140140
.. autofunction:: pytest.register_assert_rewrite
141141

142+
pytest.register_fixture
143+
~~~~~~~~~~~~~~~~~~~~~~~
144+
145+
.. autofunction:: pytest.register_fixture
146+
142147
pytest.warns
143148
~~~~~~~~~~~~
144149

src/_pytest/fixtures.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,3 +2318,62 @@ def _showfixtures_main(config: Config, session: Session) -> None:
23182318
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
23192319
for line in doc.split("\n"):
23202320
tw.line(indent + line)
2321+
2322+
2323+
def register_fixture(
2324+
*,
2325+
name: str,
2326+
func: _FixtureFunc[object],
2327+
node: nodes.Node,
2328+
scope: ScopeName | Callable[[str, Config], ScopeName] = "function",
2329+
params: Sequence[object] | None = None,
2330+
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
2331+
autouse: bool = False,
2332+
) -> None:
2333+
"""Register a fixture imperatively.
2334+
2335+
This is an advanced function intended for use by plugins.
2336+
2337+
Normally, fixtures should be registered declaratively using the
2338+
:func:`@pytest.fixture <pytest.fixture>` decorator. Pytest looks for these
2339+
fixture definitions during the collection phase and registers them
2340+
automatically. For some plugin usecases the declarative interface can be
2341+
cumbersome or nonviable, in which case the imperative interface can be used.
2342+
2343+
Fixture registration is expected to happen during the collection phase, and
2344+
this is the only sanctioned use. However, to allow for more creative uses,
2345+
this is not enforced. But do so at your own risk!
2346+
2347+
.. versionadded: 9.1
2348+
2349+
:param name:
2350+
The fixture's name.
2351+
:param func:
2352+
The fixture's implementation function.
2353+
:param node:
2354+
The visibility of the fixture.
2355+
2356+
Only items that are descendents of this node in the collection tree will
2357+
be able to request this fixture. You can think of this as the place
2358+
where you would put the `@pytest.fixture`.
2359+
2360+
For global visibility, pass the :class:`session <pytest.Session>` node,
2361+
which is the root of the collection tree.
2362+
:param scope:
2363+
The fixture's scope.
2364+
:param params:
2365+
The fixture's parametrization params.
2366+
:param ids:
2367+
The fixture's IDs.
2368+
:param autouse:
2369+
Whether this is an autouse fixture.
2370+
"""
2371+
node.session._fixturemanager._register_fixture(
2372+
name=name,
2373+
func=func,
2374+
node=node,
2375+
scope=scope,
2376+
params=params,
2377+
ids=ids,
2378+
autouse=autouse,
2379+
)

src/_pytest/python.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def xunit_setup_module_fixture(request) -> Generator[None]:
591591
if teardown_module is not None:
592592
_call_with_optional_argument(teardown_module, module)
593593

594-
self.session._fixturemanager._register_fixture(
594+
fixtures.register_fixture(
595595
# Use a unique name to speed up lookup.
596596
name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
597597
func=xunit_setup_module_fixture,
@@ -627,7 +627,7 @@ def xunit_setup_function_fixture(request) -> Generator[None]:
627627
if teardown_function is not None:
628628
_call_with_optional_argument(teardown_function, function)
629629

630-
self.session._fixturemanager._register_fixture(
630+
fixtures.register_fixture(
631631
# Use a unique name to speed up lookup.
632632
name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
633633
func=xunit_setup_function_fixture,
@@ -807,7 +807,7 @@ def xunit_setup_class_fixture(request) -> Generator[None]:
807807
func = getimfunc(teardown_class)
808808
_call_with_optional_argument(func, cls)
809809

810-
self.session._fixturemanager._register_fixture(
810+
fixtures.register_fixture(
811811
# Use a unique name to speed up lookup.
812812
name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
813813
func=xunit_setup_class_fixture,
@@ -841,7 +841,7 @@ def xunit_setup_method_fixture(request) -> Generator[None]:
841841
func = getattr(instance, teardown_name)
842842
_call_with_optional_argument(func, method)
843843

844-
self.session._fixturemanager._register_fixture(
844+
fixtures.register_fixture(
845845
# Use a unique name to speed up lookup.
846846
name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
847847
func=xunit_setup_method_fixture,

src/_pytest/unittest.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import TYPE_CHECKING
1818
from unittest import TestCase
1919

20+
from _pytest import fixtures
2021
import _pytest._code
2122
from _pytest._code import ExceptionInfo
2223
from _pytest.compat import assert_never
@@ -170,7 +171,7 @@ def unittest_setup_class_fixture(
170171
cleanup()
171172
process_teardown_exceptions()
172173

173-
self.session._fixturemanager._register_fixture(
174+
fixtures.register_fixture(
174175
# Use a unique name to speed up lookup.
175176
name=f"_unittest_setUpClass_fixture_{cls.__qualname__}",
176177
func=unittest_setup_class_fixture,
@@ -187,7 +188,7 @@ def unittest_skip_fixture(request: FixtureRequest) -> None:
187188
reason = getattr(cls, "__unittest_skip_why__", "")
188189
raise skip.Exception(reason, _use_item_location=True)
189190

190-
self.session._fixturemanager._register_fixture(
191+
fixtures.register_fixture(
191192
name=f"_unittest_skip_fixture_{cls.__qualname__}",
192193
func=unittest_skip_fixture,
193194
node=self,
@@ -216,7 +217,7 @@ def unittest_setup_method_fixture(
216217
if teardown is not None:
217218
teardown(self, request.function)
218219

219-
self.session._fixturemanager._register_fixture(
220+
fixtures.register_fixture(
220221
# Use a unique name to speed up lookup.
221222
name=f"_unittest_setup_method_fixture_{cls.__qualname__}",
222223
func=unittest_setup_method_fixture,

src/pytest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from _pytest.fixtures import FixtureDef
2727
from _pytest.fixtures import FixtureLookupError
2828
from _pytest.fixtures import FixtureRequest
29+
from _pytest.fixtures import register_fixture
2930
from _pytest.fixtures import yield_fixture # type: ignore[deprecated]
3031
from _pytest.freeze_support import freeze_includes
3132
from _pytest.legacypath import TempdirFactory
@@ -177,6 +178,7 @@
177178
"param",
178179
"raises",
179180
"register_assert_rewrite",
181+
"register_fixture",
180182
"set_trace",
181183
"skip",
182184
"version_tuple",

testing/python/fixtures.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1914,15 +1914,15 @@ def test_register_fixture_ordered_by_visibility(self, pytester: Pytester) -> Non
19141914
@pytest.hookimpl(wrapper=True)
19151915
def pytest_collection(session):
19161916
result = yield
1917-
fm = session._fixturemanager
19181917
item = session.items[0]
1919-
fm._register_fixture(name="fix", func=lambda: "session1", node=session)
1918+
pytest.register_fixture(name="fix", func=lambda: "session1", node=session)
19201919
# For coverage; can be removed once nodeid= deprecation is over.
1920+
fm = session._fixturemanager
19211921
fm._register_fixture(name="fix", func=lambda: "session-legacy", nodeid="")
19221922
fm._register_fixture(name="fix", func=lambda: "broken-legacy", nodeid="broken")
1923-
fm._register_fixture(name="fix", func=lambda fix: f"item1-{fix}", node=item)
1924-
fm._register_fixture(name="fix", func=lambda fix: f"item2-{fix}", node=item)
1925-
fm._register_fixture(name="fix", func=lambda: "session2", node=session)
1923+
pytest.register_fixture(name="fix", func=lambda fix: f"item1-{fix}", node=item)
1924+
pytest.register_fixture(name="fix", func=lambda fix: f"item2-{fix}", node=item)
1925+
pytest.register_fixture(name="fix", func=lambda: "session2", node=session)
19261926
return result
19271927
"""
19281928
)

0 commit comments

Comments
 (0)