diff --git a/external-deps/spyder-kernels/.gitrepo b/external-deps/spyder-kernels/.gitrepo index dc79d5abbad..8fb9af97241 100644 --- a/external-deps/spyder-kernels/.gitrepo +++ b/external-deps/spyder-kernels/.gitrepo @@ -4,9 +4,9 @@ ; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme ; [subrepo] - remote = https://github.com/spyder-ide/spyder-kernels.git - branch = master - commit = f3778e6e6daeb8d4cccfa1f9f2df2f4546749c66 - parent = 01c4bd538e7531d494bc680781d1a94bf5a2322b + remote = /Users/rclary/Documents/Repos/Spyder-IDE/spyder-kernels + branch = ppm-syspath + commit = a7a55e27f869f1fadbc80896311e5738f41fb89e + parent = 52c0034ebbc984a486a4223ea7e2c00c68173496 method = merge - cmdver = 0.4.3 + cmdver = 0.4.6 diff --git a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py index 9c837f62a13..44a5e9a027d 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py @@ -76,6 +76,11 @@ def __init__(self, *args, **kwargs): # Socket to signal shell_stream locally self.loopback_socket = None + # Store original sys.path. Kernels are started with PYTHONPATH + # removed from environment variables, so this will never have + # user paths and should be clean. + self._sys_path = sys.path.copy() + @property def kernel_info(self): # Used for checking correct version by spyder @@ -595,7 +600,6 @@ def set_matplotlib_conf(self, conf): if bbox_inches_n in conf: self.set_mpl_inline_bbox_inches(conf[bbox_inches_n]) - def set_mpl_inline_bbox_inches(self, bbox_inches): """ Set inline print figure bbox inches. @@ -725,7 +729,7 @@ def set_special_kernel(self, special): exec("from pylab import *", self.shell.user_ns) self.shell.special = special return - + if special == "sympy": import sympy # noqa sympy_init = "\n".join([ @@ -765,27 +769,35 @@ def set_special_kernel(self, special): raise NotImplementedError(f"{special}") @comm_handler - def update_syspath(self, path_dict, new_path_dict): + def update_syspath(self, new_path, prioritize): """ Update the PYTHONPATH of the kernel. - `path_dict` and `new_path_dict` have the paths as keys and the state - as values. The state is `True` for active and `False` for inactive. + `new_path` corresponds to the new state of the PYTHONPATH. + `prioritize` determines whether to prioritize PYTHONPATH in sys.path. - `path_dict` corresponds to the previous state of the PYTHONPATH. - `new_path_dict` corresponds to the new state of the PYTHONPATH. + A copy of sys.path is made at instantiation, which should be clean, + so we can just prepend/append to the copy without having to explicitly + remove old user paths. PYTHONPATH can just be overwritten. """ - # Remove old paths - for path in path_dict: - while path in sys.path: - sys.path.remove(path) - - # Add new paths - pypath = [path for path, active in new_path_dict.items() if active] - if pypath: - sys.path.extend(pypath) - os.environ.update({'PYTHONPATH': os.pathsep.join(pypath)}) + if new_path is not None: + # Overwrite PYTHONPATH + os.environ.update({'PYTHONPATH': os.pathsep.join(new_path)}) + + # Add new paths to original sys.path + if prioritize: + sys.path[:] = new_path + self._sys_path + + # Ensure current directory is always first to imitate Python + # standard behavior + if '' in sys.path: + sys.path.remove('') + sys.path.insert(0, '') + else: + sys.path[:] = self._sys_path + new_path else: + # Restore original sys.path and remove PYTHONPATH + sys.path[:] = self._sys_path os.environ.pop('PYTHONPATH', None) # -- Private API --------------------------------------------------- @@ -907,9 +919,9 @@ def _set_mpl_backend(self, backend, pylab=False): return generic_error = ( - "\n" + "="*73 + "\n" + "\n" + "=" * 73 + "\n" "NOTE: The following error appeared when setting " - "your Matplotlib backend!!\n" + "="*73 + "\n\n" + "your Matplotlib backend!!\n" + "=" * 73 + "\n\n" "{0}" ) @@ -931,7 +943,7 @@ def _set_mpl_backend(self, backend, pylab=False): # trying to set a backend. See issue 5541 if "GUI eventloops" in str(err): previous_backend = matplotlib.get_backend() - if not backend in previous_backend.lower(): + if backend not in previous_backend.lower(): # Only inform about an error if the user selected backend # and the one set by Matplotlib are different. Else this # message is very confusing. diff --git a/external-deps/spyder-kernels/spyder_kernels/console/start.py b/external-deps/spyder-kernels/spyder_kernels/console/start.py index eb910305957..2d79a1ddb62 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/start.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/start.py @@ -16,6 +16,15 @@ import sys import site +# Remove current directory from sys.path to prevent kernel +# crashes when people name Python files or modules with +# the same name as standard library modules. +# See spyder-ide/spyder#8007 +# Inject it back into sys.path after all imports in this module but +# before the kernel is initialized +while '' in sys.path: + sys.path.remove('') + # Third-party imports from traitlets import DottedObjectName @@ -29,13 +38,6 @@ def import_spydercustomize(): parent = osp.dirname(here) customize_dir = osp.join(parent, 'customize') - # Remove current directory from sys.path to prevent kernel - # crashes when people name Python files or modules with - # the same name as standard library modules. - # See spyder-ide/spyder#8007 - while '' in sys.path: - sys.path.remove('') - # Import our customizations site.addsitedir(customize_dir) import spydercustomize # noqa @@ -46,6 +48,7 @@ def import_spydercustomize(): except ValueError: pass + def kernel_config(): """Create a config object with IPython kernel options.""" from IPython.core.application import get_ipython_dir @@ -175,13 +178,6 @@ def main(): # Import our customizations into the kernel import_spydercustomize() - # Remove current directory from sys.path to prevent kernel - # crashes when people name Python files or modules with - # the same name as standard library modules. - # See spyder-ide/spyder#8007 - while '' in sys.path: - sys.path.remove('') - # Main imports from ipykernel.kernelapp import IPKernelApp from spyder_kernels.console.kernel import SpyderKernel @@ -214,6 +210,12 @@ def close(self): kernel.config = kernel_config() except: pass + + # Re-add current working directory path into sys.path after all of the + # import statements, but before initiializing the kernel. + if '' not in sys.path: + sys.path.insert(0, '') + kernel.initialize() # Set our own magics diff --git a/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py index a9564febed3..5636c727a74 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py @@ -75,13 +75,15 @@ def setup_kernel(cmd): ) # wait for connection file to exist, timeout after 5s tic = time.time() - while not os.path.exists(connection_file) \ - and kernel.poll() is None \ - and time.time() < tic + SETUP_TIMEOUT: + while ( + not os.path.exists(connection_file) + and kernel.poll() is None + and time.time() < tic + SETUP_TIMEOUT + ): time.sleep(0.1) if kernel.poll() is not None: - o,e = kernel.communicate() + o, e = kernel.communicate() raise IOError("Kernel failed to start:\n%s" % e) if not os.path.exists(connection_file): @@ -227,7 +229,7 @@ def kernel(request): 'True_' ], 'minmax': False, - 'filter_on':True + 'filter_on': True } # Teardown @@ -277,7 +279,7 @@ def test_get_namespace_view(kernel): """ Test the namespace view of the kernel. """ - execute = asyncio.run(kernel.do_execute('a = 1', True)) + asyncio.run(kernel.do_execute('a = 1', True)) nsview = repr(kernel.get_namespace_view()) assert "'a':" in nsview @@ -293,7 +295,7 @@ def test_get_namespace_view_filter_on(kernel, filter_on): """ Test the namespace view of the kernel with filters on and off. """ - execute = asyncio.run(kernel.do_execute('a = 1', True)) + asyncio.run(kernel.do_execute('a = 1', True)) asyncio.run(kernel.do_execute('TestFilterOff = 1', True)) settings = kernel.namespace_view_settings @@ -466,8 +468,11 @@ def test_is_defined(kernel): def test_get_doc(kernel): """Test to get object documentation dictionary.""" objtxt = 'help' - assert ("Define the builtin 'help'" in kernel.get_doc(objtxt)['docstring'] or - "Define the built-in 'help'" in kernel.get_doc(objtxt)['docstring']) + assert ( + "Define the builtin 'help'" in kernel.get_doc(objtxt)['docstring'] + or "Define the built-in 'help'" in kernel.get_doc(objtxt)['docstring'] + ) + def test_get_source(kernel): """Test to get object source.""" @@ -505,7 +510,7 @@ def test_cwd_in_sys_path(): with setup_kernel(cmd) as client: reply = client.execute_interactive( "import sys; sys_path = sys.path", - user_expressions={'output':'sys_path'}, timeout=TIMEOUT) + user_expressions={'output': 'sys_path'}, timeout=TIMEOUT) # Transform value obtained through user_expressions user_expressions = reply['content']['user_expressions'] @@ -516,6 +521,21 @@ def test_cwd_in_sys_path(): assert '' in value +def test_prioritize(kernel): + """Test that user path priority is honored in sys.path.""" + syspath = kernel.get_syspath() + append_path = ['/test/append/path'] + prepend_path = ['/test/prepend/path'] + + kernel.update_syspath(append_path, prioritize=False) + new_syspath = kernel.get_syspath() + assert new_syspath == syspath + append_path + + kernel.update_syspath(prepend_path, prioritize=True) + new_syspath = kernel.get_syspath() + assert new_syspath == prepend_path + syspath + + @flaky(max_runs=3) def test_multiprocessing(tmpdir): """ @@ -699,8 +719,10 @@ def test_runfile(tmpdir): assert content['found'] # Run code file `u` with current namespace - msg = client.execute_interactive("%runfile {} --current-namespace" - .format(repr(str(u))), timeout=TIMEOUT) + msg = client.execute_interactive( + "%runfile {} --current-namespace".format(repr(str(u))), + timeout=TIMEOUT + ) content = msg['content'] # Verify that the variable `result3` is defined @@ -725,7 +747,9 @@ def test_runfile(tmpdir): sys.platform == 'darwin' and sys.version_info[:2] == (3, 8), reason="Fails on Mac with Python 3.8") def test_np_threshold(kernel): - """Test that setting Numpy threshold doesn't make the Variable Explorer slow.""" + """Test that setting Numpy threshold doesn't make the Variable Explorer + slow. + """ cmd = "from spyder_kernels.console import start; start.main()" @@ -784,7 +808,9 @@ def test_np_threshold(kernel): while "data" not in msg['content']: msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content']['data']['text/plain'] - assert "{'float_kind':