mirror of
https://github.com/python/cpython.git
synced 2025-07-07 19:35:27 +00:00
gh-129889: Support context manager protocol by contextvars.Token (#129888)
This commit is contained in:
parent
e1b38ea82e
commit
469d2e416c
6 changed files with 223 additions and 2 deletions
|
@ -101,6 +101,21 @@ Context Variables
|
|||
the value of the variable to what it was before the corresponding
|
||||
*set*.
|
||||
|
||||
The token supports :ref:`context manager protocol <context-managers>`
|
||||
to restore the corresponding context variable value at the exit from
|
||||
:keyword:`with` block::
|
||||
|
||||
var = ContextVar('var', default='default value')
|
||||
|
||||
with var.set('new value'):
|
||||
assert var.get() == 'new value'
|
||||
|
||||
assert var.get() == 'default value'
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
Added support for usage as a context manager.
|
||||
|
||||
.. attribute:: Token.var
|
||||
|
||||
A read-only property. Points to the :class:`ContextVar` object
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
****************************
|
||||
What's new in Python 3.14
|
||||
****************************
|
||||
|
@ -362,6 +361,13 @@ concurrent.futures
|
|||
supplying a *mp_context* to :class:`concurrent.futures.ProcessPoolExecutor`.
|
||||
(Contributed by Gregory P. Smith in :gh:`84559`.)
|
||||
|
||||
contextvars
|
||||
-----------
|
||||
|
||||
* Support context manager protocol by :class:`contextvars.Token`.
|
||||
(Contributed by Andrew Svetlov in :gh:`129889`.)
|
||||
|
||||
|
||||
ctypes
|
||||
------
|
||||
|
||||
|
|
|
@ -383,6 +383,115 @@ class ContextTest(unittest.TestCase):
|
|||
tp.shutdown()
|
||||
self.assertEqual(results, list(range(10)))
|
||||
|
||||
def test_token_contextmanager_with_default(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
with c.set(36):
|
||||
self.assertEqual(c.get(), 36)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_without_default(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c')
|
||||
|
||||
def fun():
|
||||
with c.set(36):
|
||||
self.assertEqual(c.get(), 36)
|
||||
|
||||
with self.assertRaisesRegex(LookupError, "<ContextVar name='c'"):
|
||||
c.get()
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_on_exception(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
with c.set(36):
|
||||
self.assertEqual(c.get(), 36)
|
||||
raise ValueError("custom exception")
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "custom exception"):
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_reentrant(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
token = c.set(36)
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"<Token .+ has already been used once"
|
||||
):
|
||||
with token:
|
||||
with token:
|
||||
self.assertEqual(c.get(), 36)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_multiple_c_set(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
with c.set(36):
|
||||
self.assertEqual(c.get(), 36)
|
||||
c.set(24)
|
||||
self.assertEqual(c.get(), 24)
|
||||
c.set(12)
|
||||
self.assertEqual(c.get(), 12)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_with_explicit_reset_the_same_token(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
"<Token .+ has already been used once"
|
||||
):
|
||||
with c.set(36) as token:
|
||||
self.assertEqual(c.get(), 36)
|
||||
c.reset(token)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
def test_token_contextmanager_with_explicit_reset_another_token(self):
|
||||
ctx = contextvars.Context()
|
||||
c = contextvars.ContextVar('c', default=42)
|
||||
|
||||
def fun():
|
||||
with c.set(36):
|
||||
self.assertEqual(c.get(), 36)
|
||||
|
||||
token = c.set(24)
|
||||
self.assertEqual(c.get(), 24)
|
||||
c.reset(token)
|
||||
self.assertEqual(c.get(), 36)
|
||||
|
||||
self.assertEqual(c.get(), 42)
|
||||
|
||||
ctx.run(fun)
|
||||
|
||||
|
||||
# HAMT Tests
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Support context manager protocol by :class:`contextvars.Token`. Patch by
|
||||
Andrew Svetlov.
|
53
Python/clinic/context.c.h
generated
53
Python/clinic/context.c.h
generated
|
@ -179,4 +179,55 @@ PyDoc_STRVAR(_contextvars_ContextVar_reset__doc__,
|
|||
|
||||
#define _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF \
|
||||
{"reset", (PyCFunction)_contextvars_ContextVar_reset, METH_O, _contextvars_ContextVar_reset__doc__},
|
||||
/*[clinic end generated code: output=444567eaf0df25e0 input=a9049054013a1b77]*/
|
||||
|
||||
PyDoc_STRVAR(token_enter__doc__,
|
||||
"__enter__($self, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Enter into Token context manager.");
|
||||
|
||||
#define TOKEN_ENTER_METHODDEF \
|
||||
{"__enter__", (PyCFunction)token_enter, METH_NOARGS, token_enter__doc__},
|
||||
|
||||
static PyObject *
|
||||
token_enter_impl(PyContextToken *self);
|
||||
|
||||
static PyObject *
|
||||
token_enter(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
return token_enter_impl((PyContextToken *)self);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(token_exit__doc__,
|
||||
"__exit__($self, type, val, tb, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Exit from Token context manager, restore the linked ContextVar.");
|
||||
|
||||
#define TOKEN_EXIT_METHODDEF \
|
||||
{"__exit__", _PyCFunction_CAST(token_exit), METH_FASTCALL, token_exit__doc__},
|
||||
|
||||
static PyObject *
|
||||
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
|
||||
PyObject *tb);
|
||||
|
||||
static PyObject *
|
||||
token_exit(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
PyObject *type;
|
||||
PyObject *val;
|
||||
PyObject *tb;
|
||||
|
||||
if (!_PyArg_CheckPositional("__exit__", nargs, 3, 3)) {
|
||||
goto exit;
|
||||
}
|
||||
type = args[0];
|
||||
val = args[1];
|
||||
tb = args[2];
|
||||
return_value = token_exit_impl((PyContextToken *)self, type, val, tb);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=01987cdbf68a951a input=a9049054013a1b77]*/
|
||||
|
|
|
@ -1231,9 +1231,47 @@ static PyGetSetDef PyContextTokenType_getsetlist[] = {
|
|||
{NULL}
|
||||
};
|
||||
|
||||
/*[clinic input]
|
||||
_contextvars.Token.__enter__ as token_enter
|
||||
|
||||
Enter into Token context manager.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
token_enter_impl(PyContextToken *self)
|
||||
/*[clinic end generated code: output=9af4d2054e93fb75 input=41a3d6c4195fd47a]*/
|
||||
{
|
||||
return Py_NewRef(self);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_contextvars.Token.__exit__ as token_exit
|
||||
|
||||
type: object
|
||||
val: object
|
||||
tb: object
|
||||
/
|
||||
|
||||
Exit from Token context manager, restore the linked ContextVar.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
token_exit_impl(PyContextToken *self, PyObject *type, PyObject *val,
|
||||
PyObject *tb)
|
||||
/*[clinic end generated code: output=3e6a1c95d3da703a input=7f117445f0ccd92e]*/
|
||||
{
|
||||
int ret = PyContextVar_Reset((PyObject *)self->tok_var, (PyObject *)self);
|
||||
if (ret < 0) {
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef PyContextTokenType_methods[] = {
|
||||
{"__class_getitem__", Py_GenericAlias,
|
||||
METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
|
||||
TOKEN_ENTER_METHODDEF
|
||||
TOKEN_EXIT_METHODDEF
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue