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.
This commit is contained in:
Serhiy Storchaka 2021-06-29 11:27:04 +03:00 committed by GitHub
parent 48e3a1d95a
commit 20a88004ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 26 deletions

View file

@ -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 New Modules
=========== ===========

View file

@ -491,7 +491,7 @@ class TestContextDecorator(unittest.TestCase):
def __exit__(self, *exc): def __exit__(self, *exc):
pass pass
with self.assertRaises(AttributeError): with self.assertRaisesRegex(TypeError, 'the context manager'):
with mycontext(): with mycontext():
pass pass
@ -503,7 +503,7 @@ class TestContextDecorator(unittest.TestCase):
def __uxit__(self, *exc): def __uxit__(self, *exc):
pass pass
with self.assertRaises(AttributeError): with self.assertRaisesRegex(TypeError, 'the context manager.*__exit__'):
with mycontext(): with mycontext():
pass pass

View file

@ -1212,7 +1212,7 @@ class CoroutineTest(unittest.TestCase):
async with CM(): async with CM():
body_executed = True body_executed = True
with self.assertRaisesRegex(AttributeError, '__aexit__'): with self.assertRaisesRegex(TypeError, 'asynchronous context manager.*__aexit__'):
run_async(foo()) run_async(foo())
self.assertIs(body_executed, False) self.assertIs(body_executed, False)
@ -1228,7 +1228,7 @@ class CoroutineTest(unittest.TestCase):
async with CM(): async with CM():
body_executed = True body_executed = True
with self.assertRaisesRegex(AttributeError, '__aenter__'): with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
run_async(foo()) run_async(foo())
self.assertIs(body_executed, False) self.assertIs(body_executed, False)
@ -1243,7 +1243,7 @@ class CoroutineTest(unittest.TestCase):
async with CM(): async with CM():
body_executed = True body_executed = True
with self.assertRaisesRegex(AttributeError, '__aenter__'): with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
run_async(foo()) run_async(foo())
self.assertIs(body_executed, False) self.assertIs(body_executed, False)

View file

@ -117,7 +117,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksEnter(): def fooLacksEnter():
foo = LacksEnter() foo = LacksEnter()
with foo: pass with foo: pass
self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnter) self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnter)
def testEnterAttributeError2(self): def testEnterAttributeError2(self):
class LacksEnterAndExit(object): class LacksEnterAndExit(object):
@ -126,7 +126,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksEnterAndExit(): def fooLacksEnterAndExit():
foo = LacksEnterAndExit() foo = LacksEnterAndExit()
with foo: pass with foo: pass
self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnterAndExit) self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnterAndExit)
def testExitAttributeError(self): def testExitAttributeError(self):
class LacksExit(object): class LacksExit(object):
@ -136,7 +136,7 @@ class FailureTestCase(unittest.TestCase):
def fooLacksExit(): def fooLacksExit():
foo = LacksExit() foo = LacksExit()
with foo: pass with foo: pass
self.assertRaisesRegex(AttributeError, '__exit__', fooLacksExit) self.assertRaisesRegex(TypeError, 'the context manager.*__exit__', fooLacksExit)
def assertRaisesSyntaxError(self, codestr): def assertRaisesSyntaxError(self, codestr):
def shouldRaiseSyntaxError(s): def shouldRaiseSyntaxError(s):

View file

@ -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.

View file

@ -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 void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *, static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
PyFrameObject *, const _Py_CODEUNIT *); PyFrameObject *, const _Py_CODEUNIT *);
static PyObject * special_lookup(PyThreadState *, PyObject *, _Py_Identifier *);
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg); static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs); static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int); 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(__aenter__);
_Py_IDENTIFIER(__aexit__); _Py_IDENTIFIER(__aexit__);
PyObject *mgr = TOP(); PyObject *mgr = TOP();
PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
PyObject *res; PyObject *res;
PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___aenter__);
if (enter == NULL) { 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; goto error;
} }
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__); PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___aexit__);
if (exit == NULL) { 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); Py_DECREF(enter);
goto error; goto error;
} }
@ -3869,13 +3881,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
_Py_IDENTIFIER(__enter__); _Py_IDENTIFIER(__enter__);
_Py_IDENTIFIER(__exit__); _Py_IDENTIFIER(__exit__);
PyObject *mgr = TOP(); PyObject *mgr = TOP();
PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
PyObject *res; PyObject *res;
PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___enter__);
if (enter == NULL) { 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; goto error;
} }
PyObject *exit = special_lookup(tstate, mgr, &PyId___exit__); PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___exit__);
if (exit == NULL) { 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); Py_DECREF(enter);
goto error; 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). /* Logic for the raise statement (too complicated for inlining).
This *consumes* a reference count to each of its arguments. */ This *consumes* a reference count to each of its arguments. */
static int static int