Skip to content

Commit 3128080

Browse files
committed
pytest: bring back direct imports of TempdirFactory, Testdir
The monkeypatch approach doesn't work for `import pytest; pytest.TempdirFactory`. Fix #9432.
1 parent 0fecfff commit 3128080

File tree

2 files changed

+53
-46
lines changed

2 files changed

+53
-46
lines changed

src/_pytest/legacypath.py

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,26 @@
1010
import attr
1111
from iniconfig import SectionWrapper
1212

13-
import pytest
13+
from _pytest.cacheprovider import Cache
1414
from _pytest.compat import final
1515
from _pytest.compat import LEGACY_PATH
1616
from _pytest.compat import legacy_path
17+
from _pytest.config import Config
18+
from _pytest.config import hookimpl
19+
from _pytest.config import PytestPluginManager
1720
from _pytest.deprecated import check_ispytest
21+
from _pytest.fixtures import fixture
22+
from _pytest.fixtures import FixtureRequest
23+
from _pytest.main import Session
24+
from _pytest.monkeypatch import MonkeyPatch
25+
from _pytest.nodes import Collector
26+
from _pytest.nodes import Item
1827
from _pytest.nodes import Node
28+
from _pytest.pytester import HookRecorder
29+
from _pytest.pytester import Pytester
30+
from _pytest.pytester import RunResult
1931
from _pytest.terminal import TerminalReporter
32+
from _pytest.tmpdir import TempPathFactory
2033

2134
if TYPE_CHECKING:
2235
from typing_extensions import Final
@@ -35,10 +48,10 @@ class Testdir:
3548

3649
__test__ = False
3750

38-
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
39-
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
51+
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
52+
TimeoutExpired: "Final" = Pytester.TimeoutExpired
4053

41-
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
54+
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
4255
check_ispytest(_ispytest)
4356
self._pytester = pytester
4457

@@ -64,10 +77,10 @@ def plugins(self, plugins):
6477
self._pytester.plugins = plugins
6578

6679
@property
67-
def monkeypatch(self) -> pytest.MonkeyPatch:
80+
def monkeypatch(self) -> MonkeyPatch:
6881
return self._pytester._monkeypatch
6982

70-
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
83+
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
7184
"""See :meth:`Pytester.make_hook_recorder`."""
7285
return self._pytester.make_hook_recorder(pluginmanager)
7386

@@ -131,19 +144,15 @@ def copy_example(self, name=None) -> LEGACY_PATH:
131144
"""See :meth:`Pytester.copy_example`."""
132145
return legacy_path(self._pytester.copy_example(name))
133146

134-
def getnode(
135-
self, config: pytest.Config, arg
136-
) -> Optional[Union[pytest.Item, pytest.Collector]]:
147+
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
137148
"""See :meth:`Pytester.getnode`."""
138149
return self._pytester.getnode(config, arg)
139150

140151
def getpathnode(self, path):
141152
"""See :meth:`Pytester.getpathnode`."""
142153
return self._pytester.getpathnode(path)
143154

144-
def genitems(
145-
self, colitems: List[Union[pytest.Item, pytest.Collector]]
146-
) -> List[pytest.Item]:
155+
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
147156
"""See :meth:`Pytester.genitems`."""
148157
return self._pytester.genitems(colitems)
149158

@@ -165,19 +174,19 @@ def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
165174
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
166175
)
167176

168-
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
177+
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
169178
"""See :meth:`Pytester.runpytest_inprocess`."""
170179
return self._pytester.runpytest_inprocess(*args, **kwargs)
171180

172-
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
181+
def runpytest(self, *args, **kwargs) -> RunResult:
173182
"""See :meth:`Pytester.runpytest`."""
174183
return self._pytester.runpytest(*args, **kwargs)
175184

176-
def parseconfig(self, *args) -> pytest.Config:
185+
def parseconfig(self, *args) -> Config:
177186
"""See :meth:`Pytester.parseconfig`."""
178187
return self._pytester.parseconfig(*args)
179188

180-
def parseconfigure(self, *args) -> pytest.Config:
189+
def parseconfigure(self, *args) -> Config:
181190
"""See :meth:`Pytester.parseconfigure`."""
182191
return self._pytester.parseconfigure(*args)
183192

@@ -196,8 +205,8 @@ def getmodulecol(self, source, configargs=(), withinit=False):
196205
)
197206

198207
def collect_by_name(
199-
self, modcol: pytest.Collector, name: str
200-
) -> Optional[Union[pytest.Item, pytest.Collector]]:
208+
self, modcol: Collector, name: str
209+
) -> Optional[Union[Item, Collector]]:
201210
"""See :meth:`Pytester.collect_by_name`."""
202211
return self._pytester.collect_by_name(modcol, name)
203212

@@ -212,19 +221,19 @@ def popen(
212221
"""See :meth:`Pytester.popen`."""
213222
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
214223

215-
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
224+
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
216225
"""See :meth:`Pytester.run`."""
217226
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
218227

219-
def runpython(self, script) -> pytest.RunResult:
228+
def runpython(self, script) -> RunResult:
220229
"""See :meth:`Pytester.runpython`."""
221230
return self._pytester.runpython(script)
222231

223232
def runpython_c(self, command):
224233
"""See :meth:`Pytester.runpython_c`."""
225234
return self._pytester.runpython_c(command)
226235

227-
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
236+
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
228237
"""See :meth:`Pytester.runpytest_subprocess`."""
229238
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
230239

@@ -245,13 +254,10 @@ def __str__(self) -> str:
245254
return str(self.tmpdir)
246255

247256

248-
pytest.Testdir = Testdir # type: ignore[attr-defined]
249-
250-
251257
class LegacyTestdirPlugin:
252258
@staticmethod
253-
@pytest.fixture
254-
def testdir(pytester: pytest.Pytester) -> Testdir:
259+
@fixture
260+
def testdir(pytester: Pytester) -> Testdir:
255261
"""
256262
Identical to :fixture:`pytester`, and provides an instance whose methods return
257263
legacy ``LEGACY_PATH`` objects instead when applicable.
@@ -267,10 +273,10 @@ class TempdirFactory:
267273
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
268274
for :class:``TempPathFactory``."""
269275

270-
_tmppath_factory: pytest.TempPathFactory
276+
_tmppath_factory: TempPathFactory
271277

272278
def __init__(
273-
self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False
279+
self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
274280
) -> None:
275281
check_ispytest(_ispytest)
276282
self._tmppath_factory = tmppath_factory
@@ -284,19 +290,16 @@ def getbasetemp(self) -> LEGACY_PATH:
284290
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
285291

286292

287-
pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined]
288-
289-
290293
class LegacyTmpdirPlugin:
291294
@staticmethod
292-
@pytest.fixture(scope="session")
293-
def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory:
295+
@fixture(scope="session")
296+
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
294297
"""Return a :class:`pytest.TempdirFactory` instance for the test session."""
295298
# Set dynamically by pytest_configure().
296299
return request.config._tmpdirhandler # type: ignore
297300

298301
@staticmethod
299-
@pytest.fixture
302+
@fixture
300303
def tmpdir(tmp_path: Path) -> LEGACY_PATH:
301304
"""Return a temporary directory path object which is unique to each test
302305
function invocation, created as a sub directory of the base temporary
@@ -314,15 +317,15 @@ def tmpdir(tmp_path: Path) -> LEGACY_PATH:
314317
return legacy_path(tmp_path)
315318

316319

317-
def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
320+
def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH:
318321
"""Return a directory path object with the given name.
319322
320323
Same as :func:`mkdir`, but returns a legacy py path instance.
321324
"""
322325
return legacy_path(self.mkdir(name))
323326

324327

325-
def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH:
328+
def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH:
326329
"""(deprecated) The file system path of the test module which collected this test."""
327330
return legacy_path(self.path)
328331

@@ -337,7 +340,7 @@ def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
337340
return legacy_path(self.startpath)
338341

339342

340-
def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
343+
def Config_invocation_dir(self: Config) -> LEGACY_PATH:
341344
"""The directory from which pytest was invoked.
342345
343346
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
@@ -348,7 +351,7 @@ def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
348351
return legacy_path(str(self.invocation_params.dir))
349352

350353

351-
def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
354+
def Config_rootdir(self: Config) -> LEGACY_PATH:
352355
"""The path to the :ref:`rootdir <rootdir>`.
353356
354357
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
@@ -358,7 +361,7 @@ def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
358361
return legacy_path(str(self.rootpath))
359362

360363

361-
def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
364+
def Config_inifile(self: Config) -> Optional[LEGACY_PATH]:
362365
"""The path to the :ref:`configfile <configfiles>`.
363366
364367
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
@@ -368,7 +371,7 @@ def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
368371
return legacy_path(str(self.inipath)) if self.inipath else None
369372

370373

371-
def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
374+
def Session_stardir(self: Session) -> LEGACY_PATH:
372375
"""The path from which pytest was invoked.
373376
374377
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
@@ -400,8 +403,10 @@ def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
400403
self.path = Path(value)
401404

402405

403-
@pytest.hookimpl
404-
def pytest_configure(config: pytest.Config) -> None:
406+
@hookimpl
407+
def pytest_configure(config: Config) -> None:
408+
import pytest
409+
405410
mp = pytest.MonkeyPatch()
406411
config.add_cleanup(mp.undo)
407412

@@ -452,10 +457,8 @@ def pytest_configure(config: pytest.Config) -> None:
452457
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
453458

454459

455-
@pytest.hookimpl
456-
def pytest_plugin_registered(
457-
plugin: object, manager: pytest.PytestPluginManager
458-
) -> None:
460+
@hookimpl
461+
def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None:
459462
# pytester is not loaded by default and is commonly loaded from a conftest,
460463
# so checking for it in `pytest_configure` is not enough.
461464
is_pytester = plugin is manager.get_plugin("pytester")

src/pytest/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from _pytest.fixtures import FixtureRequest
2424
from _pytest.fixtures import yield_fixture
2525
from _pytest.freeze_support import freeze_includes
26+
from _pytest.legacypath import TempdirFactory
27+
from _pytest.legacypath import Testdir
2628
from _pytest.logging import LogCaptureFixture
2729
from _pytest.main import Session
2830
from _pytest.mark import Mark
@@ -142,7 +144,9 @@
142144
"Stash",
143145
"StashKey",
144146
"version_tuple",
147+
"TempdirFactory",
145148
"TempPathFactory",
149+
"Testdir",
146150
"TestReport",
147151
"UsageError",
148152
"WarningsRecorder",

0 commit comments

Comments
 (0)