diff --git a/google/colab/_kernel.py b/google/colab/_kernel.py index 73eaab8b..5c36618e 100644 --- a/google/colab/_kernel.py +++ b/google/colab/_kernel.py @@ -13,12 +13,24 @@ # limitations under the License. """Colab-specific kernel customizations.""" +import inspect +import warnings + from google.colab import _shell from google.colab import _shell_customizations from ipykernel import ipkernel from ipykernel import jsonutil from IPython.utils import tokenutil +_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 an `asyncio.Future`). It might become a' + ' requirement in the future. Coroutine functions and awaitables have been' + ' supported since ipykernel 6.0 (2021). {target} does not seem to return an' + ' awaitable' +) + class Kernel(ipkernel.IPythonKernel): """Kernel with additional Colab-specific features.""" @@ -54,7 +66,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0, *args, **kwargs): return reply_content - def complete_request(self, stream, ident, parent): + async def complete_request(self, stream, ident, parent): """Colab-specific complete_request handler. Overrides the default to allow providing additional metadata in the @@ -71,6 +83,16 @@ def complete_request(self, stream, ident, parent): cursor_pos = content['cursor_pos'] matches = self.do_complete(code, cursor_pos) + if inspect.isawaitable(matches): + matches = await matches + else: + warnings.warn( + _AWAITABLE_MESSAGE.format( + func_name='do_complete', target=self.do_complete + ), + PendingDeprecationWarning, + stacklevel=1, + ) if ( parent.get('metadata', {}) .get('colab_options', {}) @@ -82,7 +104,7 @@ def complete_request(self, stream, ident, parent): # # Note that 100 is an arbitrarily chosen bound for the number of # completions to return. - matches_incomplete = len(matches['matches']) > 100 + matches_incomplete = len(matches.get('matches', [])) > 100 if matches_incomplete: matches['matches'] = matches['matches'][:100] matches['metadata'] = { diff --git a/google/colab/_shell.py b/google/colab/_shell.py index 1471145c..50ac210c 100644 --- a/google/colab/_shell.py +++ b/google/colab/_shell.py @@ -17,7 +17,6 @@ import os import pathlib import sys -import traceback from google.colab import _history from google.colab import _inspector @@ -27,7 +26,6 @@ from ipykernel import compiler from ipykernel import jsonutil from ipykernel import zmqshell -from IPython.core import events from IPython.core import interactiveshell from IPython.core import oinspect from IPython.utils import PyColorize @@ -90,17 +88,13 @@ def get_code_name(self, raw_code, code, number): code_name = super().get_code_name(raw_code, code, number) if code_name.endswith('.py'): path = pathlib.Path(code_name) - code_name = f'/tmp/ipython-input-{path.name}' + code_name = f'/tmp/ipython-input-{number}-{path.name}' return code_name class Shell(zmqshell.ZMQInteractiveShell): """Shell with additional Colab-specific features.""" - def init_events(self): - self.events = events.EventManager(self, events.available_events) - self.events.register('pre_execute', self._clear_warning_registry) - def init_inspector(self): """Initialize colab's custom inspector.""" self.inspector = _inspector.ColabInspector( @@ -238,34 +232,6 @@ def _getattr_property(obj, attrname): # Nothing helped, fall back. return getattr(obj, attrname) - def object_inspect(self, oname, detail_level=0): - info = self._ofind(oname) - - if info['found']: - try: - info = self._object_find(oname) - # We need to avoid arbitrary python objects remaining in info (and - # potentially being serialized below); `obj` itself needs to be - # removed, but retained for use below, and `parent` isn't used at all. - obj = info.pop('obj', '') - info.pop('parent', '') - result = self.inspector.info( - obj, oname, info=info, detail_level=detail_level - ) - except Exception as e: # pylint: disable=broad-except - self.kernel.log.info( - 'Exception caught during object inspection: ' - '{!r}\nTraceback:\n{}'.format( - e, ''.join(traceback.format_tb(sys.exc_info()[2])) - ) - ) - result = oinspect.InfoDict() - else: - result = super(Shell, self).object_inspect( - oname, detail_level=detail_level - ) - return result - def run_cell_magic(self, magic_name, line, cell): # We diverge from Jupyter behavior here: we want to allow cell magics with a # nonempty line and no cell to execute, to unblock users executing a cell diff --git a/setup.py b/setup.py index 2ba82ee5..ad3ff447 100644 --- a/setup.py +++ b/setup.py @@ -20,9 +20,9 @@ # Note: these dependency versions should be kept in-sync with the versions # specified in the docker container requirements files. 'google-auth==2.38.0', - 'ipykernel==6.17.1', + 'ipykernel==6.21.3', 'ipyparallel==8.8.0', - 'ipython==7.34.0', + 'ipython==8.15.0', 'pandas==2.2.2', 'jupyter-server==2.14.0', 'portpicker==1.5.2', diff --git a/tests/jupyter_autocomplete_test.py b/tests/jupyter_autocomplete_test.py index 43a4b7d4..8dee2239 100644 --- a/tests/jupyter_autocomplete_test.py +++ b/tests/jupyter_autocomplete_test.py @@ -36,6 +36,7 @@ def testBasicAutocompletions(self): ]) self.assertIn("'getpass.getpass'", output) + @unittest.skip('Inside expressions autocomplete currently not working') def testInlineAutocomplete(self): """Test that autocomplete works inside another expression.""" output = _run_under_jupyter([