summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRan Benita <ran@unusedvar.com>2020-12-29 18:22:02 +0200
committerGitHub <noreply@github.com>2020-12-29 18:22:02 +0200
commit77519048753f629847d3edaf8344753ad6689ada (patch)
treee3dc566276d58ebcacad3f75f956a4cbcaeeebab
parente772f02d1589c3d2d8e08007aad5976796d9e658 (diff)
parent96ea867fec556a8d0e2b60392927572da38c88df (diff)
downloadpytest-77519048753f629847d3edaf8344753ad6689ada.tar.gz
Merge pull request #8194 from bluetech/typing-public-3
Export pytest.Metafunc and pytest.Callinfo, hide NodeKeywords
-rw-r--r--changelog/7469.deprecation.rst2
-rw-r--r--changelog/7469.feature.rst2
-rw-r--r--doc/en/deprecations.rst4
-rw-r--r--doc/en/funcarg_compare.rst4
-rw-r--r--doc/en/reference.rst6
-rw-r--r--src/_pytest/nodes.py5
-rw-r--r--src/_pytest/python.py12
-rw-r--r--src/_pytest/runner.py73
-rw-r--r--src/pytest/__init__.py4
-rw-r--r--testing/python/metafunc.py2
10 files changed, 80 insertions, 34 deletions
diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst
index 6bbc80755..0d7908ef8 100644
--- a/changelog/7469.deprecation.rst
+++ b/changelog/7469.deprecation.rst
@@ -3,5 +3,7 @@ Directly constructing the following classes is now deprecated:
- ``_pytest.mark.structures.Mark``
- ``_pytest.mark.structures.MarkDecorator``
- ``_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.
diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst
index 81f93d1f7..f9948d686 100644
--- a/changelog/7469.feature.rst
+++ b/changelog/7469.feature.rst
@@ -5,6 +5,8 @@ The newly-exported types are:
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
- ``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.
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.
diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst
index 5ef1053e0..ec2397e59 100644
--- a/doc/en/deprecations.rst
+++ b/doc/en/deprecations.rst
@@ -397,8 +397,8 @@ Metafunc.addcall
.. versionremoved:: 4.0
-``_pytest.python.Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use
-:meth:`_pytest.python.Metafunc.parametrize` instead.
+``Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use
+:meth:`pytest.Metafunc.parametrize` instead.
Example:
diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst
index 0c4913edf..5e2a05006 100644
--- a/doc/en/funcarg_compare.rst
+++ b/doc/en/funcarg_compare.rst
@@ -47,7 +47,7 @@ There are several limitations and difficulties with this approach:
2. parametrizing the "db" resource is not straight forward:
you need to apply a "parametrize" decorator or implement a
: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
is used. Moreover, you need to modify the factory to use an
``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
``request.param`` was already used when test functions/classes were
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:
diff --git a/doc/en/reference.rst b/doc/en/reference.rst
index c8e8dca75..bc6c5670a 100644
--- a/doc/en/reference.rst
+++ b/doc/en/reference.rst
@@ -138,7 +138,7 @@ pytest.mark.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`:
@@ -758,7 +758,7 @@ Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hoo
CallInfo
~~~~~~~~
-.. autoclass:: _pytest.runner.CallInfo()
+.. autoclass:: pytest.CallInfo()
:members:
@@ -870,7 +870,7 @@ Mark
Metafunc
~~~~~~~~
-.. autoclass:: _pytest.python.Metafunc
+.. autoclass:: pytest.Metafunc()
:members:
Module
diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py
index da2a0a7ea..c6eb49dec 100644
--- a/src/_pytest/nodes.py
+++ b/src/_pytest/nodes.py
@@ -1,10 +1,12 @@
import os
import warnings
from pathlib import Path
+from typing import Any
from typing import Callable
from typing import Iterable
from typing import Iterator
from typing import List
+from typing import MutableMapping
from typing import Optional
from typing import overload
from typing import Set
@@ -148,8 +150,9 @@ class Node(metaclass=NodeMeta):
#: Filesystem path where this node was collected from (can be None).
self.fspath = fspath or getattr(parent, "fspath", None)
+ # The explicit annotation is to avoid publicly exposing NodeKeywords.
#: Keywords/markers collected from all scopes.
- self.keywords = NodeKeywords(self)
+ self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
#: The marker objects belonging to this node.
self.own_markers: List[Mark] = []
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index b46050920..31d91853f 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -55,6 +55,7 @@ from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
+from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
from _pytest.fixtures import FuncFixtureInfo
from _pytest.main import Session
@@ -467,7 +468,12 @@ class PyCollector(PyobjMixin, nodes.Collector):
fixtureinfo = definition._fixtureinfo
metafunc = Metafunc(
- definition, fixtureinfo, self.config, cls=cls, module=module
+ definition=definition,
+ fixtureinfo=fixtureinfo,
+ config=self.config,
+ cls=cls,
+ module=module,
+ _ispytest=True,
)
methods = []
if hasattr(module, "pytest_generate_tests"):
@@ -971,7 +977,11 @@ class Metafunc:
config: Config,
cls=None,
module=None,
+ *,
+ _ispytest: bool = False,
) -> None:
+ check_ispytest(_ispytest)
+
#: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
self.definition = definition
diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py
index 794690ddb..df046a78a 100644
--- a/src/_pytest/runner.py
+++ b/src/_pytest/runner.py
@@ -26,6 +26,7 @@ from _pytest._code.code import ExceptionInfo
from _pytest._code.code import TerminalRepr
from _pytest.compat import final
from _pytest.config.argparsing import Parser
+from _pytest.deprecated import check_ispytest
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
@@ -260,34 +261,47 @@ TResult = TypeVar("TResult", covariant=True)
@final
-@attr.s(repr=False)
+@attr.s(repr=False, init=False, auto_attribs=True)
class CallInfo(Generic[TResult]):
- """Result/Exception info a function invocation.
-
- :param T result:
- The return value of the call, if it didn't raise. Can only be
- accessed if excinfo is None.
- :param Optional[ExceptionInfo] excinfo:
- The captured exception of the call, if it raised.
- :param float start:
- The system time when the call started, in seconds since the epoch.
- :param float stop:
- The system time when the call ended, in seconds since the epoch.
- :param float duration:
- The call duration, in seconds.
- :param str when:
- The context of invocation: "setup", "call", "teardown", ...
- """
-
- _result = attr.ib(type="Optional[TResult]")
- excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]])
- start = attr.ib(type=float)
- stop = attr.ib(type=float)
- duration = attr.ib(type=float)
- when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']")
+ """Result/Exception info of a function invocation."""
+
+ _result: Optional[TResult]
+ #: The captured exception of the call, if it raised.
+ excinfo: Optional[ExceptionInfo[BaseException]]
+ #: The system time when the call started, in seconds since the epoch.
+ start: float
+ #: The system time when the call ended, in seconds since the epoch.
+ stop: float
+ #: The call duration, in seconds.
+ duration: float
+ #: The context of invocation: "collect", "setup", "call" or "teardown".
+ when: "Literal['collect', 'setup', 'call', 'teardown']"
+
+ def __init__(
+ self,
+ result: Optional[TResult],
+ excinfo: Optional[ExceptionInfo[BaseException]],
+ start: float,
+ 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
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:
raise AttributeError(f"{self!r} has no valid result")
# 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], ...]]
] = None,
) -> "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
start = timing.time()
precise_start = timing.perf_counter()
@@ -325,6 +349,7 @@ class CallInfo(Generic[TResult]):
when=when,
result=result,
excinfo=excinfo,
+ _ispytest=True,
)
def __repr__(self) -> str:
diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py
index 74cf00ee2..53917340f 100644
--- a/src/pytest/__init__.py
+++ b/src/pytest/__init__.py
@@ -40,6 +40,7 @@ from _pytest.pytester import Testdir
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Instance
+from _pytest.python import Metafunc
from _pytest.python import Module
from _pytest.python import Package
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 WarningsRecorder
from _pytest.recwarn import warns
+from _pytest.runner import CallInfo
from _pytest.tmpdir import TempdirFactory
from _pytest.tmpdir import TempPathFactory
from _pytest.warning_types import PytestAssertRewriteWarning
@@ -68,6 +70,7 @@ __all__ = [
"_fillfuncargs",
"approx",
"Cache",
+ "CallInfo",
"CaptureFixture",
"Class",
"cmdline",
@@ -95,6 +98,7 @@ __all__ = [
"Mark",
"MarkDecorator",
"MarkGenerator",
+ "Metafunc",
"Module",
"MonkeyPatch",
"Package",
diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py
index c50ea53d2..58a902a3a 100644
--- a/testing/python/metafunc.py
+++ b/testing/python/metafunc.py
@@ -47,7 +47,7 @@ class TestMetafunc:
names = getfuncargnames(func)
fixtureinfo: Any = FuncFixtureInfoMock(names)
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 function():