From 2f295cd4aa57bebf14ddd2b4587562d406ca43ed Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Wed, 13 Nov 2024 17:51:35 +0100 Subject: [PATCH] Handle async do_complete. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A number of methods of ipykernel can optinally return `awaitable[T]` instead of just `T`, this is the case for `do_complete`. I think it's a mistake ; see ipython/ipykernel#1295 ; in particular because it's easy to forget / hard to properly type-check, and I'd like to make it mandatory in the long term to have await. Spyder seem to not handle the case where do_completer return an awaitable (or more partiularly is `do_complete` is a coroutine function. This tries to handle it – and as of course `do_completer` _can_ be async, all caller _must_ be async. So I try to do all the required updates. Note: I also add explict imports in some test, to get better error message in case those deps are not installed. --- .../console/tests/test_console_kernel.py | 7 ++++++- spyder_kernels/customize/spyderpdb.py | 14 +++++++------- spyder_kernels/utils/tests/test_iofuncs.py | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/spyder_kernels/console/tests/test_console_kernel.py b/spyder_kernels/console/tests/test_console_kernel.py index 7e623b30..938c29a5 100644 --- a/spyder_kernels/console/tests/test_console_kernel.py +++ b/spyder_kernels/console/tests/test_console_kernel.py @@ -875,13 +875,15 @@ def test_matplotlib_inline(kernel): assert 'inline' in value -def test_do_complete(kernel): +async def test_do_complete(kernel): """ Check do complete works in normal and debugging mode. """ asyncio.run(kernel.do_execute('abba = 1', True)) assert kernel.get_value('abba') == 1 match = kernel.do_complete('ab', 2) + if isawaitable(match): + match = await match assert 'abba' in match['matches'] # test pdb @@ -890,6 +892,8 @@ def test_do_complete(kernel): pdb_obj.completenames = lambda *ignore: ['baba'] kernel.shell._namespace_stack = [pdb_obj] match = kernel.do_complete('ba', 2) + if isawaitable(match): + match = await match assert 'baba' in match['matches'] pdb_obj.curframe = None @@ -1390,6 +1394,7 @@ def test_django_settings(kernel): This is a regression test for issue spyder-ide/spyder#19516 """ + import django asyncio.run(kernel.do_execute('from django.conf import settings', True)) nsview = repr(kernel.get_namespace_view()) assert "'settings':" in nsview diff --git a/spyder_kernels/customize/spyderpdb.py b/spyder_kernels/customize/spyderpdb.py index a522afa5..a11dfe6e 100755 --- a/spyder_kernels/customize/spyderpdb.py +++ b/spyder_kernels/customize/spyderpdb.py @@ -351,16 +351,16 @@ def do_where(self, arg): do_bt = do_where # --- Method defined by us to respond to ipython complete protocol - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """ Respond to a complete request. """ if self.pdb_use_exclamation_mark: - return self._complete_exclamation(code, cursor_pos) + return await self._complete_exclamation(code, cursor_pos) else: - return self._complete_default(code, cursor_pos) + return await self._complete_default(code, cursor_pos) - def _complete_default(self, code, cursor_pos): + async def _complete_default(self, code, cursor_pos): """ Respond to a complete request if not pdb_use_exclamation_mark. """ @@ -422,7 +422,7 @@ def is_name_or_composed(text): else: frame = self.curframe self.shell.set_completer_frame(frame) - result = self.shell.kernel._do_complete(code, cursor_pos) + result = await self.shell.kernel._do_complete(code, cursor_pos) # Reset frame self.shell.set_completer_frame() # If there is no Pdb results to merge, return the result @@ -450,7 +450,7 @@ def is_name_or_composed(text): 'metadata': {}, 'status': 'ok'} - def _complete_exclamation(self, code, cursor_pos): + async def _complete_exclamation(self, code, cursor_pos): """ Respond to a complete request if pdb_use_exclamation_mark. """ @@ -529,7 +529,7 @@ def is_name_or_composed(text): else: frame = self.curframe self.shell.set_completer_frame(frame) - result = self.shell.kernel._do_complete(code, cursor_pos) + result = await self.shell.kernel._do_complete(code, cursor_pos) # Reset frame self.shell.set_completer_frame() return result diff --git a/spyder_kernels/utils/tests/test_iofuncs.py b/spyder_kernels/utils/tests/test_iofuncs.py index ee8c1b1b..400d128e 100644 --- a/spyder_kernels/utils/tests/test_iofuncs.py +++ b/spyder_kernels/utils/tests/test_iofuncs.py @@ -342,6 +342,7 @@ def test_spydata_export(input_namespace, expected_namespace, def test_save_load_hdf5_files(tmp_path): """Simple test to check that we can save and load HDF5 files.""" + import h5py h5_file = tmp_path / "test.h5" data = {'a' : [1, 2, 3, 4], 'b' : 4.5} iofuncs.save_hdf5(data, h5_file)