Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions Lib/asyncio/base_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ def format_cb(callback):
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__"""
Expand All @@ -60,21 +50,19 @@ 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))
if future._source_traceback:
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}>'
7 changes: 7 additions & 0 deletions Lib/asyncio/base_tasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import linecache
import reprlib
import traceback

from . import base_futures
Expand All @@ -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'):
Expand Down
5 changes: 1 addition & 4 deletions Lib/asyncio/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,8 @@ def __init__(self, *, loop=None):
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:
Expand Down
4 changes: 2 additions & 2 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ def __del__(self):

__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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Normalize ``repr()`` of asyncio future and task objects.
77 changes: 21 additions & 56 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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

Expand All @@ -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)
{
Expand Down Expand Up @@ -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 */
};
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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")

Expand Down
36 changes: 1 addition & 35 deletions Modules/clinic/_asynciomodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.