1"""A class for managing IPython extensions."""
2
3# Copyright (c) IPython Development Team.
4# Distributed under the terms of the Modified BSD License.
5
6import os
7import os.path
8import sys
9from importlib import import_module, reload
10
11from traitlets.config.configurable import Configurable
12from IPython.utils.path import ensure_dir_exists
13from traitlets import Instance
14
15
16#-----------------------------------------------------------------------------
17# Main class
18#-----------------------------------------------------------------------------
19
20BUILTINS_EXTS = {"storemagic": False, "autoreload": False}
21
22
23class ExtensionManager(Configurable):
24 """A class to manage IPython extensions.
25
26 An IPython extension is an importable Python module that has
27 a function with the signature::
28
29 def load_ipython_extension(ipython):
30 # Do things with ipython
31
32 This function is called after your extension is imported and the
33 currently active :class:`InteractiveShell` instance is passed as
34 the only argument. You can do anything you want with IPython at
35 that point, including defining new magic and aliases, adding new
36 components, etc.
37
38 You can also optionally define an :func:`unload_ipython_extension(ipython)`
39 function, which will be called if the user unloads or reloads the extension.
40 The extension manager will only call :func:`load_ipython_extension` again
41 if the extension is reloaded.
42
43 You can put your extension modules anywhere you want, as long as
44 they can be imported by Python's standard import mechanism.
45 """
46
47 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
48
49 def __init__(self, shell=None, **kwargs):
50 super().__init__(shell=shell, **kwargs)
51 self.loaded = set()
52
53 def load_extension(self, module_str: str):
54 """Load an IPython extension by its module name.
55
56 Returns the string "already loaded" if the extension is already loaded,
57 "no load function" if the module doesn't have a load_ipython_extension
58 function, or None if it succeeded.
59 """
60 try:
61 return self._load_extension(module_str)
62 except ModuleNotFoundError:
63 if module_str in BUILTINS_EXTS:
64 BUILTINS_EXTS[module_str] = True
65 return self._load_extension("IPython.extensions." + module_str)
66 raise
67
68 def _load_extension(self, module_str: str):
69 if module_str in self.loaded:
70 return "already loaded"
71
72 assert self.shell is not None
73
74 with self.shell.builtin_trap:
75 if module_str not in sys.modules:
76 mod = import_module(module_str)
77 mod = sys.modules[module_str]
78 if self._call_load_ipython_extension(mod):
79 self.loaded.add(module_str)
80 else:
81 return "no load function"
82
83 def unload_extension(self, module_str: str):
84 """Unload an IPython extension by its module name.
85
86 This function looks up the extension's name in ``sys.modules`` and
87 simply calls ``mod.unload_ipython_extension(self)``.
88
89 Returns the string "no unload function" if the extension doesn't define
90 a function to unload itself, "not loaded" if the extension isn't loaded,
91 otherwise None.
92 """
93 if BUILTINS_EXTS.get(module_str, False) is True:
94 module_str = "IPython.extensions." + module_str
95 if module_str not in self.loaded:
96 return "not loaded"
97
98 if module_str in sys.modules:
99 mod = sys.modules[module_str]
100 if self._call_unload_ipython_extension(mod):
101 self.loaded.discard(module_str)
102 else:
103 return "no unload function"
104
105 def reload_extension(self, module_str: str):
106 """Reload an IPython extension by calling reload.
107
108 If the module has not been loaded before,
109 :meth:`InteractiveShell.load_extension` is called. Otherwise
110 :func:`reload` is called and then the :func:`load_ipython_extension`
111 function of the module, if it exists is called.
112 """
113
114 if BUILTINS_EXTS.get(module_str, False) is True:
115 module_str = "IPython.extensions." + module_str
116
117 if (module_str in self.loaded) and (module_str in sys.modules):
118 self.unload_extension(module_str)
119 mod = sys.modules[module_str]
120 reload(mod)
121 if self._call_load_ipython_extension(mod):
122 self.loaded.add(module_str)
123 else:
124 self.load_extension(module_str)
125
126 def _call_load_ipython_extension(self, mod):
127 if hasattr(mod, 'load_ipython_extension'):
128 mod.load_ipython_extension(self.shell)
129 return True
130
131 def _call_unload_ipython_extension(self, mod):
132 if hasattr(mod, 'unload_ipython_extension'):
133 mod.unload_ipython_extension(self.shell)
134 return True