Skip to content

Commit db539ed

Browse files
authored
Merge pull request #8437 from bluetech/rm-typevar-prefix
Remove `_` prefix from TypeVars, expose ExceptionInfo
2 parents 9fa6673 + f2d65c8 commit db539ed

File tree

13 files changed

+102
-76
lines changed

13 files changed

+102
-76
lines changed

changelog/7469.deprecation.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ Directly constructing the following classes is now deprecated:
55
- ``_pytest.mark.structures.MarkGenerator``
66
- ``_pytest.python.Metafunc``
77
- ``_pytest.runner.CallInfo``
8+
- ``_pytest._code.ExceptionInfo``
89

910
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ 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.
8+
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
9+
- ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
10+
- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
1011

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

doc/en/how-to/assert.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ and if you need to have access to the actual exception info you may use:
9898
f()
9999
assert "maximum recursion" in str(excinfo.value)
100100
101-
``excinfo`` is an ``ExceptionInfo`` instance, which is a wrapper around
101+
``excinfo`` is an :class:`~pytest.ExceptionInfo` instance, which is a wrapper around
102102
the actual exception raised. The main attributes of interest are
103103
``.type``, ``.value`` and ``.traceback``.
104104

doc/en/reference/reference.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ Config
793793
ExceptionInfo
794794
~~~~~~~~~~~~~
795795

796-
.. autoclass:: _pytest._code.ExceptionInfo
796+
.. autoclass:: pytest.ExceptionInfo()
797797
:members:
798798

799799

src/_pytest/_code/code.py

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from _pytest._io.saferepr import saferepr
4343
from _pytest.compat import final
4444
from _pytest.compat import get_real_func
45+
from _pytest.deprecated import check_ispytest
4546
from _pytest.pathlib import absolutepath
4647
from _pytest.pathlib import bestrelpath
4748

@@ -436,26 +437,39 @@ def recursionindex(self) -> Optional[int]:
436437
)
437438

438439

439-
_E = TypeVar("_E", bound=BaseException, covariant=True)
440+
E = TypeVar("E", bound=BaseException, covariant=True)
440441

441442

442443
@final
443-
@attr.s(repr=False)
444-
class ExceptionInfo(Generic[_E]):
444+
@attr.s(repr=False, init=False)
445+
class ExceptionInfo(Generic[E]):
445446
"""Wraps sys.exc_info() objects and offers help for navigating the traceback."""
446447

447448
_assert_start_repr = "AssertionError('assert "
448449

449-
_excinfo = attr.ib(type=Optional[Tuple[Type["_E"], "_E", TracebackType]])
450-
_striptext = attr.ib(type=str, default="")
451-
_traceback = attr.ib(type=Optional[Traceback], default=None)
450+
_excinfo = attr.ib(type=Optional[Tuple[Type["E"], "E", TracebackType]])
451+
_striptext = attr.ib(type=str)
452+
_traceback = attr.ib(type=Optional[Traceback])
453+
454+
def __init__(
455+
self,
456+
excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
457+
striptext: str = "",
458+
traceback: Optional[Traceback] = None,
459+
*,
460+
_ispytest: bool = False,
461+
) -> None:
462+
check_ispytest(_ispytest)
463+
self._excinfo = excinfo
464+
self._striptext = striptext
465+
self._traceback = traceback
452466

453467
@classmethod
454468
def from_exc_info(
455469
cls,
456-
exc_info: Tuple[Type[_E], _E, TracebackType],
470+
exc_info: Tuple[Type[E], E, TracebackType],
457471
exprinfo: Optional[str] = None,
458-
) -> "ExceptionInfo[_E]":
472+
) -> "ExceptionInfo[E]":
459473
"""Return an ExceptionInfo for an existing exc_info tuple.
460474
461475
.. warning::
@@ -475,7 +489,7 @@ def from_exc_info(
475489
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
476490
_striptext = "AssertionError: "
477491

478-
return cls(exc_info, _striptext)
492+
return cls(exc_info, _striptext, _ispytest=True)
479493

480494
@classmethod
481495
def from_current(
@@ -500,25 +514,25 @@ def from_current(
500514
return ExceptionInfo.from_exc_info(exc_info, exprinfo)
501515

502516
@classmethod
503-
def for_later(cls) -> "ExceptionInfo[_E]":
517+
def for_later(cls) -> "ExceptionInfo[E]":
504518
"""Return an unfilled ExceptionInfo."""
505-
return cls(None)
519+
return cls(None, _ispytest=True)
506520

507-
def fill_unfilled(self, exc_info: Tuple[Type[_E], _E, TracebackType]) -> None:
521+
def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None:
508522
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
509523
assert self._excinfo is None, "ExceptionInfo was already filled"
510524
self._excinfo = exc_info
511525

512526
@property
513-
def type(self) -> Type[_E]:
527+
def type(self) -> Type[E]:
514528
"""The exception class."""
515529
assert (
516530
self._excinfo is not None
517531
), ".type can only be used after the context manager exits"
518532
return self._excinfo[0]
519533

520534
@property
521-
def value(self) -> _E:
535+
def value(self) -> E:
522536
"""The exception value."""
523537
assert (
524538
self._excinfo is not None
@@ -562,10 +576,10 @@ def __repr__(self) -> str:
562576
def exconly(self, tryshort: bool = False) -> str:
563577
"""Return the exception as a string.
564578
565-
When 'tryshort' resolves to True, and the exception is a
566-
_pytest._code._AssertionError, only the actual exception part of
567-
the exception representation is returned (so 'AssertionError: ' is
568-
removed from the beginning).
579+
When 'tryshort' resolves to True, and the exception is an
580+
AssertionError, only the actual exception part of the exception
581+
representation is returned (so 'AssertionError: ' is removed from
582+
the beginning).
569583
"""
570584
lines = format_exception_only(self.type, self.value)
571585
text = "".join(lines)
@@ -922,7 +936,7 @@ def repr_excinfo(
922936
if e.__cause__ is not None and self.chain:
923937
e = e.__cause__
924938
excinfo_ = (
925-
ExceptionInfo((type(e), e, e.__traceback__))
939+
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
926940
if e.__traceback__
927941
else None
928942
)
@@ -932,7 +946,7 @@ def repr_excinfo(
932946
):
933947
e = e.__context__
934948
excinfo_ = (
935-
ExceptionInfo((type(e), e, e.__traceback__))
949+
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
936950
if e.__traceback__
937951
else None
938952
)

src/_pytest/config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def main(
145145
try:
146146
config = _prepareconfig(args, plugins)
147147
except ConftestImportFailure as e:
148-
exc_info = ExceptionInfo(e.excinfo)
148+
exc_info = ExceptionInfo.from_exc_info(e.excinfo)
149149
tw = TerminalWriter(sys.stderr)
150150
tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
151151
exc_info.traceback = exc_info.traceback.filter(

src/_pytest/doctest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def repr_failure( # type: ignore[override]
365365
example, failure.got, report_choice
366366
).split("\n")
367367
else:
368-
inner_excinfo = ExceptionInfo(failure.exc_info)
368+
inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
369369
lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
370370
lines += [
371371
x.strip("\n")

src/_pytest/fixtures.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,18 @@
7979

8080

8181
# The value of the fixture -- return/yield of the fixture function (type variable).
82-
_FixtureValue = TypeVar("_FixtureValue")
82+
FixtureValue = TypeVar("FixtureValue")
8383
# The type of the fixture function (type variable).
84-
_FixtureFunction = TypeVar("_FixtureFunction", bound=Callable[..., object])
84+
FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object])
8585
# The type of a fixture function (type alias generic in fixture value).
8686
_FixtureFunc = Union[
87-
Callable[..., _FixtureValue], Callable[..., Generator[_FixtureValue, None, None]]
87+
Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]]
8888
]
8989
# The type of FixtureDef.cached_result (type alias generic in fixture value).
9090
_FixtureCachedResult = Union[
9191
Tuple[
9292
# The result.
93-
_FixtureValue,
93+
FixtureValue,
9494
# Cache key.
9595
object,
9696
None,
@@ -106,8 +106,8 @@
106106

107107

108108
@attr.s(frozen=True)
109-
class PseudoFixtureDef(Generic[_FixtureValue]):
110-
cached_result = attr.ib(type="_FixtureCachedResult[_FixtureValue]")
109+
class PseudoFixtureDef(Generic[FixtureValue]):
110+
cached_result = attr.ib(type="_FixtureCachedResult[FixtureValue]")
111111
scope = attr.ib(type="_Scope")
112112

113113

@@ -928,11 +928,11 @@ def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":
928928

929929

930930
def call_fixture_func(
931-
fixturefunc: "_FixtureFunc[_FixtureValue]", request: FixtureRequest, kwargs
932-
) -> _FixtureValue:
931+
fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
932+
) -> FixtureValue:
933933
if is_generator(fixturefunc):
934934
fixturefunc = cast(
935-
Callable[..., Generator[_FixtureValue, None, None]], fixturefunc
935+
Callable[..., Generator[FixtureValue, None, None]], fixturefunc
936936
)
937937
generator = fixturefunc(**kwargs)
938938
try:
@@ -942,7 +942,7 @@ def call_fixture_func(
942942
finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
943943
request.addfinalizer(finalizer)
944944
else:
945-
fixturefunc = cast(Callable[..., _FixtureValue], fixturefunc)
945+
fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)
946946
fixture_result = fixturefunc(**kwargs)
947947
return fixture_result
948948

@@ -985,15 +985,15 @@ def _eval_scope_callable(
985985

986986

987987
@final
988-
class FixtureDef(Generic[_FixtureValue]):
988+
class FixtureDef(Generic[FixtureValue]):
989989
"""A container for a factory definition."""
990990

991991
def __init__(
992992
self,
993993
fixturemanager: "FixtureManager",
994994
baseid: Optional[str],
995995
argname: str,
996-
func: "_FixtureFunc[_FixtureValue]",
996+
func: "_FixtureFunc[FixtureValue]",
997997
scope: "Union[_Scope, Callable[[str, Config], _Scope]]",
998998
params: Optional[Sequence[object]],
999999
unittest: bool = False,
@@ -1026,7 +1026,7 @@ def __init__(
10261026
)
10271027
self.unittest = unittest
10281028
self.ids = ids
1029-
self.cached_result: Optional[_FixtureCachedResult[_FixtureValue]] = None
1029+
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
10301030
self._finalizers: List[Callable[[], object]] = []
10311031

10321032
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
@@ -1055,7 +1055,7 @@ def finish(self, request: SubRequest) -> None:
10551055
self.cached_result = None
10561056
self._finalizers = []
10571057

1058-
def execute(self, request: SubRequest) -> _FixtureValue:
1058+
def execute(self, request: SubRequest) -> FixtureValue:
10591059
# Get required arguments and register our own finish()
10601060
# with their finalization.
10611061
for argname in self.argnames:
@@ -1096,8 +1096,8 @@ def __repr__(self) -> str:
10961096

10971097

10981098
def resolve_fixture_function(
1099-
fixturedef: FixtureDef[_FixtureValue], request: FixtureRequest
1100-
) -> "_FixtureFunc[_FixtureValue]":
1099+
fixturedef: FixtureDef[FixtureValue], request: FixtureRequest
1100+
) -> "_FixtureFunc[FixtureValue]":
11011101
"""Get the actual callable that can be called to obtain the fixture
11021102
value, dealing with unittest-specific instances and bound methods."""
11031103
fixturefunc = fixturedef.func
@@ -1123,8 +1123,8 @@ def resolve_fixture_function(
11231123

11241124

11251125
def pytest_fixture_setup(
1126-
fixturedef: FixtureDef[_FixtureValue], request: SubRequest
1127-
) -> _FixtureValue:
1126+
fixturedef: FixtureDef[FixtureValue], request: SubRequest
1127+
) -> FixtureValue:
11281128
"""Execution of fixture setup."""
11291129
kwargs = {}
11301130
for argname in fixturedef.argnames:
@@ -1174,9 +1174,9 @@ def _params_converter(
11741174

11751175

11761176
def wrap_function_to_error_out_if_called_directly(
1177-
function: _FixtureFunction,
1177+
function: FixtureFunction,
11781178
fixture_marker: "FixtureFunctionMarker",
1179-
) -> _FixtureFunction:
1179+
) -> FixtureFunction:
11801180
"""Wrap the given fixture function so we can raise an error about it being called directly,
11811181
instead of used as an argument in a test function."""
11821182
message = (
@@ -1194,7 +1194,7 @@ def result(*args, **kwargs):
11941194
# further than this point and lose useful wrappings like @mock.patch (#3774).
11951195
result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined]
11961196

1197-
return cast(_FixtureFunction, result)
1197+
return cast(FixtureFunction, result)
11981198

11991199

12001200
@final
@@ -1213,7 +1213,7 @@ class FixtureFunctionMarker:
12131213
)
12141214
name = attr.ib(type=Optional[str], default=None)
12151215

1216-
def __call__(self, function: _FixtureFunction) -> _FixtureFunction:
1216+
def __call__(self, function: FixtureFunction) -> FixtureFunction:
12171217
if inspect.isclass(function):
12181218
raise ValueError("class fixtures not supported (maybe in the future)")
12191219

@@ -1241,7 +1241,7 @@ def __call__(self, function: _FixtureFunction) -> _FixtureFunction:
12411241

12421242
@overload
12431243
def fixture(
1244-
fixture_function: _FixtureFunction,
1244+
fixture_function: FixtureFunction,
12451245
*,
12461246
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ...,
12471247
params: Optional[Iterable[object]] = ...,
@@ -1253,7 +1253,7 @@ def fixture(
12531253
]
12541254
] = ...,
12551255
name: Optional[str] = ...,
1256-
) -> _FixtureFunction:
1256+
) -> FixtureFunction:
12571257
...
12581258

12591259

@@ -1276,7 +1276,7 @@ def fixture(
12761276

12771277

12781278
def fixture(
1279-
fixture_function: Optional[_FixtureFunction] = None,
1279+
fixture_function: Optional[FixtureFunction] = None,
12801280
*,
12811281
scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function",
12821282
params: Optional[Iterable[object]] = None,
@@ -1288,7 +1288,7 @@ def fixture(
12881288
]
12891289
] = None,
12901290
name: Optional[str] = None,
1291-
) -> Union[FixtureFunctionMarker, _FixtureFunction]:
1291+
) -> Union[FixtureFunctionMarker, FixtureFunction]:
12921292
"""Decorator to mark a fixture factory function.
12931293
12941294
This decorator can be used, with or without parameters, to define a

src/_pytest/nodes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ def _repr_failure_py(
396396
from _pytest.fixtures import FixtureLookupError
397397

398398
if isinstance(excinfo.value, ConftestImportFailure):
399-
excinfo = ExceptionInfo(excinfo.value.excinfo)
399+
excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo)
400400
if isinstance(excinfo.value, fail.Exception):
401401
if not excinfo.value.pytrace:
402402
style = "value"

0 commit comments

Comments
 (0)