|
|
|
|
|
"""A class for managing IPython extensions.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os |
|
|
import os.path |
|
|
import sys |
|
|
from importlib import import_module, reload |
|
|
|
|
|
from traitlets.config.configurable import Configurable |
|
|
from IPython.utils.path import ensure_dir_exists, compress_user |
|
|
from IPython.utils.decorators import undoc |
|
|
from traitlets import Instance |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BUILTINS_EXTS = {"storemagic": False, "autoreload": False} |
|
|
|
|
|
|
|
|
class ExtensionManager(Configurable): |
|
|
"""A class to manage IPython extensions. |
|
|
|
|
|
An IPython extension is an importable Python module that has |
|
|
a function with the signature:: |
|
|
|
|
|
def load_ipython_extension(ipython): |
|
|
# Do things with ipython |
|
|
|
|
|
This function is called after your extension is imported and the |
|
|
currently active :class:`InteractiveShell` instance is passed as |
|
|
the only argument. You can do anything you want with IPython at |
|
|
that point, including defining new magic and aliases, adding new |
|
|
components, etc. |
|
|
|
|
|
You can also optionally define an :func:`unload_ipython_extension(ipython)` |
|
|
function, which will be called if the user unloads or reloads the extension. |
|
|
The extension manager will only call :func:`load_ipython_extension` again |
|
|
if the extension is reloaded. |
|
|
|
|
|
You can put your extension modules anywhere you want, as long as |
|
|
they can be imported by Python's standard import mechanism. However, |
|
|
to make it easy to write extensions, you can also put your extensions |
|
|
in ``os.path.join(self.ipython_dir, 'extensions')``. This directory |
|
|
is added to ``sys.path`` automatically. |
|
|
""" |
|
|
|
|
|
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) |
|
|
|
|
|
def __init__(self, shell=None, **kwargs): |
|
|
super(ExtensionManager, self).__init__(shell=shell, **kwargs) |
|
|
self.shell.observe( |
|
|
self._on_ipython_dir_changed, names=('ipython_dir',) |
|
|
) |
|
|
self.loaded = set() |
|
|
|
|
|
@property |
|
|
def ipython_extension_dir(self): |
|
|
return os.path.join(self.shell.ipython_dir, u'extensions') |
|
|
|
|
|
def _on_ipython_dir_changed(self, change): |
|
|
ensure_dir_exists(self.ipython_extension_dir) |
|
|
|
|
|
def load_extension(self, module_str: str): |
|
|
"""Load an IPython extension by its module name. |
|
|
|
|
|
Returns the string "already loaded" if the extension is already loaded, |
|
|
"no load function" if the module doesn't have a load_ipython_extension |
|
|
function, or None if it succeeded. |
|
|
""" |
|
|
try: |
|
|
return self._load_extension(module_str) |
|
|
except ModuleNotFoundError: |
|
|
if module_str in BUILTINS_EXTS: |
|
|
BUILTINS_EXTS[module_str] = True |
|
|
return self._load_extension("IPython.extensions." + module_str) |
|
|
raise |
|
|
|
|
|
def _load_extension(self, module_str: str): |
|
|
if module_str in self.loaded: |
|
|
return "already loaded" |
|
|
|
|
|
from IPython.utils.syspathcontext import prepended_to_syspath |
|
|
|
|
|
with self.shell.builtin_trap: |
|
|
if module_str not in sys.modules: |
|
|
mod = import_module(module_str) |
|
|
mod = sys.modules[module_str] |
|
|
if self._call_load_ipython_extension(mod): |
|
|
self.loaded.add(module_str) |
|
|
else: |
|
|
return "no load function" |
|
|
|
|
|
def unload_extension(self, module_str: str): |
|
|
"""Unload an IPython extension by its module name. |
|
|
|
|
|
This function looks up the extension's name in ``sys.modules`` and |
|
|
simply calls ``mod.unload_ipython_extension(self)``. |
|
|
|
|
|
Returns the string "no unload function" if the extension doesn't define |
|
|
a function to unload itself, "not loaded" if the extension isn't loaded, |
|
|
otherwise None. |
|
|
""" |
|
|
if BUILTINS_EXTS.get(module_str, False) is True: |
|
|
module_str = "IPython.extensions." + module_str |
|
|
if module_str not in self.loaded: |
|
|
return "not loaded" |
|
|
|
|
|
if module_str in sys.modules: |
|
|
mod = sys.modules[module_str] |
|
|
if self._call_unload_ipython_extension(mod): |
|
|
self.loaded.discard(module_str) |
|
|
else: |
|
|
return "no unload function" |
|
|
|
|
|
def reload_extension(self, module_str: str): |
|
|
"""Reload an IPython extension by calling reload. |
|
|
|
|
|
If the module has not been loaded before, |
|
|
:meth:`InteractiveShell.load_extension` is called. Otherwise |
|
|
:func:`reload` is called and then the :func:`load_ipython_extension` |
|
|
function of the module, if it exists is called. |
|
|
""" |
|
|
from IPython.utils.syspathcontext import prepended_to_syspath |
|
|
|
|
|
if BUILTINS_EXTS.get(module_str, False) is True: |
|
|
module_str = "IPython.extensions." + module_str |
|
|
|
|
|
if (module_str in self.loaded) and (module_str in sys.modules): |
|
|
self.unload_extension(module_str) |
|
|
mod = sys.modules[module_str] |
|
|
with prepended_to_syspath(self.ipython_extension_dir): |
|
|
reload(mod) |
|
|
if self._call_load_ipython_extension(mod): |
|
|
self.loaded.add(module_str) |
|
|
else: |
|
|
self.load_extension(module_str) |
|
|
|
|
|
def _call_load_ipython_extension(self, mod): |
|
|
if hasattr(mod, 'load_ipython_extension'): |
|
|
mod.load_ipython_extension(self.shell) |
|
|
return True |
|
|
|
|
|
def _call_unload_ipython_extension(self, mod): |
|
|
if hasattr(mod, 'unload_ipython_extension'): |
|
|
mod.unload_ipython_extension(self.shell) |
|
|
return True |
|
|
|