From e42cd73fe9857366154d783e2a85e3d8a72ce5e7 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sat, 26 Jun 2021 20:43:11 +1000 Subject: [PATCH] Issue #33: convert to package and include typeshed type hints --- CONTRIBUTING.md | 4 +- LICENSE.txt | 4 +- MANIFEST.in | 7 +- README.rst | 19 +++- contextlib2.py => contextlib2/__init__.py | 0 contextlib2/contextlib.pyi | 122 ++++++++++++++++++++++ contextlib2/py.typed | 0 setup.py | 4 +- 8 files changed, 153 insertions(+), 7 deletions(-) rename contextlib2.py => contextlib2/__init__.py (100%) create mode 100644 contextlib2/contextlib.pyi create mode 100644 contextlib2/py.typed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad78220..c3fec94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,5 @@ [![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) -This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines). +This is a [Jazzband](https://jazzband.co/) project. By contributing you agree +to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) +and follow the [guidelines](https://jazzband.co/about/guidelines). diff --git a/LICENSE.txt b/LICENSE.txt index 5de2027..e40caa1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,6 @@ - +Note: The type hints included in this package come from the typeshed project, +and are hence distributed under the Apache License 2.0 rather than under the +Python Software License that covers the module implementation and test suite. A. HISTORY OF THE SOFTWARE ========================== diff --git a/MANIFEST.in b/MANIFEST.in index 5b839f9..46fdec8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,5 @@ -include *.py *.txt *.rst *.md *.ini MANIFEST.in -recursive-include dev test docs *.rst *.py make.bat Makefile +include *.py *.cfg *.txt *.rst *.md *.ini MANIFEST.in +recursive-include contextlib2 *.py *.pyi py.typed +recursive-include docs *.rst *.py make.bat Makefile +recursive-include test *.py +recursive-include dev *.patch diff --git a/README.rst b/README.rst index d64ba87..2927e6f 100644 --- a/README.rst +++ b/README.rst @@ -18,9 +18,19 @@ contextlib2 is a backport of the `standard library's contextlib module `_ to earlier Python versions. -It also serves as a real world proving ground for possible future +It also sometimes serves as a real world proving ground for possible future enhancements to the standard library version. +Licensing +--------- + +As a backport of Python standard library software, the implementation, test +suite and other supporting files for this project are distributed under the +Python Software License used for the CPython reference implementation. + +The one exception is the included type hints file, which comes from the +``typeshed`` project, and is hence distributed under the Apache License 2.0. + Development ----------- @@ -53,10 +63,15 @@ Updating to a new stdlib reference version As of Python 3.10, 3 files needed to be copied from the CPython reference implementation to contextlib2: -* ``Lib/contextlib.py`` -> ``contextlib2.py`` +* ``Lib/contextlib.py`` -> ``contextlib2/__init__.py`` * ``Lib/test/test_contextlib.py`` -> ``test/test_contextlib.py`` * ``Lib/test/test_contextlib_async.py`` -> ``test/test_contextlib_async.py`` +The corresponding version of ``contextlib2/__init__.py`` also needs to be +retrieved from the ``typeshed`` project:: + + wget https://raw.githubusercontent.com/python/typeshed/master/stdlib/contextlib.pyi + For the 3.10 sync, the only changes needed to the test files were to import from ``contextlib2`` rather than ``contextlib``. The test directory is laid out so that the test suite's imports from ``test.support`` work the same way they do in diff --git a/contextlib2.py b/contextlib2/__init__.py similarity index 100% rename from contextlib2.py rename to contextlib2/__init__.py diff --git a/contextlib2/contextlib.pyi b/contextlib2/contextlib.pyi new file mode 100644 index 0000000..c2674fe --- /dev/null +++ b/contextlib2/contextlib.pyi @@ -0,0 +1,122 @@ +# Type hints copied from the typeshed project under the Apache License 2.0 +# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE + +import sys +from types import TracebackType +from typing import ( + IO, + Any, + AsyncContextManager, + AsyncIterator, + Awaitable, + Callable, + ContextManager, + Iterator, + Optional, + Type, + TypeVar, + overload, +) +from typing_extensions import ParamSpec, Protocol + +AbstractContextManager = ContextManager +if sys.version_info >= (3, 7): + AbstractAsyncContextManager = AsyncContextManager + +_T = TypeVar("_T") +_T_co = TypeVar("_T_co", covariant=True) +_T_io = TypeVar("_T_io", bound=Optional[IO[str]]) +_F = TypeVar("_F", bound=Callable[..., Any]) +_P = ParamSpec("_P") + +_ExitFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], bool] +_CM_EF = TypeVar("_CM_EF", ContextManager[Any], _ExitFunc) + +class _GeneratorContextManager(ContextManager[_T_co]): + def __call__(self, func: _F) -> _F: ... + +# type ignore to deal with incomplete ParamSpec support in mypy +def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P, _GeneratorContextManager[_T]]: ... # type: ignore + +if sys.version_info >= (3, 7): + def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore + +class _SupportsClose(Protocol): + def close(self) -> object: ... + +_SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose) + +class closing(ContextManager[_SupportsCloseT]): + def __init__(self, thing: _SupportsCloseT) -> None: ... + +if sys.version_info >= (3, 10): + class _SupportsAclose(Protocol): + async def aclose(self) -> object: ... + _SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose) + class aclosing(AsyncContextManager[_SupportsAcloseT]): + def __init__(self, thing: _SupportsAcloseT) -> None: ... + _AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]]) + class AsyncContextDecorator: + def __call__(self, func: _AF) -> _AF: ... + +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[_T_io]): + def __init__(self, new_target: _T_io) -> None: ... + +class redirect_stderr(ContextManager[_T_io]): + def __init__(self, new_target: _T_io) -> None: ... + +class ContextDecorator: + def __call__(self, func: _F) -> _F: ... + +_U = TypeVar("_U", bound=ExitStack) + +class ExitStack(ContextManager[ExitStack]): + def __init__(self) -> None: ... + def enter_context(self, cm: ContextManager[_T]) -> _T: ... + def push(self, exit: _CM_EF) -> _CM_EF: ... + def callback(self, callback: Callable[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ... + 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): + _S = TypeVar("_S", bound=AsyncExitStack) + + _ExitCoroFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], Awaitable[bool]] + _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: _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 + def nullcontext(enter_result: _T) -> ContextManager[_T]: ... + @overload + def nullcontext() -> ContextManager[None]: ... diff --git a/contextlib2/py.typed b/contextlib2/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index afa7938..dc64785 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,8 @@ setup( name='contextlib2', version=open('VERSION.txt').read().strip(), python_requires='>=3.6', - py_modules=['contextlib2'], + packages=['contextlib2'], + include_package_data=True, license='PSF License', description='Backports and enhancements for the contextlib module', long_description=open('README.rst').read(), @@ -17,6 +18,7 @@ setup( url='http://contextlib2.readthedocs.org', classifiers=[ 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: Apache Software License', 'License :: OSI Approved :: Python Software Foundation License', # These are the Python versions tested, it may work on others # It definitely won't work on versions without native async support