creeper-adventure/.venv/lib/python3.8/site-packages/pyflyby/_interactive.py
2022-03-31 20:20:07 -05:00

2597 lines
102 KiB
Python

# 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<TAB>", 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<TAB>' or 'foo().bar<TAB>'. 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="<input>",
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 <statement>".
# 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 <statement>".
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.