extensions.py
184 lines
| 6.9 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2731 | # encoding: utf-8 | ||
"""A class for managing IPython extensions. | ||||
Authors: | ||||
* Brian Granger | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2010-2011 The IPython Development Team | ||
Brian Granger
|
r2731 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
import os | ||||
Thomas Kluyver
|
r6578 | from shutil import copyfile | ||
Brian Granger
|
r2731 | import sys | ||
Thomas Kluyver
|
r6128 | from urllib import urlretrieve | ||
from urlparse import urlparse | ||||
Brian Granger
|
r2731 | |||
Thomas Kluyver
|
r8549 | from IPython.core.error import UsageError | ||
Brian Granger
|
r2731 | from IPython.config.configurable import Configurable | ||
from IPython.utils.traitlets import Instance | ||||
Thomas Kluyver
|
r8550 | from IPython.utils.py3compat import PY3 | ||
if PY3: | ||||
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 | |||
You can also optionaly 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. | ||||
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. | ||||
""" | ||||
Brian Granger
|
r2760 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | ||
Brian Granger
|
r2731 | |||
Brian Granger
|
r2740 | def __init__(self, shell=None, config=None): | ||
super(ExtensionManager, self).__init__(shell=shell, config=config) | ||||
Brian Granger
|
r2731 | self.shell.on_trait_change( | ||
self._on_ipython_dir_changed, 'ipython_dir' | ||||
) | ||||
Thomas Kluyver
|
r8515 | self.loaded = set() | ||
Brian Granger
|
r2731 | |||
def __del__(self): | ||||
self.shell.on_trait_change( | ||||
self._on_ipython_dir_changed, 'ipython_dir', remove=True | ||||
) | ||||
@property | ||||
def ipython_extension_dir(self): | ||||
return os.path.join(self.shell.ipython_dir, u'extensions') | ||||
def _on_ipython_dir_changed(self): | ||||
if not os.path.isdir(self.ipython_extension_dir): | ||||
Bradley M. Froehle
|
r8490 | os.makedirs(self.ipython_extension_dir, mode = 0o777) | ||
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 | ||
if module_str not in sys.modules: | ||||
with prepended_to_syspath(self.ipython_extension_dir): | ||||
__import__(module_str) | ||||
mod = sys.modules[module_str] | ||||
Thomas Kluyver
|
r8515 | if self._call_load_ipython_extension(mod): | ||
self.loaded.add(module_str) | ||||
Thomas Kluyver
|
r8586 | 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 | ||||
Thomas Kluyver
|
r6128 | |||
def install_extension(self, url, filename=None): | ||||
"""Download and install an IPython extension. | ||||
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. | ||||
Thomas Kluyver
|
r6617 | |||
Returns the full path to the installed file. | ||||
Thomas Kluyver
|
r6128 | """ | ||
# Ensure the extension directory exists | ||||
if not os.path.isdir(self.ipython_extension_dir): | ||||
Bradley M. Froehle
|
r8490 | os.makedirs(self.ipython_extension_dir, mode = 0o777) | ||
Thomas Kluyver
|
r6128 | |||
Thomas Kluyver
|
r6578 | if os.path.isfile(url): | ||
src_filename = os.path.basename(url) | ||||
copy = copyfile | ||||
else: | ||||
src_filename = urlparse(url).path.split('/')[-1] | ||||
copy = urlretrieve | ||||
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) | ||||
filename = os.path.join(self.ipython_extension_dir, filename) | ||||
Thomas Kluyver
|
r6617 | copy(url, filename) | ||
return filename | ||||