ENH Add then, catch, and finally_ to the Tasks too (#3748)

This commit is contained in:
Hood Chatham 2023-04-10 22:09:06 -07:00 committed by GitHub
parent 6213477940
commit 0c458f4469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 8 deletions

View File

@ -15,6 +15,10 @@ myst:
## Unreleased
- {{ Enhancement }} The promise methods `then`, `catch` and `finally_` are now
present also on `Task`s as well as `Future`s.
{pr}`3748`
### Deployment
- {{ Fix }} Export `python_stdlib.zip` in `package.json`.

View File

@ -4,7 +4,7 @@ import inspect
import sys
import time
import traceback
from asyncio import Future
from asyncio import Future, Task
from collections.abc import Awaitable, Callable
from typing import Any, TypeVar, overload
@ -18,10 +18,11 @@ S = TypeVar("S")
class PyodideFuture(Future[T]):
"""A future with extra ``then``, ``catch``, and ``finally_`` methods based
on the Javascript promise API. ``Webloop.create_future`` returns these so in
practice all futures encountered in Pyodide should be an instance of
``PyodideFuture``.
"""A :py:class:`~asyncio.Future` with extra :js:meth:`~Promise.then`,
:js:meth:`~Promise.catch`, and :js:meth:`finally_() <Promise.finally>` methods
based on the Javascript promise API. :py:meth:`~asyncio.loop.create_future`
returns these so in practice all futures encountered in Pyodide should be an
instance of :py:class:`~pyodide.webloop.PyodideFuture`.
"""
@overload
@ -141,7 +142,7 @@ class PyodideFuture(Future[T]):
return self.then(None, onrejected)
def finally_(self, onfinally: Callable[[], None]) -> "PyodideFuture[T]":
"""When the future is either resolved or rejected, call onfinally with
"""When the future is either resolved or rejected, call ``onfinally`` with
no arguments.
"""
result: PyodideFuture[T] = PyodideFuture()
@ -167,6 +168,16 @@ class PyodideFuture(Future[T]):
return result
class PyodideTask(Task[T], PyodideFuture[T]):
"""Inherits from both :py:class:`~asyncio.Task` and
:py:class:`~pyodide.webloop.PyodideFuture`
Instantiation is discouraged unless you are writing your own event loop.
"""
pass
class WebLoop(asyncio.AbstractEventLoop):
"""A custom event loop for use in Pyodide.
@ -400,7 +411,7 @@ class WebLoop(asyncio.AbstractEventLoop):
"""
self._check_closed()
if self._task_factory is None:
task = asyncio.tasks.Task(coro, loop=self, name=name)
task = PyodideTask(coro, loop=self, name=name)
if task._source_traceback: # type: ignore[attr-defined]
# Added comment:
# this only happens if get_debug() returns True.
@ -599,4 +610,4 @@ def _initialize_event_loop():
policy.get_event_loop()
__all__ = ["WebLoop", "WebLoopPolicy", "PyodideFuture"]
__all__ = ["WebLoop", "WebLoopPolicy", "PyodideFuture", "PyodideTask"]

View File

@ -355,6 +355,51 @@ async def test_pyodide_future2(selenium):
assert name == "pytest"
@run_in_pyodide
async def test_pyodide_task(selenium):
from asyncio import Future, ensure_future, sleep
async def taskify(fut):
return await fut
def do_the_thing():
d = dict(
did_onresolve=None,
did_onreject=None,
did_onfinally=False,
)
f: Future[int] = Future()
t = ensure_future(taskify(f))
t.then(
lambda v: d.update(did_onresolve=v), lambda e: d.update(did_onreject=e)
).finally_(lambda: d.update(did_onfinally=True))
return f, d
f, d = do_the_thing()
f.set_result(7)
await sleep(0.1)
assert d == dict(
did_onresolve=7,
did_onreject=None,
did_onfinally=True,
)
f, d = do_the_thing()
e = Exception("Oops!")
f.set_exception(e)
assert d == dict(
did_onresolve=None,
did_onreject=None,
did_onfinally=False,
)
await sleep(0.1)
assert d == dict(
did_onresolve=None,
did_onreject=e,
did_onfinally=True,
)
@run_in_pyodide
async def test_inprogress(selenium):
import asyncio