# pyflyby/_interactive.py. # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen. # License: MIT http://opensource.org/licenses/MIT from __future__ import (absolute_import, division, print_function, with_statement) import ast from contextlib import contextmanager import errno import inspect import os import re import subprocess import sys import six from six import PY2, text_type as unicode from six.moves import builtins from pyflyby._autoimp import (LoadSymbolError, ScopeStack, auto_eval, auto_import, clear_failed_imports_cache, load_symbol) from pyflyby._comms import (initialize_comms, remove_comms, send_comm_message, MISSING_IMPORTS) from pyflyby._file import Filename, atomic_write_file, read_file from pyflyby._idents import is_identifier from pyflyby._importdb import ImportDB from pyflyby._log import logger from pyflyby._modules import ModuleHandle from pyflyby._parse import PythonBlock from pyflyby._util import (AdviceCtx, Aspect, CwdCtx, FunctionWithGlobals, NullCtx, advise, indent) if False: __original__ = None # for pyflakes # TODO: also support arbitrary code (in the form of a lambda and/or # assignment) as new way to do "lazy" creations, e.g. foo = a.b.c(d.e+f.g()) class NoIPythonPackageError(Exception): """ Exception raised when the IPython package is not installed in the system. """ class NoActiveIPythonAppError(Exception): """ Exception raised when there is no current IPython application instance. """ def _get_or_create_ipython_terminal_app(): """ Create/get the singleton IPython terminal application. :rtype: ``TerminalIPythonApp`` :raise NoIPythonPackageError: IPython is not installed in the system. """ try: import IPython except ImportError as e: raise NoIPythonPackageError(e) # The following has been tested on IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3. try: TerminalIPythonApp = IPython.terminal.ipapp.TerminalIPythonApp except AttributeError: pass else: return TerminalIPythonApp.instance() # The following has been tested on IPython 0.11, 0.12, 0.13. try: TerminalIPythonApp = IPython.frontend.terminal.ipapp.TerminalIPythonApp except AttributeError: pass else: return TerminalIPythonApp.instance() # The following has been tested on IPython 0.10. if hasattr(IPython, "ipapi"): return _IPython010TerminalApplication.instance() raise RuntimeError( "Couldn't get TerminalIPythonApp class. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def _app_is_initialized(app): """ Return whether ``app.initialize()`` has been called. :type app: `IPython.Application` :rtype: ``bool`` """ # There's no official way to tell whether app.initialize() has been called # before. We guess whether the app has been initialized by checking # whether all traits have values. # # There's a method app.initialized(), but it doesn't do what we want. It # does not return whether app.initialize() has been called - rather, # type(app).initialized() returns whether an instance of the class has # ever been constructed, i.e. app.initialized() always returns True. cache_name = "__is_initialized_54283907" if cache_name in app.__dict__: return True if all(n in app._trait_values for n in app.trait_names()): app.__dict__[cache_name] = True return True else: return False class _IPython010TerminalApplication(object): """ Shim class that mimics IPython 0.11+ application classes, for use in IPython 0.10. """ # IPython.ipapi.launch_instance() => IPython.Shell.start() creates an # instance of "IPShell". IPShell has an attribute named "IP" which is an # "InteractiveShell". _instance = None @classmethod def instance(cls): if cls._instance is not None: self = cls._instance self.init_shell() return self import IPython if not hasattr(IPython, "ipapi"): raise RuntimeError("Inappropriate version of IPython %r" % (IPython.__version__,)) self = cls._instance = cls() self.init_shell() return self def init_shell(self): import IPython ipapi = IPython.ipapi.get() # IPApi instance if ipapi is not None: self.shell = ipapi.IP # InteractiveShell instance else: self.shell = None def initialize(self, argv=None): import IPython logger.debug("Creating IPython 0.10 session") self._session = IPython.ipapi.make_session() # IPShell instance self.init_shell() assert self._session is not None def start(self): self._session.mainloop() class _DummyIPythonEmbeddedApp(object): """ Small wrapper around an `InteractiveShellEmbed`. """ def __init__(self, shell): self.shell = shell def _get_or_create_ipython_kernel_app(): """ Create/get the singleton IPython kernel application. :rtype: ``callable`` :return: The function that can be called to start the kernel application. """ import IPython # The following has been tested on IPython 4.0 try: from ipykernel.kernelapp import IPKernelApp except ImportError: pass else: return IPKernelApp.instance() # The following has been tested on IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, # 2.4, 3.0, 3.1, 3.2 try: from IPython.kernel.zmq.kernelapp import IPKernelApp except ImportError: pass else: return IPKernelApp.instance() # The following has been tested on IPython 0.12, 0.13 try: from IPython.zmq.ipkernel import IPKernelApp except ImportError: pass else: return IPKernelApp.instance() raise RuntimeError( "Couldn't get IPKernelApp class. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def get_ipython_terminal_app_with_autoimporter(): """ Return an initialized ``TerminalIPythonApp``. If a ``TerminalIPythonApp`` has already been created, then use it (whether we are inside that app or not). If there isn't already one, then create one. Enable the auto importer, if it hasn't already been enabled. If the app hasn't been initialized yet, then initialize() it (but don't start() it). :rtype: ``TerminalIPythonApp`` :raise NoIPythonPackageError: IPython is not installed in the system. """ app = _get_or_create_ipython_terminal_app() AutoImporter(app).enable() if not _app_is_initialized(app): old_display_banner = app.display_banner try: app.display_banner = False app.initialize([]) finally: app.display_banner = old_display_banner return app def start_ipython_with_autoimporter(argv=None, app=None, _user_ns=None): """ Start IPython (terminal) with autoimporter enabled. """ if app is None: subcmd = argv and argv[0] if subcmd == 'console': # The following has been tested on IPython 5.8 / Jupyter console 5.2. # Note: jupyter_console.app.JupyterApp also appears to work in some # contexts, but that actually execs the script jupyter-console which # uses ZMQTerminalIPythonApp. The exec makes the target use whatever # shebang line is in that script, which may be a different python # major version than what we're currently running. We want to avoid # the exec in general (as a library function) and avoid changing # python versions. try: from ipkernel.app import IPKernelApp except (ImportError, AttributeError): pass else: app = IPKernelApp.instance() argv = argv[1:] elif subcmd == 'notebook': try: from notebook.notebookapp import NotebookApp except (ImportError, AttributeError): pass else: app = NotebookApp.instance() argv = argv[1:] if app is None: app = _get_or_create_ipython_terminal_app() if _user_ns is not None: # Tested with IPython 1.2, 2.0, 2.1, 2.2, 2.3. 2.4, 3.0, 3.1, 3.2 # TODO: support older versions of IPython. # FIXME TODO: fix attaching debugger to IPython started this way. It # has to do with assigning user_ns. Apparently if user_ns["__name__"] # is "__main__" (which IPython defaults to, and we want to use # anyway), then user_module must be a true ModuleType in order for # attaching to work correctly. If you specify user_ns but not # user_module, then user_module is a DummyModule rather than a true # ModuleType (since ModuleType.__dict__ is read-only). Thus, if we # specify user_ns, we should specify user_module also. However, while # user_module is a constructor parameter to InteractiveShell, # IPythonTerminalApp doesn't pass that parameter to it. We can't # assign after initialize() because user_module and user_ns are # already used during initialization. One workaround idea is to let # IPython initialize without specifying either user_ns or user_module, # and then patch in members. However, that has the downside of # breaking func_globals of lambdas, e.g. if a script does 'def f(): # global x; x=4', then we run it with 'py -i', our globals dict won't # be the same dict. We should create a true ModuleType anyway even if # not using IPython. We might need to resort to advising # init_create_namespaces etc. depending on IPython version. if getattr(app, 'shell', None) is not None: app.shell.user_ns.update(_user_ns) else: app.user_ns = _user_ns return _initialize_and_start_app_with_autoimporter(app, argv) def start_ipython_kernel_with_autoimporter(argv=None): """ Start IPython kernel with autoimporter enabled. """ app = _get_or_create_ipython_kernel_app() return _initialize_and_start_app_with_autoimporter(app, argv) def _initialize_and_start_app_with_autoimporter(app, argv): """ Initialize and start an IPython app, with autoimporting enabled. :type app: `BaseIPythonApplication` """ # Enable the auto importer. AutoImporter(app).enable() # Save the value of the "_" name in the user namespace, to avoid # initialize() clobbering it. user_ns = getattr(app, "user_ns", None) saved_user_ns = {} if user_ns is not None: for k in ["_"]: try: saved_user_ns[k] = user_ns[k] except KeyError: pass # Initialize the app. if not _app_is_initialized(app): app.initialize(argv) if user_ns is not None: user_ns.update(saved_user_ns) # Start the app mainloop. return app.start() def run_ipython_line_magic(arg): """ Run IPython magic command. If necessary, start an IPython terminal app to do so. """ import IPython if not arg.startswith("%"): arg = "%" + arg app = _get_or_create_ipython_terminal_app() AutoImporter(app).enable() # TODO: only initialize if not already initialized. if not _app_is_initialized(app): app.initialize([]) ip = app.shell if hasattr(ip, "magic"): # IPython 0.11+. # The following has been tested on IPython 0.11, 0.12, 0.13, 1.0, 1.2, # 2.0, 2.1, 2.2, 2.3. # TODO: may want to wrap in one or two layers of dummy functions to make # sure run_line_magic() doesn't inspect our locals. return ip.magic(arg) elif hasattr(ip, "runlines"): # IPython 0.10 return ip.runlines(arg) else: raise RuntimeError( "Couldn't run IPython magic. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def _python_can_import_pyflyby(expected_path, sys_path_entry=None): """ Try to figure out whether python (when started from scratch) can get the same pyflyby package as the current process. """ with CwdCtx("/"): cmd = 'import pyflyby; print(pyflyby.__path__[0])' if sys_path_entry is not None: impcmd = "import sys; sys.path.insert(0, %r)\n" % (sys_path_entry,) cmd = impcmd + cmd proc = subprocess.Popen( [sys.executable, '-c', cmd], stdin=open("/dev/null"), stdout=subprocess.PIPE, stderr=open("/dev/null",'w')) result = proc.communicate()[0].strip() if not result: return False try: return os.path.samefile(result, expected_path) except OSError: return False def install_in_ipython_config_file(): """ Install the call to 'pyflyby.enable_auto_importer()' to the default IPython startup file. This makes all "ipython" sessions behave like "autoipython", i.e. start with the autoimporter already enabled. """ import IPython # The following has been tested on IPython 4.0, 5.0 try: IPython.paths except AttributeError: pass else: _install_in_ipython_config_file_40() return # The following has been tested on IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, # 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. try: IPython.core.profiledir.ProfileDir.startup_dir except AttributeError: pass else: _install_in_ipython_config_file_012() return # The following has been tested on IPython 0.11. try: IPython.core.profiledir.ProfileDir except AttributeError: pass else: _install_in_ipython_config_file_011() return try: IPython.genutils.get_ipython_dir except AttributeError: pass else: _install_in_ipython_config_file_010() return raise RuntimeError( "Couldn't install pyflyby autoimporter in IPython. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def _generate_enabler_code(): """ Generate code for enabling the auto importer. :rtype: ``str`` """ funcdef = ( "import pyflyby\n" "pyflyby.enable_auto_importer()\n" ) # Check whether we need to include the path in sys.path, and if so, add # that to the contents. import pyflyby pyflyby_path = pyflyby.__path__[0] if not _python_can_import_pyflyby(pyflyby_path): path_entry = os.path.dirname(os.path.realpath(pyflyby_path)) assert _python_can_import_pyflyby(pyflyby_path, path_entry) funcdef = ( "import sys\n" "saved_sys_path = sys.path[:]\n" "try:\n" " sys.path.insert(0, %r)\n" % (path_entry,) + indent(funcdef, " ") + "finally:\n" " sys.path = saved_sys_path\n" ) # Wrap the code in a temporary function, call it, then delete the # function. This avoids polluting the user's global namespace. Although # the global name "pyflyby" will almost always end up meaning the module # "pyflyby" anyway, if the user types it, there's still value in not # polluting the namespace in case something enumerates over globals(). # For the function name we use a name that's unlikely to be used by the # user. contents = ( "def __pyflyby_enable_auto_importer_60321389():\n" + indent(funcdef, " ") + "__pyflyby_enable_auto_importer_60321389()\n" "del __pyflyby_enable_auto_importer_60321389\n" ) return contents def _install_in_ipython_config_file_40(): """ Implementation of `install_in_ipython_config_file` for IPython 4.0+. """ import IPython ipython_dir = Filename(IPython.paths.get_ipython_dir()) if not ipython_dir.isdir: raise RuntimeError( "Couldn't find IPython config dir. Tried %s" % (ipython_dir,)) # Add to extensions list in ~/.ipython/profile_default/ipython_config.py config_fn = ipython_dir / "profile_default" / "ipython_config.py" if not config_fn.exists: subprocess.call(['ipython', 'profile', 'create']) if not config_fn.exists: raise RuntimeError( "Couldn't find IPython config file. Tried %s" % (config_fn,)) old_config_blob = read_file(config_fn) # This is the line we'll add. line_to_add = 'c.InteractiveShellApp.extensions.append("pyflyby")' non_comment_lines = [re.sub("#.*", "", line) for line in old_config_blob.lines] if any(line_to_add in line for line in non_comment_lines): logger.info("[NOTHING TO DO] File %s already loads pyflyby", config_fn) elif any("pyflyby" in line for line in non_comment_lines): logger.info("[NOTHING TO DO] File %s already references pyflyby in some nonstandard way, assuming you configured it manually", config_fn) else: # Add pyflyby to config file. lines_to_add = [line_to_add] # Check whether we need to include the path in sys.path, and if so, add # that to the contents. This is only needed if pyflyby is running out # of a home directory rather than site-packages/virtualenv/etc. # TODO: we should use tidy-imports to insert the 'import sys', if # needed, at the top, rather than always appending it at the bottom. import pyflyby pyflyby_path = pyflyby.__path__[0] if not _python_can_import_pyflyby(pyflyby_path): path_entry = os.path.dirname(os.path.realpath(pyflyby_path)) assert _python_can_import_pyflyby(pyflyby_path, path_entry) lines_to_add = [ "import sys", "sys.path.append(%r)" % (path_entry,) ] + lines_to_add lines_to_add.insert(0, "# Pyflyby") blob_to_add = "\n\n" + "\n".join(lines_to_add) + "\n" new_config_blob = old_config_blob.joined.rstrip() + blob_to_add atomic_write_file(config_fn, new_config_blob) logger.info("[DONE] Appended to %s: %s", config_fn, line_to_add) # Delete file installed with older approach. startup_dir = ipython_dir / "profile_default" / "startup" old_fn = startup_dir / "50-pyflyby.py" if old_fn.exists: trash_dir = old_fn.dir / ".TRASH" trash_fn = trash_dir / old_fn.base try: os.mkdir(str(trash_dir)) except EnvironmentError as e: if e.errno == errno.EEXIST: pass else: raise RuntimeError("Couldn't mkdir %s: %s: %s" % (trash_dir, type(e).__name__, e)) try: os.rename(str(old_fn), str(trash_fn)) except EnvironmentError as e: raise RuntimeError("Couldn't rename %s to %s: %s: %s" % (old_fn, trash_fn, type(e).__name__, e)) logger.info("[DONE] Removed old file %s (moved to %s)", old_fn, trash_fn) def _install_in_ipython_config_file_012(): """ Implementation of `install_in_ipython_config_file` for IPython 0.12+. Tested with IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. """ import IPython ipython_dir = Filename(IPython.utils.path.get_ipython_dir()) if not ipython_dir.isdir: raise RuntimeError( "Couldn't find IPython config dir. Tried %s" % (ipython_dir,)) startup_dir = ipython_dir / "profile_default" / "startup" if not startup_dir.isdir: raise RuntimeError( "Couldn't find IPython startup dir. Tried %s" % (startup_dir,)) fn = startup_dir / "50-pyflyby.py" if fn.exists: logger.info("Doing nothing, because %s already exists", fn) return argv = sys.argv[:] argv[0] = os.path.realpath(argv[0]) argv = ' '.join(argv) header = ( "# File: {fn}\n" "#\n" "# Generated by {argv}\n" "#\n" "# This file causes IPython to enable the Pyflyby Auto Importer.\n" "#\n" "# To uninstall, just delete this file.\n" "#\n" ).format(**locals()) contents = header + _generate_enabler_code() logger.info("Installing pyflyby auto importer in your IPython startup") logger.info("Writing to %s:\n%s", fn, contents) atomic_write_file(fn, contents) def _install_in_ipython_config_file_011(): """ Implementation of `install_in_ipython_config_file` for IPython 0.11. """ import IPython ipython_dir = Filename(IPython.utils.path.get_ipython_dir()) fn = ipython_dir / "profile_default" / "ipython_config.py" if not fn.exists: raise RuntimeError( "Couldn't find IPython startup file. Tried %s" % (fn,)) old_contents = read_file(fn).joined if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M): logger.info("Doing nothing, because already installed in %s", fn) return header = ( "\n" "\n" "#\n" "# Enable the Pyflyby Auto Importer.\n" ) new_contents = header + _generate_enabler_code() contents = old_contents.rstrip() + new_contents logger.info("Installing pyflyby auto importer in your IPython startup") logger.info("Appending to %s:\n%s", fn, new_contents) atomic_write_file(fn, contents) def _install_in_ipython_config_file_010(): """ Implementation of `install_in_ipython_config_file` for IPython 0.10. """ import IPython ipython_dir = Filename(IPython.genutils.get_ipython_dir()) fn = ipython_dir / "ipy_user_conf.py" if not fn.exists: raise RuntimeError( "Couldn't find IPython config file. Tried %s" % (fn,)) old_contents = read_file(fn).joined if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M): logger.info("Doing nothing, because already installed in %s", fn) return header = ( "\n" "\n" "#\n" "# Enable the Pyflyby Auto Importer.\n" ) new_contents = header + _generate_enabler_code() contents = old_contents.rstrip() + new_contents logger.info("Installing pyflyby auto importer in your IPython startup") logger.info("Appending to %s:\n%s", fn, new_contents) atomic_write_file(fn, contents) def _ipython_in_multiline(ip): """ Return ``False`` if the user has entered only one line of input so far, including the current line, or ``True`` if it is the second or later line. :type ip: ``InteractiveShell`` :rtype: ``bool`` """ if hasattr(ip, "input_splitter"): # IPython 0.11+. Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, # 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. return bool(ip.input_splitter.source) elif hasattr(ip, "buffer"): # IPython 0.10 return bool(ip.buffer) else: # IPython version too old or too new? return False def InterceptPrintsDuringPromptCtx(ip): """ Decorator that hooks our logger so that:: 1. Before the first print, if any, print an extra newline. 2. Upon context exit, if any lines were printed, redisplay the prompt. :type ip: ``InteractiveShell`` """ if not ip: return NullCtx() if not hasattr(ip, 'readline'): if type(sys.stdout).__module__.startswith("prompt_toolkit."): # prompt_toolkit replaces stdout with a proxy that takes # care of redrawing the prompt correctly. return NullCtx() if not hasattr(ip, "prompts_class"): # This could be a Jupyter console/notebook. return NullCtx() def pre(): sys.stdout.write("\n") sys.stdout.flush() def post(): # Re-display the current line. sys.stdout.write("\n") t = ip.prompts_class(ip).in_prompt_tokens() ip.pt_cli.print_tokens(t) sys.stdout.write(ip.pt_cli.current_buffer.document.current_line) sys.stdout.flush() return logger.HookCtx(pre=pre, post=post) readline = ip.readline if not hasattr(readline, "redisplay"): # May be IPython Notebook. return NullCtx() redisplay = readline.redisplay get_prompt = None if type(ip).__module__ == "rlipython.shell": # IPython 5.4 with # interactive_shell_class=rlipython.TerminalInteractiveShell def get_prompt_rlipython(): pdb_instance = _get_pdb_if_is_in_pdb() if pdb_instance is not None: return pdb_instance.prompt elif _ipython_in_multiline(ip): return ip.prompt_in2 else: return ip.separate_in + ip.prompt_in1.format(ip.execution_count) get_prompt = get_prompt_rlipython elif hasattr(ip, "prompt_manager"): # IPython >= 0.12 (known to work including up to 1.2, 2.1) prompt_manager = ip.prompt_manager def get_prompt_ipython_012(): pdb_instance = _get_pdb_if_is_in_pdb() if pdb_instance is not None: return pdb_instance.prompt elif _ipython_in_multiline(ip): return prompt_manager.render("in2") else: return ip.separate_in + prompt_manager.render("in") get_prompt = get_prompt_ipython_012 elif hasattr(ip.hooks, "generate_prompt"): # IPython 0.10, 0.11 generate_prompt = ip.hooks.generate_prompt def get_prompt_ipython_010(): pdb_instance = _get_pdb_if_is_in_pdb() if pdb_instance is not None: return pdb_instance.prompt elif _ipython_in_multiline(ip): return generate_prompt(True) else: if hasattr(ip, "outputcache"): # IPython 0.10 (but not 0.11+): # Decrement the prompt_count since it otherwise # auto-increments. (It's hard to avoid the # auto-increment as it happens as a side effect of # __str__!) ip.outputcache.prompt_count -= 1 return generate_prompt(False) get_prompt = get_prompt_ipython_010 else: # Too old or too new IPython version? return NullCtx() def pre(): sys.stdout.write("\n") sys.stdout.flush() def post(): # Re-display the current line. prompt = get_prompt() prompt = prompt.replace("\x01", "").replace("\x02", "") line = readline.get_line_buffer()[:readline.get_endidx()] sys.stdout.write(prompt + line) redisplay() sys.stdout.flush() return logger.HookCtx(pre=pre, post=post) def _get_ipython_app(): """ Get an IPython application instance, if we are inside an IPython session. If there isn't already an IPython application, raise an exception; don't create one. If there is a subapp, return it. :rtype: `BaseIPythonApplication` or an object that mimics some of its behavior """ try: IPython = sys.modules['IPython'] except KeyError: # The 'IPython' module isn't already loaded, so we're not in an # IPython session. Don't import it. raise NoActiveIPythonAppError( "No active IPython application (IPython not even imported yet)") # The following has been tested on IPython 0.11, 0.12, 0.13, 1.0, 1.2, # 2.0, 2.1, 2.2, 2.3. try: App = IPython.core.application.BaseIPythonApplication except AttributeError: pass else: app = App._instance if app is not None: if app.subapp is not None: return app.subapp else: return app # If we're inside an embedded shell, then there will be an active # InteractiveShellEmbed but no application. In that case, create a # fake application. # (An alternative implementation would be to use # IPython.core.interactiveshell.InteractiveShell._instance. However, # that doesn't work with older versions of IPython, where the embedded # shell is not a singleton.) if hasattr(builtins, "get_ipython"): shell = builtins.get_ipython() else: shell = None if shell is not None: return _DummyIPythonEmbeddedApp(shell) # No active IPython app/shell. raise NoActiveIPythonAppError("No active IPython application") # The following has been tested on IPython 0.10. if hasattr(IPython, "ipapi"): return _IPython010TerminalApplication.instance() raise NoActiveIPythonAppError( "Could not figure out how to get active IPython application for IPython version %s" % (IPython.__version__,)) def _ipython_namespaces(ip): """ Return the (global) namespaces used for IPython. The ordering follows IPython convention of most-local to most-global. :type ip: ``InteractiveShell`` :rtype: ``list`` :return: List of (name, namespace_dict) tuples. """ # This list is copied from IPython 2.2's InteractiveShell._ofind(). # Earlier versions of IPython (back to 1.x) also include # ip.alias_manager.alias_table at the end. This doesn't work in IPython # 2.2 and isn't necessary anyway in earlier versions of IPython. return [ ('Interactive' , ip.user_ns), ('Interactive (global)', ip.user_global_ns), ('Python builtin' , builtins.__dict__), ] # TODO class NamespaceList(tuple): _IS_PDB_IGNORE_PKGS = frozenset([ 'IPython', 'cmd', 'contextlib', 'prompt_toolkit', 'pyflyby', 'rlipython', 'asyncio', ]) _IS_PDB_IGNORE_PKGS_OTHER_THREADS = frozenset([ 'IPython', 'cmd', 'contextlib', 'prompt_toolkit', 'pyflyby', 'threading', ]) def _get_pdb_if_is_in_pdb(): """ Return the current Pdb instance, if we're currently called from Pdb. :rtype: ``pdb.Pdb`` or ``NoneType`` """ # This is kludgy. Todo: Is there a better way to do this? pframe, pkgname = _skip_frames(sys._getframe(1), _IS_PDB_IGNORE_PKGS) if pkgname == "threading": # _skip_frames skipped all the way back to threading.__bootstrap. # prompt_toolkit calls completion in a separate thread. # Search all other threads for pdb. # TODO: make this less kludgy. import threading current_tid = threading.current_thread().ident pframes = [_skip_frames(frame, _IS_PDB_IGNORE_PKGS_OTHER_THREADS) for tid, frame in sys._current_frames().items() if tid != current_tid] else: pframes = [(pframe, pkgname)] logger.debug("_get_pdb_if_is_in_pdb(): pframes = %r", pframes) del pframe, pkgname pdb_frames = [pframe for pframe,pkgname in pframes if pkgname == "pdb"] if not pdb_frames: return None # Found a pdb frame. pdb_frame = pdb_frames[0] import pdb pdb_instance = pdb_frame.f_locals.get("self", None) if (type(pdb_instance).__name__ == "Pdb" or isinstance(pdb_instance, pdb.Pdb)): return pdb_instance else: return None def _skip_frames(frame, ignore_pkgs): # import traceback;print("".join(traceback.format_stack(frame))) while True: if frame is None: return None, None modname = frame.f_globals.get("__name__", None) or "" pkgname = modname.split(".",1)[0] # logger.debug("_skip_frames: frame: %r %r", frame, modname) if pkgname in ignore_pkgs: frame = frame.f_back continue break # logger.debug("_skip_frames: => %r %r", frame, pkgname) return frame, pkgname def get_global_namespaces(ip): """ Get the global interactive namespaces. :type ip: ``InteractiveShell`` :param ip: IPython shell or ``None`` to assume not in IPython. :rtype: ``list`` of ``dict`` """ # logger.debug("get_global_namespaces()") pdb_instance = _get_pdb_if_is_in_pdb() # logger.debug("get_global_namespaces(): pdb_instance=%r", pdb_instance) if pdb_instance: frame = pdb_instance.curframe return [frame.f_globals, pdb_instance.curframe_locals] elif ip: return [ns for nsname, ns in _ipython_namespaces(ip)][::-1] else: import __main__ return [builtins.__dict__, __main__.__dict__] def complete_symbol(fullname, namespaces, db=None, autoimported=None, ip=None, allow_eval=False): """ Enumerate possible completions for ``fullname``. Includes globals and auto-importable symbols. >>> complete_symbol("threadi", [{}]) # doctest:+ELLIPSIS [...'threading'...] Completion works on attributes, even on modules not yet imported - modules are auto-imported first if not yet imported:: >>> ns = {} >>> complete_symbol("threading.Threa", namespaces=[ns]) [PYFLYBY] import threading ['threading.Thread', 'threading.ThreadError'] >>> 'threading' in ns True >>> complete_symbol("threading.Threa", namespaces=[ns]) ['threading.Thread', 'threading.ThreadError'] We only need to import *parent* modules (packages) of the symbol being completed. If the user asks to complete "foo.bar.quu", we need to import foo.bar, but we don't need to import foo.bar.quux. :type fullname: ``str`` :param fullname: String to complete. ("Full" refers to the fact that it should contain dots starting from global level.) :type namespaces: ``dict`` or ``list`` of ``dict`` :param namespaces: Namespaces of (already-imported) globals. :type db: `importDB` :param db: Import database to use. :type ip: ``InteractiveShell`` :param ip: IPython shell instance if in IPython; ``None`` to assume not in IPython. :param allow_eval: Whether to allow evaluating code, which is necessary to allow completing e.g. 'foo[0].bar' or 'foo().bar'. Note that IPython will only pass such strings if IPCompleter.greedy is configured to True by the user. :rtype: ``list`` of ``str`` :return: Completion candidates. """ namespaces = ScopeStack(namespaces) logger.debug("complete_symbol(%r)", fullname) splt = fullname.rsplit(".", 1) attrname = splt[-1] # The right-hand-side of the split (the part to complete, possibly the # fullname) must be a prefix of a valid symbol. Otherwise don't bother # generating completions. # As for the left-hand-side of the split, load_symbol() will validate it # or evaluate it depending on ``allow_eval``. if not is_identifier(attrname, prefix=True): return [] # Get the database of known imports. db = ImportDB.interpret_arg(db, target_filename=".") known = db.known_imports if len(splt) == 1: # Check global names, including global-level known modules and # importable modules. results = set() for ns in namespaces: for name in ns: if '.' not in name: results.add(name) results.update(known.member_names.get("", [])) results.update([str(m) for m in ModuleHandle.list()]) assert all('.' not in r for r in results) results = sorted([r for r in results if r.startswith(attrname)]) elif len(splt) == 2: # Check members, including known sub-modules and importable sub-modules. pname = splt[0] if allow_eval: # Evaluate the parent, with autoimporting. ns_g, ns_l = namespaces.merged_to_two() # TODO: only catch exceptions around the eval, not the other stuff # (loading import db, etc). try: parent = auto_eval(pname, globals=ns_g, locals=ns_l, db=db) except Exception as e: logger.debug("complete_symbol(%r): couldn't evaluate %r: %s: %s", fullname, pname, type(e).__name__, e) return [] else: try: parent = load_symbol(pname, namespaces, autoimport=True, db=db, autoimported=autoimported) except LoadSymbolError as e2: # Even after attempting auto-import, the symbol is still # unavailable, or some other error occurred. Nothing to complete. e = getattr(e2, "__cause__", e2) logger.debug("complete_symbol(%r): couldn't load symbol %r: %s: %s", fullname, pname, type(e).__name__, e) return [] logger.debug("complete_symbol(%r): %s == %r", fullname, pname, parent) results = set() # Add current attribute members. results.update(_list_members_for_completion(parent, ip)) # Is the parent a package/module? if sys.modules.get(pname, Ellipsis) is parent and parent.__name__ == pname: # Add known_imports entries from the database. results.update(known.member_names.get(pname, [])) # Get the module handle. Note that we use ModuleHandle() on the # *name* of the module (``pname``) instead of the module instance # (``parent``). Using the module instance normally works, but # breaks if the module hackily replaced itself with a pseudo # module (e.g. https://github.com/josiahcarlson/mprop). pmodule = ModuleHandle(pname) # Add importable submodules. results.update([m.name.parts[-1] for m in pmodule.submodules]) results = sorted([r for r in results if r.startswith(attrname)]) results = ["%s.%s" % (pname, r) for r in results] else: raise AssertionError if six.PY2: results = [unicode(s) for s in results] logger.debug("complete_symbol(%r) => %r", fullname, results) return results def _list_members_for_completion(obj, ip): """ Enumerate the existing member attributes of an object. This emulates the regular Python/IPython completion items. It does not include not-yet-imported submodules. :param obj: Object whose member attributes to enumerate. :rtype: ``list`` of ``str`` """ if ip is None: words = dir(obj) else: try: limit_to__all__ = ip.Completer.limit_to__all__ except AttributeError: limit_to__all__ = False if limit_to__all__ and hasattr(obj, '__all__'): words = getattr(obj, '__all__') elif "IPython.core.error" in sys.modules: from IPython.utils import generics from IPython.utils.dir2 import dir2 from IPython.core.error import TryNext words = dir2(obj) try: words = generics.complete_object(obj, words) except TryNext: pass else: words = dir(obj) return [w for w in words if isinstance(w, six.string_types)] def _auto_import_in_pdb_frame(pdb_instance, arg): frame = pdb_instance.curframe namespaces = [ frame.f_globals, pdb_instance.curframe_locals ] filename = frame.f_code.co_filename if not filename or filename.startswith("<"): filename = "." db = ImportDB.get_default(filename) auto_import(arg, namespaces=namespaces, db=db) def _enable_pdb_hooks(pdb_instance): # Enable hooks in pdb.Pdb. # Should be called after pdb.Pdb.__init__(). logger.debug("_enable_pdb_hooks(%r)", pdb_instance) # Patch Pdb._getval() to use auto_eval. # This supports 'ipdb> p foo'. @advise(pdb_instance._getval) def _getval_with_autoimport(arg): logger.debug("Pdb._getval(%r)", arg) _auto_import_in_pdb_frame(pdb_instance, arg) return __original__(arg) # Patch Pdb.default() to use auto_import. # This supports 'ipdb> foo()'. @advise(pdb_instance.default) def default_with_autoimport(arg): logger.debug("Pdb.default(%r)", arg) if arg.startswith("!"): arg = arg[1:] _auto_import_in_pdb_frame(pdb_instance, arg) return __original__(arg) def _enable_terminal_pdb_hooks(pdb_instance, auto_importer=None): # Should be called after TerminalPdb.__init__(). # Tested with IPython 5.8 with prompt_toolkit. logger.debug("_enable_terminal_pdb_hooks(%r)", pdb_instance) ptcomp = getattr(pdb_instance, "_ptcomp", None) completer = getattr(ptcomp, "ipy_completer", None) logger.debug("_enable_terminal_pdb_hooks(): completer=%r", completer) if completer is not None and auto_importer is not None: auto_importer._enable_completer_hooks(completer) def _get_IPdb_class(): """ Get the IPython (core) Pdb class. """ try: import IPython except ImportError: raise NoIPythonPackageError() try: # IPython 0.11+. Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, # 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0 from IPython.core import debugger return debugger.Pdb except ImportError: pass try: # IPython 0.10 from IPython import Debugger return Debugger.Pdb except ImportError: pass # IPython exists but couldn't figure out how to get Pdb. raise RuntimeError( "Couldn't get IPython Pdb. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def _get_TerminalPdb_class(): """ Get the IPython TerminalPdb class. """ # The TerminalPdb subclasses the (core) Pdb class. If the TerminalPdb # class is being used, then in that case we only need to advise # TerminalPdb stuff, not (core) Pdb stuff. However, in some cases the # TerminalPdb class is not used even if it exists, so we advise the (core) # Pdb class separately. try: import IPython del IPython except ImportError: raise NoIPythonPackageError() try: from IPython.terminal.debugger import TerminalPdb return TerminalPdb except ImportError: pass raise RuntimeError("Couldn't get TerminalPdb") def new_IPdb_instance(): """ Create a new Pdb instance. If IPython is available, then use IPython's Pdb. Initialize a new IPython terminal application if necessary. If the IPython package is not installed in the system, then use regular Pdb. Enable the auto importer. :rtype: `Pdb` """ logger.debug("new_IPdb_instance()") try: app = get_ipython_terminal_app_with_autoimporter() except Exception as e: if isinstance(e, NoIPythonPackageError) or e.__class__.__name__ == "MultipleInstanceError": logger.debug("%s: %s", type(e).__name__, e) from pdb import Pdb pdb_instance = Pdb() _enable_pdb_hooks(pdb_instance) _enable_terminal_pdb_hooks(pdb_instance) return pdb_instance else: raise pdb_class = _get_IPdb_class() logger.debug("new_IPdb_instance(): pdb_class=%s", pdb_class) color_scheme = _get_ipython_color_scheme(app) pdb_instance = pdb_class(color_scheme) _enable_pdb_hooks(pdb_instance) _enable_terminal_pdb_hooks(pdb_instance) return pdb_instance def _get_ipython_color_scheme(app): """ Get the configured IPython color scheme. :type app: `TerminalIPythonApp` :param app: An initialized IPython terminal application. :rtype: ``str`` """ try: # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. return app.shell.colors except AttributeError: pass try: # Tested with IPython 0.10. import IPython ipapi = IPython.ipapi.get() return ipapi.options.colors except AttributeError: pass import IPython raise RuntimeError( "Couldn't get IPython colors. " "Is your IPython version too old (or too new)? " "IPython.__version__=%r" % (IPython.__version__)) def print_verbose_tb(*exc_info): """ Print a traceback, using IPython's ultraTB if possible. :param exc_info: 3 arguments as returned by sys.exc_info(). """ if not exc_info: exc_info = sys.exc_info() elif len(exc_info) == 1 and isinstance(exc_info[0], tuple): exc_info, = exc_info if len(exc_info) != 3: raise TypeError( "Expected 3 items for exc_info; got %d" % len(exc_info)) try: # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. from IPython.core.ultratb import VerboseTB except ImportError: try: # Tested with IPython 0.10. from IPython.ultraTB import VerboseTB except ImportError: VerboseTB = None exc_type, exc_value, exc_tb = exc_info # TODO: maybe use ip.showtraceback() instead? if VerboseTB is not None: VerboseTB(include_vars=False)(exc_type, exc_value, exc_tb) else: import traceback def red(x): return "\033[0m\033[31;1m%s\033[0m" % (x,) exc_name = exc_type try: exc_name = exc_name.__name__ except AttributeError: pass exc_name = str(exc_name) print(red("---------------------------------------------------------------------------")) print(red(exc_name.ljust(42)) + "Traceback (most recent call last)") traceback.print_tb(exc_tb) print() print("%s: %s" % (red(exc_name), exc_value), file=sys.stderr) print() @contextmanager def UpdateIPythonStdioCtx(): """ Context manager that updates IPython's cached stdin/stdout/stderr handles to match the current values of sys.stdin/sys.stdout/sys.stderr. """ if "IPython" not in sys.modules: yield return if "IPython.utils.io" in sys.modules: # Tested with IPython 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, # 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. module = sys.modules["IPython.utils.io"] container = module IOStream = module.IOStream elif "IPython.genutils" in sys.modules: # Tested with IPython 0.10. module = sys.modules["IPython.genutils"] container = module.Term IOStream = module.IOStream else: # IPython version too old or too new? # For now just silently do nothing. yield return old_stdin = container.stdin old_stdout = container.stdout old_stderr = container.stderr try: container.stdin = IOStream(sys.stdin) container.stdout = IOStream(sys.stdout) container.stderr = IOStream(sys.stderr) yield finally: container.stdin = old_stdin container.stdout = old_stdout container.stderr = old_stderr class _EnableState(object): DISABLING = "DISABLING" DISABLED = "DISABLED" ENABLING = "ENABLING" ENABLED = "ENABLED" class AutoImporter(object): """ Auto importer enable state. The state is attached to an IPython "application". """ def __new__(cls, arg=Ellipsis): """ Get the AutoImporter for the given app, or create and assign one. :type arg: `AutoImporter`, `BaseIPythonApplication`, `InteractiveShell` """ if isinstance(arg, AutoImporter): return arg # Check the type of the arg. Avoid isinstance because it's so hard # to know where to import something from. # Todo: make this more robust. if arg is Ellipsis: app = _get_ipython_app() return cls._from_app(app) clsname = type(arg).__name__ if "App" in clsname: return cls._from_app(arg) elif "Shell" in clsname: # If given an ``InteractiveShell`` argument, then get its parent app. # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, # 3.2, 4.0. if hasattr(arg, 'parent') and getattr(arg.parent, 'shell', None) is arg: app = arg.parent return cls._from_app(app) # Tested with IPython 0.10, 0.11, 0.12, 0.13. app = _get_ipython_app() if app.shell is arg: return cls._from_app(app) raise ValueError( "Got a shell instance %r but couldn't match it to an app" % (arg,)) else: raise TypeError("AutoImporter(): unexpected %s" % (clsname,)) @classmethod def _from_app(cls, app): subapp = getattr(app, "subapp", None) if subapp is not None: app = subapp try: self = app.auto_importer except AttributeError: pass else: assert isinstance(self, cls) return self # Create a new instance and assign to the app. self = cls._construct(app) app.auto_importer = self return self @classmethod def _construct(cls, app): """ Create a new AutoImporter for ``app``. :type app: `IPython.core.application.BaseIPythonApplication` """ self = object.__new__(cls) self.app = app logger.debug("Constructing %r for app=%r, subapp=%r", self, app, getattr(app, "subapp", None)) # Functions to call to disable the auto importer. self._disablers = [] # Current enabling state. self._state = _EnableState.DISABLED # Whether there has been an error implying a bug in pyflyby code or a # problem with the import database. self._errored = False # A reference to the IPython shell object. self._ip = None # The AST transformer, if any (IPython 1.0+). self._ast_transformer = None # Dictionary of things we've attempted to autoimport for this cell. self._autoimported_this_cell = {} return self def enable(self, even_if_previously_errored=False): """ Turn on the auto-importer in the current IPython session. """ # Check that we are not enabled/enabling yet. if self._state is _EnableState.DISABLED: pass elif self._state is _EnableState.ENABLED: logger.debug("Already enabled") return elif self._state is _EnableState.ENABLING: logger.debug("Already enabling") return elif self._state is _EnableState.DISABLING: logger.debug("Still disabling (run disable() to completion first)") return else: raise AssertionError self.reset_state_new_cell() # Check if previously errored. if self._errored: if even_if_previously_errored: self._errored = False else: # Be conservative: Once we've had problems, don't try again # this session. Exceptions in the interactive loop can be # annoying to deal with. logger.warning( "Not reattempting to enable auto importer after earlier " "error") return import IPython logger.debug("Enabling auto importer for IPython version %s, pid=%r", IPython.__version__, os.getpid()) logger.debug("enable(): state %s=>ENABLING", self._state) self._errored = False self._state = _EnableState.ENABLING self._safe_call(self._enable_internal) def _continue_enable(self): if self._state != _EnableState.ENABLING: logger.debug("_enable_internal(): state = %s", self._state) return logger.debug("Continuing enabling auto importer") self._safe_call(self._enable_internal) def _enable_internal(self): # Main enabling entry point. This function can get called multiple # times, depending on what's been initialized so far. app = self.app assert app is not None if getattr(app, "subapp", None) is not None: app = app.subapp self.app = app logger.debug("app = %r", app) ok = True ok &= self._enable_ipython_bugfixes() ok &= self._enable_initializer_hooks(app) ok &= self._enable_kernel_manager_hook(app) ok &= self._enable_shell_hooks(app) if ok: logger.debug("_enable_internal(): success! state: %s=>ENABLED", self._state) self._state = _EnableState.ENABLED elif self._pending_initializers: logger.debug("_enable_internal(): did what we can for now; " "will enable more after further IPython initialization. " "state=%s", self._state) else: logger.debug("_enable_internal(): did what we can, but not " "fully successful. state: %s=>ENABLED", self._state) self._state = _EnableState.ENABLED def _enable_initializer_hooks(self, app): # Hook initializers. There are various things we want to hook, and # the hooking needs to be done at different times, depending on the # IPython version and the "app". For example, for most versions of # IPython, terminal app, many things need to be done after # initialize()/init_shell(); on the other hand, in some cases # (e.g. IPython console), we need to do stuff *inside* the # initialization function. # Thus, we take a brute force approach: add hooks to a bunch of # places, if they seem to not have run yet, and each time add any # hooks that are ready to be added. ok = True pending = False ip = getattr(app, "shell", None) if ip is None: if hasattr(app, "init_shell"): @self._advise(app.init_shell) def init_shell_enable_auto_importer(): __original__() logger.debug("init_shell() completed") ip = app.shell if ip is None: logger.debug("Aborting enabling AutoImporter: " "even after init_shell(), " "still no shell in app=%r", app) return self._continue_enable() elif not hasattr(app, "shell") and hasattr(app, "kernel_manager"): logger.debug("No shell applicable; ok because using kernel manager") pass else: logger.debug("App shell missing and no init_shell() to advise") ok = False if hasattr(app, "initialize_subcommand"): # Hook the subapp, if any. This requires some cleverness: # 'ipython console' requires us to do some stuff *before* # initialize() is called on the new app, while 'ipython # notebook' requires us to do stuff *after* initialize() is # called. @self._advise(app.initialize_subcommand) def init_subcmd_enable_auto_importer(*args, **kwargs): logger.debug("initialize_subcommand()") from IPython.core.application import Application @advise((Application, "instance")) def app_instance_enable_auto_importer(cls, *args, **kwargs): logger.debug("%s.instance()", cls.__name__) app = __original__(cls, *args, **kwargs) if app != self.app: self.app = app self._continue_enable() return app try: __original__(*args, **kwargs) finally: app_instance_enable_auto_importer.unadvise() self._continue_enable() pending = True if (hasattr(ip, "post_config_initialization") and not hasattr(ip, "rl_next_input")): # IPython 0.10 might not be ready to hook yet because we're called # from the config phase, and certain stuff (like Completer) is set # up in post-config. Re-run after post_config_initialization. # Kludge: post_config_initialization() sets ip.rl_next_input=None, # so detect whether it's been run by checking for that attribute. @self._advise(ip.post_config_initialization) def post_config_enable_auto_importer(): __original__() logger.debug("post_config_initialization() completed") if not hasattr(ip, "rl_next_input"): # Post-config initialization failed? return self._continue_enable() pending = True self._pending_initializers = pending return ok def _enable_kernel_manager_hook(self, app): # For IPython notebook, by the time we get here, there's generally a # kernel_manager already assigned, but kernel_manager.start_kernel() # hasn't been called yet. Hook app.kernel_manager.start_kernel(). kernel_manager = getattr(app, "kernel_manager", None) ok = True if kernel_manager is not None: ok &= self._enable_start_kernel_hook(kernel_manager) # For IPython console, a single function constructs the kernel_manager # and then immediately calls kernel_manager.start_kernel(). The # easiest way to intercept start_kernel() is by installing a hook # after the kernel_manager is constructed. if getattr(app, "kernel_manager_class", None) is not None: @self._advise((app, "kernel_manager_class")) def kernel_manager_class_with_autoimport(*args, **kwargs): logger.debug("kernel_manager_class_with_autoimport()") kernel_manager = __original__(*args, **kwargs) self._enable_start_kernel_hook(kernel_manager) return kernel_manager # It's OK if no kernel_manager nor kernel_manager_class; this is the # typical case, when using regular IPython terminal console (not # IPython notebook/console). return True def _enable_start_kernel_hook(self, kernel_manager): # Various IPython versions have different 'main' commands called from # here, e.g. # IPython 2: IPython.kernel.zmq.kernelapp.main # IPython 3: IPython.kernel.__main__ # IPython 4: ipykernel.__main__ # These essentially all do 'kernelapp.launch_new_instance()' (imported # from different places). We hook the guts of that to enable the # autoimporter. new_cmd = [ '-c', 'from pyflyby._interactive import start_ipython_kernel_with_autoimporter; ' 'start_ipython_kernel_with_autoimporter()' ] try: # Tested with Jupyter/IPython 4.0 from jupyter_client.manager import KernelManager as JupyterKernelManager except ImportError: pass else: @self._advise(kernel_manager.start_kernel) def start_kernel_with_autoimport_jupyter(*args, **kwargs): logger.debug("start_kernel()") # Advise format_kernel_cmd(), which is the function that # computes the command line for a subprocess to run a new # kernel. Note that we advise the method on the class, rather # than this instance of kernel_manager, because start_kernel() # actually creates a *new* KernelInstance for this. @advise(JupyterKernelManager.format_kernel_cmd) def format_kernel_cmd_with_autoimport(*args, **kwargs): result = __original__(*args, **kwargs) logger.debug("intercepting format_kernel_cmd(): orig = %r", result) if (len(result) >= 3 and result[1] == '-m' and result[2] in ['ipykernel', 'ipykernel_launcher']): result[1:3] = new_cmd logger.debug("intercepting format_kernel_cmd(): new = %r", result) return result else: logger.debug("intercepting format_kernel_cmd(): unexpected output; not modifying it") return result try: return __original__(*args, **kwargs) finally: format_kernel_cmd_with_autoimport.unadvise() return True try: # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, # 3.2. from IPython.kernel.manager import KernelManager as IPythonKernelManager except ImportError: pass else: @self._advise(kernel_manager.start_kernel) def start_kernel_with_autoimport_ipython(*args, **kwargs): logger.debug("start_kernel()") # Advise format_kernel_cmd(), which is the function that # computes the command line for a subprocess to run a new # kernel. Note that we advise the method on the class, rather # than this instance of kernel_manager, because start_kernel() # actually creates a *new* KernelInstance for this. @advise(IPythonKernelManager.format_kernel_cmd) def format_kernel_cmd_with_autoimport(*args, **kwargs): result = __original__(*args, **kwargs) logger.debug("intercepting format_kernel_cmd(): orig = %r", result) if result[1:3] in [ # IPython 3.x ['-m', 'IPython.kernel'], # IPython 1.x, 2.x ['-c', 'from IPython.kernel.zmq.kernelapp import main; main()'], ]: result[1:3] = new_cmd logger.debug("intercepting format_kernel_cmd(): new = %r", result) return result else: logger.debug("intercepting format_kernel_cmd(): unexpected output; not modifying it") return result try: return __original__(*args, **kwargs) finally: format_kernel_cmd_with_autoimport.unadvise() return True # Tested with IPython 0.12, 0.13 try: import IPython.zmq.ipkernel except ImportError: pass else: @self._advise(kernel_manager.start_kernel) def start_kernel_with_autoimport013(*args, **kwargs): logger.debug("start_kernel()") @advise((IPython.zmq.ipkernel, 'base_launch_kernel')) def base_launch_kernel_with_autoimport(cmd, *args, **kwargs): logger.debug("base_launch_kernel()") expected_cmd = 'from IPython.zmq.ipkernel import main; main()' if cmd != expected_cmd: logger.debug("unexpected command, not modifying it: %r", cmd) else: cmd = ( 'from pyflyby._interactive import start_ipython_kernel_with_autoimporter; ' 'start_ipython_kernel_with_autoimporter()') return __original__(cmd, *args, **kwargs) try: return __original__(*args, **kwargs) finally: base_launch_kernel_with_autoimport.unadvise() return True logger.debug("Couldn't enable start_kernel hook") return False def _enable_shell_hooks(self, app): """ Enable hooks to run auto_import before code execution. """ # Check again in case this was registered delayed if self._state != _EnableState.ENABLING: return False try: ip = app.shell except AttributeError: logger.debug("_enable_shell_hooks(): no shell at all") return True if ip is None: logger.debug("_enable_shell_hooks(): no shell yet") return False logger.debug("Enabling IPython shell hooks, shell=%r", ip) self._ip = ip # Notes on why we hook what we hook: # # There are many different places within IPython we can consider # hooking/advising, depending on the version: # * ip.input_transformer_manager.logical_line_transforms # * ip.compile.ast_parse (IPython 0.12+) # * ip.run_ast_nodes (IPython 0.11+) # * ip.runsource (IPython 0.10) # * ip.prefilter_manager.checks # * ip.prefilter_manager.handlers["auto"] # * ip.ast_transformers # * ip.hooks['pre_run_code_hook'] # * ip._ofind # # We choose to hook in two places: (1) _ofind and (2) # ast_transformers. The motivation follows. We want to handle # auto-imports for all of these input cases: # (1) "foo.bar" # (2) "arbitrarily_complicated_stuff((lambda: foo.bar)())" # (3) "foo.bar?", "foo.bar??" (pinfo/pinfo2) # (4) "foo.bar 1, 2" => "foo.bar(1, 2)" (autocall) # # Case 1 is the easiest and can be handled by nearly any method. Case # 2 must be done either as a prefilter or as an AST transformer. # Cases 3 and 4 must be done either as an input line transformer or by # monkey-patching _ofind, because by the time the # prefilter/ast_transformer is called, it's too late. # # To handle case 2, we use an AST transformer (for IPython > 1.0), or # monkey-patch one of the compilation steps (ip.compile for IPython # 0.10 and ip.run_ast_nodes for IPython 0.11-0.13). # prefilter_manager.checks() is the "supported" way to add a # pre-execution hook, but it only works for single lines, not for # multi-line cells. (There is no explanation in the IPython source # for why prefilter hooks are seemingly intentionally skipped for # multi-line cells). # # To handle cases 3/4 (pinfo/autocall), we choose to advise _ofind. # This is a private function that is called by both pinfo and autocall # code paths. (Alternatively, we could have added something to the # logical_line_transforms. The downside of that is that we would need # to re-implement all the parsing perfectly matching IPython. # Although monkey-patching is in general bad, it seems the lesser of # the two evils in this case.) # # Since we have two invocations of auto_import(), case 1 is # handled twice. That's fine, because it runs quickly. ok = True ok &= self._enable_reset_hook(ip) ok &= self._enable_ofind_hook(ip) ok &= self._enable_ast_hook(ip) ok &= self._enable_time_hook(ip) ok &= self._enable_timeit_hook(ip) ok &= self._enable_prun_hook(ip) ok &= self._enable_completion_hook(ip) ok &= self._enable_run_hook(ip) ok &= self._enable_debugger_hook(ip) ok &= self._enable_ipython_shell_bugfixes(ip) return ok def _enable_reset_hook(self, ip): # Register a hook that resets autoimporter state per input cell. # The only per-input-cell state we currently have is the recording of # which autoimports we've attempted but failed. We keep track of this # to avoid multiple error messages for a single import, in case of # overlapping hooks. # Note: Some of the below approaches (both registering an # input_transformer_manager hook or advising reset()) cause the reset # function to get called twice per cell. This seems like an # unintentional repeated call in IPython itself. This is harmless for # us, since doing an extra reset shouldn't hurt. if hasattr(ip, "input_transformers_post"): # In IPython 7.0+, the input transformer API changed. def reset_auto_importer_state(line): # There is a bug in IPython that causes the transformer to be # called multiple times # (https://github.com/ipython/ipython/issues/11714). Until it # is fixed, workaround it by skipping one of the calls. stack = inspect.stack() if any([ stack[3].function == 'run_cell_async', # These are the other places it is called. # stack[3].function == 'should_run_async', # stack[1].function == 'check_complete' ]): return line logger.debug("reset_auto_importer_state(%r)", line) self.reset_state_new_cell() return line # on IPython 7.17 (July 2020) or above, the check_complete # path of the code will not call transformer that have this magic attribute # when trying to check whether the code is complete. reset_auto_importer_state.has_side_effect = True ip.input_transformers_cleanup.append(reset_auto_importer_state) return True elif hasattr(ip, "input_transformer_manager"): # Tested with IPython 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, # 3.2, 4.0. class ResetAutoImporterState(object): def push(self_, line): return line def reset(self_): logger.debug("ResetAutoImporterState.reset()") self.reset_state_new_cell() t = ResetAutoImporterState() transforms = ip.input_transformer_manager.python_line_transforms transforms.append(t) def unregister_input_transformer(): try: transforms.remove(t) except ValueError: logger.info( "Couldn't remove python_line_transformer hook") self._disablers.append(unregister_input_transformer) return True elif hasattr(ip, "input_splitter"): # Tested with IPython 0.13. Also works with later versions, but # for those versions, we can use a real hook instead of advising. @self._advise(ip.input_splitter.reset) def reset_input_splitter_and_autoimporter_state(): logger.debug("reset_input_splitter_and_autoimporter_state()") self.reset_state_new_cell() return __original__() return True elif hasattr(ip, "resetbuffer"): # Tested with IPython 0.10. @self._advise(ip.resetbuffer) def resetbuffer_and_autoimporter_state(): logger.debug("resetbuffer_and_autoimporter_state") self.reset_state_new_cell() return __original__() return True else: logger.debug("Couldn't enable reset hook") return False def _enable_ofind_hook(self, ip): """ Enable a hook of _ofind(), which is used for pinfo, autocall, etc. """ # Advise _ofind. if hasattr(ip, "_ofind"): # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, # 2.4, 3.0, 3.1, 3.2, 4.0. @self._advise(ip._ofind) def ofind_with_autoimport(oname, namespaces=None): logger.debug("_ofind(oname=%r, namespaces=%r)", oname, namespaces) is_multiline = False if hasattr(ip, "buffer"): # In IPython 0.10, _ofind() gets called for each line of a # multiline input. Skip them. is_multiline = len(ip.buffer) > 0 if namespaces is None: namespaces = _ipython_namespaces(ip) if not is_multiline and is_identifier(oname, dotted=True): self.auto_import(str(oname), [ns for nsname,ns in namespaces][::-1]) result = __original__(oname, namespaces=namespaces) return result return True else: logger.debug("Couldn't enable ofind hook") return False def _enable_ast_hook(self, ip): """ Enable a hook somewhere in the source => parsed AST => compiled code pipeline. """ # Register an AST transformer. if hasattr(ip, 'ast_transformers'): logger.debug("Registering an ast_transformer") # First choice: register a formal ast_transformer. # Tested with IPython 1.0, 1.2, 2.0, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. class _AutoImporter_ast_transformer(object): """ A NodeVisitor-like wrapper around ``auto_import_for_ast`` for the API that IPython 1.x's ``ast_transformers`` needs. """ def visit(self_, node): # We don't actually transform the node; we just use # the ast_transformers mechanism instead of the # prefilter mechanism as an optimization to avoid # re-parsing the text into an AST. # # We use raise_on_error=False to avoid propagating any # exceptions here. That would cause IPython to try to # remove the ast_transformer. On error, we've already # done that ourselves. logger.debug("_AutoImporter_ast_transformer.visit()") self.auto_import(node, raise_on_error=False) return node self._ast_transformer = t = _AutoImporter_ast_transformer() ip.ast_transformers.append(t) def unregister_ast_transformer(): try: ip.ast_transformers.remove(t) except ValueError: logger.info( "Couldn't remove ast_transformer hook - already gone?") self._ast_transformer = None self._disablers.append(unregister_ast_transformer) return True elif hasattr(ip, "run_ast_nodes"): # Second choice: advise the run_ast_nodes() function. Tested with # IPython 0.11, 0.12, 0.13. This is the most robust way available # for those versions. # (ip.compile.ast_parse also works in IPython 0.12-0.13; no major # flaw, but might as well use the same mechanism that works in # 0.11.) @self._advise(ip.run_ast_nodes) def run_ast_nodes_with_autoimport(nodelist, *args, **kwargs): logger.debug("run_ast_nodes") ast_node = ast.Module(nodelist) self.auto_import(ast_node) return __original__(nodelist, *args, **kwargs) return True elif hasattr(ip, 'compile'): # Third choice: Advise ip.compile. # Tested with IPython 0.10. # We don't hook prefilter because that gets called once per line, # not per multiline code. # We don't hook runsource because that gets called incrementally # with partial multiline source until the source is complete. @self._advise((ip, "compile")) def compile_with_autoimport(source, filename="", symbol="single"): result = __original__(source, filename, symbol) if result is None: # The original ip.compile is an instance of # codeop.CommandCompiler. CommandCompiler.__call__ # returns None if the source is a possibly incomplete # multiline block of code. In that case we don't # autoimport yet. pass else: # Got full code that our caller, runsource, will execute. self.auto_import(source) return result return True else: logger.debug("Couldn't enable parse hook") return False def _enable_time_hook(self, ip): """ Enable a hook so that %time will autoimport. """ # For IPython 1.0+, the ast_transformer takes care of it. if self._ast_transformer: return True # Otherwise, we advise %time to temporarily override the compile() # builtin within it. if hasattr(ip, 'magics_manager'): # Tested with IPython 0.13. (IPython 1.0+ also has # magics_manager, but for those versions, ast_transformer takes # care of %time.) line_magics = ip.magics_manager.magics['line'] @self._advise((line_magics, 'time')) def time_with_autoimport(*args, **kwargs): logger.debug("time_with_autoimport()") wrapped = FunctionWithGlobals( __original__, compile=self.compile_with_autoimport) return wrapped(*args, **kwargs) return True elif hasattr(ip, 'magic_time'): # Tested with IPython 0.10, 0.11, 0.12 @self._advise(ip.magic_time) def magic_time_with_autoimport(*args, **kwargs): logger.debug("time_with_autoimport()") wrapped = FunctionWithGlobals( __original__, compile=self.compile_with_autoimport) return wrapped(*args, **kwargs) return True else: logger.debug("Couldn't enable time hook") return False def _enable_timeit_hook(self, ip): """ Enable a hook so that %timeit will autoimport. """ # For IPython 1.0+, the ast_transformer takes care of it. if self._ast_transformer: return True # Otherwise, we advise %timeit to temporarily override the compile() # builtin within it. if hasattr(ip, 'magics_manager'): # Tested with IPython 0.13. (IPython 1.0+ also has # magics_manager, but for those versions, ast_transformer takes # care of %timeit.) line_magics = ip.magics_manager.magics['line'] @self._advise((line_magics, 'timeit')) def timeit_with_autoimport(*args, **kwargs): logger.debug("timeit_with_autoimport()") wrapped = FunctionWithGlobals( __original__, compile=self.compile_with_autoimport) return wrapped(*args, **kwargs) return True elif hasattr(ip, 'magic_timeit'): # Tested with IPython 0.10, 0.11, 0.12 @self._advise(ip.magic_timeit) def magic_timeit_with_autoimport(*args, **kwargs): logger.debug("timeit_with_autoimport()") wrapped = FunctionWithGlobals( __original__, compile=self.compile_with_autoimport) return wrapped(*args, **kwargs) return True else: logger.debug("Couldn't enable timeit hook") return False def _enable_prun_hook(self, ip): """ Enable a hook so that %prun will autoimport. """ if hasattr(ip, 'magics_manager'): # Tested with IPython 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, # 3.1, 3.2, 4.0. line_magics = ip.magics_manager.magics['line'] execmgr = six.get_method_self(line_magics['prun'])#.im_self if hasattr(execmgr, "_run_with_profiler"): @self._advise(execmgr._run_with_profiler) def run_with_profiler_with_autoimport(code, opts, namespace): logger.debug("run_with_profiler_with_autoimport()") self.auto_import(code, [namespace]) return __original__(code, opts, namespace) return True else: # Tested with IPython 0.13. class ProfileFactory_with_autoimport(object): def Profile(self_, *args): import profile p = profile.Profile() @advise(p.runctx) def runctx_with_autoimport(cmd, globals, locals): self.auto_import(cmd, [globals, locals]) return __original__(cmd, globals, locals) return p @self._advise((line_magics, 'prun')) def prun_with_autoimport(*args, **kwargs): logger.debug("prun_with_autoimport()") wrapped = FunctionWithGlobals( __original__, profile=ProfileFactory_with_autoimport()) return wrapped(*args, **kwargs) return True elif hasattr(ip, "magic_prun"): # Tested with IPython 0.10, 0.11, 0.12. class ProfileFactory_with_autoimport(object): def Profile(self_, *args): import profile p = profile.Profile() @advise(p.runctx) def runctx_with_autoimport(cmd, globals, locals): self.auto_import(cmd, [globals, locals]) return __original__(cmd, globals, locals) return p @self._advise(ip.magic_prun) def magic_prun_with_autoimport(*args, **kwargs): logger.debug("magic_prun_with_autoimport()") wrapped = FunctionWithGlobals( __original__, profile=ProfileFactory_with_autoimport()) return wrapped(*args, **kwargs) return True else: logger.debug("Couldn't enable prun hook") return False def _enable_completer_hooks(self, completer): # Hook a completer instance. # # This is called: # - initially when enabling pyflyby # - each time we enter the debugger, since each Pdb instance has its # own completer # # There are a few different places within IPython we can consider # hooking/advising: # * ip.completer.custom_completers / ip.set_hook("complete_command") # * ip.completer.python_matches # * ip.completer.global_matches # * ip.completer.attr_matches # * ip.completer.python_func_kw_matches # # The "custom_completers" list, which set_hook("complete_command") # manages, is not useful because that only works for specific commands. # (A "command" refers to the first word on a line, such as "cd".) # # We choose to advise global_matches() and attr_matches(), which are # called to enumerate global and non-global attribute symbols # respectively. (python_matches() calls these two. We advise # global_matches() and attr_matches() instead of python_matches() # because a few other functions call global_matches/attr_matches # directly.) logger.debug("_enable_completer_hooks(%r)", completer) if hasattr(completer, "global_matches"): # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, # 2.4, 3.0, 3.1, 3.2, 4.0, 5.8. try: completer.shell.pt_cli is_pt = True except AttributeError: is_pt = False if is_pt: def get_completer_namespaces(): return [completer.namespace, completer.global_namespace] else: def get_completer_namespaces(): # For non-prompt_toolkit, (1) completer.namespace is not # reliable inside pdb, and (2) _get_pdb_if_is_in_pdb() is # reliable inside pdb because no threading. # Use get_global_namespaces(), which relies on # _get_pdb_if_is_in_pdb(). return None if getattr(completer, 'use_jedi', False): # IPython 6.0+ uses jedi completion by default, which bypasses # the global and attr matchers. For now we manually reenable # them. A TODO would be to hook the Jedi completer itself. if completer.python_matches not in completer.matchers: @self._advise(type(completer).matchers) def matchers_with_python_matches(completer): return __original__.fget(completer)+[completer.python_matches] @self._advise(completer.global_matches) def global_matches_with_autoimport(fullname): if len(fullname) == 0: return [] logger.debug("global_matches_with_autoimport(%r)", fullname) namespaces = get_completer_namespaces() return self.complete_symbol(fullname, namespaces, on_error=__original__) @self._advise(completer.attr_matches) def attr_matches_with_autoimport(fullname): logger.debug("attr_matches_with_autoimport(%r)", fullname) namespaces = get_completer_namespaces() return self.complete_symbol(fullname, namespaces, on_error=__original__) return True elif hasattr(completer, "complete_request"): # This is a ZMQCompleter, so nothing to do. return True else: logger.debug("Couldn't enable completion hook") return False def _enable_completion_hook(self, ip): """ Enable a tab-completion hook. """ return self._enable_completer_hooks(getattr(ip, "Completer", None)) def _enable_run_hook(self, ip): """ Enable a hook so that %run will autoimport. """ if hasattr(ip, "safe_execfile"): # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.3, # 2.4, 3.0, 3.1, 3.2, 4.0. @self._advise(ip.safe_execfile) def safe_execfile_with_autoimport(filename, globals=None, locals=None, **kwargs): logger.debug("safe_execfile %r", filename) if globals is None: globals = {} if locals is None: locals = globals namespaces = [globals, locals] try: block = PythonBlock(Filename(filename)) ast_node = block.ast_node self.auto_import(ast_node, namespaces) except Exception as e: logger.error("%s: %s", type(e).__name__, e) return __original__(filename, *namespaces, **kwargs) return True else: logger.debug("Couldn't enable execfile hook") return False def _enable_debugger_hook(self, ip): try: Pdb = _get_IPdb_class() except Exception as e: logger.debug("Couldn't locate Pdb class: %s: %s", type(e).__name__, e) return False try: TerminalPdb = _get_TerminalPdb_class() except Exception as e: logger.debug("Couldn't locate TerminalPdb class: %s: %s", type(e).__name__, e) TerminalPdb = None @contextmanager def HookPdbCtx(): def Pdb_with_autoimport(self_pdb, *args): __original__(self_pdb, *args) _enable_pdb_hooks(self_pdb) def TerminalPdb_with_autoimport(self_pdb, *args): __original__(self_pdb, *args) _enable_terminal_pdb_hooks(self_pdb, self) with AdviceCtx(Pdb.__init__, Pdb_with_autoimport): if TerminalPdb is None: yield else: with AdviceCtx(TerminalPdb.__init__, TerminalPdb_with_autoimport): yield iptb = getattr(ip, "InteractiveTB", None) ok = True if hasattr(iptb, "debugger"): # Hook ip.InteractiveTB.debugger(). This implements auto # importing for "%debug" (postmortem mode). # Tested with IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.1, 1.2, 2.0, # 2.1, 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0. @self._advise(iptb.debugger) def debugger_with_autoimport(*args, **kwargs): with HookPdbCtx(): return __original__(*args, **kwargs) else: ok = False if hasattr(ip, 'magics_manager'): # Hook ExecutionMagics._run_with_debugger(). This implements auto # importing for "%debug ". # Tested with IPython 1.0, 1.1, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0, # 3.1, 3.2, 4.0, 5.8. line_magics = ip.magics_manager.magics['line'] execmgr = six.get_method_self(line_magics['debug']) if hasattr(execmgr, "_run_with_debugger"): @self._advise(execmgr._run_with_debugger) def run_with_debugger_with_autoimport(code, code_ns, filename=None, *args, **kwargs): db = ImportDB.get_default(filename or ".") auto_import(code, namespaces=[code_ns], db=db) with HookPdbCtx(): return __original__(code, code_ns, filename, *args, **kwargs ) else: # IPython 0.13 and earlier don't have "%debug ". pass else: ok = False return ok def _enable_ipython_shell_bugfixes(self, ip): """ Enable some advice that's actually just fixing bugs in IPython. """ # IPython 2.x on Python 2.x has a bug where 'run -n' doesn't work # because it uses Unicode for the module name. This is a bug in # IPython itself ("run -n" is plain broken for ipython-2.x on # python-2.x); we patch it here. if (PY2 and hasattr(ip, "new_main_mod")): try: args = inspect.getargspec(ip.new_main_mod).args except Exception: # getargspec fails if we already advised. # For now just skip under the assumption that we already # advised (or the code changed in some way that doesn't # require advising?) # Minor todo: Ideally we would be relying on _advise to check # that we haven't already advised. args = None if args == ["self","filename","modname"]: @self._advise(ip.new_main_mod) def new_main_mod_fix_str(filename, modname): if six.PY2: if type(modname) is unicode: modname = str(modname) return __original__(filename, modname) return True def _enable_ipython_bugfixes(self): """ Enable some advice that's actually just fixing bugs in IPython. """ ok = True ok &= self._enable_ipython_bugfixes_LevelFormatter() return ok def _enable_ipython_bugfixes_LevelFormatter(self): # New versions of IPython complain if you import 'IPython.config'. # Old versions of IPython already have it imported. if 'IPython.config' not in sys.modules: return True try: from IPython.config.application import LevelFormatter except ImportError: return True if (not issubclass(LevelFormatter, object) and "super" in LevelFormatter.format.__func__.__code__.co_names and "logging" not in LevelFormatter.format.__func__.__code__.co_names): # In IPython 1.0, LevelFormatter uses super(), which assumes # that logging.Formatter is a subclass of object. However, # this is only true in Python 2.7+, not in Python 2.6. So # Python 2.6 + IPython 1.0 causes problems. IPython 1.2 # already includes this fix. from logging import Formatter @self._advise(LevelFormatter.format) def format_patched(self, record): if record.levelno >= self.highlevel_limit: record.highlevel = self.highlevel_format % record.__dict__ else: record.highlevel = "" return Formatter.format(self, record) return True def disable(self): """ Turn off auto-importer in the current IPython session. """ if self._state is _EnableState.DISABLED: logger.debug("disable(): already disabled") return logger.debug("disable(): state: %s=>DISABLING", self._state) self._state = _EnableState.DISABLING while self._disablers: f = self._disablers.pop(-1) try: f() except Exception as e: self._errored = True logger.error("Error while disabling: %s: %s", type(e).__name__, e) if logger.debug_enabled: raise else: logger.info( "Set the env var PYFLYBY_LOG_LEVEL=DEBUG to debug.") logger.debug("disable(): state: %s=>DISABLED", self._state) self._state = _EnableState.DISABLED def _safe_call(self, function, *args, **kwargs): on_error = kwargs.pop("on_error", None) raise_on_error = kwargs.pop("raise_on_error", "if_debug") if self._errored: # If we previously errored, then we should already have # unregistered the hook that led to here. However, in some corner # cases we can get called one more time. If so, go straight to # the on_error case. pass else: try: return function(*args, **kwargs) except Exception as e: # Something went wrong. Remember that we've had a problem. self._errored = True logger.error("%s: %s", type(e).__name__, e) if not logger.debug_enabled: logger.info( "Set the env var PYFLYBY_LOG_LEVEL=DEBUG to debug.") logger.warning("Disabling pyflyby auto importer.") # Disable everything. If something's broken, chances are # other stuff is broken too. try: self.disable() except Exception as e2: logger.error("Error trying to disable: %s: %s", type(e2).__name__, e2) # Raise or print traceback in debug mode. if raise_on_error == True: raise elif raise_on_error == 'if_debug': if logger.debug_enabled: if type(e) == SyntaxError: # The traceback for SyntaxError tends to get # swallowed, so print it out now. import traceback traceback.print_exc() raise elif raise_on_error == False: if logger.debug_enabled: import traceback traceback.print_exc() else: logger.error("internal error: invalid raise_on_error=%r", raise_on_error) # Return what user wanted to in case of error. if on_error: return on_error(*args, **kwargs) else: return None # just to be explicit def reset_state_new_cell(self): # Reset the state for a new cell. if logger.debug_enabled: autoimported = self._autoimported_this_cell logger.debug("reset_state_new_cell(): previously autoimported: " "succeeded=%s, failed=%s", sorted([k for k,v in autoimported.items() if v]), sorted([k for k,v in autoimported.items() if not v])) self._autoimported_this_cell = {} def auto_import(self, arg, namespaces=None, raise_on_error='if_debug', on_error=None): if namespaces is None: namespaces = get_global_namespaces(self._ip) def post_import_hook(imp): send_comm_message(MISSING_IMPORTS, {"missing_imports": str(imp)}) return self._safe_call( auto_import, arg, namespaces, autoimported=self._autoimported_this_cell, raise_on_error=raise_on_error, on_error=on_error, post_import_hook=post_import_hook) def complete_symbol(self, fullname, namespaces, raise_on_error='if_debug', on_error=None): with InterceptPrintsDuringPromptCtx(self._ip): if namespaces is None: namespaces = get_global_namespaces(self._ip) if on_error is not None: def on_error1(fullname, namespaces, autoimported, ip, allow_eval): return on_error(fullname) else: on_error1 = None return self._safe_call( complete_symbol, fullname, namespaces, autoimported=self._autoimported_this_cell, ip=self._ip, allow_eval=True, raise_on_error=raise_on_error, on_error=on_error1) def compile_with_autoimport(self, src, filename, mode, flags=0): logger.debug("compile_with_autoimport(%r)", src) ast_node = compile(src, filename, mode, flags|ast.PyCF_ONLY_AST, dont_inherit=True) self.auto_import(ast_node) if flags & ast.PyCF_ONLY_AST: return ast_node else: return compile(ast_node, filename, mode, flags, dont_inherit=True) def _advise(self, joinpoint): def advisor(f): aspect = Aspect(joinpoint) if aspect.advise(f, once=True): self._disablers.append(aspect.unadvise) return advisor def enable_auto_importer(if_no_ipython='raise'): """ Turn on the auto-importer in the current IPython application. :param if_no_ipython: If we are not inside IPython and if_no_ipython=='ignore', then silently do nothing. If we are not inside IPython and if_no_ipython=='raise', then raise NoActiveIPythonAppError. """ try: app = _get_ipython_app() except NoActiveIPythonAppError: if if_no_ipython=='ignore': return else: raise auto_importer = AutoImporter(app) auto_importer.enable() def disable_auto_importer(): """ Turn off the auto-importer in the current IPython application. """ try: app = _get_ipython_app() except NoActiveIPythonAppError: return auto_importer = AutoImporter(app) auto_importer.disable() def load_ipython_extension(arg=Ellipsis): """ Turn on pyflyby features, including the auto-importer, for the given IPython shell. Clear the ImportDB cache of known-imports. This function is used by IPython's extension mechanism. To load pyflyby in an existing IPython session, run:: In [1]: %load_ext pyflyby To refresh the imports database (if you modified ~/.pyflyby), run:: In [1]: %reload_ext pyflyby To load pyflyby automatically on IPython startup, appendto ~/.ipython/profile_default/ipython_config.py:: c.InteractiveShellApp.extensions.append("pyflyby") :type arg: ``InteractiveShell`` :see: http://ipython.org/ipython-doc/dev/config/extensions/index.html """ logger.debug("load_ipython_extension() called for %s", os.path.dirname(__file__)) # Turn on the auto-importer. auto_importer = AutoImporter(arg) auto_importer.enable(even_if_previously_errored=True) # Clear ImportDB cache. ImportDB.clear_default_cache() # Clear the set of errored imports. clear_failed_imports_cache() # Enable debugging tools. These aren't IPython-specific, and are better # put in usercustomize.py. But this is a convenient way for them to be # loaded. They're fine to run again even if they've already been run via # usercustomize.py. from ._dbg import (enable_faulthandler, enable_signal_handler_debugger, enable_sigterm_handler, add_debug_functions_to_builtins) enable_faulthandler() enable_signal_handler_debugger() enable_sigterm_handler(on_existing_handler='keep_existing') add_debug_functions_to_builtins() initialize_comms() def unload_ipython_extension(arg=Ellipsis): """ Turn off pyflyby features, including the auto-importer. This function is used by IPython's extension mechanism. To unload interactively, run:: In [1]: %unload_ext pyflyby """ logger.debug("unload_ipython_extension() called for %s", os.path.dirname(__file__)) auto_importer = AutoImporter(arg) auto_importer.disable() remove_comms() # TODO: disable signal handlers etc.