Skip to content

Commit b6b7fbb

Browse files
committed
capture: add type annotations to CaptureFixture
It now has a str/bytes type parameter.
1 parent f665070 commit b6b7fbb

File tree

2 files changed

+42
-30
lines changed

2 files changed

+42
-30
lines changed

src/_pytest/capture.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import sys
77
from io import UnsupportedOperation
88
from tempfile import TemporaryFile
9+
from typing import Any
10+
from typing import AnyStr
911
from typing import Generator
12+
from typing import Generic
13+
from typing import Iterator
1014
from typing import Optional
1115
from typing import TextIO
1216
from typing import Tuple
@@ -495,30 +499,32 @@ def writeorg(self, data):
495499
# make it a namedtuple again.
496500
# [0]: https://github.com/python/mypy/issues/685
497501
@functools.total_ordering
498-
class CaptureResult:
502+
class CaptureResult(Generic[AnyStr]):
499503
"""The result of :method:`CaptureFixture.readouterr`."""
500504

501505
__slots__ = ("out", "err")
502506

503-
def __init__(self, out, err) -> None:
504-
self.out = out
505-
self.err = err
507+
def __init__(self, out: AnyStr, err: AnyStr) -> None:
508+
self.out = out # type: AnyStr
509+
self.err = err # type: AnyStr
506510

507511
def __len__(self) -> int:
508512
return 2
509513

510-
def __iter__(self):
514+
def __iter__(self) -> Iterator[AnyStr]:
511515
return iter((self.out, self.err))
512516

513-
def __getitem__(self, item: int):
517+
def __getitem__(self, item: int) -> AnyStr:
514518
return tuple(self)[item]
515519

516-
def _replace(self, out=None, err=None) -> "CaptureResult":
520+
def _replace(
521+
self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None
522+
) -> "CaptureResult[AnyStr]":
517523
return CaptureResult(
518524
out=self.out if out is None else out, err=self.err if err is None else err
519525
)
520526

521-
def count(self, value) -> int:
527+
def count(self, value: AnyStr) -> int:
522528
return tuple(self).count(value)
523529

524530
def index(self, value) -> int:
@@ -541,7 +547,7 @@ def __repr__(self) -> str:
541547
return "CaptureResult(out={!r}, err={!r})".format(self.out, self.err)
542548

543549

544-
class MultiCapture:
550+
class MultiCapture(Generic[AnyStr]):
545551
_state = None
546552
_in_suspended = False
547553

@@ -564,7 +570,7 @@ def start_capturing(self) -> None:
564570
if self.err:
565571
self.err.start()
566572

567-
def pop_outerr_to_orig(self):
573+
def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
568574
"""Pop current snapshot out/err capture and flush to orig streams."""
569575
out, err = self.readouterr()
570576
if out:
@@ -605,7 +611,7 @@ def stop_capturing(self) -> None:
605611
if self.in_:
606612
self.in_.done()
607613

608-
def readouterr(self) -> CaptureResult:
614+
def readouterr(self) -> CaptureResult[AnyStr]:
609615
if self.out:
610616
out = self.out.snap()
611617
else:
@@ -617,7 +623,7 @@ def readouterr(self) -> CaptureResult:
617623
return CaptureResult(out, err)
618624

619625

620-
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture:
626+
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]:
621627
if method == "fd":
622628
return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
623629
elif method == "sys":
@@ -655,8 +661,8 @@ class CaptureManager:
655661

656662
def __init__(self, method: "_CaptureMethod") -> None:
657663
self._method = method
658-
self._global_capturing = None # type: Optional[MultiCapture]
659-
self._capture_fixture = None # type: Optional[CaptureFixture]
664+
self._global_capturing = None # type: Optional[MultiCapture[str]]
665+
self._capture_fixture = None # type: Optional[CaptureFixture[Any]]
660666

661667
def __repr__(self) -> str:
662668
return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
@@ -705,13 +711,13 @@ def resume(self) -> None:
705711
self.resume_global_capture()
706712
self.resume_fixture()
707713

708-
def read_global_capture(self):
714+
def read_global_capture(self) -> CaptureResult[str]:
709715
assert self._global_capturing is not None
710716
return self._global_capturing.readouterr()
711717

712718
# Fixture Control
713719

714-
def set_fixture(self, capture_fixture: "CaptureFixture") -> None:
720+
def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
715721
if self._capture_fixture:
716722
current_fixture = self._capture_fixture.request.fixturename
717723
requested_fixture = capture_fixture.request.fixturename
@@ -810,14 +816,14 @@ def pytest_internalerror(self) -> None:
810816
self.stop_global_capturing()
811817

812818

813-
class CaptureFixture:
819+
class CaptureFixture(Generic[AnyStr]):
814820
"""Object returned by the :py:func:`capsys`, :py:func:`capsysbinary`,
815821
:py:func:`capfd` and :py:func:`capfdbinary` fixtures."""
816822

817823
def __init__(self, captureclass, request: SubRequest) -> None:
818824
self.captureclass = captureclass
819825
self.request = request
820-
self._capture = None # type: Optional[MultiCapture]
826+
self._capture = None # type: Optional[MultiCapture[AnyStr]]
821827
self._captured_out = self.captureclass.EMPTY_BUFFER
822828
self._captured_err = self.captureclass.EMPTY_BUFFER
823829

@@ -836,7 +842,7 @@ def close(self) -> None:
836842
self._capture.stop_capturing()
837843
self._capture = None
838844

839-
def readouterr(self):
845+
def readouterr(self) -> CaptureResult[AnyStr]:
840846
"""Read and return the captured output so far, resetting the internal
841847
buffer.
842848
@@ -875,15 +881,15 @@ def disabled(self) -> Generator[None, None, None]:
875881

876882

877883
@pytest.fixture
878-
def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]:
884+
def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
879885
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
880886
881887
The captured output is made available via ``capsys.readouterr()`` method
882888
calls, which return a ``(out, err)`` namedtuple.
883889
``out`` and ``err`` will be ``text`` objects.
884890
"""
885891
capman = request.config.pluginmanager.getplugin("capturemanager")
886-
capture_fixture = CaptureFixture(SysCapture, request)
892+
capture_fixture = CaptureFixture[str](SysCapture, request)
887893
capman.set_fixture(capture_fixture)
888894
capture_fixture._start()
889895
yield capture_fixture
@@ -892,15 +898,15 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]:
892898

893899

894900
@pytest.fixture
895-
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
901+
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
896902
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
897903
898904
The captured output is made available via ``capsysbinary.readouterr()``
899905
method calls, which return a ``(out, err)`` namedtuple.
900906
``out`` and ``err`` will be ``bytes`` objects.
901907
"""
902908
capman = request.config.pluginmanager.getplugin("capturemanager")
903-
capture_fixture = CaptureFixture(SysCaptureBinary, request)
909+
capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request)
904910
capman.set_fixture(capture_fixture)
905911
capture_fixture._start()
906912
yield capture_fixture
@@ -909,15 +915,15 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
909915

910916

911917
@pytest.fixture
912-
def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]:
918+
def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
913919
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
914920
915921
The captured output is made available via ``capfd.readouterr()`` method
916922
calls, which return a ``(out, err)`` namedtuple.
917923
``out`` and ``err`` will be ``text`` objects.
918924
"""
919925
capman = request.config.pluginmanager.getplugin("capturemanager")
920-
capture_fixture = CaptureFixture(FDCapture, request)
926+
capture_fixture = CaptureFixture[str](FDCapture, request)
921927
capman.set_fixture(capture_fixture)
922928
capture_fixture._start()
923929
yield capture_fixture
@@ -926,15 +932,15 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]:
926932

927933

928934
@pytest.fixture
929-
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
935+
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
930936
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
931937
932938
The captured output is made available via ``capfd.readouterr()`` method
933939
calls, which return a ``(out, err)`` namedtuple.
934940
``out`` and ``err`` will be ``byte`` objects.
935941
"""
936942
capman = request.config.pluginmanager.getplugin("capturemanager")
937-
capture_fixture = CaptureFixture(FDCaptureBinary, request)
943+
capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request)
938944
capman.set_fixture(capture_fixture)
939945
capture_fixture._start()
940946
yield capture_fixture

testing/test_capture.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,29 @@
2222
# pylib 1.4.20.dev2 (rev 13d9af95547e)
2323

2424

25-
def StdCaptureFD(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture:
25+
def StdCaptureFD(
26+
out: bool = True, err: bool = True, in_: bool = True
27+
) -> MultiCapture[str]:
2628
return capture.MultiCapture(
2729
in_=capture.FDCapture(0) if in_ else None,
2830
out=capture.FDCapture(1) if out else None,
2931
err=capture.FDCapture(2) if err else None,
3032
)
3133

3234

33-
def StdCapture(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture:
35+
def StdCapture(
36+
out: bool = True, err: bool = True, in_: bool = True
37+
) -> MultiCapture[str]:
3438
return capture.MultiCapture(
3539
in_=capture.SysCapture(0) if in_ else None,
3640
out=capture.SysCapture(1) if out else None,
3741
err=capture.SysCapture(2) if err else None,
3842
)
3943

4044

41-
def TeeStdCapture(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture:
45+
def TeeStdCapture(
46+
out: bool = True, err: bool = True, in_: bool = True
47+
) -> MultiCapture[str]:
4248
return capture.MultiCapture(
4349
in_=capture.SysCapture(0, tee=True) if in_ else None,
4450
out=capture.SysCapture(1, tee=True) if out else None,

0 commit comments

Comments
 (0)