Fix typechecking with recent mypy releases (#59)

* sync with latest typeshed stub file (closes #54)
* publish `dev/mypy.allowlist` in sdist (closes #53)
* drop Python 3.7 support due to positional-only arg
  syntax in the updated stub file
This commit is contained in:
Alyssa Coghlan 2024-05-23 00:26:48 +10:00 committed by GitHub
parent 7b862aaa80
commit 8fe4d73971
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 189 additions and 97 deletions

View file

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
max-parallel: 5 max-parallel: 5
matrix: matrix:
python-version: [3.7, 3.8, 3.9, '3.10', 3.11, 3.12, 'pypy-3.10'] python-version: [3.8, 3.9, '3.10', 3.11, 3.12, 'pypy-3.10']
# Check https://github.com/actions/action-versions/tree/main/config/actions # Check https://github.com/actions/action-versions/tree/main/config/actions
# for latest versions if the standard actions start emitting warnings # for latest versions if the standard actions start emitting warnings

View file

@ -1,4 +1,4 @@
include *.py *.cfg *.txt *.rst *.md *.ini MANIFEST.in include *.py *.cfg *.txt *.rst *.md *.ini MANIFEST.in dev/mypy.allowlist
recursive-include contextlib2 *.py *.pyi py.typed recursive-include contextlib2 *.py *.pyi py.typed
recursive-include docs *.rst *.py make.bat Makefile recursive-include docs *.rst *.py make.bat Makefile
recursive-include test *.py recursive-include test *.py

View file

@ -1,6 +1,28 @@
Release History Release History
--------------- ---------------
24.6.0 (2024-06-??)
^^^^^^^^^^^^^^^^^^^
* Due to the use of positional-only argument syntax, the minimum supported
Python version is now Python 3.8.
* Update ``mypy stubtest`` to work with recent mypy versions (mypy 1.8.0 tested)
(`#54 <https://github.com/jazzband/contextlib2/issues/54>`__)
* The ``dev/mypy.allowlist`` file needed for the ``mypy stubtest`` step in the
``tox`` test configuration is now included in the published sdist
(`#53 <https://github.com/jazzband/contextlib2/issues/53>`__)
* Type hints have been updated to include ``nullcontext`` (3.10 API added in
21.6.0) (`#41 <https://github.com/jazzband/contextlib2/issues/41>`__)
* Test suite updated to pass on Python 3.11 and 3.12 (21.6.0 works on these
versions, the test suite just failed due to no longer valid assumptions)
(`#51 <https://github.com/jazzband/contextlib2/issues/51>`__)
* Updates to the default compatibility testing matrix:
* Added: CPython 3.11, CPython 3.12
* Dropped: CPython 3.6, CPython 3.7
python -m mypy.stubtest --allowlist dev/mypy.allowlist contextlib2
21.6.0 (2021-06-27) 21.6.0 (2021-06-27)
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^

View file

@ -8,7 +8,7 @@ from collections import deque
from functools import wraps from functools import wraps
from types import MethodType from types import MethodType
# Python 3.6/3.7/3.8 compatibility: GenericAlias may not be defined # Python 3.7/3.8 compatibility: GenericAlias may not be defined
try: try:
from types import GenericAlias from types import GenericAlias
except ImportError: except ImportError:
@ -23,8 +23,6 @@ __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
"AsyncExitStack", "ContextDecorator", "ExitStack", "AsyncExitStack", "ContextDecorator", "ExitStack",
"redirect_stdout", "redirect_stderr", "suppress", "aclosing"] "redirect_stdout", "redirect_stderr", "suppress", "aclosing"]
# Backwards compatibility
__all__ += ["ContextStack"]
class AbstractContextManager(abc.ABC): class AbstractContextManager(abc.ABC):
"""An abstract base class for context managers.""" """An abstract base class for context managers."""

View file

@ -1,132 +1,204 @@
# Type hints copied from the typeshed project under the Apache License 2.0 # Type hints copied from the typeshed project under the Apache License 2.0
# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE # https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE
import sys # For updates: https://github.com/python/typeshed/blob/main/stdlib/contextlib.pyi
from ._typeshed import Self
from types import TracebackType # Last updated: 2024-05-22
from typing import ( # Updated from: https://github.com/python/typeshed/blob/aa2d33df211e1e4f70883388febf750ac524d2bb/stdlib/contextlib.pyi
IO,
Any,
AsyncContextManager,
AsyncIterator,
Awaitable,
Callable,
ContextManager,
Iterator,
Optional,
Type,
TypeVar,
overload,
)
from typing_extensions import ParamSpec, Protocol
# contextlib2 API adaptation notes: # contextlib2 API adaptation notes:
# * the various 'if True:' guards replace sys.version checks in the original # * the various 'if True:' guards replace sys.version checks in the original
# typeshed file (those APIs are available on all supported versions) # typeshed file (those APIs are available on all supported versions)
# * any commented out 'if True:' guards replace sys.version checks in the original
# typeshed file where the affected APIs haven't been backported yet
# * deliberately omitted APIs are listed in `dev/mypy.allowlist` # * deliberately omitted APIs are listed in `dev/mypy.allowlist`
# (e.g. deprecated experimental APIs that never graduated to the stdlib) # (e.g. deprecated experimental APIs that never graduated to the stdlib)
AbstractContextManager = ContextManager import abc
import sys
from _typeshed import FileDescriptorOrPath, Unused
from abc import abstractmethod
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator
from types import TracebackType
from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable
from typing_extensions import ParamSpec, Self, TypeAlias
__all__ = [
"contextmanager",
"closing",
"AbstractContextManager",
"ContextDecorator",
"ExitStack",
"redirect_stdout",
"redirect_stderr",
"suppress",
"AbstractAsyncContextManager",
"AsyncExitStack",
"asynccontextmanager",
"nullcontext",
]
if True: if True:
AbstractAsyncContextManager = AsyncContextManager __all__ += ["aclosing"]
# if True:
# __all__ += ["chdir"]
_T = TypeVar("_T") _T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True) _T_co = TypeVar("_T_co", covariant=True)
_T_io = TypeVar("_T_io", bound=Optional[IO[str]]) _T_io = TypeVar("_T_io", bound=IO[str] | None)
_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None)
_F = TypeVar("_F", bound=Callable[..., Any]) _F = TypeVar("_F", bound=Callable[..., Any])
_P = ParamSpec("_P") _P = ParamSpec("_P")
_ExitFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], bool] _ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None]
_CM_EF = TypeVar("_CM_EF", ContextManager[Any], _ExitFunc) _CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc)
class _GeneratorContextManager(ContextManager[_T_co]): @runtime_checkable
class AbstractContextManager(Protocol[_T_co, _ExitT_co]):
def __enter__(self) -> _T_co: ...
@abstractmethod
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
) -> _ExitT_co: ...
@runtime_checkable
class AbstractAsyncContextManager(Protocol[_T_co, _ExitT_co]):
async def __aenter__(self) -> _T_co: ...
@abstractmethod
async def __aexit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
) -> _ExitT_co: ...
class ContextDecorator:
def __call__(self, func: _F) -> _F: ... def __call__(self, func: _F) -> _F: ...
# type ignore to deal with incomplete ParamSpec support in mypy class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator):
def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P, _GeneratorContextManager[_T]]: ... # type: ignore # __init__ and all instance attributes are actually inherited from _GeneratorContextManagerBase
# _GeneratorContextManagerBase is more trouble than it's worth to include in the stub; see #6676
def __init__(self, func: Callable[..., Iterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
gen: Generator[_T_co, Any, Any]
func: Callable[..., Generator[_T_co, Any, Any]]
args: tuple[Any, ...]
kwds: dict[str, Any]
if False:
def __exit__(
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> bool | None: ...
else:
def __exit__(
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> bool | None: ...
def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ...
if True: if True:
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
class AsyncContextDecorator:
def __call__(self, func: _AF) -> _AF: ...
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator):
# __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase,
# which is more trouble than it's worth to include in the stub (see #6676)
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
gen: AsyncGenerator[_T_co, Any]
func: Callable[..., AsyncGenerator[_T_co, Any]]
args: tuple[Any, ...]
kwds: dict[str, Any]
async def __aexit__(
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> bool | None: ...
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
class _SupportsClose(Protocol): class _SupportsClose(Protocol):
def close(self) -> object: ... def close(self) -> object: ...
_SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose) _SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose)
class closing(ContextManager[_SupportsCloseT]): class closing(AbstractContextManager[_SupportsCloseT, None]):
def __init__(self, thing: _SupportsCloseT) -> None: ... def __init__(self, thing: _SupportsCloseT) -> None: ...
def __exit__(self, *exc_info: Unused) -> None: ...
if True: if True:
class _SupportsAclose(Protocol): class _SupportsAclose(Protocol):
def aclose(self) -> Awaitable[object]: ... def aclose(self) -> Awaitable[object]: ...
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose) _SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
class aclosing(AsyncContextManager[_SupportsAcloseT]):
class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]):
def __init__(self, thing: _SupportsAcloseT) -> None: ... def __init__(self, thing: _SupportsAcloseT) -> None: ...
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) async def __aexit__(self, *exc_info: Unused) -> None: ...
class AsyncContextDecorator:
def __call__(self, func: _AF) -> _AF: ...
class suppress(ContextManager[None]): class suppress(AbstractContextManager[None, bool]):
def __init__(self, *exceptions: Type[BaseException]) -> None: ... def __init__(self, *exceptions: type[BaseException]) -> None: ...
def __exit__( def __exit__(
self, exctype: Optional[Type[BaseException]], excinst: Optional[BaseException], exctb: Optional[TracebackType] self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
) -> bool: ... ) -> bool: ...
class redirect_stdout(ContextManager[_T_io]): class _RedirectStream(AbstractContextManager[_T_io, None]):
def __init__(self, new_target: _T_io) -> None: ... def __init__(self, new_target: _T_io) -> None: ...
def __exit__(
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
) -> None: ...
class redirect_stderr(ContextManager[_T_io]): class redirect_stdout(_RedirectStream[_T_io]): ...
def __init__(self, new_target: _T_io) -> None: ... class redirect_stderr(_RedirectStream[_T_io]): ...
class ContextDecorator: # In reality this is a subclass of `AbstractContextManager`;
def __call__(self, func: _F) -> _F: ... # see #7961 for why we don't do that in the stub
class ExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
class ExitStack(ContextManager[ExitStack]): def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
def __init__(self) -> None: ...
def enter_context(self, cm: ContextManager[_T]) -> _T: ...
def push(self, exit: _CM_EF) -> _CM_EF: ... def push(self, exit: _CM_EF) -> _CM_EF: ...
def callback(self, callback: Callable[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ... def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
def pop_all(self: Self) -> Self: ... def pop_all(self) -> Self: ...
def close(self) -> None: ... def close(self) -> None: ...
def __enter__(self: Self) -> Self: ... def __enter__(self) -> Self: ...
def __exit__( def __exit__(
self, self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
__exc_type: Optional[Type[BaseException]], ) -> _ExitT_co: ...
__exc_value: Optional[BaseException],
__traceback: Optional[TracebackType], _ExitCoroFunc: TypeAlias = Callable[
[type[BaseException] | None, BaseException | None, TracebackType | None], Awaitable[bool | None]
]
_ACM_EF = TypeVar("_ACM_EF", bound=AbstractAsyncContextManager[Any, Any] | _ExitCoroFunc)
# In reality this is a subclass of `AbstractAsyncContextManager`;
# see #7961 for why we don't do that in the stub
class AsyncExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
async def enter_async_context(self, cm: AbstractAsyncContextManager[_T, _ExitT_co]) -> _T: ...
def push(self, exit: _CM_EF) -> _CM_EF: ...
def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ...
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
def push_async_callback(
self, callback: Callable[_P, Awaitable[_T]], /, *args: _P.args, **kwds: _P.kwargs
) -> Callable[_P, Awaitable[_T]]: ...
def pop_all(self) -> Self: ...
async def aclose(self) -> None: ...
async def __aenter__(self) -> Self: ...
async def __aexit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
) -> bool: ... ) -> bool: ...
if True: if True:
_ExitCoroFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], Awaitable[bool]] class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]):
_CallbackCoroFunc = Callable[..., Awaitable[Any]]
_ACM_EF = TypeVar("_ACM_EF", AsyncContextManager[Any], _ExitCoroFunc)
class AsyncExitStack(AsyncContextManager[AsyncExitStack]):
def __init__(self) -> None: ...
def enter_context(self, cm: ContextManager[_T]) -> _T: ...
def enter_async_context(self, cm: AsyncContextManager[_T]) -> Awaitable[_T]: ...
def push(self, exit: _CM_EF) -> _CM_EF: ...
def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ...
def callback(self, callback: Callable[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ...
def push_async_callback(self, callback: _CallbackCoroFunc, *args: Any, **kwds: Any) -> _CallbackCoroFunc: ...
def pop_all(self: Self) -> Self: ...
def aclose(self) -> Awaitable[None]: ...
def __aenter__(self: Self) -> Awaitable[Self]: ...
def __aexit__(
self,
__exc_type: Optional[Type[BaseException]],
__exc_value: Optional[BaseException],
__traceback: Optional[TracebackType],
) -> Awaitable[bool]: ...
if True:
class nullcontext(AbstractContextManager[_T], AbstractAsyncContextManager[_T]):
enter_result: _T enter_result: _T
@overload @overload
def __init__(self: nullcontext[None], enter_result: None = ...) -> None: ... def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
@overload @overload
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
def __enter__(self) -> _T: ... def __enter__(self) -> _T: ...
def __exit__(self, *exctype: Any) -> None: ... def __exit__(self, *exctype: Unused) -> None: ...
async def __aenter__(self) -> _T: ... async def __aenter__(self) -> _T: ...
async def __aexit__(self, *exctype: Any) -> None: ... async def __aexit__(self, *exctype: Unused) -> None: ...
# if True:
# _T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath)
# class chdir(AbstractContextManager[None, None], Generic[_T_fd_or_any_path]):
# path: _T_fd_or_any_path
# def __init__(self, path: _T_fd_or_any_path) -> None: ...
# def __enter__(self) -> None: ...
# def __exit__(self, *excinfo: Unused) -> None: ...

View file

@ -1,5 +0,0 @@
from typing import TypeVar # pragma: no cover
# Use for "self" annotations:
# def __enter__(self: Self) -> Self: ...
Self = TypeVar("Self") # pragma: no cover

View file

@ -1,3 +1,10 @@
# Deprecated APIs that never graduated to the standard library # Deprecated APIs that never graduated to the standard library
contextlib2.ContextDecorator.refresh_cm contextlib2.ContextDecorator.refresh_cm
contextlib2.ContextStack
# stubcheck no longer complains about this one for some reason
# (but it does complain about the unused allowlist entry)
# contextlib2.ContextStack
# mypy seems to be confused by the GenericAlias compatibility hack
contextlib2.AbstractAsyncContextManager.__class_getitem__
contextlib2.AbstractContextManager.__class_getitem__

View file

@ -55,7 +55,7 @@ PyPI page`_.
There are no operating system or distribution specific versions of this There are no operating system or distribution specific versions of this
module - it is a pure Python module that should work on all platforms. module - it is a pure Python module that should work on all platforms.
Supported Python versions are currently 3.6+. Supported Python versions are currently 3.8+.
.. _Python Package Index: http://pypi.python.org .. _Python Package Index: http://pypi.python.org
.. _pip: http://www.pip-installer.org .. _pip: http://www.pip-installer.org

10
tox.ini
View file

@ -1,6 +1,6 @@
[tox] [tox]
# No Python 3.6 available on current generation GitHub test runners # Python 3.8 is the first version with positional-only argument syntax support
envlist = py{37,38,39,3.10,3.11,3.12,py3} envlist = py{38,39,3.10,3.11,3.12,py3}
skip_missing_interpreters = True skip_missing_interpreters = True
[testenv] [testenv]
@ -9,18 +9,16 @@ commands =
coverage report coverage report
coverage xml coverage xml
# mypy won't install on PyPy, so only run the typechecking on CPython # mypy won't install on PyPy, so only run the typechecking on CPython
# Typechecking is currently failing: https://github.com/jazzband/contextlib2/issues/54 !pypy3: python -m mypy.stubtest --allowlist dev/mypy.allowlist contextlib2
# !pypy3: python -m mypy.stubtest --allowlist dev/mypy.allowlist contextlib2
deps = deps =
coverage coverage
!pypy3: mypy !pypy3: mypy
[gh-actions] [gh-actions]
python = python =
3.7: py37
3.8: py38 3.8: py38
3.9: py39 3.9: py39
3.10: py3.10 3.10: py3.10
3.11: py3.11 3.11: py3.11
3.12: py3.12 3.12: py3.12
pypy-3.8: pypy3 pypy-3.10: pypy3