Skip to content

Commit 312c427

Browse files
authored
gh-158192: expose the future in asyncio FutureIter as fi_future (#158470)
1 parent c79146a commit 312c427

4 files changed

Lines changed: 57 additions & 1 deletion

File tree

‎Lib/asyncio/futures.py‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,17 +293,51 @@ def set_exception(self, exception):
293293
self.__schedule_callbacks()
294294
self.__log_traceback = True
295295

296-
def __await__(self):
296+
def _await(self):
297297
if not self.done():
298298
self._asyncio_future_blocking = True
299299
yield self # This tells Task to wait for completion.
300300
if not self.done():
301301
raise RuntimeError("await wasn't used with future")
302302
return self.result() # May raise too.
303303

304+
def __await__(self):
305+
# Return an object that exposes fi_future in addition to being
306+
# a generator, for compatability with the C version.
307+
return _FutureIter(self)
308+
304309
__iter__ = __await__ # make compatible with 'yield from'.
305310

306311

312+
class _FutureIter:
313+
__slots__ = ('__future', '__gen')
314+
315+
def __init__(self, fut):
316+
self.__future = fut
317+
self.__gen = fut._await()
318+
319+
@property
320+
def fi_future(self):
321+
return self.__future
322+
323+
def __iter__(self):
324+
return self
325+
326+
def __next__(self):
327+
return self.__gen.__next__()
328+
329+
def send(self, v, /):
330+
return self.__gen.send(v)
331+
332+
def throw(self, *args):
333+
self.__future = None
334+
return self.__gen.throw(*args)
335+
336+
def close(self):
337+
self.__future = None
338+
self.__gen.close()
339+
340+
307341
# Needed for testing purposes.
308342
_PyFuture = Future
309343

‎Lib/test/test_asyncio/test_futures.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,10 @@ def test_future_stop_iteration_args(self):
712712
self.fail('StopIteration was expected')
713713
self.assertEqual(result, (1, 2))
714714

715+
def test_future_iter_fut(self):
716+
fut = self._new_future(loop=self.loop)
717+
self.assertIs(iter(fut).fi_future, fut)
718+
715719
def test_future_iter_throw(self):
716720
fut = self._new_future(loop=self.loop)
717721
fi = iter(fut)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose the future being waited on by a ``FutureIter`` as ``fi_future``.

‎Modules/_asynciomodule.c‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,22 @@ static PyMethodDef FutureIter_methods[] = {
19881988
{NULL, NULL} /* Sentinel */
19891989
};
19901990

1991+
static PyObject *
1992+
FutureIter_get_fi_future(PyObject *op, void *Py_UNUSED(closure))
1993+
{
1994+
futureiterobject *it = (futureiterobject*)op;
1995+
PyObject *fut;
1996+
Py_BEGIN_CRITICAL_SECTION(op);
1997+
fut = it->future ? Py_NewRef(it->future) : Py_None;
1998+
Py_END_CRITICAL_SECTION();
1999+
return fut;
2000+
}
2001+
2002+
static PyGetSetDef FutureIter_getsetlist[] = {
2003+
{"fi_future", FutureIter_get_fi_future, NULL, NULL},
2004+
{NULL} /* Sentinel */
2005+
};
2006+
19912007
static PyType_Slot FutureIter_slots[] = {
19922008
{Py_tp_dealloc, FutureIter_dealloc},
19932009
{Py_tp_getattro, PyObject_GenericGetAttr},
@@ -1996,6 +2012,7 @@ static PyType_Slot FutureIter_slots[] = {
19962012
{Py_tp_iter, PyObject_SelfIter},
19972013
{Py_tp_iternext, FutureIter_iternext},
19982014
{Py_tp_methods, FutureIter_methods},
2015+
{Py_tp_getset, FutureIter_getsetlist},
19992016

20002017
// async methods
20012018
{Py_am_send, FutureIter_am_send},

0 commit comments

Comments
 (0)