##// END OF EJS Templates
load extensions in builtin trap...
MinRK -
Show More
@@ -1,184 +1,185 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """A class for managing IPython extensions.
2 """A class for managing IPython extensions.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2010-2011 The IPython Development Team
10 # Copyright (C) 2010-2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 from shutil import copyfile
21 from shutil import copyfile
22 import sys
22 import sys
23
23
24 from IPython.core.error import UsageError
24 from IPython.core.error import UsageError
25 from IPython.config.configurable import Configurable
25 from IPython.config.configurable import Configurable
26 from IPython.utils.traitlets import Instance
26 from IPython.utils.traitlets import Instance
27 from IPython.utils.py3compat import PY3
27 from IPython.utils.py3compat import PY3
28 if PY3:
28 if PY3:
29 from imp import reload
29 from imp import reload
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Main class
32 # Main class
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 class ExtensionManager(Configurable):
35 class ExtensionManager(Configurable):
36 """A class to manage IPython extensions.
36 """A class to manage IPython extensions.
37
37
38 An IPython extension is an importable Python module that has
38 An IPython extension is an importable Python module that has
39 a function with the signature::
39 a function with the signature::
40
40
41 def load_ipython_extension(ipython):
41 def load_ipython_extension(ipython):
42 # Do things with ipython
42 # Do things with ipython
43
43
44 This function is called after your extension is imported and the
44 This function is called after your extension is imported and the
45 currently active :class:`InteractiveShell` instance is passed as
45 currently active :class:`InteractiveShell` instance is passed as
46 the only argument. You can do anything you want with IPython at
46 the only argument. You can do anything you want with IPython at
47 that point, including defining new magic and aliases, adding new
47 that point, including defining new magic and aliases, adding new
48 components, etc.
48 components, etc.
49
49
50 You can also optionaly define an :func:`unload_ipython_extension(ipython)`
50 You can also optionaly define an :func:`unload_ipython_extension(ipython)`
51 function, which will be called if the user unloads or reloads the extension.
51 function, which will be called if the user unloads or reloads the extension.
52 The extension manager will only call :func:`load_ipython_extension` again
52 The extension manager will only call :func:`load_ipython_extension` again
53 if the extension is reloaded.
53 if the extension is reloaded.
54
54
55 You can put your extension modules anywhere you want, as long as
55 You can put your extension modules anywhere you want, as long as
56 they can be imported by Python's standard import mechanism. However,
56 they can be imported by Python's standard import mechanism. However,
57 to make it easy to write extensions, you can also put your extensions
57 to make it easy to write extensions, you can also put your extensions
58 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
58 in ``os.path.join(self.ipython_dir, 'extensions')``. This directory
59 is added to ``sys.path`` automatically.
59 is added to ``sys.path`` automatically.
60 """
60 """
61
61
62 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
62 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63
63
64 def __init__(self, shell=None, config=None):
64 def __init__(self, shell=None, config=None):
65 super(ExtensionManager, self).__init__(shell=shell, config=config)
65 super(ExtensionManager, self).__init__(shell=shell, config=config)
66 self.shell.on_trait_change(
66 self.shell.on_trait_change(
67 self._on_ipython_dir_changed, 'ipython_dir'
67 self._on_ipython_dir_changed, 'ipython_dir'
68 )
68 )
69 self.loaded = set()
69 self.loaded = set()
70
70
71 def __del__(self):
71 def __del__(self):
72 self.shell.on_trait_change(
72 self.shell.on_trait_change(
73 self._on_ipython_dir_changed, 'ipython_dir', remove=True
73 self._on_ipython_dir_changed, 'ipython_dir', remove=True
74 )
74 )
75
75
76 @property
76 @property
77 def ipython_extension_dir(self):
77 def ipython_extension_dir(self):
78 return os.path.join(self.shell.ipython_dir, u'extensions')
78 return os.path.join(self.shell.ipython_dir, u'extensions')
79
79
80 def _on_ipython_dir_changed(self):
80 def _on_ipython_dir_changed(self):
81 if not os.path.isdir(self.ipython_extension_dir):
81 if not os.path.isdir(self.ipython_extension_dir):
82 os.makedirs(self.ipython_extension_dir, mode = 0o777)
82 os.makedirs(self.ipython_extension_dir, mode = 0o777)
83
83
84 def load_extension(self, module_str):
84 def load_extension(self, module_str):
85 """Load an IPython extension by its module name.
85 """Load an IPython extension by its module name.
86
86
87 Returns the string "already loaded" if the extension is already loaded,
87 Returns the string "already loaded" if the extension is already loaded,
88 "no load function" if the module doesn't have a load_ipython_extension
88 "no load function" if the module doesn't have a load_ipython_extension
89 function, or None if it succeeded.
89 function, or None if it succeeded.
90 """
90 """
91 if module_str in self.loaded:
91 if module_str in self.loaded:
92 return "already loaded"
92 return "already loaded"
93
93
94 from IPython.utils.syspathcontext import prepended_to_syspath
94 from IPython.utils.syspathcontext import prepended_to_syspath
95
95
96 if module_str not in sys.modules:
96 with self.shell.builtin_trap:
97 with prepended_to_syspath(self.ipython_extension_dir):
97 if module_str not in sys.modules:
98 __import__(module_str)
98 with prepended_to_syspath(self.ipython_extension_dir):
99 mod = sys.modules[module_str]
99 __import__(module_str)
100 if self._call_load_ipython_extension(mod):
100 mod = sys.modules[module_str]
101 self.loaded.add(module_str)
101 if self._call_load_ipython_extension(mod):
102 else:
102 self.loaded.add(module_str)
103 return "no load function"
103 else:
104 return "no load function"
104
105
105 def unload_extension(self, module_str):
106 def unload_extension(self, module_str):
106 """Unload an IPython extension by its module name.
107 """Unload an IPython extension by its module name.
107
108
108 This function looks up the extension's name in ``sys.modules`` and
109 This function looks up the extension's name in ``sys.modules`` and
109 simply calls ``mod.unload_ipython_extension(self)``.
110 simply calls ``mod.unload_ipython_extension(self)``.
110
111
111 Returns the string "no unload function" if the extension doesn't define
112 Returns the string "no unload function" if the extension doesn't define
112 a function to unload itself, "not loaded" if the extension isn't loaded,
113 a function to unload itself, "not loaded" if the extension isn't loaded,
113 otherwise None.
114 otherwise None.
114 """
115 """
115 if module_str not in self.loaded:
116 if module_str not in self.loaded:
116 return "not loaded"
117 return "not loaded"
117
118
118 if module_str in sys.modules:
119 if module_str in sys.modules:
119 mod = sys.modules[module_str]
120 mod = sys.modules[module_str]
120 if self._call_unload_ipython_extension(mod):
121 if self._call_unload_ipython_extension(mod):
121 self.loaded.discard(module_str)
122 self.loaded.discard(module_str)
122 else:
123 else:
123 return "no unload function"
124 return "no unload function"
124
125
125 def reload_extension(self, module_str):
126 def reload_extension(self, module_str):
126 """Reload an IPython extension by calling reload.
127 """Reload an IPython extension by calling reload.
127
128
128 If the module has not been loaded before,
129 If the module has not been loaded before,
129 :meth:`InteractiveShell.load_extension` is called. Otherwise
130 :meth:`InteractiveShell.load_extension` is called. Otherwise
130 :func:`reload` is called and then the :func:`load_ipython_extension`
131 :func:`reload` is called and then the :func:`load_ipython_extension`
131 function of the module, if it exists is called.
132 function of the module, if it exists is called.
132 """
133 """
133 from IPython.utils.syspathcontext import prepended_to_syspath
134 from IPython.utils.syspathcontext import prepended_to_syspath
134
135
135 if (module_str in self.loaded) and (module_str in sys.modules):
136 if (module_str in self.loaded) and (module_str in sys.modules):
136 self.unload_extension(module_str)
137 self.unload_extension(module_str)
137 mod = sys.modules[module_str]
138 mod = sys.modules[module_str]
138 with prepended_to_syspath(self.ipython_extension_dir):
139 with prepended_to_syspath(self.ipython_extension_dir):
139 reload(mod)
140 reload(mod)
140 if self._call_load_ipython_extension(mod):
141 if self._call_load_ipython_extension(mod):
141 self.loaded.add(module_str)
142 self.loaded.add(module_str)
142 else:
143 else:
143 self.load_extension(module_str)
144 self.load_extension(module_str)
144
145
145 def _call_load_ipython_extension(self, mod):
146 def _call_load_ipython_extension(self, mod):
146 if hasattr(mod, 'load_ipython_extension'):
147 if hasattr(mod, 'load_ipython_extension'):
147 mod.load_ipython_extension(self.shell)
148 mod.load_ipython_extension(self.shell)
148 return True
149 return True
149
150
150 def _call_unload_ipython_extension(self, mod):
151 def _call_unload_ipython_extension(self, mod):
151 if hasattr(mod, 'unload_ipython_extension'):
152 if hasattr(mod, 'unload_ipython_extension'):
152 mod.unload_ipython_extension(self.shell)
153 mod.unload_ipython_extension(self.shell)
153 return True
154 return True
154
155
155 def install_extension(self, url, filename=None):
156 def install_extension(self, url, filename=None):
156 """Download and install an IPython extension.
157 """Download and install an IPython extension.
157
158
158 If filename is given, the file will be so named (inside the extension
159 If filename is given, the file will be so named (inside the extension
159 directory). Otherwise, the name from the URL will be used. The file must
160 directory). Otherwise, the name from the URL will be used. The file must
160 have a .py or .zip extension; otherwise, a ValueError will be raised.
161 have a .py or .zip extension; otherwise, a ValueError will be raised.
161
162
162 Returns the full path to the installed file.
163 Returns the full path to the installed file.
163 """
164 """
164 # Ensure the extension directory exists
165 # Ensure the extension directory exists
165 if not os.path.isdir(self.ipython_extension_dir):
166 if not os.path.isdir(self.ipython_extension_dir):
166 os.makedirs(self.ipython_extension_dir, mode = 0o777)
167 os.makedirs(self.ipython_extension_dir, mode = 0o777)
167
168
168 if os.path.isfile(url):
169 if os.path.isfile(url):
169 src_filename = os.path.basename(url)
170 src_filename = os.path.basename(url)
170 copy = copyfile
171 copy = copyfile
171 else:
172 else:
172 from urllib import urlretrieve # Deferred imports
173 from urllib import urlretrieve # Deferred imports
173 from urlparse import urlparse
174 from urlparse import urlparse
174 src_filename = urlparse(url).path.split('/')[-1]
175 src_filename = urlparse(url).path.split('/')[-1]
175 copy = urlretrieve
176 copy = urlretrieve
176
177
177 if filename is None:
178 if filename is None:
178 filename = src_filename
179 filename = src_filename
179 if os.path.splitext(filename)[1] not in ('.py', '.zip'):
180 if os.path.splitext(filename)[1] not in ('.py', '.zip'):
180 raise ValueError("The file must have a .py or .zip extension", filename)
181 raise ValueError("The file must have a .py or .zip extension", filename)
181
182
182 filename = os.path.join(self.ipython_extension_dir, filename)
183 filename = os.path.join(self.ipython_extension_dir, filename)
183 copy(url, filename)
184 copy(url, filename)
184 return filename
185 return filename
@@ -1,73 +1,96 b''
1 import os.path
1 import os.path
2
2
3 import nose.tools as nt
3 import nose.tools as nt
4
4
5 import IPython.testing.tools as tt
5 import IPython.testing.tools as tt
6 from IPython.utils.syspathcontext import prepended_to_syspath
6 from IPython.utils.syspathcontext import prepended_to_syspath
7 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.utils.tempdir import TemporaryDirectory
8
8
9 ext1_content = """
9 ext1_content = """
10 def load_ipython_extension(ip):
10 def load_ipython_extension(ip):
11 print("Running ext1 load")
11 print("Running ext1 load")
12
12
13 def unload_ipython_extension(ip):
13 def unload_ipython_extension(ip):
14 print("Running ext1 unload")
14 print("Running ext1 unload")
15 """
15 """
16
16
17 ext2_content = """
17 ext2_content = """
18 def load_ipython_extension(ip):
18 def load_ipython_extension(ip):
19 print("Running ext2 load")
19 print("Running ext2 load")
20 """
20 """
21
21
22 ext3_content = """
23 def load_ipython_extension(ip):
24 ip2 = get_ipython()
25 print(ip is ip2)
26 """
27
22 def test_extension_loading():
28 def test_extension_loading():
23 em = get_ipython().extension_manager
29 em = get_ipython().extension_manager
24 with TemporaryDirectory() as td:
30 with TemporaryDirectory() as td:
25 ext1 = os.path.join(td, 'ext1.py')
31 ext1 = os.path.join(td, 'ext1.py')
26 with open(ext1, 'w') as f:
32 with open(ext1, 'w') as f:
27 f.write(ext1_content)
33 f.write(ext1_content)
28
34
29 ext2 = os.path.join(td, 'ext2.py')
35 ext2 = os.path.join(td, 'ext2.py')
30 with open(ext2, 'w') as f:
36 with open(ext2, 'w') as f:
31 f.write(ext2_content)
37 f.write(ext2_content)
32
38
33 with prepended_to_syspath(td):
39 with prepended_to_syspath(td):
34 assert 'ext1' not in em.loaded
40 assert 'ext1' not in em.loaded
35 assert 'ext2' not in em.loaded
41 assert 'ext2' not in em.loaded
36
42
37 # Load extension
43 # Load extension
38 with tt.AssertPrints("Running ext1 load"):
44 with tt.AssertPrints("Running ext1 load"):
39 assert em.load_extension('ext1') is None
45 assert em.load_extension('ext1') is None
40 assert 'ext1' in em.loaded
46 assert 'ext1' in em.loaded
41
47
42 # Should refuse to load it again
48 # Should refuse to load it again
43 with tt.AssertNotPrints("Running ext1 load"):
49 with tt.AssertNotPrints("Running ext1 load"):
44 assert em.load_extension('ext1') == 'already loaded'
50 assert em.load_extension('ext1') == 'already loaded'
45
51
46 # Reload
52 # Reload
47 with tt.AssertPrints("Running ext1 unload"):
53 with tt.AssertPrints("Running ext1 unload"):
48 with tt.AssertPrints("Running ext1 load", suppress=False):
54 with tt.AssertPrints("Running ext1 load", suppress=False):
49 em.reload_extension('ext1')
55 em.reload_extension('ext1')
50
56
51 # Unload
57 # Unload
52 with tt.AssertPrints("Running ext1 unload"):
58 with tt.AssertPrints("Running ext1 unload"):
53 assert em.unload_extension('ext1') is None
59 assert em.unload_extension('ext1') is None
54
60
55 # Can't unload again
61 # Can't unload again
56 with tt.AssertNotPrints("Running ext1 unload"):
62 with tt.AssertNotPrints("Running ext1 unload"):
57 assert em.unload_extension('ext1') == 'not loaded'
63 assert em.unload_extension('ext1') == 'not loaded'
58 assert em.unload_extension('ext2') == 'not loaded'
64 assert em.unload_extension('ext2') == 'not loaded'
59
65
60 # Load extension 2
66 # Load extension 2
61 with tt.AssertPrints("Running ext2 load"):
67 with tt.AssertPrints("Running ext2 load"):
62 assert em.load_extension('ext2') is None
68 assert em.load_extension('ext2') is None
63
69
64 # Can't unload this
70 # Can't unload this
65 assert em.unload_extension('ext2') == 'no unload function'
71 assert em.unload_extension('ext2') == 'no unload function'
66
72
67 # But can reload it
73 # But can reload it
68 with tt.AssertPrints("Running ext2 load"):
74 with tt.AssertPrints("Running ext2 load"):
69 em.reload_extension('ext2')
75 em.reload_extension('ext2')
70
76
77
78 def test_extension_builtins():
79 em = get_ipython().extension_manager
80 with TemporaryDirectory() as td:
81 ext3 = os.path.join(td, 'ext3.py')
82 with open(ext3, 'w') as f:
83 f.write(ext3_content)
84
85 assert 'ext3' not in em.loaded
86
87 with prepended_to_syspath(td):
88 # Load extension
89 with tt.AssertPrints("True"):
90 assert em.load_extension('ext3') is None
91 assert 'ext3' in em.loaded
92
93
71 def test_non_extension():
94 def test_non_extension():
72 em = get_ipython().extension_manager
95 em = get_ipython().extension_manager
73 nt.assert_equal(em.load_extension('sys'), "no load function")
96 nt.assert_equal(em.load_extension('sys'), "no load function")
General Comments 0
You need to be logged in to leave comments. Login now