Merge pull request #8194 from bluetech/typing-public-3

Export pytest.Metafunc and pytest.Callinfo, hide NodeKeywords
This commit is contained in:
Ran Benita 2020-12-29 18:22:02 +02:00 committed by GitHub
commit 7751904875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 78 additions and 32 deletions

View File

@ -3,5 +3,7 @@ Directly constructing the following classes is now deprecated:
- ``_pytest.mark.structures.Mark`` - ``_pytest.mark.structures.Mark``
- ``_pytest.mark.structures.MarkDecorator`` - ``_pytest.mark.structures.MarkDecorator``
- ``_pytest.mark.structures.MarkGenerator`` - ``_pytest.mark.structures.MarkGenerator``
- ``_pytest.python.Metafunc``
- ``_pytest.runner.CallInfo``
These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0. These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.

View File

@ -5,6 +5,8 @@ The newly-exported types are:
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`. - ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`. - ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
- ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton. - ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the `pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
- ``pytest.runner.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
Constructing them directly is not supported; they are only meant for use in type annotations. Constructing them directly is not supported; they are only meant for use in type annotations.
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0. Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.

View File

@ -397,8 +397,8 @@ Metafunc.addcall
.. versionremoved:: 4.0 .. versionremoved:: 4.0
``_pytest.python.Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use ``Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use
:meth:`_pytest.python.Metafunc.parametrize` instead. :meth:`pytest.Metafunc.parametrize` instead.
Example: Example:

View File

@ -47,7 +47,7 @@ There are several limitations and difficulties with this approach:
2. parametrizing the "db" resource is not straight forward: 2. parametrizing the "db" resource is not straight forward:
you need to apply a "parametrize" decorator or implement a you need to apply a "parametrize" decorator or implement a
:py:func:`~hookspec.pytest_generate_tests` hook :py:func:`~hookspec.pytest_generate_tests` hook
calling :py:func:`~python.Metafunc.parametrize` which calling :py:func:`~pytest.Metafunc.parametrize` which
performs parametrization at the places where the resource performs parametrization at the places where the resource
is used. Moreover, you need to modify the factory to use an is used. Moreover, you need to modify the factory to use an
``extrakey`` parameter containing ``request.param`` to the ``extrakey`` parameter containing ``request.param`` to the
@ -113,7 +113,7 @@ This new way of parametrizing funcarg factories should in many cases
allow to re-use already written factories because effectively allow to re-use already written factories because effectively
``request.param`` was already used when test functions/classes were ``request.param`` was already used when test functions/classes were
parametrized via parametrized via
:py:func:`metafunc.parametrize(indirect=True) <_pytest.python.Metafunc.parametrize>` calls. :py:func:`metafunc.parametrize(indirect=True) <pytest.Metafunc.parametrize>` calls.
Of course it's perfectly fine to combine parametrization and scoping: Of course it's perfectly fine to combine parametrization and scoping:

View File

@ -138,7 +138,7 @@ pytest.mark.parametrize
**Tutorial**: :doc:`parametrize`. **Tutorial**: :doc:`parametrize`.
This mark has the same signature as :py:meth:`_pytest.python.Metafunc.parametrize`; see there. This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there.
.. _`pytest.mark.skip ref`: .. _`pytest.mark.skip ref`:
@ -758,7 +758,7 @@ Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hoo
CallInfo CallInfo
~~~~~~~~ ~~~~~~~~
.. autoclass:: _pytest.runner.CallInfo() .. autoclass:: pytest.CallInfo()
:members: :members:
@ -870,7 +870,7 @@ Mark
Metafunc Metafunc
~~~~~~~~ ~~~~~~~~
.. autoclass:: _pytest.python.Metafunc .. autoclass:: pytest.Metafunc()
:members: :members:
Module Module

View File

@ -1,10 +1,12 @@
import os import os
import warnings import warnings
from pathlib import Path from pathlib import Path
from typing import Any
from typing import Callable from typing import Callable
from typing import Iterable from typing import Iterable
from typing import Iterator from typing import Iterator
from typing import List from typing import List
from typing import MutableMapping
from typing import Optional from typing import Optional
from typing import overload from typing import overload
from typing import Set from typing import Set
@ -148,8 +150,9 @@ class Node(metaclass=NodeMeta):
#: Filesystem path where this node was collected from (can be None). #: Filesystem path where this node was collected from (can be None).
self.fspath = fspath or getattr(parent, "fspath", None) self.fspath = fspath or getattr(parent, "fspath", None)
# The explicit annotation is to avoid publicly exposing NodeKeywords.
#: Keywords/markers collected from all scopes. #: Keywords/markers collected from all scopes.
self.keywords = NodeKeywords(self) self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
#: The marker objects belonging to this node. #: The marker objects belonging to this node.
self.own_markers: List[Mark] = [] self.own_markers: List[Mark] = []

View File

@ -55,6 +55,7 @@ from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
from _pytest.fixtures import FuncFixtureInfo from _pytest.fixtures import FuncFixtureInfo
from _pytest.main import Session from _pytest.main import Session
@ -467,7 +468,12 @@ class PyCollector(PyobjMixin, nodes.Collector):
fixtureinfo = definition._fixtureinfo fixtureinfo = definition._fixtureinfo
metafunc = Metafunc( metafunc = Metafunc(
definition, fixtureinfo, self.config, cls=cls, module=module definition=definition,
fixtureinfo=fixtureinfo,
config=self.config,
cls=cls,
module=module,
_ispytest=True,
) )
methods = [] methods = []
if hasattr(module, "pytest_generate_tests"): if hasattr(module, "pytest_generate_tests"):
@ -971,7 +977,11 @@ class Metafunc:
config: Config, config: Config,
cls=None, cls=None,
module=None, module=None,
*,
_ispytest: bool = False,
) -> None: ) -> None:
check_ispytest(_ispytest)
#: Access to the underlying :class:`_pytest.python.FunctionDefinition`. #: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
self.definition = definition self.definition = definition

View File

@ -26,6 +26,7 @@ from _pytest._code.code import ExceptionInfo
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
from _pytest.compat import final from _pytest.compat import final
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.nodes import Collector from _pytest.nodes import Collector
from _pytest.nodes import Item from _pytest.nodes import Item
from _pytest.nodes import Node from _pytest.nodes import Node
@ -260,34 +261,47 @@ TResult = TypeVar("TResult", covariant=True)
@final @final
@attr.s(repr=False) @attr.s(repr=False, init=False, auto_attribs=True)
class CallInfo(Generic[TResult]): class CallInfo(Generic[TResult]):
"""Result/Exception info a function invocation. """Result/Exception info of a function invocation."""
:param T result: _result: Optional[TResult]
The return value of the call, if it didn't raise. Can only be #: The captured exception of the call, if it raised.
accessed if excinfo is None. excinfo: Optional[ExceptionInfo[BaseException]]
:param Optional[ExceptionInfo] excinfo: #: The system time when the call started, in seconds since the epoch.
The captured exception of the call, if it raised. start: float
:param float start: #: The system time when the call ended, in seconds since the epoch.
The system time when the call started, in seconds since the epoch. stop: float
:param float stop: #: The call duration, in seconds.
The system time when the call ended, in seconds since the epoch. duration: float
:param float duration: #: The context of invocation: "collect", "setup", "call" or "teardown".
The call duration, in seconds. when: "Literal['collect', 'setup', 'call', 'teardown']"
:param str when:
The context of invocation: "setup", "call", "teardown", ...
"""
_result = attr.ib(type="Optional[TResult]") def __init__(
excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]]) self,
start = attr.ib(type=float) result: Optional[TResult],
stop = attr.ib(type=float) excinfo: Optional[ExceptionInfo[BaseException]],
duration = attr.ib(type=float) start: float,
when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']") stop: float,
duration: float,
when: "Literal['collect', 'setup', 'call', 'teardown']",
*,
_ispytest: bool = False,
) -> None:
check_ispytest(_ispytest)
self._result = result
self.excinfo = excinfo
self.start = start
self.stop = stop
self.duration = duration
self.when = when
@property @property
def result(self) -> TResult: def result(self) -> TResult:
"""The return value of the call, if it didn't raise.
Can only be accessed if excinfo is None.
"""
if self.excinfo is not None: if self.excinfo is not None:
raise AttributeError(f"{self!r} has no valid result") raise AttributeError(f"{self!r} has no valid result")
# The cast is safe because an exception wasn't raised, hence # The cast is safe because an exception wasn't raised, hence
@ -304,6 +318,16 @@ class CallInfo(Generic[TResult]):
Union[Type[BaseException], Tuple[Type[BaseException], ...]] Union[Type[BaseException], Tuple[Type[BaseException], ...]]
] = None, ] = None,
) -> "CallInfo[TResult]": ) -> "CallInfo[TResult]":
"""Call func, wrapping the result in a CallInfo.
:param func:
The function to call. Called without arguments.
:param when:
The phase in which the function is called.
:param reraise:
Exception or exceptions that shall propagate if raised by the
function, instead of being wrapped in the CallInfo.
"""
excinfo = None excinfo = None
start = timing.time() start = timing.time()
precise_start = timing.perf_counter() precise_start = timing.perf_counter()
@ -325,6 +349,7 @@ class CallInfo(Generic[TResult]):
when=when, when=when,
result=result, result=result,
excinfo=excinfo, excinfo=excinfo,
_ispytest=True,
) )
def __repr__(self) -> str: def __repr__(self) -> str:

View File

@ -40,6 +40,7 @@ from _pytest.pytester import Testdir
from _pytest.python import Class from _pytest.python import Class
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Instance from _pytest.python import Instance
from _pytest.python import Metafunc
from _pytest.python import Module from _pytest.python import Module
from _pytest.python import Package from _pytest.python import Package
from _pytest.python_api import approx from _pytest.python_api import approx
@ -47,6 +48,7 @@ from _pytest.python_api import raises
from _pytest.recwarn import deprecated_call from _pytest.recwarn import deprecated_call
from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import WarningsRecorder
from _pytest.recwarn import warns from _pytest.recwarn import warns
from _pytest.runner import CallInfo
from _pytest.tmpdir import TempdirFactory from _pytest.tmpdir import TempdirFactory
from _pytest.tmpdir import TempPathFactory from _pytest.tmpdir import TempPathFactory
from _pytest.warning_types import PytestAssertRewriteWarning from _pytest.warning_types import PytestAssertRewriteWarning
@ -68,6 +70,7 @@ __all__ = [
"_fillfuncargs", "_fillfuncargs",
"approx", "approx",
"Cache", "Cache",
"CallInfo",
"CaptureFixture", "CaptureFixture",
"Class", "Class",
"cmdline", "cmdline",
@ -95,6 +98,7 @@ __all__ = [
"Mark", "Mark",
"MarkDecorator", "MarkDecorator",
"MarkGenerator", "MarkGenerator",
"Metafunc",
"Module", "Module",
"MonkeyPatch", "MonkeyPatch",
"Package", "Package",

View File

@ -47,7 +47,7 @@ class TestMetafunc:
names = getfuncargnames(func) names = getfuncargnames(func)
fixtureinfo: Any = FuncFixtureInfoMock(names) fixtureinfo: Any = FuncFixtureInfoMock(names)
definition: Any = DefinitionMock._create(func, "mock::nodeid") definition: Any = DefinitionMock._create(func, "mock::nodeid")
return python.Metafunc(definition, fixtureinfo, config) return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
def test_no_funcargs(self) -> None: def test_no_funcargs(self) -> None:
def function(): def function():