mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-17 06:00:23 +00:00
Merge pull request #30 from ncoghlan/issue-29-switch-to-calver-update-min-py-version
Issue #29: Switch to CalVer, require Python >= 3.6
This commit is contained in:
commit
94f3881963
8 changed files with 94 additions and 145 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
|
||||
- name: Get pip cache dir
|
||||
id: pip-cache
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
strategy:
|
||||
max-parallel: 5
|
||||
matrix:
|
||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 'pypy2', 'pypy3']
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, '3.10.0-beta - 3.10', 'pypy3']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
|
|
|||
27
NEWS.rst
27
NEWS.rst
|
|
@ -1,6 +1,33 @@
|
|||
Release History
|
||||
---------------
|
||||
|
||||
21.6.0 (2021-06-TBD)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* Switched to calendar based versioning rather than continuing with pre-1.0
|
||||
semantic versioning (`#29 <https://github.com/jazzband/contextlib2/issues/29>`__)
|
||||
* 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>`__)
|
||||
* (WIP) Synchronised with the Python 3.10 version of contextlib, bringing the
|
||||
following new features to Python 3.6+ (
|
||||
`#12 <https://github.com/jazzband/contextlib2/issues/12>`__,
|
||||
`#19 <https://github.com/jazzband/contextlib2/issues/19>`__,
|
||||
`#27 <https://github.com/jazzband/contextlib2/issues/27>`__):
|
||||
|
||||
* ``asyncontextmanager`` (Python 3.7)
|
||||
* ``aclosing`` (Python 3.10)
|
||||
* ``AbstractAsyncContextManager`` (Python 3.7)
|
||||
* ``AsyncContextDecorator`` (Python 3.10)
|
||||
* ``AsyncExitStack`` (Python 3.7)
|
||||
* async support in ``nullcontext`` (Python 3.10)
|
||||
|
||||
* Updates to the default compatibility testing matrix:
|
||||
|
||||
* Added: CPython 3.9, CPython 3.10
|
||||
* Dropped: CPython 2.7, CPython 3.5, PyPy2
|
||||
|
||||
|
||||
0.6.0.post1 (2019-10-10)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.6.0.post1
|
||||
21.6.0
|
||||
|
|
|
|||
122
contextlib2.py
122
contextlib2.py
|
|
@ -6,6 +6,8 @@ import warnings
|
|||
from collections import deque
|
||||
from functools import wraps
|
||||
|
||||
from _collections_abc import _check_methods
|
||||
|
||||
__all__ = ["contextmanager", "closing", "nullcontext",
|
||||
"AbstractContextManager",
|
||||
"ContextDecorator", "ExitStack",
|
||||
|
|
@ -14,43 +16,7 @@ __all__ = ["contextmanager", "closing", "nullcontext",
|
|||
# Backwards compatibility
|
||||
__all__ += ["ContextStack"]
|
||||
|
||||
|
||||
# Backport abc.ABC
|
||||
if sys.version_info[:2] >= (3, 4):
|
||||
_abc_ABC = abc.ABC
|
||||
else:
|
||||
_abc_ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})
|
||||
|
||||
|
||||
# Backport classic class MRO
|
||||
def _classic_mro(C, result):
|
||||
if C in result:
|
||||
return
|
||||
result.append(C)
|
||||
for B in C.__bases__:
|
||||
_classic_mro(B, result)
|
||||
return result
|
||||
|
||||
|
||||
# Backport _collections_abc._check_methods
|
||||
def _check_methods(C, *methods):
|
||||
try:
|
||||
mro = C.__mro__
|
||||
except AttributeError:
|
||||
mro = tuple(_classic_mro(C, []))
|
||||
|
||||
for method in methods:
|
||||
for B in mro:
|
||||
if method in B.__dict__:
|
||||
if B.__dict__[method] is None:
|
||||
return NotImplemented
|
||||
break
|
||||
else:
|
||||
return NotImplemented
|
||||
return True
|
||||
|
||||
|
||||
class AbstractContextManager(_abc_ABC):
|
||||
class AbstractContextManager(abc.ABC):
|
||||
"""An abstract base class for context managers."""
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -167,7 +133,7 @@ class _GeneratorContextManager(ContextDecorator):
|
|||
# Likewise, avoid suppressing if a StopIteration exception
|
||||
# was passed to throw() and later wrapped into a RuntimeError
|
||||
# (see PEP 479).
|
||||
if _HAVE_EXCEPTION_CHAINING and exc.__cause__ is value:
|
||||
if exc.__cause__ is value:
|
||||
return False
|
||||
raise
|
||||
except:
|
||||
|
|
@ -313,58 +279,32 @@ class suppress(object):
|
|||
return exctype is not None and issubclass(exctype, self._exceptions)
|
||||
|
||||
|
||||
# Context manipulation is Python 3 only
|
||||
_HAVE_EXCEPTION_CHAINING = sys.version_info[0] >= 3
|
||||
if _HAVE_EXCEPTION_CHAINING:
|
||||
def _make_context_fixer(frame_exc):
|
||||
def _fix_exception_context(new_exc, old_exc):
|
||||
# Context may not be correct, so find the end of the chain
|
||||
while 1:
|
||||
exc_context = new_exc.__context__
|
||||
if exc_context is old_exc:
|
||||
# Context is already set correctly (see issue 20317)
|
||||
return
|
||||
if exc_context is None or exc_context is frame_exc:
|
||||
break
|
||||
new_exc = exc_context
|
||||
# Change the end of the chain to point to the exception
|
||||
# we expect it to reference
|
||||
new_exc.__context__ = old_exc
|
||||
return _fix_exception_context
|
||||
# Context manipulation helpers
|
||||
def _make_context_fixer(frame_exc):
|
||||
def _fix_exception_context(new_exc, old_exc):
|
||||
# Context may not be correct, so find the end of the chain
|
||||
while 1:
|
||||
exc_context = new_exc.__context__
|
||||
if exc_context is old_exc:
|
||||
# Context is already set correctly (see issue 20317)
|
||||
return
|
||||
if exc_context is None or exc_context is frame_exc:
|
||||
break
|
||||
new_exc = exc_context
|
||||
# Change the end of the chain to point to the exception
|
||||
# we expect it to reference
|
||||
new_exc.__context__ = old_exc
|
||||
return _fix_exception_context
|
||||
|
||||
def _reraise_with_existing_context(exc_details):
|
||||
try:
|
||||
# bare "raise exc_details[1]" replaces our carefully
|
||||
# set-up context
|
||||
fixed_ctx = exc_details[1].__context__
|
||||
raise exc_details[1]
|
||||
except BaseException:
|
||||
exc_details[1].__context__ = fixed_ctx
|
||||
raise
|
||||
else:
|
||||
# No exception context in Python 2
|
||||
def _make_context_fixer(frame_exc):
|
||||
return lambda new_exc, old_exc: None
|
||||
|
||||
# Use 3 argument raise in Python 2,
|
||||
# but use exec to avoid SyntaxError in Python 3
|
||||
def _reraise_with_existing_context(exc_details):
|
||||
exc_type, exc_value, exc_tb = exc_details
|
||||
exec("raise exc_type, exc_value, exc_tb")
|
||||
|
||||
# Handle old-style classes if they exist
|
||||
try:
|
||||
from types import InstanceType
|
||||
except ImportError:
|
||||
# Python 3 doesn't have old-style classes
|
||||
_get_type = type
|
||||
else:
|
||||
# Need to handle old-style context managers on Python 2
|
||||
def _get_type(obj):
|
||||
obj_type = type(obj)
|
||||
if obj_type is InstanceType:
|
||||
return obj.__class__ # Old-style class
|
||||
return obj_type # New-style class
|
||||
def _reraise_with_existing_context(exc_details):
|
||||
try:
|
||||
# bare "raise exc_details[1]" replaces our carefully
|
||||
# set-up context
|
||||
fixed_ctx = exc_details[1].__context__
|
||||
raise exc_details[1]
|
||||
except BaseException:
|
||||
exc_details[1].__context__ = fixed_ctx
|
||||
raise
|
||||
|
||||
|
||||
# Inspired by discussions on http://bugs.python.org/issue13585
|
||||
|
|
@ -407,7 +347,7 @@ class ExitStack(object):
|
|||
"""
|
||||
# We use an unbound method rather than a bound method to follow
|
||||
# the standard lookup behaviour for special methods
|
||||
_cb_type = _get_type(exit)
|
||||
_cb_type = type(exit)
|
||||
try:
|
||||
exit_method = _cb_type.__exit__
|
||||
except AttributeError:
|
||||
|
|
@ -437,7 +377,7 @@ class ExitStack(object):
|
|||
returns the result of the __enter__ method.
|
||||
"""
|
||||
# We look up the special methods on the type to match the with statement
|
||||
_cm_type = _get_type(cm)
|
||||
_cm_type = type(cm)
|
||||
_exit = _cm_type.__exit__
|
||||
result = _cm_type.__enter__(cm)
|
||||
self._push_cm_exit(cm, _exit)
|
||||
|
|
|
|||
10
setup.py
10
setup.py
|
|
@ -7,7 +7,7 @@ except ImportError:
|
|||
setup(
|
||||
name='contextlib2',
|
||||
version=open('VERSION.txt').read().strip(),
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
|
||||
python_requires='>=3.6',
|
||||
py_modules=['contextlib2'],
|
||||
license='PSF License',
|
||||
description='Backports and enhancements for the contextlib module',
|
||||
|
|
@ -19,13 +19,13 @@ setup(
|
|||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: Python Software Foundation License',
|
||||
# These are the Python versions tested, it may work on others
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
# It definitely won't work on versions without native async support
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
],
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,9 +9,6 @@ import __future__ # For PEP 479 conditional test
|
|||
import contextlib2
|
||||
from contextlib2 import * # Tests __all__
|
||||
|
||||
if not hasattr(unittest.TestCase, "assertRaisesRegex"):
|
||||
import unittest2 as unittest
|
||||
|
||||
requires_docstrings = unittest.skipIf(sys.flags.optimize >= 2,
|
||||
"Test requires docstrings")
|
||||
|
||||
|
|
@ -421,9 +418,6 @@ class TestContextDecorator(unittest.TestCase):
|
|||
test('something else')
|
||||
self.assertEqual(state, [1, 'something else', 999])
|
||||
|
||||
# Detailed exception chaining checks only make sense on Python 3
|
||||
check_exception_chaining = contextlib2._HAVE_EXCEPTION_CHAINING
|
||||
|
||||
class TestExitStack(unittest.TestCase):
|
||||
|
||||
@requires_docstrings
|
||||
|
|
@ -592,18 +586,16 @@ class TestExitStack(unittest.TestCase):
|
|||
with RaiseExc(ValueError):
|
||||
1 / 0
|
||||
except IndexError as exc:
|
||||
if check_exception_chaining:
|
||||
self.assertIsInstance(exc.__context__, KeyError)
|
||||
self.assertIsInstance(exc.__context__.__context__, AttributeError)
|
||||
# Inner exceptions were suppressed
|
||||
self.assertIsNone(exc.__context__.__context__.__context__)
|
||||
self.assertIsInstance(exc.__context__, KeyError)
|
||||
self.assertIsInstance(exc.__context__.__context__, AttributeError)
|
||||
# Inner exceptions were suppressed
|
||||
self.assertIsNone(exc.__context__.__context__.__context__)
|
||||
else:
|
||||
self.fail("Expected IndexError, but no exception was raised")
|
||||
# Check the inner exceptions
|
||||
inner_exc = SuppressExc.saved_details[1]
|
||||
self.assertIsInstance(inner_exc, ValueError)
|
||||
if check_exception_chaining:
|
||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||
|
||||
def test_exit_exception_chaining(self):
|
||||
# Ensure exception chaining matches the reference behaviour
|
||||
|
|
@ -624,18 +616,16 @@ class TestExitStack(unittest.TestCase):
|
|||
stack.callback(raise_exc, ValueError)
|
||||
1 / 0
|
||||
except IndexError as exc:
|
||||
if check_exception_chaining:
|
||||
self.assertIsInstance(exc.__context__, KeyError)
|
||||
self.assertIsInstance(exc.__context__.__context__, AttributeError)
|
||||
# Inner exceptions were suppressed
|
||||
self.assertIsNone(exc.__context__.__context__.__context__)
|
||||
self.assertIsInstance(exc.__context__, KeyError)
|
||||
self.assertIsInstance(exc.__context__.__context__, AttributeError)
|
||||
# Inner exceptions were suppressed
|
||||
self.assertIsNone(exc.__context__.__context__.__context__)
|
||||
else:
|
||||
self.fail("Expected IndexError, but no exception was raised")
|
||||
# Check the inner exceptions
|
||||
inner_exc = saved_details[0][1]
|
||||
self.assertIsInstance(inner_exc, ValueError)
|
||||
if check_exception_chaining:
|
||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||
|
||||
def test_exit_exception_non_suppressing(self):
|
||||
# http://bugs.python.org/issue19092
|
||||
|
|
@ -689,12 +679,11 @@ class TestExitStack(unittest.TestCase):
|
|||
raise exc1
|
||||
except Exception as exc:
|
||||
self.assertIs(exc, exc4)
|
||||
if check_exception_chaining:
|
||||
self.assertIs(exc.__context__, exc3)
|
||||
self.assertIs(exc.__context__.__context__, exc2)
|
||||
self.assertIs(exc.__context__.__context__.__context__, exc1)
|
||||
self.assertIsNone(
|
||||
exc.__context__.__context__.__context__.__context__)
|
||||
self.assertIs(exc.__context__, exc3)
|
||||
self.assertIs(exc.__context__.__context__, exc2)
|
||||
self.assertIs(exc.__context__.__context__.__context__, exc1)
|
||||
self.assertIsNone(
|
||||
exc.__context__.__context__.__context__.__context__)
|
||||
|
||||
def test_exit_exception_with_existing_context(self):
|
||||
# Addresses a lack of test coverage discovered after checking in a
|
||||
|
|
@ -716,16 +705,13 @@ class TestExitStack(unittest.TestCase):
|
|||
raise exc1
|
||||
except Exception as exc:
|
||||
self.assertIs(exc, exc5)
|
||||
if check_exception_chaining:
|
||||
self.assertIs(exc.__context__, exc4)
|
||||
self.assertIs(exc.__context__.__context__, exc3)
|
||||
self.assertIs(exc.__context__.__context__.__context__, exc2)
|
||||
self.assertIs(
|
||||
exc.__context__.__context__.__context__.__context__, exc1)
|
||||
self.assertIsNone(
|
||||
exc.__context__.__context__.__context__.__context__.__context__)
|
||||
|
||||
|
||||
self.assertIs(exc.__context__, exc4)
|
||||
self.assertIs(exc.__context__.__context__, exc3)
|
||||
self.assertIs(exc.__context__.__context__.__context__, exc2)
|
||||
self.assertIs(
|
||||
exc.__context__.__context__.__context__.__context__, exc1)
|
||||
self.assertIsNone(
|
||||
exc.__context__.__context__.__context__.__context__.__context__)
|
||||
|
||||
def test_body_exception_suppress(self):
|
||||
def suppress_exc(*exc_details):
|
||||
|
|
@ -824,10 +810,9 @@ class TestExitStack(unittest.TestCase):
|
|||
exc = err_ctx.exception
|
||||
self.assertIsInstance(exc, UniqueException)
|
||||
self.assertIsInstance(exc.__cause__, UniqueRuntimeError)
|
||||
if check_exception_chaining:
|
||||
self.assertIs(exc.__context__, exc.__cause__)
|
||||
self.assertIsNone(exc.__cause__.__context__)
|
||||
self.assertIsNone(exc.__cause__.__cause__)
|
||||
self.assertIs(exc.__context__, exc.__cause__)
|
||||
self.assertIsNone(exc.__cause__.__context__)
|
||||
self.assertIsNone(exc.__cause__.__cause__)
|
||||
|
||||
|
||||
class TestRedirectStream:
|
||||
|
|
|
|||
9
tox.ini
9
tox.ini
|
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py{27,35,36,37,py,py3}
|
||||
envlist = py{36,37,38,39,3_10,py3}
|
||||
skip_missing_interpreters = True
|
||||
|
||||
[testenv]
|
||||
|
|
@ -9,15 +9,12 @@ commands =
|
|||
coverage xml
|
||||
deps =
|
||||
coverage
|
||||
py27: unittest2
|
||||
pypy: unittest2
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
2.7: py27
|
||||
3.5: py35
|
||||
3.6: py36
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
pypy2: pypy
|
||||
3.9: py39
|
||||
3.10: py3_10
|
||||
pypy3: pypy3
|
||||
|
|
|
|||
Loading…
Reference in a new issue