capture: add type annotations to CaptureFixture

It now has a str/bytes type parameter.
This commit is contained in:
Ran Benita 2020-08-09 20:15:19 +03:00
parent 8a66f0a96d
commit acc9310c17
2 changed files with 42 additions and 30 deletions

View File

@ -6,7 +6,11 @@ import os
import sys import sys
from io import UnsupportedOperation from io import UnsupportedOperation
from tempfile import TemporaryFile from tempfile import TemporaryFile
from typing import Any
from typing import AnyStr
from typing import Generator from typing import Generator
from typing import Generic
from typing import Iterator
from typing import Optional from typing import Optional
from typing import TextIO from typing import TextIO
from typing import Tuple from typing import Tuple
@ -495,32 +499,34 @@ class FDCapture(FDCaptureBinary):
# make it a namedtuple again. # make it a namedtuple again.
# [0]: https://github.com/python/mypy/issues/685 # [0]: https://github.com/python/mypy/issues/685
@functools.total_ordering @functools.total_ordering
class CaptureResult: class CaptureResult(Generic[AnyStr]):
"""The result of :method:`CaptureFixture.readouterr`.""" """The result of :method:`CaptureFixture.readouterr`."""
# Can't use slots in Python<3.5.3 due to https://bugs.python.org/issue31272 # Can't use slots in Python<3.5.3 due to https://bugs.python.org/issue31272
if sys.version_info >= (3, 5, 3): if sys.version_info >= (3, 5, 3):
__slots__ = ("out", "err") __slots__ = ("out", "err")
def __init__(self, out, err) -> None: def __init__(self, out: AnyStr, err: AnyStr) -> None:
self.out = out self.out = out # type: AnyStr
self.err = err self.err = err # type: AnyStr
def __len__(self) -> int: def __len__(self) -> int:
return 2 return 2
def __iter__(self): def __iter__(self) -> Iterator[AnyStr]:
return iter((self.out, self.err)) return iter((self.out, self.err))
def __getitem__(self, item: int): def __getitem__(self, item: int) -> AnyStr:
return tuple(self)[item] return tuple(self)[item]
def _replace(self, out=None, err=None) -> "CaptureResult": def _replace(
self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None
) -> "CaptureResult[AnyStr]":
return CaptureResult( return CaptureResult(
out=self.out if out is None else out, err=self.err if err is None else err out=self.out if out is None else out, err=self.err if err is None else err
) )
def count(self, value) -> int: def count(self, value: AnyStr) -> int:
return tuple(self).count(value) return tuple(self).count(value)
def index(self, value) -> int: def index(self, value) -> int:
@ -543,7 +549,7 @@ class CaptureResult:
return "CaptureResult(out={!r}, err={!r})".format(self.out, self.err) return "CaptureResult(out={!r}, err={!r})".format(self.out, self.err)
class MultiCapture: class MultiCapture(Generic[AnyStr]):
_state = None _state = None
_in_suspended = False _in_suspended = False
@ -566,7 +572,7 @@ class MultiCapture:
if self.err: if self.err:
self.err.start() self.err.start()
def pop_outerr_to_orig(self): def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
"""Pop current snapshot out/err capture and flush to orig streams.""" """Pop current snapshot out/err capture and flush to orig streams."""
out, err = self.readouterr() out, err = self.readouterr()
if out: if out:
@ -607,7 +613,7 @@ class MultiCapture:
if self.in_: if self.in_:
self.in_.done() self.in_.done()
def readouterr(self) -> CaptureResult: def readouterr(self) -> CaptureResult[AnyStr]:
if self.out: if self.out:
out = self.out.snap() out = self.out.snap()
else: else:
@ -619,7 +625,7 @@ class MultiCapture:
return CaptureResult(out, err) return CaptureResult(out, err)
def _get_multicapture(method: "_CaptureMethod") -> MultiCapture: def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]:
if method == "fd": if method == "fd":
return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
elif method == "sys": elif method == "sys":
@ -657,8 +663,8 @@ class CaptureManager:
def __init__(self, method: "_CaptureMethod") -> None: def __init__(self, method: "_CaptureMethod") -> None:
self._method = method self._method = method
self._global_capturing = None # type: Optional[MultiCapture] self._global_capturing = None # type: Optional[MultiCapture[str]]
self._capture_fixture = None # type: Optional[CaptureFixture] self._capture_fixture = None # type: Optional[CaptureFixture[Any]]
def __repr__(self) -> str: def __repr__(self) -> str:
return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format( return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
@ -707,13 +713,13 @@ class CaptureManager:
self.resume_global_capture() self.resume_global_capture()
self.resume_fixture() self.resume_fixture()
def read_global_capture(self): def read_global_capture(self) -> CaptureResult[str]:
assert self._global_capturing is not None assert self._global_capturing is not None
return self._global_capturing.readouterr() return self._global_capturing.readouterr()
# Fixture Control # Fixture Control
def set_fixture(self, capture_fixture: "CaptureFixture") -> None: def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
if self._capture_fixture: if self._capture_fixture:
current_fixture = self._capture_fixture.request.fixturename current_fixture = self._capture_fixture.request.fixturename
requested_fixture = capture_fixture.request.fixturename requested_fixture = capture_fixture.request.fixturename
@ -812,14 +818,14 @@ class CaptureManager:
self.stop_global_capturing() self.stop_global_capturing()
class CaptureFixture: class CaptureFixture(Generic[AnyStr]):
"""Object returned by the :py:func:`capsys`, :py:func:`capsysbinary`, """Object returned by the :py:func:`capsys`, :py:func:`capsysbinary`,
:py:func:`capfd` and :py:func:`capfdbinary` fixtures.""" :py:func:`capfd` and :py:func:`capfdbinary` fixtures."""
def __init__(self, captureclass, request: SubRequest) -> None: def __init__(self, captureclass, request: SubRequest) -> None:
self.captureclass = captureclass self.captureclass = captureclass
self.request = request self.request = request
self._capture = None # type: Optional[MultiCapture] self._capture = None # type: Optional[MultiCapture[AnyStr]]
self._captured_out = self.captureclass.EMPTY_BUFFER self._captured_out = self.captureclass.EMPTY_BUFFER
self._captured_err = self.captureclass.EMPTY_BUFFER self._captured_err = self.captureclass.EMPTY_BUFFER
@ -838,7 +844,7 @@ class CaptureFixture:
self._capture.stop_capturing() self._capture.stop_capturing()
self._capture = None self._capture = None
def readouterr(self): def readouterr(self) -> CaptureResult[AnyStr]:
"""Read and return the captured output so far, resetting the internal """Read and return the captured output so far, resetting the internal
buffer. buffer.
@ -877,7 +883,7 @@ class CaptureFixture:
@pytest.fixture @pytest.fixture
def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]: def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method The captured output is made available via ``capsys.readouterr()`` method
@ -885,7 +891,7 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]:
``out`` and ``err`` will be ``text`` objects. ``out`` and ``err`` will be ``text`` objects.
""" """
capman = request.config.pluginmanager.getplugin("capturemanager") capman = request.config.pluginmanager.getplugin("capturemanager")
capture_fixture = CaptureFixture(SysCapture, request) capture_fixture = CaptureFixture[str](SysCapture, request)
capman.set_fixture(capture_fixture) capman.set_fixture(capture_fixture)
capture_fixture._start() capture_fixture._start()
yield capture_fixture yield capture_fixture
@ -894,7 +900,7 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture, None, None]:
@pytest.fixture @pytest.fixture
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]: def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()`` The captured output is made available via ``capsysbinary.readouterr()``
@ -902,7 +908,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
``out`` and ``err`` will be ``bytes`` objects. ``out`` and ``err`` will be ``bytes`` objects.
""" """
capman = request.config.pluginmanager.getplugin("capturemanager") capman = request.config.pluginmanager.getplugin("capturemanager")
capture_fixture = CaptureFixture(SysCaptureBinary, request) capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request)
capman.set_fixture(capture_fixture) capman.set_fixture(capture_fixture)
capture_fixture._start() capture_fixture._start()
yield capture_fixture yield capture_fixture
@ -911,7 +917,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
@pytest.fixture @pytest.fixture
def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]: def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
"""Enable text capturing of writes to file descriptors ``1`` and ``2``. """Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method The captured output is made available via ``capfd.readouterr()`` method
@ -919,7 +925,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]:
``out`` and ``err`` will be ``text`` objects. ``out`` and ``err`` will be ``text`` objects.
""" """
capman = request.config.pluginmanager.getplugin("capturemanager") capman = request.config.pluginmanager.getplugin("capturemanager")
capture_fixture = CaptureFixture(FDCapture, request) capture_fixture = CaptureFixture[str](FDCapture, request)
capman.set_fixture(capture_fixture) capman.set_fixture(capture_fixture)
capture_fixture._start() capture_fixture._start()
yield capture_fixture yield capture_fixture
@ -928,7 +934,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture, None, None]:
@pytest.fixture @pytest.fixture
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]: def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. """Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method The captured output is made available via ``capfd.readouterr()`` method
@ -936,7 +942,7 @@ def capfdbinary(request: SubRequest) -> Generator[CaptureFixture, None, None]:
``out`` and ``err`` will be ``byte`` objects. ``out`` and ``err`` will be ``byte`` objects.
""" """
capman = request.config.pluginmanager.getplugin("capturemanager") capman = request.config.pluginmanager.getplugin("capturemanager")
capture_fixture = CaptureFixture(FDCaptureBinary, request) capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request)
capman.set_fixture(capture_fixture) capman.set_fixture(capture_fixture)
capture_fixture._start() capture_fixture._start()
yield capture_fixture yield capture_fixture

View File

@ -22,7 +22,9 @@ from _pytest.config import ExitCode
# pylib 1.4.20.dev2 (rev 13d9af95547e) # pylib 1.4.20.dev2 (rev 13d9af95547e)
def StdCaptureFD(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture: def StdCaptureFD(
out: bool = True, err: bool = True, in_: bool = True
) -> MultiCapture[str]:
return capture.MultiCapture( return capture.MultiCapture(
in_=capture.FDCapture(0) if in_ else None, in_=capture.FDCapture(0) if in_ else None,
out=capture.FDCapture(1) if out else None, out=capture.FDCapture(1) if out else None,
@ -30,7 +32,9 @@ def StdCaptureFD(out: bool = True, err: bool = True, in_: bool = True) -> MultiC
) )
def StdCapture(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture: def StdCapture(
out: bool = True, err: bool = True, in_: bool = True
) -> MultiCapture[str]:
return capture.MultiCapture( return capture.MultiCapture(
in_=capture.SysCapture(0) if in_ else None, in_=capture.SysCapture(0) if in_ else None,
out=capture.SysCapture(1) if out else None, out=capture.SysCapture(1) if out else None,
@ -38,7 +42,9 @@ def StdCapture(out: bool = True, err: bool = True, in_: bool = True) -> MultiCap
) )
def TeeStdCapture(out: bool = True, err: bool = True, in_: bool = True) -> MultiCapture: def TeeStdCapture(
out: bool = True, err: bool = True, in_: bool = True
) -> MultiCapture[str]:
return capture.MultiCapture( return capture.MultiCapture(
in_=capture.SysCapture(0, tee=True) if in_ else None, in_=capture.SysCapture(0, tee=True) if in_ else None,
out=capture.SysCapture(1, tee=True) if out else None, out=capture.SysCapture(1, tee=True) if out else None,