Skip to content

Commit acc9310

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

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,32 +499,34 @@ 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
# Can't use slots in Python<3.5.3 due to https://bugs.python.org/issue31272
502506
if sys.version_info >= (3, 5, 3):
503507
__slots__ = ("out", "err")
504508

505-
def __init__(self, out, err) -> None:
506-
self.out = out
507-
self.err = err
509+
def __init__(self, out: AnyStr, err: AnyStr) -> None:
510+
self.out = out # type: AnyStr
511+
self.err = err # type: AnyStr
508512

509513
def __len__(self) -> int:
510514
return 2
511515

512-
def __iter__(self):
516+
def __iter__(self) -> Iterator[AnyStr]:
513517
return iter((self.out, self.err))
514518

515-
def __getitem__(self, item: int):
519+
def __getitem__(self, item: int) -> AnyStr:
516520
return tuple(self)[item]
517521

518-
def _replace(self, out=None, err=None) -> "CaptureResult":
522+
def _replace(
523+
self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None
524+
) -> "CaptureResult[AnyStr]":
519525
return CaptureResult(
520526
out=self.out if out is None else out, err=self.err if err is None else err
521527
)
522528

523-
def count(self, value) -> int:
529+
def count(self, value: AnyStr) -> int:
524530
return tuple(self).count(value)
525531

526532
def index(self, value) -> int:
@@ -543,7 +549,7 @@ def __repr__(self) -> str:
543549
return "CaptureResult(out={!r}, err={!r})".format(self.out, self.err)
544550

545551

546-
class MultiCapture:
552+
class MultiCapture(Generic[AnyStr]):
547553
_state = None
548554
_in_suspended = False
549555

@@ -566,7 +572,7 @@ def start_capturing(self) -> None:
566572
if self.err:
567573
self.err.start()
568574

569-
def pop_outerr_to_orig(self):
575+
def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
570576
"""Pop current snapshot out/err capture and flush to orig streams."""
571577
out, err = self.readouterr()
572578
if out:
@@ -607,7 +613,7 @@ def stop_capturing(self) -> None:
607613
if self.in_:
608614
self.in_.done()
609615

610-
def readouterr(self) -> CaptureResult:
616+
def readouterr(self) -> CaptureResult[AnyStr]:
611617
if self.out:
612618
out = self.out.snap()
613619
else:
@@ -619,7 +625,7 @@ def readouterr(self) -> CaptureResult:
619625
return CaptureResult(out, err)
620626

621627

622-
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture:
628+
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]:
623629
if method == "fd":
624630
return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
625631
elif method == "sys":
@@ -657,8 +663,8 @@ class CaptureManager:
657663

658664
def __init__(self, method: "_CaptureMethod") -> None:
659665
self._method = method
660-
self._global_capturing = None # type: Optional[MultiCapture]
661-
self._capture_fixture = None # type: Optional[CaptureFixture]
666+
self._global_capturing = None # type: Optional[MultiCapture[str]]
667+
self._capture_fixture = None # type: Optional[CaptureFixture[Any]]
662668

663669
def __repr__(self) -> str:
664670
return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
@@ -707,13 +713,13 @@ def resume(self) -> None:
707713
self.resume_global_capture()
708714
self.resume_fixture()
709715

710-
def read_global_capture(self):
716+
def read_global_capture(self) -> CaptureResult[str]:
711717
assert self._global_capturing is not None
712718
return self._global_capturing.readouterr()
713719

714720
# Fixture Control
715721

716-
def set_fixture(self, capture_fixture: "CaptureFixture") -> None:
722+
def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
717723
if self._capture_fixture:
718724
current_fixture = self._capture_fixture.request.fixturename
719725
requested_fixture = capture_fixture.request.fixturename
@@ -812,14 +818,14 @@ def pytest_internalerror(self) -> None:
812818
self.stop_global_capturing()
813819

814820

815-
class CaptureFixture:
821+
class CaptureFixture(Generic[AnyStr]):
816822
"""Object returned by the :py:func:`capsys`, :py:func:`capsysbinary`,
817823
:py:func:`capfd` and :py:func:`capfdbinary` fixtures."""
818824

819825
def __init__(self, captureclass, request: SubRequest) -> None:
820826
self.captureclass = captureclass
821827
self.request = request
822-
self._capture = None # type: Optional[MultiCapture]
828+
self._capture = None # type: Optional[MultiCapture[AnyStr]]
823829
self._captured_out = self.captureclass.EMPTY_BUFFER
824830
self._captured_err = self.captureclass.EMPTY_BUFFER
825831

@@ -838,7 +844,7 @@ def close(self) -> None:
838844
self._capture.stop_capturing()
839845
self._capture = None
840846

841-
def readouterr(self):
847+
def readouterr(self) -> CaptureResult[AnyStr]:
842848
"""Read and return the captured output so far, resetting the internal
843849
buffer.
844850
@@ -877,15 +883,15 @@ def disabled(self) -> Generator[None, None, None]:
877883

878884

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

895901

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

912918

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

929935

930936
@pytest.fixture
931-
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
937+
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
932938
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
933939
934940
The captured output is made available via ``capfd.readouterr()`` method
935941
calls, which return a ``(out, err)`` namedtuple.
936942
``out`` and ``err`` will be ``byte`` objects.
937943
"""
938944
capman = request.config.pluginmanager.getplugin("capturemanager")
939-
capture_fixture = CaptureFixture(FDCaptureBinary, request)
945+
capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request)
940946
capman.set_fixture(capture_fixture)
941947
capture_fixture._start()
942948
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)