bpo-47039: Normalize repr() of asyncio future and task objects (GH-31950)

This commit is contained in:
Andrew Svetlov 2022-03-17 03:03:09 +02:00 committed by GitHub
parent a7c5414832
commit 30b5d41fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 42 additions and 118 deletions

View File

@ -42,16 +42,6 @@ def _format_callbacks(cb):
return f'cb=[{cb}]'
# bpo-42183: _repr_running is needed for repr protection
# when a Future or Task result contains itself directly or indirectly.
# The logic is borrowed from @reprlib.recursive_repr decorator.
# Unfortunately, the direct decorator usage is impossible because of
# AttributeError: '_asyncio.Task' object has no attribute '__module__' error.
#
# After fixing this thing we can return to the decorator based approach.
_repr_running = set()
def _future_repr_info(future):
# (Future) -> str
"""helper function for Future.__repr__"""
@ -60,17 +50,9 @@ def _future_repr_info(future):
if future._exception is not None:
info.append(f'exception={future._exception!r}')
else:
key = id(future), get_ident()
if key in _repr_running:
result = '...'
else:
_repr_running.add(key)
try:
# use reprlib to limit the length of the output, especially
# for very long strings
result = reprlib.repr(future._result)
finally:
_repr_running.discard(key)
# use reprlib to limit the length of the output, especially
# for very long strings
result = reprlib.repr(future._result)
info.append(f'result={result}')
if future._callbacks:
info.append(_format_callbacks(future._callbacks))
@ -78,3 +60,9 @@ def _future_repr_info(future):
frame = future._source_traceback[-1]
info.append(f'created at {frame[0]}:{frame[1]}')
return info
@reprlib.recursive_repr()
def _future_repr(future):
info = ' '.join(_future_repr_info(future))
return f'<{future.__class__.__name__} {info}>'

View File

@ -1,4 +1,5 @@
import linecache
import reprlib
import traceback
from . import base_futures
@ -22,6 +23,12 @@ def _task_repr_info(task):
return info
@reprlib.recursive_repr()
def _task_repr(task):
info = ' '.join(_task_repr_info(task))
return f'<{task.__class__.__name__} {info}>'
def _task_get_stack(task, limit):
frames = []
if hasattr(task._coro, 'cr_frame'):

View File

@ -85,11 +85,8 @@ class Future:
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
_repr_info = base_futures._future_repr_info
def __repr__(self):
return '<{} {}>'.format(self.__class__.__name__,
' '.join(self._repr_info()))
return base_futures._future_repr(self)
def __del__(self):
if not self.__log_traceback:

View File

@ -133,8 +133,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
__class_getitem__ = classmethod(GenericAlias)
def _repr_info(self):
return base_tasks._task_repr_info(self)
def __repr__(self):
return base_tasks._task_repr(self)
def get_coro(self):
return self._coro

View File

@ -0,0 +1 @@
Normalize ``repr()`` of asyncio future and task objects.

View File

@ -29,11 +29,11 @@ _Py_IDENTIFIER(throw);
static PyObject *asyncio_mod;
static PyObject *traceback_extract_stack;
static PyObject *asyncio_get_event_loop_policy;
static PyObject *asyncio_future_repr_info_func;
static PyObject *asyncio_future_repr_func;
static PyObject *asyncio_iscoroutine_func;
static PyObject *asyncio_task_get_stack_func;
static PyObject *asyncio_task_print_stack_func;
static PyObject *asyncio_task_repr_info_func;
static PyObject *asyncio_task_repr_func;
static PyObject *asyncio_InvalidStateError;
static PyObject *asyncio_CancelledError;
static PyObject *context_kwname;
@ -1360,6 +1360,13 @@ FutureObj_get_state(FutureObj *fut, void *Py_UNUSED(ignored))
return ret;
}
static PyObject *
FutureObj_repr(FutureObj *fut)
{
ENSURE_FUTURE_ALIVE(fut)
return PyObject_CallOneArg(asyncio_future_repr_func, (PyObject *)fut);
}
/*[clinic input]
_asyncio.Future._make_cancelled_error
@ -1376,42 +1383,6 @@ _asyncio_Future__make_cancelled_error_impl(FutureObj *self)
return create_cancelled_error(self);
}
/*[clinic input]
_asyncio.Future._repr_info
[clinic start generated code]*/
static PyObject *
_asyncio_Future__repr_info_impl(FutureObj *self)
/*[clinic end generated code: output=fa69e901bd176cfb input=f21504d8e2ae1ca2]*/
{
return PyObject_CallOneArg(asyncio_future_repr_info_func, (PyObject *)self);
}
static PyObject *
FutureObj_repr(FutureObj *fut)
{
_Py_IDENTIFIER(_repr_info);
ENSURE_FUTURE_ALIVE(fut)
PyObject *rinfo = _PyObject_CallMethodIdNoArgs((PyObject*)fut,
&PyId__repr_info);
if (rinfo == NULL) {
return NULL;
}
PyObject *rinfo_s = PyUnicode_Join(NULL, rinfo);
Py_DECREF(rinfo);
if (rinfo_s == NULL) {
return NULL;
}
PyObject *rstr = PyUnicode_FromFormat("<%s %U>",
_PyType_Name(Py_TYPE(fut)), rinfo_s);
Py_DECREF(rinfo_s);
return rstr;
}
static void
FutureObj_finalize(FutureObj *fut)
{
@ -1497,7 +1468,6 @@ static PyMethodDef FutureType_methods[] = {
_ASYNCIO_FUTURE_DONE_METHODDEF
_ASYNCIO_FUTURE_GET_LOOP_METHODDEF
_ASYNCIO_FUTURE__MAKE_CANCELLED_ERROR_METHODDEF
_ASYNCIO_FUTURE__REPR_INFO_METHODDEF
{"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
{NULL, NULL} /* Sentinel */
};
@ -2145,6 +2115,13 @@ TaskObj_get_fut_waiter(TaskObj *task, void *Py_UNUSED(ignored))
Py_RETURN_NONE;
}
static PyObject *
TaskObj_repr(TaskObj *task)
{
return PyObject_CallOneArg(asyncio_task_repr_func, (PyObject *)task);
}
/*[clinic input]
_asyncio.Task._make_cancelled_error
@ -2163,17 +2140,6 @@ _asyncio_Task__make_cancelled_error_impl(TaskObj *self)
}
/*[clinic input]
_asyncio.Task._repr_info
[clinic start generated code]*/
static PyObject *
_asyncio_Task__repr_info_impl(TaskObj *self)
/*[clinic end generated code: output=6a490eb66d5ba34b input=3c6d051ed3ddec8b]*/
{
return PyObject_CallOneArg(asyncio_task_repr_info_func, (PyObject *)self);
}
/*[clinic input]
_asyncio.Task.cancel
@ -2514,7 +2480,6 @@ static PyMethodDef TaskType_methods[] = {
_ASYNCIO_TASK_GET_STACK_METHODDEF
_ASYNCIO_TASK_PRINT_STACK_METHODDEF
_ASYNCIO_TASK__MAKE_CANCELLED_ERROR_METHODDEF
_ASYNCIO_TASK__REPR_INFO_METHODDEF
_ASYNCIO_TASK_GET_NAME_METHODDEF
_ASYNCIO_TASK_SET_NAME_METHODDEF
_ASYNCIO_TASK_GET_CORO_METHODDEF
@ -2539,7 +2504,7 @@ static PyTypeObject TaskType = {
.tp_base = &FutureType,
.tp_dealloc = TaskObj_dealloc,
.tp_as_async = &FutureType_as_async,
.tp_repr = (reprfunc)FutureObj_repr,
.tp_repr = (reprfunc)TaskObj_repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
.tp_doc = _asyncio_Task___init____doc__,
.tp_traverse = (traverseproc)TaskObj_traverse,
@ -3337,12 +3302,12 @@ module_free(void *m)
{
Py_CLEAR(asyncio_mod);
Py_CLEAR(traceback_extract_stack);
Py_CLEAR(asyncio_future_repr_info_func);
Py_CLEAR(asyncio_future_repr_func);
Py_CLEAR(asyncio_get_event_loop_policy);
Py_CLEAR(asyncio_iscoroutine_func);
Py_CLEAR(asyncio_task_get_stack_func);
Py_CLEAR(asyncio_task_print_stack_func);
Py_CLEAR(asyncio_task_repr_info_func);
Py_CLEAR(asyncio_task_repr_func);
Py_CLEAR(asyncio_InvalidStateError);
Py_CLEAR(asyncio_CancelledError);
@ -3403,14 +3368,14 @@ module_init(void)
GET_MOD_ATTR(asyncio_get_event_loop_policy, "get_event_loop_policy")
WITH_MOD("asyncio.base_futures")
GET_MOD_ATTR(asyncio_future_repr_info_func, "_future_repr_info")
GET_MOD_ATTR(asyncio_future_repr_func, "_future_repr")
WITH_MOD("asyncio.exceptions")
GET_MOD_ATTR(asyncio_InvalidStateError, "InvalidStateError")
GET_MOD_ATTR(asyncio_CancelledError, "CancelledError")
WITH_MOD("asyncio.base_tasks")
GET_MOD_ATTR(asyncio_task_repr_info_func, "_task_repr_info")
GET_MOD_ATTR(asyncio_task_repr_func, "_task_repr")
GET_MOD_ATTR(asyncio_task_get_stack_func, "_task_get_stack")
GET_MOD_ATTR(asyncio_task_print_stack_func, "_task_print_stack")

View File

@ -292,23 +292,6 @@ _asyncio_Future__make_cancelled_error(FutureObj *self, PyObject *Py_UNUSED(ignor
return _asyncio_Future__make_cancelled_error_impl(self);
}
PyDoc_STRVAR(_asyncio_Future__repr_info__doc__,
"_repr_info($self, /)\n"
"--\n"
"\n");
#define _ASYNCIO_FUTURE__REPR_INFO_METHODDEF \
{"_repr_info", (PyCFunction)_asyncio_Future__repr_info, METH_NOARGS, _asyncio_Future__repr_info__doc__},
static PyObject *
_asyncio_Future__repr_info_impl(FutureObj *self);
static PyObject *
_asyncio_Future__repr_info(FutureObj *self, PyObject *Py_UNUSED(ignored))
{
return _asyncio_Future__repr_info_impl(self);
}
PyDoc_STRVAR(_asyncio_Task___init____doc__,
"Task(coro, *, loop=None, name=None, context=None)\n"
"--\n"
@ -383,23 +366,6 @@ _asyncio_Task__make_cancelled_error(TaskObj *self, PyObject *Py_UNUSED(ignored))
return _asyncio_Task__make_cancelled_error_impl(self);
}
PyDoc_STRVAR(_asyncio_Task__repr_info__doc__,
"_repr_info($self, /)\n"
"--\n"
"\n");
#define _ASYNCIO_TASK__REPR_INFO_METHODDEF \
{"_repr_info", (PyCFunction)_asyncio_Task__repr_info, METH_NOARGS, _asyncio_Task__repr_info__doc__},
static PyObject *
_asyncio_Task__repr_info_impl(TaskObj *self);
static PyObject *
_asyncio_Task__repr_info(TaskObj *self, PyObject *Py_UNUSED(ignored))
{
return _asyncio_Task__repr_info_impl(self);
}
PyDoc_STRVAR(_asyncio_Task_cancel__doc__,
"cancel($self, /, msg=None)\n"
"--\n"
@ -924,4 +890,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
exit:
return return_value;
}
/*[clinic end generated code: output=540ed3caf5a4d57d input=a9049054013a1b77]*/
/*[clinic end generated code: output=64b3836574e8a18c input=a9049054013a1b77]*/