Skip to content

Commit

Permalink
Merge pull request #34 from ncoghlan/issue-33-add-type-hinting-support
Browse files Browse the repository at this point in the history
Issue #33: convert to package and include typeshed type hints
  • Loading branch information
ncoghlan authored Jun 26, 2021
2 parents 77e9383 + 4b353b9 commit bed62be
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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).
4 changes: 3 additions & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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
==========================
Expand Down
7 changes: 5 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -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
14 changes: 12 additions & 2 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ Release History
21.6.0 (2021-06-27)
^^^^^^^^^^^^^^^^^^^

* Switched to calendar based versioning rather than continuing with pre-1.0
semantic versioning (`#29 <https://github.com/jazzband/contextlib2/issues/29>`__)
* License update: due to the inclusion of type hints from the ``typeshed``
project, the ``contextlib2`` project is now under a combination of the
Python Software License (existing license) and the Apache License 2.0
(``typeshed`` license)
* Switched to calendar based versioning using a "year"-"month"-"serial" scheme,
rather than continuing with pre-1.0 semantic versioning
* Due to the inclusion of asynchronous features from Python 3.7+, the
minimum supported Python version is now Python 3.6
(`#29 <https://github.com/jazzband/contextlib2/issues/29>`__)
Expand All @@ -20,6 +24,12 @@ Release History
* ``AsyncExitStack`` (added in Python 3.7)
* async support in ``nullcontext`` (Python 3.10)

* ``contextlib2`` now includes an adapted copy of the ``contextlib``
type hints from ``typeshed`` (the adaptation removes the Python version
dependencies from the API definition)
(`#33 <https://github.com/jazzband/contextlib2/issues/33>`__)
* to incorporate the type hints stub file and the ``py.typed`` marker file,
``contextlib2`` is now installed as a package rather than as a module
* Updates to the default compatibility testing matrix:

* Added: CPython 3.9, CPython 3.10
Expand Down
36 changes: 30 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@ contextlib2 is a backport of the `standard library's contextlib
module <https://docs.python.org/3/library/contextlib.html>`_ 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
-----------

Expand Down Expand Up @@ -50,18 +60,32 @@ Versions currently tested in both tox and GitHub Actions are:
Updating to a new stdlib reference version
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As of Python 3.10, 3 files needed to be copied from the CPython reference
As of Python 3.10, 4 files needed to be copied from the CPython reference
implementation to contextlib2:

* ``Lib/contextlib.py`` -> ``contextlib2.py``
* ``Doc/contextlib.rst`` -> ``docs/contextlib2.rst``
* ``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__.pyi`` 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
the main CPython test suite.

The changes made to the ``contextlib2.py`` file to get it to run on the older
versions (and to add back in the deprecated APIs that never graduated to the
standard library version) are saved as a patch file in the ``dev`` directory.
The following patch files are saved in the ``dev`` directory:

* changes made to ``contextlib2/__init__.py`` to get it to run on the older
versions (and to add back in the deprecated APIs that never graduated to
the standard library version)
* changes made to ``contextlib2/__init__.pyi`` to make the Python version
guards unconditional (since the ``contextlib2`` API is the same on all
supported versions)
* changes made to ``docs/contextlib2.rst`` to use ``contextlib2`` version
numbers in the version added/changed notes and to integrate the module
documentation with the rest of the project documentation
File renamed without changes.
125 changes: 125 additions & 0 deletions contextlib2/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# 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

# Note: the various 'if True:' guards replace sys.version checks in the
# original typeshed file that don't apply to the contextlib2 backport API

AbstractContextManager = ContextManager
if True:
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 True:
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 True:
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 True:
_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 True:
@overload
def nullcontext(enter_result: _T) -> ContextManager[_T]: ...
@overload
def nullcontext() -> ContextManager[None]: ...
Empty file added contextlib2/py.typed
Empty file.
58 changes: 58 additions & 0 deletions dev/py3_10_contextlib_pyi_to_contextlib2_pyi.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
--- ../contextlib.pyi 2021-06-26 21:36:16.491964153 +1000
+++ contextlib2/__init__.pyi 2021-06-26 21:41:08.109598690 +1000
@@ -1,3 +1,6 @@
+# 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 (
@@ -16,8 +19,11 @@
)
from typing_extensions import ParamSpec, Protocol

+# Note: the various 'if True:' guards replace sys.version checks in the
+# original typeshed file that don't apply to the contextlib2 backport API
+
AbstractContextManager = ContextManager
-if sys.version_info >= (3, 7):
+if True:
AbstractAsyncContextManager = AsyncContextManager

_T = TypeVar("_T")
@@ -35,7 +41,7 @@
# 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):
+if True:
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore

class _SupportsClose(Protocol):
@@ -46,7 +52,7 @@
class closing(ContextManager[_SupportsCloseT]):
def __init__(self, thing: _SupportsCloseT) -> None: ...

-if sys.version_info >= (3, 10):
+if True:
class _SupportsAclose(Protocol):
async def aclose(self) -> object: ...
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
@@ -88,7 +94,7 @@
__traceback: Optional[TracebackType],
) -> bool: ...

-if sys.version_info >= (3, 7):
+if True:
_S = TypeVar("_S", bound=AsyncExitStack)

_ExitCoroFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], Awaitable[bool]]
@@ -112,7 +118,7 @@
__traceback: Optional[TracebackType],
) -> Awaitable[bool]: ...

-if sys.version_info >= (3, 7):
+if True:
@overload
def nullcontext(enter_result: _T) -> ContextManager[_T]: ...
@overload
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
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(),
Expand All @@ -17,6 +18,7 @@
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
Expand Down
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ commands =
coverage run -m unittest discover -t . -s test
coverage report
coverage xml
# mypy won't install on PyPy, so only run the typechecking on CPython
!pypy3: mypy contextlib2
deps =
coverage
!pypy3: mypy

[gh-actions]
python =
Expand Down

0 comments on commit bed62be

Please sign in to comment.