Make most contextmanager __exit__ signatures return Optional[bool] (#3179)

This pull request is a follow-up to https://github.com/python/mypy/issues/7214.

In short, within that mypy issue, we found it would be helpful to
determine between contextmanagers that can "swallow" exceptions vs ones
that can't. This helps prevent some false positive when using flags that
analyze control flow such as `--warn-unreachable`. To do this,
Jelle proposed assuming that only contextmanagers where the `__exit__`
returns `bool` are assumed to swallow exceptions.

This unfortunately required the following typeshed changes:

1. The typing.IO, threading.Lock, and concurrent.futures.Executor
   were all modified so `__exit__` returns `Optional[None]` instead
   of None -- along with all of their subclasses.

   I believe these three types are meant to be subclassed, so I felt
   picking the more general type was correct.

2. There were also a few concrete types (e.g. see socketserver,
   subprocess, ftplib...) that I modified to return `None` -- I checked
   the source code, and these all seem to return None (and don't appear
   to be meant to be subclassable).

3. contextlib.suppress was changed to return bool. I also double-checked
   the unittest modules and modified a subset of those contextmanagers,
   leaving ones like `_AssertRaisesContext` alone.
This commit is contained in:
Michael Lee
2019-08-16 16:13:33 -07:00
committed by Jelle Zijlstra
parent 3cfc3150f0
commit b294782183
25 changed files with 55 additions and 38 deletions

View File

@@ -292,6 +292,15 @@ Type variables and aliases you introduce purely for legibility reasons
should be prefixed with an underscore to make it obvious to the reader
they are not part of the stubbed API.
When adding type annotations for context manager classes, annotate
the return type of `__exit__` as bool only if the context manager
sometimes suppresses annotations -- if it sometimes returns `True`
at runtime. If the context manager never suppresses exceptions,
have the return type be either `None` or `Optional[bool]`. If you
are not sure whether exceptions are suppressed or not or if the
context manager is meant to be subclassed, pick `Optional[bool]`.
See https://github.com/python/mypy/issues/7214 for more details.
NOTE: there are stubs in this repository that don't conform to the
style described above. Fixing them is a great starting point for new
contributors.

View File

@@ -38,7 +38,7 @@ class BaseServer:
def __enter__(self) -> BaseServer: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[types.TracebackType]) -> bool: ...
exc_tb: Optional[types.TracebackType]) -> None: ...
if sys.version_info >= (3, 3):
def service_actions(self) -> None: ...

View File

@@ -787,7 +787,7 @@ class memoryview(Sized, Container[_mv_container_type]):
nbytes: int
def __init__(self, obj: Union[bytes, bytearray, memoryview]) -> None: ...
def __enter__(self) -> memoryview: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> None: ...
else:
def __init__(self, obj: Union[bytes, bytearray, buffer, memoryview]) -> None: ...
@@ -1615,7 +1615,7 @@ if sys.version_info < (3,):
def next(self) -> str: ...
def read(self, n: int = ...) -> str: ...
def __enter__(self) -> BinaryIO: ...
def __exit__(self, t: Optional[type] = ..., exc: Optional[BaseException] = ..., tb: Optional[Any] = ...) -> bool: ...
def __exit__(self, t: Optional[type] = ..., exc: Optional[BaseException] = ..., tb: Optional[Any] = ...) -> Optional[bool]: ...
def flush(self) -> None: ...
def fileno(self) -> int: ...
def isatty(self) -> bool: ...

View File

@@ -32,7 +32,7 @@ class _IOBase(BinaryIO):
def truncate(self, size: Optional[int] = ...) -> int: ...
def writable(self) -> bool: ...
def __enter__(self: _T) -> _T: ...
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[Any]) -> bool: ...
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[Any]) -> Optional[bool]: ...
def __iter__(self: _T) -> _T: ...
# The parameter type of writelines[s]() is determined by that of write():
def writelines(self, lines: Iterable[bytes]) -> None: ...
@@ -146,7 +146,7 @@ class _TextIOBase(TextIO):
def write(self, pbuf: unicode) -> int: ...
def writelines(self, lines: Iterable[unicode]) -> None: ...
def __enter__(self: _T) -> _T: ...
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[Any]) -> bool: ...
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[Any]) -> Optional[bool]: ...
def __iter__(self: _T) -> _T: ...
class StringIO(_TextIOBase):

View File

@@ -106,7 +106,7 @@ class Popen(Generic[_T]):
def terminate(self) -> None: ...
def kill(self) -> None: ...
def __enter__(self) -> Popen: ...
def __exit__(self, type, value, traceback) -> bool: ...
def __exit__(self, type, value, traceback) -> None: ...
def list2cmdline(seq: Sequence[str]) -> str: ... # undocumented

View File

@@ -24,7 +24,7 @@ class _TemporaryFileWrapper(IO[str]):
def __init__(self, file: IO, name: Any, delete: bool = ...) -> None: ...
def __del__(self) -> None: ...
def __enter__(self) -> _TemporaryFileWrapper: ...
def __exit__(self, exc, value, tb) -> bool: ...
def __exit__(self, exc, value, tb) -> Optional[bool]: ...
def __getattr__(self, name: unicode) -> Any: ...
def close(self) -> None: ...
def unlink(self, path: unicode) -> None: ...
@@ -88,7 +88,7 @@ class TemporaryDirectory:
dir: Union[bytes, unicode] = ...) -> None: ...
def cleanup(self) -> None: ...
def __enter__(self) -> Any: ... # Can be str or unicode
def __exit__(self, type, value, traceback) -> bool: ...
def __exit__(self, type, value, traceback) -> None: ...
@overload
def mkstemp() -> Tuple[int, str]: ...

View File

@@ -343,7 +343,7 @@ class IO(Iterator[AnyStr], Generic[AnyStr]):
def __enter__(self) -> IO[AnyStr]: ...
@abstractmethod
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException],
traceback: Optional[TracebackType]) -> bool: ...
traceback: Optional[TracebackType]) -> Optional[bool]: ...
class BinaryIO(IO[str]):
# TODO readinto

View File

@@ -787,7 +787,7 @@ class memoryview(Sized, Container[_mv_container_type]):
nbytes: int
def __init__(self, obj: Union[bytes, bytearray, memoryview]) -> None: ...
def __enter__(self) -> memoryview: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> None: ...
else:
def __init__(self, obj: Union[bytes, bytearray, buffer, memoryview]) -> None: ...
@@ -1615,7 +1615,7 @@ if sys.version_info < (3,):
def next(self) -> str: ...
def read(self, n: int = ...) -> str: ...
def __enter__(self) -> BinaryIO: ...
def __exit__(self, t: Optional[type] = ..., exc: Optional[BaseException] = ..., tb: Optional[Any] = ...) -> bool: ...
def __exit__(self, t: Optional[type] = ..., exc: Optional[BaseException] = ..., tb: Optional[Any] = ...) -> Optional[bool]: ...
def flush(self) -> None: ...
def fileno(self) -> int: ...
def isatty(self) -> bool: ...

View File

@@ -188,7 +188,7 @@ class StreamReaderWriter(TextIO):
def __enter__(self: _T) -> _T: ...
def __exit__(
self, typ: Optional[Type[BaseException]], exc: Optional[BaseException], tb: Optional[types.TracebackType]
) -> bool: ...
) -> None: ...
def __getattr__(self, name: str) -> Any: ...
# These methods don't actually exist directly, but they are needed to satisfy the TextIO
# interface. At runtime, they are delegated through __getattr__.
@@ -229,7 +229,7 @@ class StreamRecoder(BinaryIO):
def __enter__(self: _SRT) -> _SRT: ...
def __exit__(
self, type: Optional[Type[BaseException]], value: Optional[BaseException], tb: Optional[types.TracebackType]
) -> bool: ...
) -> None: ...
# These methods don't actually exist directly, but they are needed to satisfy the BinaryIO
# interface. At runtime, they are delegated through __getattr__.
def seek(self, offset: int, whence: int = ...) -> int: ...

View File

@@ -46,6 +46,9 @@ class closing(ContextManager[_T], Generic[_T]):
if sys.version_info >= (3, 4):
class suppress(ContextManager[None]):
def __init__(self, *exceptions: Type[BaseException]) -> None: ...
def __exit__(self, exctype: Optional[Type[BaseException]],
excinst: Optional[BaseException],
exctb: Optional[TracebackType]) -> bool: ...
class redirect_stdout(ContextManager[None]):
def __init__(self, new_target: IO[str]) -> None: ...
@@ -69,6 +72,9 @@ if sys.version_info >= (3,):
def pop_all(self: _U) -> _U: ...
def close(self) -> None: ...
def __enter__(self: _U) -> _U: ...
def __exit__(self, __exc_type: Optional[Type[BaseException]],
__exc_value: Optional[BaseException],
__traceback: Optional[TracebackType]) -> bool: ...
if sys.version_info >= (3, 7):
from typing import Awaitable
@@ -94,6 +100,9 @@ if sys.version_info >= (3, 7):
def pop_all(self: _S) -> _S: ...
def aclose(self) -> Awaitable[None]: ...
def __aenter__(self: _S) -> Awaitable[_S]: ...
def __aexit__(self, __exc_type: Optional[Type[BaseException]],
__exc_value: Optional[BaseException],
__traceback: Optional[TracebackType]) -> Awaitable[bool]: ...
if sys.version_info >= (3, 7):
@overload

View File

@@ -44,7 +44,7 @@ class FTP:
encoding: str
def __enter__(self: _T) -> _T: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...
else:
file: Optional[BinaryIO]

View File

@@ -77,7 +77,7 @@ class TarFile(Iterable[TarInfo]):
def __exit__(self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...
def __iter__(self) -> Iterator[TarInfo]: ...
@classmethod
def open(cls, name: Optional[_Path] = ..., mode: str = ...,

View File

@@ -81,7 +81,7 @@ class Lock:
def __enter__(self) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
if sys.version_info >= (3,):
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
else:
@@ -95,7 +95,7 @@ class _RLock:
def __enter__(self) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
if sys.version_info >= (3,):
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
else:
@@ -111,7 +111,7 @@ class Condition:
def __enter__(self) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
if sys.version_info >= (3,):
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
else:
@@ -131,7 +131,7 @@ class Semaphore:
def __enter__(self) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
if sys.version_info >= (3,):
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
else:
@@ -143,7 +143,7 @@ class BoundedSemaphore:
def __enter__(self) -> bool: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
if sys.version_info >= (3,):
def acquire(self, blocking: bool = ..., timeout: float = ...) -> bool: ...
else:

View File

@@ -43,4 +43,4 @@ class catch_warnings:
def __enter__(self) -> Optional[List[_Record]]: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...

View File

@@ -55,7 +55,7 @@ class ZipFile:
def __enter__(self) -> ZipFile: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...
def close(self) -> None: ...
def getinfo(self, name: Text) -> ZipInfo: ...
def infolist(self) -> List[ZipInfo]: ...

View File

@@ -53,7 +53,7 @@ class Executor:
def map(self, func: Callable[..., _T], *iterables: Iterable[Any], timeout: Optional[float] = ...,) -> Iterator[_T]: ...
def shutdown(self, wait: bool = ...) -> None: ...
def __enter__(self: _T) -> _T: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Optional[bool]: ...
def as_completed(fs: Iterable[Future[_T]], timeout: Optional[float] = ...) -> Iterator[Future[_T]]: ...

View File

@@ -80,8 +80,7 @@ responses: Dict[int, str]
class HTTPMessage(email.message.Message): ...
# Ignore errors to work around python/mypy#5027
class HTTPResponse(io.BufferedIOBase, BinaryIO): # type: ignore
class HTTPResponse(io.BufferedIOBase, BinaryIO):
msg: HTTPMessage
headers: HTTPMessage
version: int
@@ -103,7 +102,7 @@ class HTTPResponse(io.BufferedIOBase, BinaryIO): # type: ignore
def __enter__(self) -> HTTPResponse: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[types.TracebackType]) -> bool: ...
exc_tb: Optional[types.TracebackType]) -> Optional[bool]: ...
def info(self) -> email.message.Message: ...
def geturl(self) -> str: ...
def getcode(self) -> int: ...

View File

@@ -28,7 +28,7 @@ class IOBase:
def __next__(self) -> bytes: ...
def __enter__(self: _T) -> _T: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
def close(self) -> None: ...
def fileno(self) -> int: ...
def flush(self) -> None: ...
@@ -86,7 +86,7 @@ class BytesIO(BinaryIO):
def __next__(self) -> bytes: ...
def __enter__(self) -> BytesIO: ...
def __exit__(self, t: Optional[Type[BaseException]] = ..., value: Optional[BaseException] = ...,
traceback: Optional[TracebackType] = ...) -> bool: ...
traceback: Optional[TracebackType] = ...) -> Optional[bool]: ...
def close(self) -> None: ...
def fileno(self) -> int: ...
def flush(self) -> None: ...
@@ -165,7 +165,7 @@ class TextIOWrapper(TextIO):
) -> None: ...
# copied from IOBase
def __exit__(self, t: Optional[Type[BaseException]] = ..., value: Optional[BaseException] = ...,
traceback: Optional[TracebackType] = ...) -> bool: ...
traceback: Optional[TracebackType] = ...) -> Optional[bool]: ...
def close(self) -> None: ...
def fileno(self) -> int: ...
def flush(self) -> None: ...

View File

@@ -38,7 +38,7 @@ class BaseServer:
def __enter__(self) -> BaseServer: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[types.TracebackType]) -> bool: ...
exc_tb: Optional[types.TracebackType]) -> None: ...
if sys.version_info >= (3, 3):
def service_actions(self) -> None: ...

View File

@@ -1170,7 +1170,7 @@ class Popen(Generic[AnyStr]):
def terminate(self) -> None: ...
def kill(self) -> None: ...
def __enter__(self) -> Popen: ...
def __exit__(self, type: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> bool: ...
def __exit__(self, type: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> None: ...
# The result really is always a str.
def getstatusoutput(cmd: _TXT) -> Tuple[int, str]: ...

View File

@@ -38,7 +38,7 @@ class SpooledTemporaryFile(IO[AnyStr]):
def __enter__(self) -> SpooledTemporaryFile: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...
# These methods are copied from the abstract methods of IO, because
# SpooledTemporaryFile implements IO.
@@ -69,7 +69,7 @@ class TemporaryDirectory(Generic[AnyStr]):
def __enter__(self) -> AnyStr: ...
def __exit__(self, exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...
def mkstemp(suffix: Optional[AnyStr] = ..., prefix: Optional[AnyStr] = ..., dir: Optional[AnyStr] = ...,
text: bool = ...) -> Tuple[int, AnyStr]: ...

View File

@@ -479,7 +479,7 @@ class IO(Iterator[AnyStr], Generic[AnyStr]):
def __enter__(self) -> IO[AnyStr]: ...
@abstractmethod
def __exit__(self, t: Optional[Type[BaseException]], value: Optional[BaseException],
traceback: Optional[TracebackType]) -> bool: ...
traceback: Optional[TracebackType]) -> Optional[bool]: ...
class BinaryIO(IO[bytes]):
# TODO readinto

View File

@@ -214,11 +214,11 @@ class _AssertWarnsContext:
lineno: int
def __enter__(self) -> _AssertWarnsContext: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> None: ...
class _AssertLogsContext:
records: List[logging.LogRecord]
output: List[str]
def __enter__(self) -> _AssertLogsContext: ...
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]) -> bool: ...
exc_tb: Optional[TracebackType]) -> Optional[bool]: ...

View File

@@ -7,7 +7,7 @@ _AIUT = TypeVar("_AIUT", bound=addbase)
class addbase(BinaryIO):
def __enter__(self: _AIUT) -> _AIUT: ...
def __exit__(self, type: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> bool: ...
def __exit__(self, type: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> None: ...
def __iter__(self: _AIUT) -> _AIUT: ...
def __next__(self) -> bytes: ...
def close(self) -> None: ...

View File

@@ -53,7 +53,7 @@ class Executor:
def map(self, func: Callable[..., _T], *iterables: Iterable[Any], timeout: Optional[float] = ...,) -> Iterator[_T]: ...
def shutdown(self, wait: bool = ...) -> None: ...
def __enter__(self: _T) -> _T: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool: ...
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Optional[bool]: ...
def as_completed(fs: Iterable[Future[_T]], timeout: Optional[float] = ...) -> Iterator[Future[_T]]: ...