Skip to content

Commit 760a73c

Browse files
authored
Merge pull request #8017 from bluetech/typing-public-fixtures
Export types of builtin fixtures for type annotations
2 parents 954151c + f1e6fdc commit 760a73c

21 files changed

+380
-152
lines changed

changelog/7469.deprecation.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Directly constructing/calling the following classes/functions is now deprecated:
2+
3+
- ``_pytest.cacheprovider.Cache``
4+
- ``_pytest.cacheprovider.Cache.for_config()``
5+
- ``_pytest.cacheprovider.Cache.clear_cache()``
6+
- ``_pytest.cacheprovider.Cache.cache_dir_from_config()``
7+
- ``_pytest.capture.CaptureFixture``
8+
- ``_pytest.fixtures.FixtureRequest``
9+
- ``_pytest.fixtures.SubRequest``
10+
- ``_pytest.logging.LogCaptureFixture``
11+
- ``_pytest.pytester.Pytester``
12+
- ``_pytest.pytester.Testdir``
13+
- ``_pytest.recwarn.WarningsRecorder``
14+
- ``_pytest.recwarn.WarningsChecker``
15+
- ``_pytest.tmpdir.TempPathFactory``
16+
- ``_pytest.tmpdir.TempdirFactory``
17+
18+
These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.

changelog/7469.improvement.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
It is now possible to construct a :class:`MonkeyPatch` object directly as ``pytest.MonkeyPatch()``,
2+
in cases when the :fixture:`monkeypatch` fixture cannot be used. Previously some users imported it
3+
from the private `_pytest.monkeypatch.MonkeyPatch` namespace.
4+
5+
The types of builtin pytest fixtures are now exported so they may be used in type annotations of test functions.
6+
The newly-exported types are:
7+
8+
- ``pytest.FixtureRequest`` for the :fixture:`request` fixture.
9+
- ``pytest.Cache`` for the :fixture:`cache` fixture.
10+
- ``pytest.CaptureFixture[str]`` for the :fixture:`capfd` and :fixture:`capsys` fixtures.
11+
- ``pytest.CaptureFixture[bytes]`` for the :fixture:`capfdbinary` and :fixture:`capsysbinary` fixtures.
12+
- ``pytest.LogCaptureFixture`` for the :fixture:`caplog` fixture.
13+
- ``pytest.Pytester`` for the :fixture:`pytester` fixture.
14+
- ``pytest.Testdir`` for the :fixture:`testdir` fixture.
15+
- ``pytest.TempdirFactory`` for the :fixture:`tmpdir_factory` fixture.
16+
- ``pytest.TempPathFactory`` for the :fixture:`tmp_path_factory` fixture.
17+
- ``pytest.MonkeyPatch`` for the :fixture:`monkeypatch` fixture.
18+
- ``pytest.WarningsRecorder`` for the :fixture:`recwarn` fixture.
19+
20+
Constructing them is not supported (except for `MonkeyPatch`); they are only meant for use in type annotations.
21+
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.
22+
23+
Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy.

doc/en/reference.rst

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,10 @@ request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache`
314314
Under the hood, the cache plugin uses the simple
315315
``dumps``/``loads`` API of the :py:mod:`json` stdlib module.
316316

317-
.. currentmodule:: _pytest.cacheprovider
317+
``config.cache`` is an instance of :class:`pytest.Cache`:
318318

319-
.. automethod:: Cache.get
320-
.. automethod:: Cache.set
321-
.. automethod:: Cache.makedir
319+
.. autoclass:: pytest.Cache()
320+
:members:
322321

323322

324323
.. fixture:: capsys
@@ -328,12 +327,10 @@ capsys
328327

329328
**Tutorial**: :doc:`capture`.
330329

331-
.. currentmodule:: _pytest.capture
332-
333-
.. autofunction:: capsys()
330+
.. autofunction:: _pytest.capture.capsys()
334331
:no-auto-options:
335332

336-
Returns an instance of :py:class:`CaptureFixture`.
333+
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
337334

338335
Example:
339336

@@ -344,7 +341,7 @@ capsys
344341
captured = capsys.readouterr()
345342
assert captured.out == "hello\n"
346343
347-
.. autoclass:: CaptureFixture()
344+
.. autoclass:: pytest.CaptureFixture()
348345
:members:
349346

350347

@@ -355,10 +352,10 @@ capsysbinary
355352

356353
**Tutorial**: :doc:`capture`.
357354

358-
.. autofunction:: capsysbinary()
355+
.. autofunction:: _pytest.capture.capsysbinary()
359356
:no-auto-options:
360357

361-
Returns an instance of :py:class:`CaptureFixture`.
358+
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
362359

363360
Example:
364361

@@ -377,10 +374,10 @@ capfd
377374

378375
**Tutorial**: :doc:`capture`.
379376

380-
.. autofunction:: capfd()
377+
.. autofunction:: _pytest.capture.capfd()
381378
:no-auto-options:
382379

383-
Returns an instance of :py:class:`CaptureFixture`.
380+
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
384381

385382
Example:
386383

@@ -399,10 +396,10 @@ capfdbinary
399396

400397
**Tutorial**: :doc:`capture`.
401398

402-
.. autofunction:: capfdbinary()
399+
.. autofunction:: _pytest.capture.capfdbinary()
403400
:no-auto-options:
404401

405-
Returns an instance of :py:class:`CaptureFixture`.
402+
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
406403

407404
Example:
408405

@@ -443,7 +440,7 @@ request
443440

444441
The ``request`` fixture is a special fixture providing information of the requesting test function.
445442

446-
.. autoclass:: _pytest.fixtures.FixtureRequest()
443+
.. autoclass:: pytest.FixtureRequest()
447444
:members:
448445

449446

@@ -485,9 +482,9 @@ caplog
485482
.. autofunction:: _pytest.logging.caplog()
486483
:no-auto-options:
487484

488-
Returns a :class:`_pytest.logging.LogCaptureFixture` instance.
485+
Returns a :class:`pytest.LogCaptureFixture` instance.
489486

490-
.. autoclass:: _pytest.logging.LogCaptureFixture
487+
.. autoclass:: pytest.LogCaptureFixture()
491488
:members:
492489

493490

@@ -514,9 +511,7 @@ pytester
514511

515512
.. versionadded:: 6.2
516513

517-
.. currentmodule:: _pytest.pytester
518-
519-
Provides a :class:`Pytester` instance that can be used to run and test pytest itself.
514+
Provides a :class:`~pytest.Pytester` instance that can be used to run and test pytest itself.
520515

521516
It provides an empty directory where pytest can be executed in isolation, and contains facilities
522517
to write tests, configuration files, and match against expected output.
@@ -529,17 +524,17 @@ To use it, include in your topmost ``conftest.py`` file:
529524
530525
531526
532-
.. autoclass:: Pytester()
527+
.. autoclass:: pytest.Pytester()
533528
:members:
534529

535-
.. autoclass:: RunResult()
530+
.. autoclass:: _pytest.pytester.RunResult()
536531
:members:
537532

538-
.. autoclass:: LineMatcher()
533+
.. autoclass:: _pytest.pytester.LineMatcher()
539534
:members:
540535
:special-members: __str__
541536

542-
.. autoclass:: HookRecorder()
537+
.. autoclass:: _pytest.pytester.HookRecorder()
543538
:members:
544539

545540
.. fixture:: testdir
@@ -552,7 +547,7 @@ legacy ``py.path.local`` objects instead when applicable.
552547

553548
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
554549

555-
.. autoclass:: Testdir()
550+
.. autoclass:: pytest.Testdir()
556551
:members:
557552

558553

@@ -563,12 +558,10 @@ recwarn
563558

564559
**Tutorial**: :ref:`assertwarnings`
565560

566-
.. currentmodule:: _pytest.recwarn
567-
568-
.. autofunction:: recwarn()
561+
.. autofunction:: _pytest.recwarn.recwarn()
569562
:no-auto-options:
570563

571-
.. autoclass:: WarningsRecorder()
564+
.. autoclass:: pytest.WarningsRecorder()
572565
:members:
573566

574567
Each recorded warning is an instance of :class:`warnings.WarningMessage`.
@@ -585,13 +578,11 @@ tmp_path
585578

586579
**Tutorial**: :doc:`tmpdir`
587580

588-
.. currentmodule:: _pytest.tmpdir
589-
590-
.. autofunction:: tmp_path()
581+
.. autofunction:: _pytest.tmpdir.tmp_path()
591582
:no-auto-options:
592583

593584

594-
.. fixture:: tmp_path_factory
585+
.. fixture:: _pytest.tmpdir.tmp_path_factory
595586

596587
tmp_path_factory
597588
~~~~~~~~~~~~~~~~
@@ -600,12 +591,9 @@ tmp_path_factory
600591

601592
.. _`tmp_path_factory factory api`:
602593

603-
``tmp_path_factory`` instances have the following methods:
594+
``tmp_path_factory`` is an instance of :class:`~pytest.TempPathFactory`:
604595

605-
.. currentmodule:: _pytest.tmpdir
606-
607-
.. automethod:: TempPathFactory.mktemp
608-
.. automethod:: TempPathFactory.getbasetemp
596+
.. autoclass:: pytest.TempPathFactory()
609597

610598

611599
.. fixture:: tmpdir
@@ -615,9 +603,7 @@ tmpdir
615603

616604
**Tutorial**: :doc:`tmpdir`
617605

618-
.. currentmodule:: _pytest.tmpdir
619-
620-
.. autofunction:: tmpdir()
606+
.. autofunction:: _pytest.tmpdir.tmpdir()
621607
:no-auto-options:
622608

623609

@@ -630,12 +616,9 @@ tmpdir_factory
630616

631617
.. _`tmpdir factory api`:
632618

633-
``tmpdir_factory`` instances have the following methods:
634-
635-
.. currentmodule:: _pytest.tmpdir
619+
``tmp_path_factory`` is an instance of :class:`~pytest.TempdirFactory`:
636620

637-
.. automethod:: TempdirFactory.mktemp
638-
.. automethod:: TempdirFactory.getbasetemp
621+
.. autoclass:: pytest.TempdirFactory()
639622

640623

641624
.. _`hook-reference`:

src/_pytest/cacheprovider.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from _pytest.config import ExitCode
2626
from _pytest.config import hookimpl
2727
from _pytest.config.argparsing import Parser
28+
from _pytest.deprecated import check_ispytest
2829
from _pytest.fixtures import fixture
2930
from _pytest.fixtures import FixtureRequest
3031
from _pytest.main import Session
@@ -53,7 +54,7 @@
5354

5455

5556
@final
56-
@attr.s
57+
@attr.s(init=False)
5758
class Cache:
5859
_cachedir = attr.ib(type=Path, repr=False)
5960
_config = attr.ib(type=Config, repr=False)
@@ -64,26 +65,52 @@ class Cache:
6465
# sub-directory under cache-dir for values created by "set"
6566
_CACHE_PREFIX_VALUES = "v"
6667

68+
def __init__(
69+
self, cachedir: Path, config: Config, *, _ispytest: bool = False
70+
) -> None:
71+
check_ispytest(_ispytest)
72+
self._cachedir = cachedir
73+
self._config = config
74+
6775
@classmethod
68-
def for_config(cls, config: Config) -> "Cache":
69-
cachedir = cls.cache_dir_from_config(config)
76+
def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache":
77+
"""Create the Cache instance for a Config.
78+
79+
:meta private:
80+
"""
81+
check_ispytest(_ispytest)
82+
cachedir = cls.cache_dir_from_config(config, _ispytest=True)
7083
if config.getoption("cacheclear") and cachedir.is_dir():
71-
cls.clear_cache(cachedir)
72-
return cls(cachedir, config)
84+
cls.clear_cache(cachedir, _ispytest=True)
85+
return cls(cachedir, config, _ispytest=True)
7386

7487
@classmethod
75-
def clear_cache(cls, cachedir: Path) -> None:
76-
"""Clear the sub-directories used to hold cached directories and values."""
88+
def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None:
89+
"""Clear the sub-directories used to hold cached directories and values.
90+
91+
:meta private:
92+
"""
93+
check_ispytest(_ispytest)
7794
for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES):
7895
d = cachedir / prefix
7996
if d.is_dir():
8097
rm_rf(d)
8198

8299
@staticmethod
83-
def cache_dir_from_config(config: Config) -> Path:
100+
def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path:
101+
"""Get the path to the cache directory for a Config.
102+
103+
:meta private:
104+
"""
105+
check_ispytest(_ispytest)
84106
return resolve_from_str(config.getini("cache_dir"), config.rootpath)
85107

86-
def warn(self, fmt: str, **args: object) -> None:
108+
def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
109+
"""Issue a cache warning.
110+
111+
:meta private:
112+
"""
113+
check_ispytest(_ispytest)
87114
import warnings
88115
from _pytest.warning_types import PytestCacheWarning
89116

@@ -152,15 +179,15 @@ def set(self, key: str, value: object) -> None:
152179
cache_dir_exists_already = self._cachedir.exists()
153180
path.parent.mkdir(exist_ok=True, parents=True)
154181
except OSError:
155-
self.warn("could not create cache path {path}", path=path)
182+
self.warn("could not create cache path {path}", path=path, _ispytest=True)
156183
return
157184
if not cache_dir_exists_already:
158185
self._ensure_supporting_files()
159186
data = json.dumps(value, indent=2, sort_keys=True)
160187
try:
161188
f = path.open("w")
162189
except OSError:
163-
self.warn("cache could not write path {path}", path=path)
190+
self.warn("cache could not write path {path}", path=path, _ispytest=True)
164191
else:
165192
with f:
166193
f.write(data)
@@ -469,7 +496,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
469496

470497
@hookimpl(tryfirst=True)
471498
def pytest_configure(config: Config) -> None:
472-
config.cache = Cache.for_config(config)
499+
config.cache = Cache.for_config(config, _ispytest=True)
473500
config.pluginmanager.register(LFPlugin(config), "lfplugin")
474501
config.pluginmanager.register(NFPlugin(config), "nfplugin")
475502

0 commit comments

Comments
 (0)