summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2021-06-29 08:27:04 (GMT)
committerGitHub <noreply@github.com>2021-06-29 08:27:04 (GMT)
commit20a88004bae8ead66a205a125e1fe979376fc3ea (patch)
tree6b8007ba8c981cfdb52cc0a674e0491588ab50e0
parent48e3a1d95aee013974121fcafe19816c0e9a41da (diff)
downloadcpython-20a88004bae8ead66a205a125e1fe979376fc3ea.zip
cpython-20a88004bae8ead66a205a125e1fe979376fc3ea.tar.gz
cpython-20a88004bae8ead66a205a125e1fe979376fc3ea.tar.bz2
bpo-12022: Change error type for bad objects in "with" and "async with" (GH-26809)
A TypeError is now raised instead of an AttributeError in "with" and "async with" statements for objects which do not support the context manager or asynchronous context manager protocols correspondingly.
-rw-r--r--Doc/whatsnew/3.11.rst6
-rw-r--r--Lib/test/test_contextlib.py4
-rw-r--r--Lib/test/test_coroutines.py6
-rw-r--r--Lib/test/test_with.py6
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst4
-rw-r--r--Python/ceval.c48
6 files changed, 48 insertions, 26 deletions
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index cc88c41..be1423b 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -76,6 +76,12 @@ Other Language Changes
======================
+* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
+ :keyword:`with` and :keyword:`async with` statements for objects which do not
+ support the :term:`context manager` or :term:`asynchronous context manager`
+ protocols correspondingly.
+ (Contributed by Serhiy Storchaka in :issue:`12022`.)
+
New Modules
===========
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index 453ef6c..dbc3f5f 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -491,7 +491,7 @@ class TestContextDecorator(unittest.TestCase):
def __exit__(self, *exc):
pass
- with self.assertRaises(AttributeError):
+ with self.assertRaisesRegex(TypeError, 'the context manager'):
with mycontext():
pass
@@ -503,7 +503,7 @@ class TestContextDecorator(unittest.TestCase):
def __uxit__(self, *exc):
pass
- with self.assertRaises(AttributeError):
+ with self.assertRaisesRegex(TypeError, 'the context manager.*__exit__'):
with mycontext():
pass
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index a6a199e..9b83244 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -1212,7 +1212,7 @@ class CoroutineTest(unittest.TestCase):
async with CM():
body_executed = True
- with self.assertRaisesRegex(AttributeError, '__aexit__'):
+ with self.assertRaisesRegex(TypeError, 'asynchronous context manager.*__aexit__'):
run_async(foo())
self.assertIs(body_executed, False)
@@ -1228,7 +1228,7 @@ class CoroutineTest(unittest.TestCase):
async with CM():
body_executed = True
- with self.assertRaisesRegex(AttributeError, '__aenter__'):
+ with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
run_async(foo())
self.assertIs(body_executed, False)
@@ -1243,7 +1243,7 @@ class CoroutineTest(unittest.TestCase):
async with CM():
body_executed = True
- with self.assertRaisesRegex(AttributeError, '__aenter__'):
+ with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
run_async(foo())
self.assertIs(body_executed, False)
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index f21bf65..07522bd 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -117,7 +117,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksEnter():
foo = LacksEnter()
with foo: pass
- self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnter)
+ self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnter)
def testEnterAttributeError2(self):
class LacksEnterAndExit(object):
@@ -126,7 +126,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksEnterAndExit():
foo = LacksEnterAndExit()
with foo: pass
- self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnterAndExit)
+ self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnterAndExit)
def testExitAttributeError(self):
class LacksExit(object):
@@ -136,7 +136,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksExit():
foo = LacksExit()
with foo: pass
- self.assertRaisesRegex(AttributeError, '__exit__', fooLacksExit)
+ self.assertRaisesRegex(TypeError, 'the context manager.*__exit__', fooLacksExit)
def assertRaisesSyntaxError(self, codestr):
def shouldRaiseSyntaxError(s):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst b/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst
new file mode 100644
index 0000000..98c4228
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst
@@ -0,0 +1,4 @@
+A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
+:keyword:`with` and :keyword:`async with` statements for objects which do
+not support the :term:`context manager` or :term:`asynchronous context
+manager` protocols correspondingly.
diff --git a/Python/ceval.c b/Python/ceval.c
index 3f961f6..2ae36b3 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -82,7 +82,6 @@ static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyOb
static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
PyFrameObject *, const _Py_CODEUNIT *);
-static PyObject * special_lookup(PyThreadState *, PyObject *, _Py_Identifier *);
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
@@ -3844,13 +3843,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
_Py_IDENTIFIER(__aenter__);
_Py_IDENTIFIER(__aexit__);
PyObject *mgr = TOP();
- PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
PyObject *res;
+ PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___aenter__);
if (enter == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "'%.200s' object does not support the "
+ "asynchronous context manager protocol",
+ Py_TYPE(mgr)->tp_name);
+ }
goto error;
}
- PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
+ PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___aexit__);
if (exit == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "'%.200s' object does not support the "
+ "asynchronous context manager protocol "
+ "(missed __aexit__ method)",
+ Py_TYPE(mgr)->tp_name);
+ }
Py_DECREF(enter);
goto error;
}
@@ -3869,13 +3881,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
_Py_IDENTIFIER(__enter__);
_Py_IDENTIFIER(__exit__);
PyObject *mgr = TOP();
- PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
PyObject *res;
+ PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___enter__);
if (enter == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "'%.200s' object does not support the "
+ "context manager protocol",
+ Py_TYPE(mgr)->tp_name);
+ }
goto error;
}
- PyObject *exit = special_lookup(tstate, mgr, &PyId___exit__);
+ PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___exit__);
if (exit == NULL) {
+ if (!_PyErr_Occurred(tstate)) {
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "'%.200s' object does not support the "
+ "context manager protocol "
+ "(missed __exit__ method)",
+ Py_TYPE(mgr)->tp_name);
+ }
Py_DECREF(enter);
goto error;
}
@@ -5110,19 +5135,6 @@ fail:
}
-static PyObject *
-special_lookup(PyThreadState *tstate, PyObject *o, _Py_Identifier *id)
-{
- PyObject *res;
- res = _PyObject_LookupSpecial(o, id);
- if (res == NULL && !_PyErr_Occurred(tstate)) {
- _PyErr_SetObject(tstate, PyExc_AttributeError, _PyUnicode_FromId(id));
- return NULL;
- }
- return res;
-}
-
-
/* Logic for the raise statement (too complicated for inlining).
This *consumes* a reference count to each of its arguments. */
static int