mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +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
|
the value of the variable to what it was before the corresponding
|
||||||
*set*.
|
*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
|
.. attribute:: Token.var
|
||||||
|
|
||||||
A read-only property. Points to the :class:`ContextVar` object
|
A read-only property. Points to the :class:`ContextVar` object
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
****************************
|
****************************
|
||||||
What's new in Python 3.14
|
What's new in Python 3.14
|
||||||
****************************
|
****************************
|
||||||
|
@ -362,6 +361,13 @@ concurrent.futures
|
||||||
supplying a *mp_context* to :class:`concurrent.futures.ProcessPoolExecutor`.
|
supplying a *mp_context* to :class:`concurrent.futures.ProcessPoolExecutor`.
|
||||||
(Contributed by Gregory P. Smith in :gh:`84559`.)
|
(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
|
ctypes
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -383,6 +383,115 @@ class ContextTest(unittest.TestCase):
|
||||||
tp.shutdown()
|
tp.shutdown()
|
||||||
self.assertEqual(results, list(range(10)))
|
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
|
# 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 \
|
#define _CONTEXTVARS_CONTEXTVAR_RESET_METHODDEF \
|
||||||
{"reset", (PyCFunction)_contextvars_ContextVar_reset, METH_O, _contextvars_ContextVar_reset__doc__},
|
{"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}
|
{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[] = {
|
static PyMethodDef PyContextTokenType_methods[] = {
|
||||||
{"__class_getitem__", Py_GenericAlias,
|
{"__class_getitem__", Py_GenericAlias,
|
||||||
METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
|
METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
|
||||||
|
TOKEN_ENTER_METHODDEF
|
||||||
|
TOKEN_EXIT_METHODDEF
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue