| | |
| | """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 |
| |
|