Skip to content

Commit 7751904

Browse files
authored
Merge pull request #8194 from bluetech/typing-public-3
Export pytest.Metafunc and pytest.Callinfo, hide NodeKeywords
2 parents e772f02 + 96ea867 commit 7751904

File tree

10 files changed

+80
-34
lines changed

10 files changed

+80
-34
lines changed

changelog/7469.deprecation.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ Directly constructing the following classes is now deprecated:
33
- ``_pytest.mark.structures.Mark``
44
- ``_pytest.mark.structures.MarkDecorator``
55
- ``_pytest.mark.structures.MarkGenerator``
6+
- ``_pytest.python.Metafunc``
7+
- ``_pytest.runner.CallInfo``
68

79
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.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The newly-exported types are:
55
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
66
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
77
- ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
8+
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the `pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
9+
- ``pytest.runner.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
810

911
Constructing them directly is not supported; they are only meant for use in type annotations.
1012
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.

doc/en/deprecations.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,8 @@ Metafunc.addcall
397397

398398
.. versionremoved:: 4.0
399399

400-
``_pytest.python.Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use
401-
:meth:`_pytest.python.Metafunc.parametrize` instead.
400+
``Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use
401+
:meth:`pytest.Metafunc.parametrize` instead.
402402

403403
Example:
404404

doc/en/funcarg_compare.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ There are several limitations and difficulties with this approach:
4747
2. parametrizing the "db" resource is not straight forward:
4848
you need to apply a "parametrize" decorator or implement a
4949
:py:func:`~hookspec.pytest_generate_tests` hook
50-
calling :py:func:`~python.Metafunc.parametrize` which
50+
calling :py:func:`~pytest.Metafunc.parametrize` which
5151
performs parametrization at the places where the resource
5252
is used. Moreover, you need to modify the factory to use an
5353
``extrakey`` parameter containing ``request.param`` to the
@@ -113,7 +113,7 @@ This new way of parametrizing funcarg factories should in many cases
113113
allow to re-use already written factories because effectively
114114
``request.param`` was already used when test functions/classes were
115115
parametrized via
116-
:py:func:`metafunc.parametrize(indirect=True) <_pytest.python.Metafunc.parametrize>` calls.
116+
:py:func:`metafunc.parametrize(indirect=True) <pytest.Metafunc.parametrize>` calls.
117117

118118
Of course it's perfectly fine to combine parametrization and scoping:
119119

doc/en/reference.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ pytest.mark.parametrize
138138

139139
**Tutorial**: :doc:`parametrize`.
140140

141-
This mark has the same signature as :py:meth:`_pytest.python.Metafunc.parametrize`; see there.
141+
This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there.
142142

143143

144144
.. _`pytest.mark.skip ref`:
@@ -758,7 +758,7 @@ Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hoo
758758
CallInfo
759759
~~~~~~~~
760760

761-
.. autoclass:: _pytest.runner.CallInfo()
761+
.. autoclass:: pytest.CallInfo()
762762
:members:
763763

764764

@@ -870,7 +870,7 @@ Mark
870870
Metafunc
871871
~~~~~~~~
872872

873-
.. autoclass:: _pytest.python.Metafunc
873+
.. autoclass:: pytest.Metafunc()
874874
:members:
875875

876876
Module

src/_pytest/nodes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import os
22
import warnings
33
from pathlib import Path
4+
from typing import Any
45
from typing import Callable
56
from typing import Iterable
67
from typing import Iterator
78
from typing import List
9+
from typing import MutableMapping
810
from typing import Optional
911
from typing import overload
1012
from typing import Set
@@ -148,8 +150,9 @@ def __init__(
148150
#: Filesystem path where this node was collected from (can be None).
149151
self.fspath = fspath or getattr(parent, "fspath", None)
150152

153+
# The explicit annotation is to avoid publicly exposing NodeKeywords.
151154
#: Keywords/markers collected from all scopes.
152-
self.keywords = NodeKeywords(self)
155+
self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
153156

154157
#: The marker objects belonging to this node.
155158
self.own_markers: List[Mark] = []

src/_pytest/python.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from _pytest.config import ExitCode
5656
from _pytest.config import hookimpl
5757
from _pytest.config.argparsing import Parser
58+
from _pytest.deprecated import check_ispytest
5859
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
5960
from _pytest.fixtures import FuncFixtureInfo
6061
from _pytest.main import Session
@@ -467,7 +468,12 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
467468
fixtureinfo = definition._fixtureinfo
468469

469470
metafunc = Metafunc(
470-
definition, fixtureinfo, self.config, cls=cls, module=module
471+
definition=definition,
472+
fixtureinfo=fixtureinfo,
473+
config=self.config,
474+
cls=cls,
475+
module=module,
476+
_ispytest=True,
471477
)
472478
methods = []
473479
if hasattr(module, "pytest_generate_tests"):
@@ -971,7 +977,11 @@ def __init__(
971977
config: Config,
972978
cls=None,
973979
module=None,
980+
*,
981+
_ispytest: bool = False,
974982
) -> None:
983+
check_ispytest(_ispytest)
984+
975985
#: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
976986
self.definition = definition
977987

src/_pytest/runner.py

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from _pytest._code.code import TerminalRepr
2727
from _pytest.compat import final
2828
from _pytest.config.argparsing import Parser
29+
from _pytest.deprecated import check_ispytest
2930
from _pytest.nodes import Collector
3031
from _pytest.nodes import Item
3132
from _pytest.nodes import Node
@@ -260,34 +261,47 @@ def call_runtest_hook(
260261

261262

262263
@final
263-
@attr.s(repr=False)
264+
@attr.s(repr=False, init=False, auto_attribs=True)
264265
class CallInfo(Generic[TResult]):
265-
"""Result/Exception info a function invocation.
266-
267-
:param T result:
268-
The return value of the call, if it didn't raise. Can only be
269-
accessed if excinfo is None.
270-
:param Optional[ExceptionInfo] excinfo:
271-
The captured exception of the call, if it raised.
272-
:param float start:
273-
The system time when the call started, in seconds since the epoch.
274-
:param float stop:
275-
The system time when the call ended, in seconds since the epoch.
276-
:param float duration:
277-
The call duration, in seconds.
278-
:param str when:
279-
The context of invocation: "setup", "call", "teardown", ...
280-
"""
281-
282-
_result = attr.ib(type="Optional[TResult]")
283-
excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]])
284-
start = attr.ib(type=float)
285-
stop = attr.ib(type=float)
286-
duration = attr.ib(type=float)
287-
when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']")
266+
"""Result/Exception info of a function invocation."""
267+
268+
_result: Optional[TResult]
269+
#: The captured exception of the call, if it raised.
270+
excinfo: Optional[ExceptionInfo[BaseException]]
271+
#: The system time when the call started, in seconds since the epoch.
272+
start: float
273+
#: The system time when the call ended, in seconds since the epoch.
274+
stop: float
275+
#: The call duration, in seconds.
276+
duration: float
277+
#: The context of invocation: "collect", "setup", "call" or "teardown".
278+
when: "Literal['collect', 'setup', 'call', 'teardown']"
279+
280+
def __init__(
281+
self,
282+
result: Optional[TResult],
283+
excinfo: Optional[ExceptionInfo[BaseException]],
284+
start: float,
285+
stop: float,
286+
duration: float,
287+
when: "Literal['collect', 'setup', 'call', 'teardown']",
288+
*,
289+
_ispytest: bool = False,
290+
) -> None:
291+
check_ispytest(_ispytest)
292+
self._result = result
293+
self.excinfo = excinfo
294+
self.start = start
295+
self.stop = stop
296+
self.duration = duration
297+
self.when = when
288298

289299
@property
290300
def result(self) -> TResult:
301+
"""The return value of the call, if it didn't raise.
302+
303+
Can only be accessed if excinfo is None.
304+
"""
291305
if self.excinfo is not None:
292306
raise AttributeError(f"{self!r} has no valid result")
293307
# The cast is safe because an exception wasn't raised, hence
@@ -304,6 +318,16 @@ def from_call(
304318
Union[Type[BaseException], Tuple[Type[BaseException], ...]]
305319
] = None,
306320
) -> "CallInfo[TResult]":
321+
"""Call func, wrapping the result in a CallInfo.
322+
323+
:param func:
324+
The function to call. Called without arguments.
325+
:param when:
326+
The phase in which the function is called.
327+
:param reraise:
328+
Exception or exceptions that shall propagate if raised by the
329+
function, instead of being wrapped in the CallInfo.
330+
"""
307331
excinfo = None
308332
start = timing.time()
309333
precise_start = timing.perf_counter()
@@ -325,6 +349,7 @@ def from_call(
325349
when=when,
326350
result=result,
327351
excinfo=excinfo,
352+
_ispytest=True,
328353
)
329354

330355
def __repr__(self) -> str:

src/pytest/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@
4040
from _pytest.python import Class
4141
from _pytest.python import Function
4242
from _pytest.python import Instance
43+
from _pytest.python import Metafunc
4344
from _pytest.python import Module
4445
from _pytest.python import Package
4546
from _pytest.python_api import approx
4647
from _pytest.python_api import raises
4748
from _pytest.recwarn import deprecated_call
4849
from _pytest.recwarn import WarningsRecorder
4950
from _pytest.recwarn import warns
51+
from _pytest.runner import CallInfo
5052
from _pytest.tmpdir import TempdirFactory
5153
from _pytest.tmpdir import TempPathFactory
5254
from _pytest.warning_types import PytestAssertRewriteWarning
@@ -68,6 +70,7 @@
6870
"_fillfuncargs",
6971
"approx",
7072
"Cache",
73+
"CallInfo",
7174
"CaptureFixture",
7275
"Class",
7376
"cmdline",
@@ -95,6 +98,7 @@
9598
"Mark",
9699
"MarkDecorator",
97100
"MarkGenerator",
101+
"Metafunc",
98102
"Module",
99103
"MonkeyPatch",
100104
"Package",

testing/python/metafunc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class DefinitionMock(python.FunctionDefinition):
4747
names = getfuncargnames(func)
4848
fixtureinfo: Any = FuncFixtureInfoMock(names)
4949
definition: Any = DefinitionMock._create(func, "mock::nodeid")
50-
return python.Metafunc(definition, fixtureinfo, config)
50+
return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
5151

5252
def test_no_funcargs(self) -> None:
5353
def function():

0 commit comments

Comments
 (0)