extensions.py
173 lines
| 6.5 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 | ||||
Thomas Kluyver
|
r6578 | from shutil import copyfile | ||
Brian Granger
|
r2731 | import sys | ||
Min RK
|
r21253 | from traitlets.config.configurable import Configurable | ||
MinRK
|
r16486 | from IPython.utils.path import ensure_dir_exists | ||
Min RK
|
r21253 | from traitlets import Instance | ||
Matthias Bussonnier
|
r21258 | |||
try: | ||||
from importlib import reload | ||||
except ImportError : | ||||
## deprecated since 3.4 | ||||
from imp import reload | ||||
Brian Granger
|
r2731 | |||
#----------------------------------------------------------------------------- | ||||
# Main class | ||||
#----------------------------------------------------------------------------- | ||||
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 | |||
def load_extension(self, module_str): | ||||
"""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 | """ | ||
Thomas Kluyver
|
r8549 | if module_str in self.loaded: | ||
return "already loaded" | ||||
Brian Granger
|
r2731 | from IPython.utils.syspathcontext import prepended_to_syspath | ||
MinRK
|
r10574 | |||
with self.shell.builtin_trap: | ||||
if module_str not in sys.modules: | ||||
with prepended_to_syspath(self.ipython_extension_dir): | ||||
__import__(module_str) | ||||
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 | |||
def unload_extension(self, module_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)``. | ||||
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 | """ | ||
Thomas Kluyver
|
r8549 | if module_str not in self.loaded: | ||
return "not loaded" | ||||
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 | |||
def reload_extension(self, module_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 | ||||
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 | ||||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6128 | def install_extension(self, url, filename=None): | ||
"""Download and install an IPython extension. | ||||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6128 | If filename is given, the file will be so named (inside the extension | ||
directory). Otherwise, the name from the URL will be used. The file must | ||||
have a .py or .zip extension; otherwise, a ValueError will be raised. | ||||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6617 | Returns the full path to the installed file. | ||
Thomas Kluyver
|
r6128 | """ | ||
# Ensure the extension directory exists | ||||
MinRK
|
r16486 | ensure_dir_exists(self.ipython_extension_dir) | ||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6578 | if os.path.isfile(url): | ||
src_filename = os.path.basename(url) | ||||
copy = copyfile | ||||
else: | ||||
Sean Vig
|
r13640 | # Deferred imports | ||
Thomas Kluyver
|
r13354 | try: | ||
from urllib.parse import urlparse # Py3 | ||||
Sean Vig
|
r13640 | from urllib.request import urlretrieve | ||
Thomas Kluyver
|
r13354 | except ImportError: | ||
from urlparse import urlparse | ||||
Sean Vig
|
r13640 | from urllib import urlretrieve | ||
Thomas Kluyver
|
r6578 | src_filename = urlparse(url).path.split('/')[-1] | ||
copy = urlretrieve | ||||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6128 | if filename is None: | ||
Thomas Kluyver
|
r6578 | filename = src_filename | ||
Thomas Kluyver
|
r6128 | if os.path.splitext(filename)[1] not in ('.py', '.zip'): | ||
raise ValueError("The file must have a .py or .zip extension", filename) | ||||
Sean Vig
|
r13640 | |||
Thomas Kluyver
|
r6128 | filename = os.path.join(self.ipython_extension_dir, filename) | ||
Thomas Kluyver
|
r6617 | copy(url, filename) | ||
return filename | ||||