extensions.py
151 lines
| 5.6 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2731 | # encoding: utf-8 | ||
MinRK
|
r16486 | """A class for managing IPython extensions.""" | ||
Brian Granger
|
r2731 | |||
MinRK
|
r16486 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Brian Granger
|
r2731 | |||
import os | ||||
Matthias Bussonnier
|
r23335 | import os.path | ||
Brian Granger
|
r2731 | import sys | ||
Subhendu Ranjan Mishra
|
r24294 | from importlib import import_module, reload | ||
Brian Granger
|
r2731 | |||
Min RK
|
r21253 | from traitlets.config.configurable import Configurable | ||
Thomas Kluyver
|
r23352 | from IPython.utils.path import ensure_dir_exists, compress_user | ||
Matthias Bussonnier
|
r23390 | from IPython.utils.decorators import undoc | ||
Min RK
|
r21253 | from traitlets import Instance | ||
Matthias Bussonnier
|
r21258 | |||
Brian Granger
|
r2731 | |||
#----------------------------------------------------------------------------- | ||||
# Main class | ||||
#----------------------------------------------------------------------------- | ||||
Matthias Bussonnier
|
r26871 | BUILTINS_EXTS = {"storemagic": False, "autoreload": False} | ||
Brian Granger
|
r2731 | class ExtensionManager(Configurable): | ||
Brian Granger
|
r2738 | """A class to manage IPython extensions. | ||
Brian Granger
|
r2731 | |||
Brian Granger
|
r2738 | An IPython extension is an importable Python module that has | ||
a function with the signature:: | ||||
def load_ipython_extension(ipython): | ||||
# Do things with ipython | ||||
Bernardo B. Marques
|
r4872 | This function is called after your extension is imported and the | ||
Brian Granger
|
r2738 | 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. | ||||
Thomas Kluyver
|
r8549 | |||
MinRK
|
r11064 | You can also optionally define an :func:`unload_ipython_extension(ipython)` | ||
Thomas Kluyver
|
r8549 | 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. | ||||
Brian Granger
|
r2738 | |||
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. | ||||
""" | ||||
Matthias Bussonnier
|
r22350 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) | ||
Brian Granger
|
r2731 | |||
MinRK
|
r11064 | def __init__(self, shell=None, **kwargs): | ||
super(ExtensionManager, self).__init__(shell=shell, **kwargs) | ||||
Matthias Bussonnier
|
r22325 | self.shell.observe( | ||
self._on_ipython_dir_changed, names=('ipython_dir',) | ||||
Brian Granger
|
r2731 | ) | ||
Thomas Kluyver
|
r8515 | self.loaded = set() | ||
Brian Granger
|
r2731 | |||
@property | ||||
def ipython_extension_dir(self): | ||||
return os.path.join(self.shell.ipython_dir, u'extensions') | ||||
Matthias Bussonnier
|
r22325 | def _on_ipython_dir_changed(self, change): | ||
MinRK
|
r16486 | ensure_dir_exists(self.ipython_extension_dir) | ||
Brian Granger
|
r2731 | |||
Matthias Bussonnier
|
r26871 | def load_extension(self, module_str: str): | ||
Brian Granger
|
r2731 | """Load an IPython extension by its module name. | ||
Thomas Kluyver
|
r8549 | Returns the string "already loaded" if the extension is already loaded, | ||
Thomas Kluyver
|
r8586 | "no load function" if the module doesn't have a load_ipython_extension | ||
function, or None if it succeeded. | ||||
Brian Granger
|
r2731 | """ | ||
Matthias Bussonnier
|
r26871 | 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): | ||||
Thomas Kluyver
|
r8549 | if module_str in self.loaded: | ||
return "already loaded" | ||||
Matthias Bussonnier
|
r23335 | |||
Brian Granger
|
r2731 | from IPython.utils.syspathcontext import prepended_to_syspath | ||
Matthias Bussonnier
|
r23335 | |||
MinRK
|
r10574 | with self.shell.builtin_trap: | ||
if module_str not in sys.modules: | ||||
Matthias Bussonnier
|
r27767 | mod = import_module(module_str) | ||
MinRK
|
r10574 | mod = sys.modules[module_str] | ||
if self._call_load_ipython_extension(mod): | ||||
self.loaded.add(module_str) | ||||
else: | ||||
return "no load function" | ||||
Brian Granger
|
r2731 | |||
Matthias Bussonnier
|
r26871 | def unload_extension(self, module_str: str): | ||
Brian Granger
|
r2731 | """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)``. | ||||
Matthias Bussonnier
|
r26498 | |||
Thomas Kluyver
|
r8549 | 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. | ||||
Brian Granger
|
r2731 | """ | ||
Matthias Bussonnier
|
r26871 | if BUILTINS_EXTS.get(module_str, False) is True: | ||
module_str = "IPython.extensions." + module_str | ||||
Thomas Kluyver
|
r8549 | if module_str not in self.loaded: | ||
return "not loaded" | ||||
Matthias Bussonnier
|
r26871 | |||
Brian Granger
|
r2731 | if module_str in sys.modules: | ||
mod = sys.modules[module_str] | ||||
Thomas Kluyver
|
r8515 | if self._call_unload_ipython_extension(mod): | ||
self.loaded.discard(module_str) | ||||
Thomas Kluyver
|
r8549 | else: | ||
return "no unload function" | ||||
Brian Granger
|
r2731 | |||
Matthias Bussonnier
|
r26871 | def reload_extension(self, module_str: str): | ||
Brian Granger
|
r2731 | """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 | ||||
Matthias Bussonnier
|
r26871 | if BUILTINS_EXTS.get(module_str, False) is True: | ||
module_str = "IPython.extensions." + module_str | ||||
Thomas Kluyver
|
r8517 | 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): | ||||
Brian Granger
|
r2731 | reload(mod) | ||
Thomas Kluyver
|
r8517 | if self._call_load_ipython_extension(mod): | ||
self.loaded.add(module_str) | ||||
else: | ||||
self.load_extension(module_str) | ||||
Brian Granger
|
r2731 | |||
def _call_load_ipython_extension(self, mod): | ||||
if hasattr(mod, 'load_ipython_extension'): | ||||
Thomas Kluyver
|
r8515 | mod.load_ipython_extension(self.shell) | ||
return True | ||||
Brian Granger
|
r2731 | |||
def _call_unload_ipython_extension(self, mod): | ||||
if hasattr(mod, 'unload_ipython_extension'): | ||||
Thomas Kluyver
|
r8515 | mod.unload_ipython_extension(self.shell) | ||
return True | ||||