##// END OF EJS Templates
Fix tests in autoreload extension for new API.
Fernando Perez -
Show More
@@ -1,231 +1,231 b''
1 """hooks for IPython.
1 """hooks for IPython.
2
2
3 In Python, it is possible to overwrite any method of any object if you really
3 In Python, it is possible to overwrite any method of any object if you really
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
5 be overwritten by users for customization purposes. This module defines the
5 be overwritten by users for customization purposes. This module defines the
6 default versions of all such hooks, which get used by IPython if not
6 default versions of all such hooks, which get used by IPython if not
7 overridden by the user.
7 overridden by the user.
8
8
9 hooks are simple functions, but they should be declared with 'self' as their
9 hooks are simple functions, but they should be declared with 'self' as their
10 first argument, because when activated they are registered into IPython as
10 first argument, because when activated they are registered into IPython as
11 instance methods. The self argument will be the IPython running instance
11 instance methods. The self argument will be the IPython running instance
12 itself, so hooks have full access to the entire IPython object.
12 itself, so hooks have full access to the entire IPython object.
13
13
14 If you wish to define a new hook and activate it, you need to put the
14 If you wish to define a new hook and activate it, you need to put the
15 necessary code into a python file which can be either imported or execfile()'d
15 necessary code into a python file which can be either imported or execfile()'d
16 from within your profile's ipython_config.py configuration.
16 from within your profile's ipython_config.py configuration.
17
17
18 For example, suppose that you have a module called 'myiphooks' in your
18 For example, suppose that you have a module called 'myiphooks' in your
19 PYTHONPATH, which contains the following definition:
19 PYTHONPATH, which contains the following definition:
20
20
21 import os
21 import os
22 from IPython.core import ipapi
22 from IPython.core import ipapi
23 ip = ipapi.get()
23 ip = ipapi.get()
24
24
25 def calljed(self,filename, linenum):
25 def calljed(self,filename, linenum):
26 "My editor hook calls the jed editor directly."
26 "My editor hook calls the jed editor directly."
27 print "Calling my own editor, jed ..."
27 print "Calling my own editor, jed ..."
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
29 raise TryNext()
29 raise TryNext()
30
30
31 ip.set_hook('editor', calljed)
31 ip.set_hook('editor', calljed)
32
32
33 You can then enable the functionality by doing 'import myiphooks'
33 You can then enable the functionality by doing 'import myiphooks'
34 somewhere in your configuration files or ipython command line.
34 somewhere in your configuration files or ipython command line.
35 """
35 """
36
36
37 #*****************************************************************************
37 #*****************************************************************************
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
39 #
39 #
40 # Distributed under the terms of the BSD License. The full license is in
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
41 # the file COPYING, distributed as part of this software.
42 #*****************************************************************************
42 #*****************************************************************************
43
43
44 import os, bisect
44 import os, bisect
45 import subprocess
45 import subprocess
46 import sys
46 import sys
47
47
48 from IPython.core.error import TryNext
48 from IPython.core.error import TryNext
49
49
50 # List here all the default hooks. For now it's just the editor functions
50 # List here all the default hooks. For now it's just the editor functions
51 # but over time we'll move here all the public API for user-accessible things.
51 # but over time we'll move here all the public API for user-accessible things.
52
52
53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
55 'show_in_pager','pre_prompt_hook',
55 'show_in_pager','pre_prompt_hook',
56 'pre_run_code_hook', 'clipboard_get']
56 'pre_run_code_hook', 'clipboard_get']
57
57
58 def editor(self, filename, linenum=None, wait=True):
58 def editor(self, filename, linenum=None, wait=True):
59 """Open the default editor at the given filename and linenumber.
59 """Open the default editor at the given filename and linenumber.
60
60
61 This is IPython's default editor hook, you can use it as an example to
61 This is IPython's default editor hook, you can use it as an example to
62 write your own modified one. To set your own editor function as the
62 write your own modified one. To set your own editor function as the
63 new editor hook, call ip.set_hook('editor',yourfunc)."""
63 new editor hook, call ip.set_hook('editor',yourfunc)."""
64
64
65 # IPython configures a default editor at startup by reading $EDITOR from
65 # IPython configures a default editor at startup by reading $EDITOR from
66 # the environment, and falling back on vi (unix) or notepad (win32).
66 # the environment, and falling back on vi (unix) or notepad (win32).
67 editor = self.editor
67 editor = self.editor
68
68
69 # marker for at which line to open the file (for existing objects)
69 # marker for at which line to open the file (for existing objects)
70 if linenum is None or editor=='notepad':
70 if linenum is None or editor=='notepad':
71 linemark = ''
71 linemark = ''
72 else:
72 else:
73 linemark = '+%d' % int(linenum)
73 linemark = '+%d' % int(linenum)
74
74
75 # Enclose in quotes if necessary and legal
75 # Enclose in quotes if necessary and legal
76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
77 editor = '"%s"' % editor
77 editor = '"%s"' % editor
78
78
79 # Call the actual editor
79 # Call the actual editor
80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
81 shell=True)
81 shell=True)
82 if wait and proc.wait() != 0:
82 if wait and proc.wait() != 0:
83 raise TryNext()
83 raise TryNext()
84
84
85 import tempfile
85 import tempfile
86 def fix_error_editor(self,filename,linenum,column,msg):
86 def fix_error_editor(self,filename,linenum,column,msg):
87 """Open the editor at the given filename, linenumber, column and
87 """Open the editor at the given filename, linenumber, column and
88 show an error message. This is used for correcting syntax errors.
88 show an error message. This is used for correcting syntax errors.
89 The current implementation only has special support for the VIM editor,
89 The current implementation only has special support for the VIM editor,
90 and falls back on the 'editor' hook if VIM is not used.
90 and falls back on the 'editor' hook if VIM is not used.
91
91
92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
93 """
93 """
94 def vim_quickfix_file():
94 def vim_quickfix_file():
95 t = tempfile.NamedTemporaryFile()
95 t = tempfile.NamedTemporaryFile()
96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
97 t.flush()
97 t.flush()
98 return t
98 return t
99 if os.path.basename(self.editor) != 'vim':
99 if os.path.basename(self.editor) != 'vim':
100 self.hooks.editor(filename,linenum)
100 self.hooks.editor(filename,linenum)
101 return
101 return
102 t = vim_quickfix_file()
102 t = vim_quickfix_file()
103 try:
103 try:
104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
105 raise TryNext()
105 raise TryNext()
106 finally:
106 finally:
107 t.close()
107 t.close()
108
108
109
109
110 def synchronize_with_editor(self, filename, linenum, column):
110 def synchronize_with_editor(self, filename, linenum, column):
111 pass
111 pass
112
112
113
113
114 class CommandChainDispatcher:
114 class CommandChainDispatcher:
115 """ Dispatch calls to a chain of commands until some func can handle it
115 """ Dispatch calls to a chain of commands until some func can handle it
116
116
117 Usage: instantiate, execute "add" to add commands (with optional
117 Usage: instantiate, execute "add" to add commands (with optional
118 priority), execute normally via f() calling mechanism.
118 priority), execute normally via f() calling mechanism.
119
119
120 """
120 """
121 def __init__(self,commands=None):
121 def __init__(self,commands=None):
122 if commands is None:
122 if commands is None:
123 self.chain = []
123 self.chain = []
124 else:
124 else:
125 self.chain = commands
125 self.chain = commands
126
126
127
127
128 def __call__(self,*args, **kw):
128 def __call__(self,*args, **kw):
129 """ Command chain is called just like normal func.
129 """ Command chain is called just like normal func.
130
130
131 This will call all funcs in chain with the same args as were given to this
131 This will call all funcs in chain with the same args as were given to
132 function, and return the result of first func that didn't raise
132 this function, and return the result of first func that didn't raise
133 TryNext """
133 TryNext"""
134
134
135 for prio,cmd in self.chain:
135 for prio,cmd in self.chain:
136 #print "prio",prio,"cmd",cmd #dbg
136 #print "prio",prio,"cmd",cmd #dbg
137 try:
137 try:
138 return cmd(*args, **kw)
138 return cmd(*args, **kw)
139 except TryNext, exc:
139 except TryNext, exc:
140 if exc.args or exc.kwargs:
140 if exc.args or exc.kwargs:
141 args = exc.args
141 args = exc.args
142 kw = exc.kwargs
142 kw = exc.kwargs
143 # if no function will accept it, raise TryNext up to the caller
143 # if no function will accept it, raise TryNext up to the caller
144 raise TryNext(*args, **kw)
144 raise TryNext(*args, **kw)
145
145
146 def __str__(self):
146 def __str__(self):
147 return str(self.chain)
147 return str(self.chain)
148
148
149 def add(self, func, priority=0):
149 def add(self, func, priority=0):
150 """ Add a func to the cmd chain with given priority """
150 """ Add a func to the cmd chain with given priority """
151 bisect.insort(self.chain,(priority,func))
151 bisect.insort(self.chain,(priority,func))
152
152
153 def __iter__(self):
153 def __iter__(self):
154 """ Return all objects in chain.
154 """ Return all objects in chain.
155
155
156 Handy if the objects are not callable.
156 Handy if the objects are not callable.
157 """
157 """
158 return iter(self.chain)
158 return iter(self.chain)
159
159
160
160
161 def input_prefilter(self,line):
161 def input_prefilter(self,line):
162 """ Default input prefilter
162 """ Default input prefilter
163
163
164 This returns the line as unchanged, so that the interpreter
164 This returns the line as unchanged, so that the interpreter
165 knows that nothing was done and proceeds with "classic" prefiltering
165 knows that nothing was done and proceeds with "classic" prefiltering
166 (%magics, !shell commands etc.).
166 (%magics, !shell commands etc.).
167
167
168 Note that leading whitespace is not passed to this hook. Prefilter
168 Note that leading whitespace is not passed to this hook. Prefilter
169 can't alter indentation.
169 can't alter indentation.
170
170
171 """
171 """
172 #print "attempt to rewrite",line #dbg
172 #print "attempt to rewrite",line #dbg
173 return line
173 return line
174
174
175
175
176 def shutdown_hook(self):
176 def shutdown_hook(self):
177 """ default shutdown hook
177 """ default shutdown hook
178
178
179 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
179 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
180 """
180 """
181
181
182 #print "default shutdown hook ok" # dbg
182 #print "default shutdown hook ok" # dbg
183 return
183 return
184
184
185
185
186 def late_startup_hook(self):
186 def late_startup_hook(self):
187 """ Executed after ipython has been constructed and configured
187 """ Executed after ipython has been constructed and configured
188
188
189 """
189 """
190 #print "default startup hook ok" # dbg
190 #print "default startup hook ok" # dbg
191
191
192
192
193 def show_in_pager(self,s):
193 def show_in_pager(self,s):
194 """ Run a string through pager """
194 """ Run a string through pager """
195 # raising TryNext here will use the default paging functionality
195 # raising TryNext here will use the default paging functionality
196 raise TryNext
196 raise TryNext
197
197
198
198
199 def pre_prompt_hook(self):
199 def pre_prompt_hook(self):
200 """ Run before displaying the next prompt
200 """ Run before displaying the next prompt
201
201
202 Use this e.g. to display output from asynchronous operations (in order
202 Use this e.g. to display output from asynchronous operations (in order
203 to not mess up text entry)
203 to not mess up text entry)
204 """
204 """
205
205
206 return None
206 return None
207
207
208
208
209 def pre_run_code_hook(self):
209 def pre_run_code_hook(self):
210 """ Executed before running the (prefiltered) code in IPython """
210 """ Executed before running the (prefiltered) code in IPython """
211 return None
211 return None
212
212
213
213
214 def clipboard_get(self):
214 def clipboard_get(self):
215 """ Get text from the clipboard.
215 """ Get text from the clipboard.
216 """
216 """
217 from IPython.lib.clipboard import (
217 from IPython.lib.clipboard import (
218 osx_clipboard_get, tkinter_clipboard_get,
218 osx_clipboard_get, tkinter_clipboard_get,
219 win32_clipboard_get
219 win32_clipboard_get
220 )
220 )
221 if sys.platform == 'win32':
221 if sys.platform == 'win32':
222 chain = [win32_clipboard_get, tkinter_clipboard_get]
222 chain = [win32_clipboard_get, tkinter_clipboard_get]
223 elif sys.platform == 'darwin':
223 elif sys.platform == 'darwin':
224 chain = [osx_clipboard_get, tkinter_clipboard_get]
224 chain = [osx_clipboard_get, tkinter_clipboard_get]
225 else:
225 else:
226 chain = [tkinter_clipboard_get]
226 chain = [tkinter_clipboard_get]
227 dispatcher = CommandChainDispatcher()
227 dispatcher = CommandChainDispatcher()
228 for func in chain:
228 for func in chain:
229 dispatcher.add(func)
229 dispatcher.add(func)
230 text = dispatcher()
230 text = dispatcher()
231 return text
231 return text
@@ -1,538 +1,536 b''
1 """IPython extension to reload modules before executing user code.
1 """IPython extension to reload modules before executing user code.
2
2
3 ``autoreload`` reloads modules automatically before entering the execution of
3 ``autoreload`` reloads modules automatically before entering the execution of
4 code typed at the IPython prompt.
4 code typed at the IPython prompt.
5
5
6 This makes for example the following workflow possible:
6 This makes for example the following workflow possible:
7
7
8 .. sourcecode:: ipython
8 .. sourcecode:: ipython
9
9
10 In [1]: %load_ext autoreload
10 In [1]: %load_ext autoreload
11
11
12 In [2]: %autoreload 2
12 In [2]: %autoreload 2
13
13
14 In [3]: from foo import some_function
14 In [3]: from foo import some_function
15
15
16 In [4]: some_function()
16 In [4]: some_function()
17 Out[4]: 42
17 Out[4]: 42
18
18
19 In [5]: # open foo.py in an editor and change some_function to return 43
19 In [5]: # open foo.py in an editor and change some_function to return 43
20
20
21 In [6]: some_function()
21 In [6]: some_function()
22 Out[6]: 43
22 Out[6]: 43
23
23
24 The module was reloaded without reloading it explicitly, and the object
24 The module was reloaded without reloading it explicitly, and the object
25 imported with ``from foo import ...`` was also updated.
25 imported with ``from foo import ...`` was also updated.
26
26
27 Usage
27 Usage
28 =====
28 =====
29
29
30 The following magic commands are provided:
30 The following magic commands are provided:
31
31
32 ``%autoreload``
32 ``%autoreload``
33
33
34 Reload all modules (except those excluded by ``%aimport``)
34 Reload all modules (except those excluded by ``%aimport``)
35 automatically now.
35 automatically now.
36
36
37 ``%autoreload 0``
37 ``%autoreload 0``
38
38
39 Disable automatic reloading.
39 Disable automatic reloading.
40
40
41 ``%autoreload 1``
41 ``%autoreload 1``
42
42
43 Reload all modules imported with ``%aimport`` every time before
43 Reload all modules imported with ``%aimport`` every time before
44 executing the Python code typed.
44 executing the Python code typed.
45
45
46 ``%autoreload 2``
46 ``%autoreload 2``
47
47
48 Reload all modules (except those excluded by ``%aimport``) every
48 Reload all modules (except those excluded by ``%aimport``) every
49 time before executing the Python code typed.
49 time before executing the Python code typed.
50
50
51 ``%aimport``
51 ``%aimport``
52
52
53 List modules which are to be automatically imported or not to be imported.
53 List modules which are to be automatically imported or not to be imported.
54
54
55 ``%aimport foo``
55 ``%aimport foo``
56
56
57 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
57 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
58
58
59 ``%aimport -foo``
59 ``%aimport -foo``
60
60
61 Mark module 'foo' to not be autoreloaded.
61 Mark module 'foo' to not be autoreloaded.
62
62
63 Caveats
63 Caveats
64 =======
64 =======
65
65
66 Reloading Python modules in a reliable way is in general difficult,
66 Reloading Python modules in a reliable way is in general difficult,
67 and unexpected things may occur. ``%autoreload`` tries to work around
67 and unexpected things may occur. ``%autoreload`` tries to work around
68 common pitfalls by replacing function code objects and parts of
68 common pitfalls by replacing function code objects and parts of
69 classes previously in the module with new versions. This makes the
69 classes previously in the module with new versions. This makes the
70 following things to work:
70 following things to work:
71
71
72 - Functions and classes imported via 'from xxx import foo' are upgraded
72 - Functions and classes imported via 'from xxx import foo' are upgraded
73 to new versions when 'xxx' is reloaded.
73 to new versions when 'xxx' is reloaded.
74
74
75 - Methods and properties of classes are upgraded on reload, so that
75 - Methods and properties of classes are upgraded on reload, so that
76 calling 'c.foo()' on an object 'c' created before the reload causes
76 calling 'c.foo()' on an object 'c' created before the reload causes
77 the new code for 'foo' to be executed.
77 the new code for 'foo' to be executed.
78
78
79 Some of the known remaining caveats are:
79 Some of the known remaining caveats are:
80
80
81 - Replacing code objects does not always succeed: changing a @property
81 - Replacing code objects does not always succeed: changing a @property
82 in a class to an ordinary method or a method to a member variable
82 in a class to an ordinary method or a method to a member variable
83 can cause problems (but in old objects only).
83 can cause problems (but in old objects only).
84
84
85 - Functions that are removed (eg. via monkey-patching) from a module
85 - Functions that are removed (eg. via monkey-patching) from a module
86 before it is reloaded are not upgraded.
86 before it is reloaded are not upgraded.
87
87
88 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
88 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
89 """
89 """
90
90
91 skip_doctest = True
91 skip_doctest = True
92
92
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94 # Copyright (C) 2000 Thomas Heller
94 # Copyright (C) 2000 Thomas Heller
95 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
95 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
96 # Copyright (C) 2012 The IPython Development Team
96 # Copyright (C) 2012 The IPython Development Team
97 #
97 #
98 # Distributed under the terms of the BSD License. The full license is in
98 # Distributed under the terms of the BSD License. The full license is in
99 # the file COPYING, distributed as part of this software.
99 # the file COPYING, distributed as part of this software.
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101 #
101 #
102 # This IPython module is written by Pauli Virtanen, based on the autoreload
102 # This IPython module is written by Pauli Virtanen, based on the autoreload
103 # code by Thomas Heller.
103 # code by Thomas Heller.
104
104
105 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
106 # Imports
106 # Imports
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108 import atexit
108 import atexit
109 import imp
109 import imp
110 import inspect
110 import inspect
111 import os
111 import os
112 import sys
112 import sys
113 import threading
113 import threading
114 import time
114 import time
115 import traceback
115 import traceback
116 import types
116 import types
117 import weakref
117 import weakref
118
118
119 try:
119 try:
120 # Reload is not defined by default in Python3.
120 # Reload is not defined by default in Python3.
121 reload
121 reload
122 except NameError:
122 except NameError:
123 from imp import reload
123 from imp import reload
124
124
125 from IPython.utils import pyfile
125 from IPython.utils import pyfile
126 from IPython.utils.py3compat import PY3
126 from IPython.utils.py3compat import PY3
127
127
128 #------------------------------------------------------------------------------
128 #------------------------------------------------------------------------------
129 # Autoreload functionality
129 # Autoreload functionality
130 #------------------------------------------------------------------------------
130 #------------------------------------------------------------------------------
131
131
132 def _get_compiled_ext():
132 def _get_compiled_ext():
133 """Official way to get the extension of compiled files (.pyc or .pyo)"""
133 """Official way to get the extension of compiled files (.pyc or .pyo)"""
134 for ext, mode, typ in imp.get_suffixes():
134 for ext, mode, typ in imp.get_suffixes():
135 if typ == imp.PY_COMPILED:
135 if typ == imp.PY_COMPILED:
136 return ext
136 return ext
137
137
138
138
139 PY_COMPILED_EXT = _get_compiled_ext()
139 PY_COMPILED_EXT = _get_compiled_ext()
140
140
141
141
142 class ModuleReloader(object):
142 class ModuleReloader(object):
143 enabled = False
143 enabled = False
144 """Whether this reloader is enabled"""
144 """Whether this reloader is enabled"""
145
145
146 failed = {}
146 failed = {}
147 """Modules that failed to reload: {module: mtime-on-failed-reload, ...}"""
147 """Modules that failed to reload: {module: mtime-on-failed-reload, ...}"""
148
148
149 modules = {}
149 modules = {}
150 """Modules specially marked as autoreloadable."""
150 """Modules specially marked as autoreloadable."""
151
151
152 skip_modules = {}
152 skip_modules = {}
153 """Modules specially marked as not autoreloadable."""
153 """Modules specially marked as not autoreloadable."""
154
154
155 check_all = True
155 check_all = True
156 """Autoreload all modules, not just those listed in 'modules'"""
156 """Autoreload all modules, not just those listed in 'modules'"""
157
157
158 old_objects = {}
158 old_objects = {}
159 """(module-name, name) -> weakref, for replacing old code objects"""
159 """(module-name, name) -> weakref, for replacing old code objects"""
160
160
161 def mark_module_skipped(self, module_name):
161 def mark_module_skipped(self, module_name):
162 """Skip reloading the named module in the future"""
162 """Skip reloading the named module in the future"""
163 try:
163 try:
164 del self.modules[module_name]
164 del self.modules[module_name]
165 except KeyError:
165 except KeyError:
166 pass
166 pass
167 self.skip_modules[module_name] = True
167 self.skip_modules[module_name] = True
168
168
169 def mark_module_reloadable(self, module_name):
169 def mark_module_reloadable(self, module_name):
170 """Reload the named module in the future (if it is imported)"""
170 """Reload the named module in the future (if it is imported)"""
171 try:
171 try:
172 del self.skip_modules[module_name]
172 del self.skip_modules[module_name]
173 except KeyError:
173 except KeyError:
174 pass
174 pass
175 self.modules[module_name] = True
175 self.modules[module_name] = True
176
176
177 def aimport_module(self, module_name):
177 def aimport_module(self, module_name):
178 """Import a module, and mark it reloadable
178 """Import a module, and mark it reloadable
179
179
180 Returns
180 Returns
181 -------
181 -------
182 top_module : module
182 top_module : module
183 The imported module if it is top-level, or the top-level
183 The imported module if it is top-level, or the top-level
184 top_name : module
184 top_name : module
185 Name of top_module
185 Name of top_module
186
186
187 """
187 """
188 self.mark_module_reloadable(module_name)
188 self.mark_module_reloadable(module_name)
189
189
190 __import__(module_name)
190 __import__(module_name)
191 top_name = module_name.split('.')[0]
191 top_name = module_name.split('.')[0]
192 top_module = sys.modules[top_name]
192 top_module = sys.modules[top_name]
193 return top_module, top_name
193 return top_module, top_name
194
194
195 def check(self, check_all=False):
195 def check(self, check_all=False):
196 """Check whether some modules need to be reloaded."""
196 """Check whether some modules need to be reloaded."""
197
197
198 if not self.enabled and not check_all:
198 if not self.enabled and not check_all:
199 return
199 return
200
200
201 if check_all or self.check_all:
201 if check_all or self.check_all:
202 modules = sys.modules.keys()
202 modules = sys.modules.keys()
203 else:
203 else:
204 modules = self.modules.keys()
204 modules = self.modules.keys()
205
205
206 for modname in modules:
206 for modname in modules:
207 m = sys.modules.get(modname, None)
207 m = sys.modules.get(modname, None)
208
208
209 if modname in self.skip_modules:
209 if modname in self.skip_modules:
210 continue
210 continue
211
211
212 if not hasattr(m, '__file__'):
212 if not hasattr(m, '__file__'):
213 continue
213 continue
214
214
215 if m.__name__ == '__main__':
215 if m.__name__ == '__main__':
216 # we cannot reload(__main__)
216 # we cannot reload(__main__)
217 continue
217 continue
218
218
219 filename = m.__file__
219 filename = m.__file__
220 path, ext = os.path.splitext(filename)
220 path, ext = os.path.splitext(filename)
221
221
222 if ext.lower() == '.py':
222 if ext.lower() == '.py':
223 ext = PY_COMPILED_EXT
223 ext = PY_COMPILED_EXT
224 pyc_filename = pyfile.cache_from_source(filename)
224 pyc_filename = pyfile.cache_from_source(filename)
225 py_filename = filename
225 py_filename = filename
226 else:
226 else:
227 pyc_filename = filename
227 pyc_filename = filename
228 try:
228 try:
229 py_filename = pyfile.source_from_cache(filename)
229 py_filename = pyfile.source_from_cache(filename)
230 except ValueError:
230 except ValueError:
231 continue
231 continue
232
232
233 try:
233 try:
234 pymtime = os.stat(py_filename).st_mtime
234 pymtime = os.stat(py_filename).st_mtime
235 if pymtime <= os.stat(pyc_filename).st_mtime:
235 if pymtime <= os.stat(pyc_filename).st_mtime:
236 continue
236 continue
237 if self.failed.get(py_filename, None) == pymtime:
237 if self.failed.get(py_filename, None) == pymtime:
238 continue
238 continue
239 except OSError:
239 except OSError:
240 continue
240 continue
241
241
242 try:
242 try:
243 superreload(m, reload, self.old_objects)
243 superreload(m, reload, self.old_objects)
244 if py_filename in self.failed:
244 if py_filename in self.failed:
245 del self.failed[py_filename]
245 del self.failed[py_filename]
246 except:
246 except:
247 print >> sys.stderr, "[autoreload of %s failed: %s]" % (
247 print >> sys.stderr, "[autoreload of %s failed: %s]" % (
248 modname, traceback.format_exc(1))
248 modname, traceback.format_exc(1))
249 self.failed[py_filename] = pymtime
249 self.failed[py_filename] = pymtime
250
250
251 #------------------------------------------------------------------------------
251 #------------------------------------------------------------------------------
252 # superreload
252 # superreload
253 #------------------------------------------------------------------------------
253 #------------------------------------------------------------------------------
254
254
255 if PY3:
255 if PY3:
256 func_attrs = ['__code__', '__defaults__', '__doc__',
256 func_attrs = ['__code__', '__defaults__', '__doc__',
257 '__closure__', '__globals__', '__dict__']
257 '__closure__', '__globals__', '__dict__']
258 else:
258 else:
259 func_attrs = ['func_code', 'func_defaults', 'func_doc',
259 func_attrs = ['func_code', 'func_defaults', 'func_doc',
260 'func_closure', 'func_globals', 'func_dict']
260 'func_closure', 'func_globals', 'func_dict']
261
261
262
262
263 def update_function(old, new):
263 def update_function(old, new):
264 """Upgrade the code object of a function"""
264 """Upgrade the code object of a function"""
265 for name in func_attrs:
265 for name in func_attrs:
266 try:
266 try:
267 setattr(old, name, getattr(new, name))
267 setattr(old, name, getattr(new, name))
268 except (AttributeError, TypeError):
268 except (AttributeError, TypeError):
269 pass
269 pass
270
270
271
271
272 def update_class(old, new):
272 def update_class(old, new):
273 """Replace stuff in the __dict__ of a class, and upgrade
273 """Replace stuff in the __dict__ of a class, and upgrade
274 method code objects"""
274 method code objects"""
275 for key in old.__dict__.keys():
275 for key in old.__dict__.keys():
276 old_obj = getattr(old, key)
276 old_obj = getattr(old, key)
277
277
278 try:
278 try:
279 new_obj = getattr(new, key)
279 new_obj = getattr(new, key)
280 except AttributeError:
280 except AttributeError:
281 # obsolete attribute: remove it
281 # obsolete attribute: remove it
282 try:
282 try:
283 delattr(old, key)
283 delattr(old, key)
284 except (AttributeError, TypeError):
284 except (AttributeError, TypeError):
285 pass
285 pass
286 continue
286 continue
287
287
288 if update_generic(old_obj, new_obj): continue
288 if update_generic(old_obj, new_obj): continue
289
289
290 try:
290 try:
291 setattr(old, key, getattr(new, key))
291 setattr(old, key, getattr(new, key))
292 except (AttributeError, TypeError):
292 except (AttributeError, TypeError):
293 pass # skip non-writable attributes
293 pass # skip non-writable attributes
294
294
295
295
296 def update_property(old, new):
296 def update_property(old, new):
297 """Replace get/set/del functions of a property"""
297 """Replace get/set/del functions of a property"""
298 update_generic(old.fdel, new.fdel)
298 update_generic(old.fdel, new.fdel)
299 update_generic(old.fget, new.fget)
299 update_generic(old.fget, new.fget)
300 update_generic(old.fset, new.fset)
300 update_generic(old.fset, new.fset)
301
301
302
302
303 def isinstance2(a, b, typ):
303 def isinstance2(a, b, typ):
304 return isinstance(a, typ) and isinstance(b, typ)
304 return isinstance(a, typ) and isinstance(b, typ)
305
305
306
306
307 UPDATE_RULES = [
307 UPDATE_RULES = [
308 (lambda a, b: isinstance2(a, b, type),
308 (lambda a, b: isinstance2(a, b, type),
309 update_class),
309 update_class),
310 (lambda a, b: isinstance2(a, b, types.FunctionType),
310 (lambda a, b: isinstance2(a, b, types.FunctionType),
311 update_function),
311 update_function),
312 (lambda a, b: isinstance2(a, b, property),
312 (lambda a, b: isinstance2(a, b, property),
313 update_property),
313 update_property),
314 ]
314 ]
315
315
316
316
317 if PY3:
317 if PY3:
318 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
318 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
319 lambda a, b: update_function(a.__func__, b.__func__)),
319 lambda a, b: update_function(a.__func__, b.__func__)),
320 ])
320 ])
321 else:
321 else:
322 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.ClassType),
322 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.ClassType),
323 update_class),
323 update_class),
324 (lambda a, b: isinstance2(a, b, types.MethodType),
324 (lambda a, b: isinstance2(a, b, types.MethodType),
325 lambda a, b: update_function(a.im_func, b.im_func)),
325 lambda a, b: update_function(a.im_func, b.im_func)),
326 ])
326 ])
327
327
328
328
329 def update_generic(a, b):
329 def update_generic(a, b):
330 for type_check, update in UPDATE_RULES:
330 for type_check, update in UPDATE_RULES:
331 if type_check(a, b):
331 if type_check(a, b):
332 update(a, b)
332 update(a, b)
333 return True
333 return True
334 return False
334 return False
335
335
336
336
337 class StrongRef(object):
337 class StrongRef(object):
338 def __init__(self, obj):
338 def __init__(self, obj):
339 self.obj = obj
339 self.obj = obj
340 def __call__(self):
340 def __call__(self):
341 return self.obj
341 return self.obj
342
342
343
343
344 def superreload(module, reload=reload, old_objects={}):
344 def superreload(module, reload=reload, old_objects={}):
345 """Enhanced version of the builtin reload function.
345 """Enhanced version of the builtin reload function.
346
346
347 superreload remembers objects previously in the module, and
347 superreload remembers objects previously in the module, and
348
348
349 - upgrades the class dictionary of every old class in the module
349 - upgrades the class dictionary of every old class in the module
350 - upgrades the code object of every old function and method
350 - upgrades the code object of every old function and method
351 - clears the module's namespace before reloading
351 - clears the module's namespace before reloading
352
352
353 """
353 """
354
354
355 # collect old objects in the module
355 # collect old objects in the module
356 for name, obj in module.__dict__.items():
356 for name, obj in module.__dict__.items():
357 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
357 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
358 continue
358 continue
359 key = (module.__name__, name)
359 key = (module.__name__, name)
360 try:
360 try:
361 old_objects.setdefault(key, []).append(weakref.ref(obj))
361 old_objects.setdefault(key, []).append(weakref.ref(obj))
362 except TypeError:
362 except TypeError:
363 # weakref doesn't work for all types;
363 # weakref doesn't work for all types;
364 # create strong references for 'important' cases
364 # create strong references for 'important' cases
365 if not PY3 and isinstance(obj, types.ClassType):
365 if not PY3 and isinstance(obj, types.ClassType):
366 old_objects.setdefault(key, []).append(StrongRef(obj))
366 old_objects.setdefault(key, []).append(StrongRef(obj))
367
367
368 # reload module
368 # reload module
369 try:
369 try:
370 # clear namespace first from old cruft
370 # clear namespace first from old cruft
371 old_dict = module.__dict__.copy()
371 old_dict = module.__dict__.copy()
372 old_name = module.__name__
372 old_name = module.__name__
373 module.__dict__.clear()
373 module.__dict__.clear()
374 module.__dict__['__name__'] = old_name
374 module.__dict__['__name__'] = old_name
375 except (TypeError, AttributeError, KeyError):
375 except (TypeError, AttributeError, KeyError):
376 pass
376 pass
377
377
378 try:
378 try:
379 module = reload(module)
379 module = reload(module)
380 except:
380 except:
381 # restore module dictionary on failed reload
381 # restore module dictionary on failed reload
382 module.__dict__.update(old_dict)
382 module.__dict__.update(old_dict)
383 raise
383 raise
384
384
385 # iterate over all objects and update functions & classes
385 # iterate over all objects and update functions & classes
386 for name, new_obj in module.__dict__.items():
386 for name, new_obj in module.__dict__.items():
387 key = (module.__name__, name)
387 key = (module.__name__, name)
388 if key not in old_objects: continue
388 if key not in old_objects: continue
389
389
390 new_refs = []
390 new_refs = []
391 for old_ref in old_objects[key]:
391 for old_ref in old_objects[key]:
392 old_obj = old_ref()
392 old_obj = old_ref()
393 if old_obj is None: continue
393 if old_obj is None: continue
394 new_refs.append(old_ref)
394 new_refs.append(old_ref)
395 update_generic(old_obj, new_obj)
395 update_generic(old_obj, new_obj)
396
396
397 if new_refs:
397 if new_refs:
398 old_objects[key] = new_refs
398 old_objects[key] = new_refs
399 else:
399 else:
400 del old_objects[key]
400 del old_objects[key]
401
401
402 return module
402 return module
403
403
404 #------------------------------------------------------------------------------
404 #------------------------------------------------------------------------------
405 # IPython connectivity
405 # IPython connectivity
406 #------------------------------------------------------------------------------
406 #------------------------------------------------------------------------------
407
407
408 from IPython.core.hooks import TryNext
408 from IPython.core.hooks import TryNext
409 from IPython.core.magic import Magics, register_magics, line_magic
409 from IPython.core.magic import Magics, register_magics, line_magic
410 from IPython.core.plugin import Plugin
410 from IPython.core.plugin import Plugin
411
411
412 @register_magics
412 @register_magics
413 class AutoreloadMagics(Magics):
413 class AutoreloadMagics(Magics):
414 def __init__(self, *a, **kw):
414 def __init__(self, *a, **kw):
415 super(AutoreloadMagics, self).__init__(*a, **kw)
415 super(AutoreloadMagics, self).__init__(*a, **kw)
416 self._reloader = ModuleReloader()
416 self._reloader = ModuleReloader()
417 self._reloader.check_all = False
417 self._reloader.check_all = False
418
418
419 @line_magic
419 @line_magic
420 def autoreload(self, parameter_s=''):
420 def autoreload(self, parameter_s=''):
421 r"""%autoreload => Reload modules automatically
421 r"""%autoreload => Reload modules automatically
422
422
423 %autoreload
423 %autoreload
424 Reload all modules (except those excluded by %aimport) automatically
424 Reload all modules (except those excluded by %aimport) automatically
425 now.
425 now.
426
426
427 %autoreload 0
427 %autoreload 0
428 Disable automatic reloading.
428 Disable automatic reloading.
429
429
430 %autoreload 1
430 %autoreload 1
431 Reload all modules imported with %aimport every time before executing
431 Reload all modules imported with %aimport every time before executing
432 the Python code typed.
432 the Python code typed.
433
433
434 %autoreload 2
434 %autoreload 2
435 Reload all modules (except those excluded by %aimport) every time
435 Reload all modules (except those excluded by %aimport) every time
436 before executing the Python code typed.
436 before executing the Python code typed.
437
437
438 Reloading Python modules in a reliable way is in general
438 Reloading Python modules in a reliable way is in general
439 difficult, and unexpected things may occur. %autoreload tries to
439 difficult, and unexpected things may occur. %autoreload tries to
440 work around common pitfalls by replacing function code objects and
440 work around common pitfalls by replacing function code objects and
441 parts of classes previously in the module with new versions. This
441 parts of classes previously in the module with new versions. This
442 makes the following things to work:
442 makes the following things to work:
443
443
444 - Functions and classes imported via 'from xxx import foo' are upgraded
444 - Functions and classes imported via 'from xxx import foo' are upgraded
445 to new versions when 'xxx' is reloaded.
445 to new versions when 'xxx' is reloaded.
446
446
447 - Methods and properties of classes are upgraded on reload, so that
447 - Methods and properties of classes are upgraded on reload, so that
448 calling 'c.foo()' on an object 'c' created before the reload causes
448 calling 'c.foo()' on an object 'c' created before the reload causes
449 the new code for 'foo' to be executed.
449 the new code for 'foo' to be executed.
450
450
451 Some of the known remaining caveats are:
451 Some of the known remaining caveats are:
452
452
453 - Replacing code objects does not always succeed: changing a @property
453 - Replacing code objects does not always succeed: changing a @property
454 in a class to an ordinary method or a method to a member variable
454 in a class to an ordinary method or a method to a member variable
455 can cause problems (but in old objects only).
455 can cause problems (but in old objects only).
456
456
457 - Functions that are removed (eg. via monkey-patching) from a module
457 - Functions that are removed (eg. via monkey-patching) from a module
458 before it is reloaded are not upgraded.
458 before it is reloaded are not upgraded.
459
459
460 - C extension modules cannot be reloaded, and so cannot be
460 - C extension modules cannot be reloaded, and so cannot be
461 autoreloaded.
461 autoreloaded.
462
462
463 """
463 """
464 if parameter_s == '':
464 if parameter_s == '':
465 self._reloader.check(True)
465 self._reloader.check(True)
466 elif parameter_s == '0':
466 elif parameter_s == '0':
467 self._reloader.enabled = False
467 self._reloader.enabled = False
468 elif parameter_s == '1':
468 elif parameter_s == '1':
469 self._reloader.check_all = False
469 self._reloader.check_all = False
470 self._reloader.enabled = True
470 self._reloader.enabled = True
471 elif parameter_s == '2':
471 elif parameter_s == '2':
472 self._reloader.check_all = True
472 self._reloader.check_all = True
473 self._reloader.enabled = True
473 self._reloader.enabled = True
474
474
475 @line_magic
475 @line_magic
476 def aimport(self, parameter_s='', stream=None):
476 def aimport(self, parameter_s='', stream=None):
477 """%aimport => Import modules for automatic reloading.
477 """%aimport => Import modules for automatic reloading.
478
478
479 %aimport
479 %aimport
480 List modules to automatically import and not to import.
480 List modules to automatically import and not to import.
481
481
482 %aimport foo
482 %aimport foo
483 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
483 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
484
484
485 %aimport -foo
485 %aimport -foo
486 Mark module 'foo' to not be autoreloaded for %autoreload 1
486 Mark module 'foo' to not be autoreloaded for %autoreload 1
487
488 """
487 """
489
490 modname = parameter_s
488 modname = parameter_s
491 if not modname:
489 if not modname:
492 to_reload = self._reloader.modules.keys()
490 to_reload = self._reloader.modules.keys()
493 to_reload.sort()
491 to_reload.sort()
494 to_skip = self._reloader.skip_modules.keys()
492 to_skip = self._reloader.skip_modules.keys()
495 to_skip.sort()
493 to_skip.sort()
496 if stream is None:
494 if stream is None:
497 stream = sys.stdout
495 stream = sys.stdout
498 if self._reloader.check_all:
496 if self._reloader.check_all:
499 stream.write("Modules to reload:\nall-except-skipped\n")
497 stream.write("Modules to reload:\nall-except-skipped\n")
500 else:
498 else:
501 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
499 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
502 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
500 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
503 elif modname.startswith('-'):
501 elif modname.startswith('-'):
504 modname = modname[1:]
502 modname = modname[1:]
505 self._reloader.mark_module_skipped(modname)
503 self._reloader.mark_module_skipped(modname)
506 else:
504 else:
507 top_module, top_name = self._reloader.aimport_module(modname)
505 top_module, top_name = self._reloader.aimport_module(modname)
508
506
509 # Inject module to user namespace
507 # Inject module to user namespace
510 self.shell.push({top_name: top_module})
508 self.shell.push({top_name: top_module})
511
509
512 def pre_run_code_hook(self, ipself):
510 def pre_run_code_hook(self, ip):
513 if not self._reloader.enabled:
511 if not self._reloader.enabled:
514 raise TryNext
512 raise TryNext
515 try:
513 try:
516 self._reloader.check()
514 self._reloader.check()
517 except:
515 except:
518 pass
516 pass
519
517
520
518
521 class AutoreloadPlugin(Plugin):
519 class AutoreloadPlugin(Plugin):
522 def __init__(self, shell=None, config=None):
520 def __init__(self, shell=None, config=None):
523 super(AutoreloadPlugin, self).__init__(shell=shell, config=config)
521 super(AutoreloadPlugin, self).__init__(shell=shell, config=config)
524 self.auto_magics = AutoreloadMagics(shell)
522 self.auto_magics = AutoreloadMagics(shell)
525 shell.register_magics(self.auto_magics)
523 shell.register_magics(self.auto_magics)
526 shell.set_hook('pre_run_code_hook', self.auto_magics.pre_run_code_hook)
524 shell.set_hook('pre_run_code_hook', self.auto_magics.pre_run_code_hook)
527
525
528
526
529 _loaded = False
527 _loaded = False
530
528
531
529
532 def load_ipython_extension(ip):
530 def load_ipython_extension(ip):
533 """Load the extension in IPython."""
531 """Load the extension in IPython."""
534 global _loaded
532 global _loaded
535 if not _loaded:
533 if not _loaded:
536 plugin = AutoreloadPlugin(shell=ip, config=ip.config)
534 plugin = AutoreloadPlugin(shell=ip, config=ip.config)
537 ip.plugin_manager.register_plugin('autoreload', plugin)
535 ip.plugin_manager.register_plugin('autoreload', plugin)
538 _loaded = True
536 _loaded = True
@@ -1,300 +1,317 b''
1 """Tests for autoreload extension.
2 """
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012 IPython Development Team.
5 #
6 # Distributed under the terms of the Modified BSD License.
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
1 import os
15 import os
2 import sys
16 import sys
3 import tempfile
17 import tempfile
4 import shutil
18 import shutil
5 import random
19 import random
6 import time
20 import time
7 from StringIO import StringIO
21 from StringIO import StringIO
8
22
9 import nose.tools as nt
23 import nose.tools as nt
10 import IPython.testing.tools as tt
24 import IPython.testing.tools as tt
11
25
12 from IPython.extensions.autoreload import AutoreloadPlugin
26 from IPython.extensions.autoreload import AutoreloadPlugin
13 from IPython.core.hooks import TryNext
27 from IPython.core.hooks import TryNext
14
28
15 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
16 # Test fixture
30 # Test fixture
17 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
18
32
33 noop = lambda *a, **kw: None
34
19 class FakeShell(object):
35 class FakeShell(object):
20 def __init__(self):
36 def __init__(self):
21 self.ns = {}
37 self.ns = {}
22 self.reloader = AutoreloadPlugin(shell=get_ipython())
38 self.reloader = AutoreloadPlugin(shell=self)
39
40 register_magics = set_hook = noop
23
41
24 def run_code(self, code):
42 def run_code(self, code):
25 try:
43 try:
26 self.reloader.auto_magics.pre_run_code_hook(self)
44 self.reloader.auto_magics.pre_run_code_hook(self)
27 except TryNext:
45 except TryNext:
28 pass
46 pass
29 exec code in self.ns
47 exec code in self.ns
30
48
31 def push(self, items):
49 def push(self, items):
32 self.ns.update(items)
50 self.ns.update(items)
33
51
34 def magic_autoreload(self, parameter):
52 def magic_autoreload(self, parameter):
35 self.reloader.auto_magics.autoreload(parameter)
53 self.reloader.auto_magics.autoreload(parameter)
36
54
37 def magic_aimport(self, parameter, stream=None):
55 def magic_aimport(self, parameter, stream=None):
38 self.reloader.auto_magics.aimport(parameter, stream=stream)
56 self.reloader.auto_magics.aimport(parameter, stream=stream)
39
57
40
58
41 class Fixture(object):
59 class Fixture(object):
42 """Fixture for creating test module files"""
60 """Fixture for creating test module files"""
43
61
44 test_dir = None
62 test_dir = None
45 old_sys_path = None
63 old_sys_path = None
46 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
64 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
47
65
48 def setUp(self):
66 def setUp(self):
49 self.test_dir = tempfile.mkdtemp()
67 self.test_dir = tempfile.mkdtemp()
50 self.old_sys_path = list(sys.path)
68 self.old_sys_path = list(sys.path)
51 sys.path.insert(0, self.test_dir)
69 sys.path.insert(0, self.test_dir)
52 self.shell = FakeShell()
70 self.shell = FakeShell()
53
71
54 def tearDown(self):
72 def tearDown(self):
55 shutil.rmtree(self.test_dir)
73 shutil.rmtree(self.test_dir)
56 sys.path = self.old_sys_path
74 sys.path = self.old_sys_path
57 self.shell.reloader.enabled = False
75 self.shell.reloader.enabled = False
58
76
59 self.test_dir = None
77 self.test_dir = None
60 self.old_sys_path = None
78 self.old_sys_path = None
61 self.shell = None
79 self.shell = None
62
80
63 def get_module(self):
81 def get_module(self):
64 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
82 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
65 if module_name in sys.modules:
83 if module_name in sys.modules:
66 del sys.modules[module_name]
84 del sys.modules[module_name]
67 file_name = os.path.join(self.test_dir, module_name + ".py")
85 file_name = os.path.join(self.test_dir, module_name + ".py")
68 return module_name, file_name
86 return module_name, file_name
69
87
70 def write_file(self, filename, content):
88 def write_file(self, filename, content):
71 """
89 """
72 Write a file, and force a timestamp difference of at least one second
90 Write a file, and force a timestamp difference of at least one second
73
91
74 Notes
92 Notes
75 -----
93 -----
76 Python's .pyc files record the timestamp of their compilation
94 Python's .pyc files record the timestamp of their compilation
77 with a time resolution of one second.
95 with a time resolution of one second.
78
96
79 Therefore, we need to force a timestamp difference between .py
97 Therefore, we need to force a timestamp difference between .py
80 and .pyc, without having the .py file be timestamped in the
98 and .pyc, without having the .py file be timestamped in the
81 future, and without changing the timestamp of the .pyc file
99 future, and without changing the timestamp of the .pyc file
82 (because that is stored in the file). The only reliable way
100 (because that is stored in the file). The only reliable way
83 to achieve this seems to be to sleep.
101 to achieve this seems to be to sleep.
84
85 """
102 """
86
103
87 # Sleep one second + eps
104 # Sleep one second + eps
88 time.sleep(1.05)
105 time.sleep(1.05)
89
106
90 # Write
107 # Write
91 f = open(filename, 'w')
108 f = open(filename, 'w')
92 try:
109 try:
93 f.write(content)
110 f.write(content)
94 finally:
111 finally:
95 f.close()
112 f.close()
96
113
97 def new_module(self, code):
114 def new_module(self, code):
98 mod_name, mod_fn = self.get_module()
115 mod_name, mod_fn = self.get_module()
99 f = open(mod_fn, 'w')
116 f = open(mod_fn, 'w')
100 try:
117 try:
101 f.write(code)
118 f.write(code)
102 finally:
119 finally:
103 f.close()
120 f.close()
104 return mod_name, mod_fn
121 return mod_name, mod_fn
105
122
106 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
107 # Test automatic reloading
124 # Test automatic reloading
108 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
109
126
110 class TestAutoreload(Fixture):
127 class TestAutoreload(Fixture):
111 def _check_smoketest(self, use_aimport=True):
128 def _check_smoketest(self, use_aimport=True):
112 """
129 """
113 Functional test for the automatic reloader using either
130 Functional test for the automatic reloader using either
114 '%autoreload 1' or '%autoreload 2'
131 '%autoreload 1' or '%autoreload 2'
115 """
132 """
116
133
117 mod_name, mod_fn = self.new_module("""
134 mod_name, mod_fn = self.new_module("""
118 x = 9
135 x = 9
119
136
120 z = 123 # this item will be deleted
137 z = 123 # this item will be deleted
121
138
122 def foo(y):
139 def foo(y):
123 return y + 3
140 return y + 3
124
141
125 class Baz(object):
142 class Baz(object):
126 def __init__(self, x):
143 def __init__(self, x):
127 self.x = x
144 self.x = x
128 def bar(self, y):
145 def bar(self, y):
129 return self.x + y
146 return self.x + y
130 @property
147 @property
131 def quux(self):
148 def quux(self):
132 return 42
149 return 42
133 def zzz(self):
150 def zzz(self):
134 '''This method will be deleted below'''
151 '''This method will be deleted below'''
135 return 99
152 return 99
136
153
137 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
154 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
138 def foo(self):
155 def foo(self):
139 return 1
156 return 1
140 """)
157 """)
141
158
142 #
159 #
143 # Import module, and mark for reloading
160 # Import module, and mark for reloading
144 #
161 #
145 if use_aimport:
162 if use_aimport:
146 self.shell.magic_autoreload("1")
163 self.shell.magic_autoreload("1")
147 self.shell.magic_aimport(mod_name)
164 self.shell.magic_aimport(mod_name)
148 stream = StringIO()
165 stream = StringIO()
149 self.shell.magic_aimport("", stream=stream)
166 self.shell.magic_aimport("", stream=stream)
150 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
167 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
151 stream.getvalue())
168 stream.getvalue())
152
169
153 nt.assert_raises(
170 nt.assert_raises(
154 ImportError,
171 ImportError,
155 self.shell.magic_aimport, "tmpmod_as318989e89ds")
172 self.shell.magic_aimport, "tmpmod_as318989e89ds")
156 else:
173 else:
157 self.shell.magic_autoreload("2")
174 self.shell.magic_autoreload("2")
158 self.shell.run_code("import %s" % mod_name)
175 self.shell.run_code("import %s" % mod_name)
159 stream = StringIO()
176 stream = StringIO()
160 self.shell.magic_aimport("", stream=stream)
177 self.shell.magic_aimport("", stream=stream)
161 nt.assert_true("Modules to reload:\nall-except-skipped" in
178 nt.assert_true("Modules to reload:\nall-except-skipped" in
162 stream.getvalue())
179 stream.getvalue())
163 nt.assert_in(mod_name, self.shell.ns)
180 nt.assert_in(mod_name, self.shell.ns)
164
181
165 mod = sys.modules[mod_name]
182 mod = sys.modules[mod_name]
166
183
167 #
184 #
168 # Test module contents
185 # Test module contents
169 #
186 #
170 old_foo = mod.foo
187 old_foo = mod.foo
171 old_obj = mod.Baz(9)
188 old_obj = mod.Baz(9)
172 old_obj2 = mod.Bar()
189 old_obj2 = mod.Bar()
173
190
174 def check_module_contents():
191 def check_module_contents():
175 nt.assert_equal(mod.x, 9)
192 nt.assert_equal(mod.x, 9)
176 nt.assert_equal(mod.z, 123)
193 nt.assert_equal(mod.z, 123)
177
194
178 nt.assert_equal(old_foo(0), 3)
195 nt.assert_equal(old_foo(0), 3)
179 nt.assert_equal(mod.foo(0), 3)
196 nt.assert_equal(mod.foo(0), 3)
180
197
181 obj = mod.Baz(9)
198 obj = mod.Baz(9)
182 nt.assert_equal(old_obj.bar(1), 10)
199 nt.assert_equal(old_obj.bar(1), 10)
183 nt.assert_equal(obj.bar(1), 10)
200 nt.assert_equal(obj.bar(1), 10)
184 nt.assert_equal(obj.quux, 42)
201 nt.assert_equal(obj.quux, 42)
185 nt.assert_equal(obj.zzz(), 99)
202 nt.assert_equal(obj.zzz(), 99)
186
203
187 obj2 = mod.Bar()
204 obj2 = mod.Bar()
188 nt.assert_equal(old_obj2.foo(), 1)
205 nt.assert_equal(old_obj2.foo(), 1)
189 nt.assert_equal(obj2.foo(), 1)
206 nt.assert_equal(obj2.foo(), 1)
190
207
191 check_module_contents()
208 check_module_contents()
192
209
193 #
210 #
194 # Simulate a failed reload: no reload should occur and exactly
211 # Simulate a failed reload: no reload should occur and exactly
195 # one error message should be printed
212 # one error message should be printed
196 #
213 #
197 self.write_file(mod_fn, """
214 self.write_file(mod_fn, """
198 a syntax error
215 a syntax error
199 """)
216 """)
200
217
201 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
218 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
202 self.shell.run_code("pass") # trigger reload
219 self.shell.run_code("pass") # trigger reload
203 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
220 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
204 self.shell.run_code("pass") # trigger another reload
221 self.shell.run_code("pass") # trigger another reload
205 check_module_contents()
222 check_module_contents()
206
223
207 #
224 #
208 # Rewrite module (this time reload should succeed)
225 # Rewrite module (this time reload should succeed)
209 #
226 #
210 self.write_file(mod_fn, """
227 self.write_file(mod_fn, """
211 x = 10
228 x = 10
212
229
213 def foo(y):
230 def foo(y):
214 return y + 4
231 return y + 4
215
232
216 class Baz(object):
233 class Baz(object):
217 def __init__(self, x):
234 def __init__(self, x):
218 self.x = x
235 self.x = x
219 def bar(self, y):
236 def bar(self, y):
220 return self.x + y + 1
237 return self.x + y + 1
221 @property
238 @property
222 def quux(self):
239 def quux(self):
223 return 43
240 return 43
224
241
225 class Bar: # old-style class
242 class Bar: # old-style class
226 def foo(self):
243 def foo(self):
227 return 2
244 return 2
228 """)
245 """)
229
246
230 def check_module_contents():
247 def check_module_contents():
231 nt.assert_equal(mod.x, 10)
248 nt.assert_equal(mod.x, 10)
232 nt.assert_false(hasattr(mod, 'z'))
249 nt.assert_false(hasattr(mod, 'z'))
233
250
234 nt.assert_equal(old_foo(0), 4) # superreload magic!
251 nt.assert_equal(old_foo(0), 4) # superreload magic!
235 nt.assert_equal(mod.foo(0), 4)
252 nt.assert_equal(mod.foo(0), 4)
236
253
237 obj = mod.Baz(9)
254 obj = mod.Baz(9)
238 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
255 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
239 nt.assert_equal(obj.bar(1), 11)
256 nt.assert_equal(obj.bar(1), 11)
240
257
241 nt.assert_equal(old_obj.quux, 43)
258 nt.assert_equal(old_obj.quux, 43)
242 nt.assert_equal(obj.quux, 43)
259 nt.assert_equal(obj.quux, 43)
243
260
244 nt.assert_false(hasattr(old_obj, 'zzz'))
261 nt.assert_false(hasattr(old_obj, 'zzz'))
245 nt.assert_false(hasattr(obj, 'zzz'))
262 nt.assert_false(hasattr(obj, 'zzz'))
246
263
247 obj2 = mod.Bar()
264 obj2 = mod.Bar()
248 nt.assert_equal(old_obj2.foo(), 2)
265 nt.assert_equal(old_obj2.foo(), 2)
249 nt.assert_equal(obj2.foo(), 2)
266 nt.assert_equal(obj2.foo(), 2)
250
267
251 self.shell.run_code("pass") # trigger reload
268 self.shell.run_code("pass") # trigger reload
252 check_module_contents()
269 check_module_contents()
253
270
254 #
271 #
255 # Another failure case: deleted file (shouldn't reload)
272 # Another failure case: deleted file (shouldn't reload)
256 #
273 #
257 os.unlink(mod_fn)
274 os.unlink(mod_fn)
258
275
259 self.shell.run_code("pass") # trigger reload
276 self.shell.run_code("pass") # trigger reload
260 check_module_contents()
277 check_module_contents()
261
278
262 #
279 #
263 # Disable autoreload and rewrite module: no reload should occur
280 # Disable autoreload and rewrite module: no reload should occur
264 #
281 #
265 if use_aimport:
282 if use_aimport:
266 self.shell.magic_aimport("-" + mod_name)
283 self.shell.magic_aimport("-" + mod_name)
267 stream = StringIO()
284 stream = StringIO()
268 self.shell.magic_aimport("", stream=stream)
285 self.shell.magic_aimport("", stream=stream)
269 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
286 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
270 stream.getvalue())
287 stream.getvalue())
271
288
272 # This should succeed, although no such module exists
289 # This should succeed, although no such module exists
273 self.shell.magic_aimport("-tmpmod_as318989e89ds")
290 self.shell.magic_aimport("-tmpmod_as318989e89ds")
274 else:
291 else:
275 self.shell.magic_autoreload("0")
292 self.shell.magic_autoreload("0")
276
293
277 self.write_file(mod_fn, """
294 self.write_file(mod_fn, """
278 x = -99
295 x = -99
279 """)
296 """)
280
297
281 self.shell.run_code("pass") # trigger reload
298 self.shell.run_code("pass") # trigger reload
282 self.shell.run_code("pass")
299 self.shell.run_code("pass")
283 check_module_contents()
300 check_module_contents()
284
301
285 #
302 #
286 # Re-enable autoreload: reload should now occur
303 # Re-enable autoreload: reload should now occur
287 #
304 #
288 if use_aimport:
305 if use_aimport:
289 self.shell.magic_aimport(mod_name)
306 self.shell.magic_aimport(mod_name)
290 else:
307 else:
291 self.shell.magic_autoreload("")
308 self.shell.magic_autoreload("")
292
309
293 self.shell.run_code("pass") # trigger reload
310 self.shell.run_code("pass") # trigger reload
294 nt.assert_equal(mod.x, -99)
311 nt.assert_equal(mod.x, -99)
295
312
296 def test_smoketest_aimport(self):
313 def test_smoketest_aimport(self):
297 self._check_smoketest(use_aimport=True)
314 self._check_smoketest(use_aimport=True)
298
315
299 def test_smoketest_autoreload(self):
316 def test_smoketest_autoreload(self):
300 self._check_smoketest(use_aimport=False)
317 self._check_smoketest(use_aimport=False)
General Comments 0
You need to be logged in to leave comments. Login now