diff --git a/IPython/frontend/qt/console/qtconsoleapp.py b/IPython/frontend/qt/console/qtconsoleapp.py index 0a4e93f..0d4f1f2 100644 --- a/IPython/frontend/qt/console/qtconsoleapp.py +++ b/IPython/frontend/qt/console/qtconsoleapp.py @@ -17,7 +17,6 @@ Authors: #----------------------------------------------------------------------------- # stdlib imports -import glob import os import signal import sys @@ -31,7 +30,7 @@ from zmq.utils import jsonapi as json from IPython.config.application import boolean_flag from IPython.core.application import BaseIPythonApplication from IPython.core.profiledir import ProfileDir -from IPython.lib.kernel import tunnel_to_kernel +from IPython.lib.kernel import tunnel_to_kernel, find_connection_file from IPython.frontend.qt.console.frontend_widget import FrontendWidget from IPython.frontend.qt.console.ipython_widget import IPythonWidget from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget @@ -363,26 +362,12 @@ class IPythonQtConsoleApp(BaseIPythonApplication): fileglob, and the matching file in the current profile's security dir with the latest access time will be used. """ - sec = self.profile_dir.security_dir if self.existing: try: - # first, try explicit name - cf = filefind(self.existing, ['.', sec]) - except IOError: - # not found by full name - if '*' in self.existing: - # given as a glob already - pat = self.existing - else: - # accept any substring match - pat = '*%s*' % self.existing - matches = glob.glob( os.path.join(sec, pat) ) - if not matches: - self.log.critical("Could not find existing kernel connection file %s", self.existing) - self.exit(1) - else: - # get most recent match: - cf = sorted(matches, key=lambda f: os.stat(f).st_atime)[-1] + cf = find_connection_file(self.existing) + except Exception: + self.log.critical("Could not find existing kernel connection file %s", self.existing) + self.exit(1) self.log.info("Connecting to existing kernel: %s" % cf) self.connection_file = cf # should load_connection_file only be used for existing? diff --git a/IPython/lib/kernel.py b/IPython/lib/kernel.py index 4abfeba..df9ec6b 100644 --- a/IPython/lib/kernel.py +++ b/IPython/lib/kernel.py @@ -17,7 +17,9 @@ Authors: # Imports #----------------------------------------------------------------------------- +import glob import json +import os import sys from getpass import getpass from subprocess import Popen, PIPE @@ -26,7 +28,8 @@ from subprocess import Popen, PIPE from IPython.external.ssh import tunnel # IPython imports -from IPython.utils.path import filefind +from IPython.core.profiledir import ProfileDir +from IPython.utils.path import filefind, get_ipython_dir from IPython.utils.py3compat import str_to_bytes @@ -49,22 +52,108 @@ def get_connection_file(app=None): app = KernelApp.instance() return filefind(app.connection_file, ['.', app.profile_dir.security_dir]) - -def get_connection_info(unpack=False): + +def find_connection_file(filename, profile=None): + """find a connection file, and return its absolute path. + + The current working directory and the profile's security + directory will be searched for the file if it is not given by + absolute path. + + If profile is unspecified, then the current running application's + profile will be used, or 'default', if not run from IPython. + + If the argument does not match an existing file, it will be interpreted as a + fileglob, and the matching file in the profile's security dir with + the latest access time will be used. + + Parameters + ---------- + filename : str + The connection file or fileglob to search for. + profile : str [optional] + The name of the profile to use when searching for the connection file, + if different from the current IPython session or 'default'. + + Returns + ------- + str : The absolute path of the connection file. + """ + from IPython.core.application import BaseIPythonApplication as IPApp + try: + # quick check for absolute path, before going through logic + return filefind(filename) + except IOError: + pass + + if profile is None: + # profile unspecified, check if running from an IPython app + if IPApp.initialized(): + app = IPApp.instance() + profile_dir = app.profile_dir + else: + # not running in IPython, use default profile + profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default') + else: + # find profiledir by profile name: + profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) + security_dir = profile_dir.security_dir + + try: + # first, try explicit name + return filefind(filename, ['.', security_dir]) + except IOError: + pass + + # not found by full name + + if '*' in filename: + # given as a glob already + pat = filename + else: + # accept any substring match + pat = '*%s*' % filename + matches = glob.glob( os.path.join(security_dir, pat) ) + if not matches: + raise IOError("Could not find %r in %r" % (filename, security_dir)) + elif len(matches) == 1: + return matches[0] + else: + # get most recent match, by access time: + return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1] + +def get_connection_info(connection_file=None, unpack=False, profile=None): """Return the connection information for the current Kernel. Parameters ---------- + connection_file : str [optional] + The connection file to be used. Can be given by absolute path, or + IPython will search in the security directory of a given profile. + If run from IPython, + + If unspecified, the connection file for the currently running + IPython Kernel will be used, which is only allowed from inside a kernel. unpack : bool [default: False] if True, return the unpacked dict, otherwise just the string contents of the file. + profile : str [optional] + The name of the profile to use when searching for the connection file, + if different from the current IPython session or 'default'. + Returns ------- The connection dictionary of the current kernel, as string or dict, depending on `unpack`. """ - cf = get_connection_file() + if connection_file is None: + # get connection file from current kernel + cf = get_connection_file() + else: + # connection file specified, allow shortnames: + cf = find_connection_file(connection_file, profile=profile) + with open(cf) as f: info = f.read() @@ -74,7 +163,7 @@ def get_connection_info(unpack=False): info['key'] = str_to_bytes(info.get('key', '')) return info -def connect_qtconsole(argv=None): +def connect_qtconsole(connection_file=None, argv=None, profile=None): """Connect a qtconsole to the current kernel. This is useful for connecting a second qtconsole to a kernel, or to a @@ -82,8 +171,19 @@ def connect_qtconsole(argv=None): Parameters ---------- + connection_file : str [optional] + The connection file to be used. Can be given by absolute path, or + IPython will search in the security directory of a given profile. + If run from IPython, + + If unspecified, the connection file for the currently running + IPython Kernel will be used, which is only allowed from inside a kernel. argv : list [optional] Any extra args to be passed to the console. + profile : str [optional] + The name of the profile to use when searching for the connection file, + if different from the current IPython session or 'default'. + Returns ------- @@ -91,8 +191,11 @@ def connect_qtconsole(argv=None): """ argv = [] if argv is None else argv - # get connection file from current kernel - cf = get_connection_file() + if connection_file is None: + # get connection file from current kernel + cf = get_connection_file() + else: + cf = find_connection_file(connection_file, profile=profile) cmd = ';'.join([ "from IPython.frontend.qt.console import qtconsoleapp", diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 1125e55..e1bb0d8 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -471,7 +471,7 @@ class ZMQInteractiveShell(InteractiveShell): debugging. """ try: - p = connect_qtconsole(arg_split(arg_s, os.name=='posix')) + p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix')) except Exception as e: error("Could not start qtconsole: %r" % e) return