From 0a69e5fff3c02385e37fb67b482af18102a6c484 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 13 Nov 2024 17:34:08 +0100 Subject: [PATCH] Suggest to make implementations of some function always return awaitable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See discussion in #1272; It is not deprecated, but being able to always know you can (and must) await should be simpler in the long run. Deprecating now is not the point, but I want to cover our bases, so that we are more confident later when and if we want to enforce those await. In particular many of those branches are not covered in our tests – and I don't even know wether they were ever taken; I changed some of the base methods to be async, but I'm happy to move those back to sync. A few other things use the `if awaitable(...):` pattern but are a bit more complicted, and some do not dates from 2021, so those will be dealt with separately. --- ipykernel/ipkernel.py | 4 +-- ipykernel/kernelbase.py | 54 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 48efa6cd..3419b924 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -491,7 +491,7 @@ async def run(execution: Execution) -> None: return reply_content - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """Handle code completion.""" if _use_experimental_60_completion and self.use_experimental_completions: return self._experimental_do_complete(code, cursor_pos) @@ -625,7 +625,7 @@ def do_history( "history": list(hist), } - def do_shutdown(self, restart): + async def do_shutdown(self, restart): """Handle kernel shutdown.""" if self.shell: self.shell.exit_now = True diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d496e0c9..f0aadb63 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -57,6 +57,14 @@ from ._version import kernel_protocol_version from .iostream import OutStream +_AWAITABLE_MESSAGE: str = ( + "For consistency across implementations, it is recommended that `{func_name}`" + " either be a coroutine function (`async def`) or return an awaitable object" + " (like a Future). It might become a requirement in the future." + " Coroutine functions and awaitables have been supported since" + " ipykernel 6.0 (2021)." +) + def _accepts_parameters(meth, param_names): parameters = inspect.signature(meth).parameters @@ -742,6 +750,12 @@ async def execute_request(self, socket, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="execute_request"), + PendingDeprecationWarning, + stacklevel=1, + ) # Flush output before sending the reply. if sys.stdout is not None: @@ -802,11 +816,17 @@ async def complete_request(self, socket, ident, parent): matches = self.do_complete(code, cursor_pos) if inspect.isawaitable(matches): matches = await matches + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_complete"), + PendingDeprecationWarning, + stacklevel=1, + ) matches = json_clean(matches) self.session.send(socket, "complete_reply", matches, parent, ident) - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """Override in subclasses to find completions.""" return { "matches": [], @@ -830,6 +850,12 @@ async def inspect_request(self, socket, ident, parent): ) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="inspect_request"), + PendingDeprecationWarning, + stacklevel=1, + ) # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) @@ -849,6 +875,12 @@ async def history_request(self, socket, ident, parent): reply_content = self.do_history(**content) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="history_request"), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) msg = self.session.send(socket, "history_reply", reply_content, parent, ident) @@ -966,6 +998,12 @@ async def shutdown_request(self, socket, ident, parent): content = self.do_shutdown(parent["content"]["restart"]) if inspect.isawaitable(content): content = await content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_shutdown"), + PendingDeprecationWarning, + stacklevel=1, + ) self.session.send(socket, "shutdown_reply", content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub self._shutdown_message = self.session.msg("shutdown_reply", content, parent) @@ -974,7 +1012,7 @@ async def shutdown_request(self, socket, ident, parent): self.stop() - def do_shutdown(self, restart): + async def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ @@ -990,6 +1028,12 @@ async def is_complete_request(self, socket, ident, parent): reply_content = self.do_is_complete(code) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_execute"), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) reply_msg = self.session.send(socket, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) @@ -1006,6 +1050,12 @@ async def debug_request(self, socket, ident, parent): reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="debug_request"), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) reply_msg = self.session.send(socket, "debug_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg)