##// END OF EJS Templates
Merging -r 1196 from lp:ipython....
Brian Granger -
r2150:b44b6920 merge
parent child Browse files
Show More
@@ -1,296 +1,295 b''
1 """Tests for various magic functions.
1 """Tests for various magic functions.
2
2
3 Needs to be run by nose (to make ipython session available).
3 Needs to be run by nose (to make ipython session available).
4 """
4 """
5
5
6 import os
6 import os
7 import sys
7 import sys
8 import tempfile
8 import tempfile
9 import types
9 import types
10
10
11 import nose.tools as nt
11 import nose.tools as nt
12
12
13 from IPython.utils.platutils import find_cmd, get_long_path_name
13 from IPython.utils.platutils import find_cmd, get_long_path_name
14 from IPython.testing import decorators as dec
14 from IPython.testing import decorators as dec
15 from IPython.testing import tools as tt
15 from IPython.testing import tools as tt
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Test functions begin
18 # Test functions begin
19
19
20 def test_rehashx():
20 def test_rehashx():
21 # clear up everything
21 # clear up everything
22 _ip.IP.alias_table.clear()
22 _ip.IP.alias_table.clear()
23 del _ip.db['syscmdlist']
23 del _ip.db['syscmdlist']
24
24
25 _ip.magic('rehashx')
25 _ip.magic('rehashx')
26 # Practically ALL ipython development systems will have more than 10 aliases
26 # Practically ALL ipython development systems will have more than 10 aliases
27
27
28 yield (nt.assert_true, len(_ip.IP.alias_table) > 10)
28 yield (nt.assert_true, len(_ip.IP.alias_table) > 10)
29 for key, val in _ip.IP.alias_table.items():
29 for key, val in _ip.IP.alias_table.items():
30 # we must strip dots from alias names
30 # we must strip dots from alias names
31 nt.assert_true('.' not in key)
31 nt.assert_true('.' not in key)
32
32
33 # rehashx must fill up syscmdlist
33 # rehashx must fill up syscmdlist
34 scoms = _ip.db['syscmdlist']
34 scoms = _ip.db['syscmdlist']
35 yield (nt.assert_true, len(scoms) > 10)
35 yield (nt.assert_true, len(scoms) > 10)
36
36
37
37
38 def doctest_hist_f():
38 def doctest_hist_f():
39 """Test %hist -f with temporary filename.
39 """Test %hist -f with temporary filename.
40
40
41 In [9]: import tempfile
41 In [9]: import tempfile
42
42
43 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
43 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
44
44
45 In [11]: %hist -n -f $tfile 3
45 In [11]: %hist -n -f $tfile 3
46
47 """
46 """
48
47
49
48
50 def doctest_hist_r():
49 def doctest_hist_r():
51 """Test %hist -r
50 """Test %hist -r
52
51
53 XXX - This test is not recording the output correctly. Not sure why...
52 XXX - This test is not recording the output correctly. Not sure why...
54
53
55 In [20]: 'hist' in _ip.IP.lsmagic()
54 In [20]: 'hist' in _ip.IP.lsmagic()
56 Out[20]: True
55 Out[20]: True
57
56
58 In [6]: x=1
57 In [6]: x=1
59
58
60 In [7]: %hist -n -r 2
59 In [7]: %hist -n -r 2
61 x=1 # random
60 x=1 # random
62 hist -n -r 2 # random
61 hist -n -r 2 # random
63 """
62 """
64
63
65 # This test is known to fail on win32.
64 # This test is known to fail on win32.
66 # See ticket https://bugs.launchpad.net/bugs/366334
65 # See ticket https://bugs.launchpad.net/bugs/366334
67 def test_obj_del():
66 def test_obj_del():
68 """Test that object's __del__ methods are called on exit."""
67 """Test that object's __del__ methods are called on exit."""
69 test_dir = os.path.dirname(__file__)
68 test_dir = os.path.dirname(__file__)
70 del_file = os.path.join(test_dir,'obj_del.py')
69 del_file = os.path.join(test_dir,'obj_del.py')
71 ipython_cmd = find_cmd('ipython')
70 ipython_cmd = find_cmd('ipython')
72 out = _ip.IP.getoutput('%s %s' % (ipython_cmd, del_file))
71 out = _ip.IP.getoutput('%s %s' % (ipython_cmd, del_file))
73 nt.assert_equals(out,'obj_del.py: object A deleted')
72 nt.assert_equals(out,'obj_del.py: object A deleted')
74
73
75
74
76 def test_shist():
75 def test_shist():
77 # Simple tests of ShadowHist class - test generator.
76 # Simple tests of ShadowHist class - test generator.
78 import os, shutil, tempfile
77 import os, shutil, tempfile
79
78
80 from IPython.extensions import pickleshare
79 from IPython.extensions import pickleshare
81 from IPython.core.history import ShadowHist
80 from IPython.core.history import ShadowHist
82
81
83 tfile = tempfile.mktemp('','tmp-ipython-')
82 tfile = tempfile.mktemp('','tmp-ipython-')
84
83
85 db = pickleshare.PickleShareDB(tfile)
84 db = pickleshare.PickleShareDB(tfile)
86 s = ShadowHist(db)
85 s = ShadowHist(db)
87 s.add('hello')
86 s.add('hello')
88 s.add('world')
87 s.add('world')
89 s.add('hello')
88 s.add('hello')
90 s.add('hello')
89 s.add('hello')
91 s.add('karhu')
90 s.add('karhu')
92
91
93 yield nt.assert_equals,s.all(),[(1, 'hello'), (2, 'world'), (3, 'karhu')]
92 yield nt.assert_equals,s.all(),[(1, 'hello'), (2, 'world'), (3, 'karhu')]
94
93
95 yield nt.assert_equal,s.get(2),'world'
94 yield nt.assert_equal,s.get(2),'world'
96
95
97 shutil.rmtree(tfile)
96 shutil.rmtree(tfile)
98
97
99 @dec.skipif_not_numpy
98 @dec.skipif_not_numpy
100 def test_numpy_clear_array_undec():
99 def test_numpy_clear_array_undec():
101 from IPython.extensions import clearcmd
100 from IPython.extensions import clearcmd
102
101
103 _ip.ex('import numpy as np')
102 _ip.ex('import numpy as np')
104 _ip.ex('a = np.empty(2)')
103 _ip.ex('a = np.empty(2)')
105 yield (nt.assert_true, 'a' in _ip.user_ns)
104 yield (nt.assert_true, 'a' in _ip.user_ns)
106 _ip.magic('clear array')
105 _ip.magic('clear array')
107 yield (nt.assert_false, 'a' in _ip.user_ns)
106 yield (nt.assert_false, 'a' in _ip.user_ns)
108
107
109
108
110 @dec.skip()
109 @dec.skip()
111 def test_fail_dec(*a,**k):
110 def test_fail_dec(*a,**k):
112 yield nt.assert_true, False
111 yield nt.assert_true, False
113
112
114 @dec.skip('This one shouldn not run')
113 @dec.skip('This one shouldn not run')
115 def test_fail_dec2(*a,**k):
114 def test_fail_dec2(*a,**k):
116 yield nt.assert_true, False
115 yield nt.assert_true, False
117
116
118 @dec.skipknownfailure
117 @dec.skipknownfailure
119 def test_fail_dec3(*a,**k):
118 def test_fail_dec3(*a,**k):
120 yield nt.assert_true, False
119 yield nt.assert_true, False
121
120
122
121
123 def doctest_refbug():
122 def doctest_refbug():
124 """Very nasty problem with references held by multiple runs of a script.
123 """Very nasty problem with references held by multiple runs of a script.
125 See: https://bugs.launchpad.net/ipython/+bug/269966
124 See: https://bugs.launchpad.net/ipython/+bug/269966
126
125
127 In [1]: _ip.IP.clear_main_mod_cache()
126 In [1]: _ip.IP.clear_main_mod_cache()
128
127
129 In [2]: run refbug
128 In [2]: run refbug
130
129
131 In [3]: call_f()
130 In [3]: call_f()
132 lowercased: hello
131 lowercased: hello
133
132
134 In [4]: run refbug
133 In [4]: run refbug
135
134
136 In [5]: call_f()
135 In [5]: call_f()
137 lowercased: hello
136 lowercased: hello
138 lowercased: hello
137 lowercased: hello
139 """
138 """
140
139
141 #-----------------------------------------------------------------------------
140 #-----------------------------------------------------------------------------
142 # Tests for %run
141 # Tests for %run
143 #-----------------------------------------------------------------------------
142 #-----------------------------------------------------------------------------
144
143
145 # %run is critical enough that it's a good idea to have a solid collection of
144 # %run is critical enough that it's a good idea to have a solid collection of
146 # tests for it, some as doctests and some as normal tests.
145 # tests for it, some as doctests and some as normal tests.
147
146
148 def doctest_run_ns():
147 def doctest_run_ns():
149 """Classes declared %run scripts must be instantiable afterwards.
148 """Classes declared %run scripts must be instantiable afterwards.
150
149
151 In [11]: run tclass foo
150 In [11]: run tclass foo
152
151
153 In [12]: isinstance(f(),foo)
152 In [12]: isinstance(f(),foo)
154 Out[12]: True
153 Out[12]: True
155 """
154 """
156
155
157
156
158 def doctest_run_ns2():
157 def doctest_run_ns2():
159 """Classes declared %run scripts must be instantiable afterwards.
158 """Classes declared %run scripts must be instantiable afterwards.
160
159
161 In [4]: run tclass C-first_pass
160 In [4]: run tclass C-first_pass
162
161
163 In [5]: run tclass C-second_pass
162 In [5]: run tclass C-second_pass
164 tclass.py: deleting object: C-first_pass
163 tclass.py: deleting object: C-first_pass
165 """
164 """
166
165
167 def doctest_run_builtins():
166 def doctest_run_builtins():
168 """Check that %run doesn't damage __builtins__ via a doctest.
167 """Check that %run doesn't damage __builtins__ via a doctest.
169
168
170 This is similar to the test_run_builtins, but I want *both* forms of the
169 This is similar to the test_run_builtins, but I want *both* forms of the
171 test to catch any possible glitches in our testing machinery, since that
170 test to catch any possible glitches in our testing machinery, since that
172 modifies %run somewhat. So for this, we have both a normal test (below)
171 modifies %run somewhat. So for this, we have both a normal test (below)
173 and a doctest (this one).
172 and a doctest (this one).
174
173
175 In [1]: import tempfile
174 In [1]: import tempfile
176
175
177 In [2]: bid1 = id(__builtins__)
176 In [2]: bid1 = id(__builtins__)
178
177
179 In [3]: fname = tempfile.mkstemp()[1]
178 In [3]: fname = tempfile.mkstemp()[1]
180
179
181 In [3]: f = open(fname,'w')
180 In [3]: f = open(fname,'w')
182
181
183 In [4]: f.write('pass\\n')
182 In [4]: f.write('pass\\n')
184
183
185 In [5]: f.flush()
184 In [5]: f.flush()
186
185
187 In [6]: print type(__builtins__)
186 In [6]: print type(__builtins__)
188 <type 'module'>
187 <type 'module'>
189
188
190 In [7]: %run "$fname"
189 In [7]: %run "$fname"
191
190
192 In [7]: f.close()
191 In [7]: f.close()
193
192
194 In [8]: bid2 = id(__builtins__)
193 In [8]: bid2 = id(__builtins__)
195
194
196 In [9]: print type(__builtins__)
195 In [9]: print type(__builtins__)
197 <type 'module'>
196 <type 'module'>
198
197
199 In [10]: bid1 == bid2
198 In [10]: bid1 == bid2
200 Out[10]: True
199 Out[10]: True
201
200
202 In [12]: try:
201 In [12]: try:
203 ....: os.unlink(fname)
202 ....: os.unlink(fname)
204 ....: except:
203 ....: except:
205 ....: pass
204 ....: pass
206 ....:
205 ....:
207 """
206 """
208
207
209 # For some tests, it will be handy to organize them in a class with a common
208 # For some tests, it will be handy to organize them in a class with a common
210 # setup that makes a temp file
209 # setup that makes a temp file
211
210
212 class TestMagicRun(object):
211 class TestMagicRun(object):
213
212
214 def setup(self):
213 def setup(self):
215 """Make a valid python temp file."""
214 """Make a valid python temp file."""
216 fname = tempfile.mkstemp()[1]
215 fname = tempfile.mkstemp()[1]
217 f = open(fname,'w')
216 f = open(fname,'w')
218 f.write('pass\n')
217 f.write('pass\n')
219 f.flush()
218 f.flush()
220 self.tmpfile = f
219 self.tmpfile = f
221 self.fname = fname
220 self.fname = fname
222
221
223 def run_tmpfile(self):
222 def run_tmpfile(self):
224 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
223 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
225 # See below and ticket https://bugs.launchpad.net/bugs/366353
224 # See below and ticket https://bugs.launchpad.net/bugs/366353
226 _ip.magic('run "%s"' % self.fname)
225 _ip.magic('run "%s"' % self.fname)
227
226
228 def test_builtins_id(self):
227 def test_builtins_id(self):
229 """Check that %run doesn't damage __builtins__ """
228 """Check that %run doesn't damage __builtins__ """
230
229
231 # Test that the id of __builtins__ is not modified by %run
230 # Test that the id of __builtins__ is not modified by %run
232 bid1 = id(_ip.user_ns['__builtins__'])
231 bid1 = id(_ip.user_ns['__builtins__'])
233 self.run_tmpfile()
232 self.run_tmpfile()
234 bid2 = id(_ip.user_ns['__builtins__'])
233 bid2 = id(_ip.user_ns['__builtins__'])
235 tt.assert_equals(bid1, bid2)
234 tt.assert_equals(bid1, bid2)
236
235
237 def test_builtins_type(self):
236 def test_builtins_type(self):
238 """Check that the type of __builtins__ doesn't change with %run.
237 """Check that the type of __builtins__ doesn't change with %run.
239
238
240 However, the above could pass if __builtins__ was already modified to
239 However, the above could pass if __builtins__ was already modified to
241 be a dict (it should be a module) by a previous use of %run. So we
240 be a dict (it should be a module) by a previous use of %run. So we
242 also check explicitly that it really is a module:
241 also check explicitly that it really is a module:
243 """
242 """
244 self.run_tmpfile()
243 self.run_tmpfile()
245 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
244 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
246
245
247 def test_prompts(self):
246 def test_prompts(self):
248 """Test that prompts correctly generate after %run"""
247 """Test that prompts correctly generate after %run"""
249 self.run_tmpfile()
248 self.run_tmpfile()
250 p2 = str(_ip.IP.outputcache.prompt2).strip()
249 p2 = str(_ip.IP.outputcache.prompt2).strip()
251 nt.assert_equals(p2[:3], '...')
250 nt.assert_equals(p2[:3], '...')
252
251
253 def teardown(self):
252 def teardown(self):
254 self.tmpfile.close()
253 self.tmpfile.close()
255 try:
254 try:
256 os.unlink(self.fname)
255 os.unlink(self.fname)
257 except:
256 except:
258 # On Windows, even though we close the file, we still can't delete
257 # On Windows, even though we close the file, we still can't delete
259 # it. I have no clue why
258 # it. I have no clue why
260 pass
259 pass
261
260
262 # Multiple tests for clipboard pasting
261 # Multiple tests for clipboard pasting
263 def test_paste():
262 def test_paste():
264
263
265 def paste(txt):
264 def paste(txt):
266 hooks.clipboard_get = lambda : txt
265 hooks.clipboard_get = lambda : txt
267 _ip.magic('paste')
266 _ip.magic('paste')
268
267
269 # Inject fake clipboard hook but save original so we can restore it later
268 # Inject fake clipboard hook but save original so we can restore it later
270 hooks = _ip.IP.hooks
269 hooks = _ip.IP.hooks
271 user_ns = _ip.user_ns
270 user_ns = _ip.user_ns
272 original_clip = hooks.clipboard_get
271 original_clip = hooks.clipboard_get
273
272
274 try:
273 try:
275 # Run tests with fake clipboard function
274 # Run tests with fake clipboard function
276 user_ns.pop('x', None)
275 user_ns.pop('x', None)
277 paste('x=1')
276 paste('x=1')
278 yield (nt.assert_equal, user_ns['x'], 1)
277 yield (nt.assert_equal, user_ns['x'], 1)
279
278
280 user_ns.pop('x', None)
279 user_ns.pop('x', None)
281 paste('>>> x=2')
280 paste('>>> x=2')
282 yield (nt.assert_equal, user_ns['x'], 2)
281 yield (nt.assert_equal, user_ns['x'], 2)
283
282
284 paste("""
283 paste("""
285 >>> x = [1,2,3]
284 >>> x = [1,2,3]
286 >>> y = []
285 >>> y = []
287 >>> for i in x:
286 >>> for i in x:
288 ... y.append(i**2)
287 ... y.append(i**2)
289 ...
288 ...
290 """)
289 """)
291 yield (nt.assert_equal, user_ns['x'], [1,2,3])
290 yield (nt.assert_equal, user_ns['x'], [1,2,3])
292 yield (nt.assert_equal, user_ns['y'], [1,4,9])
291 yield (nt.assert_equal, user_ns['y'], [1,4,9])
293
292
294 finally:
293 finally:
295 # Restore original hook
294 # Restore original hook
296 hooks.clipboard_get = original_clip
295 hooks.clipboard_get = original_clip
@@ -1,282 +1,285 b''
1 """Twisted shell support.
1 """Twisted shell support.
2
2
3 XXX - This module is missing proper docs.
3 XXX - This module is missing proper docs.
4 """
4 """
5 # Tell nose to skip this module
6 __test__ = {}
7
5 import sys
8 import sys
6
9
7 from twisted.internet import reactor, threads
10 from twisted.internet import reactor, threads
8
11
9 from IPython.core.ipmaker import make_IPython
12 from IPython.core.ipmaker import make_IPython
10 from IPython.core.iplib import InteractiveShell
13 from IPython.core.iplib import InteractiveShell
11 from IPython.utils.ipstruct import Struct
14 from IPython.utils.ipstruct import Struct
12 import Queue,thread,threading,signal
15 import Queue,thread,threading,signal
13 from signal import signal, SIGINT
16 from signal import signal, SIGINT
14 from IPython.utils.genutils import Term,warn,error,flag_calls, ask_yes_no
17 from IPython.utils.genutils import Term,warn,error,flag_calls, ask_yes_no
15 from IPython.core import shellglobals
18 from IPython.core import shellglobals
16
19
17 def install_gtk2():
20 def install_gtk2():
18 """ Install gtk2 reactor, needs to be called bef """
21 """ Install gtk2 reactor, needs to be called bef """
19 from twisted.internet import gtk2reactor
22 from twisted.internet import gtk2reactor
20 gtk2reactor.install()
23 gtk2reactor.install()
21
24
22
25
23 def hijack_reactor():
26 def hijack_reactor():
24 """Modifies Twisted's reactor with a dummy so user code does
27 """Modifies Twisted's reactor with a dummy so user code does
25 not block IPython. This function returns the original
28 not block IPython. This function returns the original
26 'twisted.internet.reactor' that has been hijacked.
29 'twisted.internet.reactor' that has been hijacked.
27
30
28 NOTE: Make sure you call this *AFTER* you've installed
31 NOTE: Make sure you call this *AFTER* you've installed
29 the reactor of your choice.
32 the reactor of your choice.
30 """
33 """
31 from twisted import internet
34 from twisted import internet
32 orig_reactor = internet.reactor
35 orig_reactor = internet.reactor
33
36
34 class DummyReactor(object):
37 class DummyReactor(object):
35 def run(self):
38 def run(self):
36 pass
39 pass
37 def __getattr__(self, name):
40 def __getattr__(self, name):
38 return getattr(orig_reactor, name)
41 return getattr(orig_reactor, name)
39 def __setattr__(self, name, value):
42 def __setattr__(self, name, value):
40 return setattr(orig_reactor, name, value)
43 return setattr(orig_reactor, name, value)
41
44
42 internet.reactor = DummyReactor()
45 internet.reactor = DummyReactor()
43 return orig_reactor
46 return orig_reactor
44
47
45 class TwistedInteractiveShell(InteractiveShell):
48 class TwistedInteractiveShell(InteractiveShell):
46 """Simple multi-threaded shell."""
49 """Simple multi-threaded shell."""
47
50
48 # Threading strategy taken from:
51 # Threading strategy taken from:
49 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian
52 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65109, by Brian
50 # McErlean and John Finlay. Modified with corrections by Antoon Pardon,
53 # McErlean and John Finlay. Modified with corrections by Antoon Pardon,
51 # from the pygtk mailing list, to avoid lockups with system calls.
54 # from the pygtk mailing list, to avoid lockups with system calls.
52
55
53 # class attribute to indicate whether the class supports threads or not.
56 # class attribute to indicate whether the class supports threads or not.
54 # Subclasses with thread support should override this as needed.
57 # Subclasses with thread support should override this as needed.
55 isthreaded = True
58 isthreaded = True
56
59
57 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
60 def __init__(self,name,usage=None,rc=Struct(opts=None,args=None),
58 user_ns=None,user_global_ns=None,banner2='',**kw):
61 user_ns=None,user_global_ns=None,banner2='',**kw):
59 """Similar to the normal InteractiveShell, but with threading control"""
62 """Similar to the normal InteractiveShell, but with threading control"""
60
63
61 InteractiveShell.__init__(self,name,usage,rc,user_ns,
64 InteractiveShell.__init__(self,name,usage,rc,user_ns,
62 user_global_ns,banner2)
65 user_global_ns,banner2)
63
66
64
67
65 # A queue to hold the code to be executed.
68 # A queue to hold the code to be executed.
66 self.code_queue = Queue.Queue()
69 self.code_queue = Queue.Queue()
67
70
68 # Stuff to do at closing time
71 # Stuff to do at closing time
69 self._kill = None
72 self._kill = None
70 on_kill = kw.get('on_kill', [])
73 on_kill = kw.get('on_kill', [])
71 # Check that all things to kill are callable:
74 # Check that all things to kill are callable:
72 for t in on_kill:
75 for t in on_kill:
73 if not callable(t):
76 if not callable(t):
74 raise TypeError,'on_kill must be a list of callables'
77 raise TypeError,'on_kill must be a list of callables'
75 self.on_kill = on_kill
78 self.on_kill = on_kill
76 # thread identity of the "worker thread" (that may execute code directly)
79 # thread identity of the "worker thread" (that may execute code directly)
77 self.worker_ident = None
80 self.worker_ident = None
78 self.reactor_started = False
81 self.reactor_started = False
79 self.first_run = True
82 self.first_run = True
80
83
81 def runsource(self, source, filename="<input>", symbol="single"):
84 def runsource(self, source, filename="<input>", symbol="single"):
82 """Compile and run some source in the interpreter.
85 """Compile and run some source in the interpreter.
83
86
84 Modified version of code.py's runsource(), to handle threading issues.
87 Modified version of code.py's runsource(), to handle threading issues.
85 See the original for full docstring details."""
88 See the original for full docstring details."""
86
89
87 # If Ctrl-C was typed, we reset the flag and return right away
90 # If Ctrl-C was typed, we reset the flag and return right away
88 if shellglobals.KBINT:
91 if shellglobals.KBINT:
89 shellglobals.KBINT = False
92 shellglobals.KBINT = False
90 return False
93 return False
91
94
92 if self._kill:
95 if self._kill:
93 # can't queue new code if we are being killed
96 # can't queue new code if we are being killed
94 return True
97 return True
95
98
96 try:
99 try:
97 code = self.compile(source, filename, symbol)
100 code = self.compile(source, filename, symbol)
98 except (OverflowError, SyntaxError, ValueError):
101 except (OverflowError, SyntaxError, ValueError):
99 # Case 1
102 # Case 1
100 self.showsyntaxerror(filename)
103 self.showsyntaxerror(filename)
101 return False
104 return False
102
105
103 if code is None:
106 if code is None:
104 # Case 2
107 # Case 2
105 return True
108 return True
106
109
107 # shortcut - if we are in worker thread, or the worker thread is not running,
110 # shortcut - if we are in worker thread, or the worker thread is not running,
108 # execute directly (to allow recursion and prevent deadlock if code is run early
111 # execute directly (to allow recursion and prevent deadlock if code is run early
109 # in IPython construction)
112 # in IPython construction)
110
113
111 if (not self.reactor_started or (self.worker_ident is None and not self.first_run)
114 if (not self.reactor_started or (self.worker_ident is None and not self.first_run)
112 or self.worker_ident == thread.get_ident() or shellglobals.run_in_frontend(source)):
115 or self.worker_ident == thread.get_ident() or shellglobals.run_in_frontend(source)):
113 InteractiveShell.runcode(self,code)
116 InteractiveShell.runcode(self,code)
114 return
117 return
115
118
116 # Case 3
119 # Case 3
117 # Store code in queue, so the execution thread can handle it.
120 # Store code in queue, so the execution thread can handle it.
118
121
119 self.first_run = False
122 self.first_run = False
120 completed_ev, received_ev = threading.Event(), threading.Event()
123 completed_ev, received_ev = threading.Event(), threading.Event()
121
124
122 self.code_queue.put((code,completed_ev, received_ev))
125 self.code_queue.put((code,completed_ev, received_ev))
123
126
124 reactor.callLater(0.0,self.runcode)
127 reactor.callLater(0.0,self.runcode)
125 received_ev.wait(5)
128 received_ev.wait(5)
126 if not received_ev.isSet():
129 if not received_ev.isSet():
127 # the mainloop is dead, start executing code directly
130 # the mainloop is dead, start executing code directly
128 print "Warning: Timeout for mainloop thread exceeded"
131 print "Warning: Timeout for mainloop thread exceeded"
129 print "switching to nonthreaded mode (until mainloop wakes up again)"
132 print "switching to nonthreaded mode (until mainloop wakes up again)"
130 self.worker_ident = None
133 self.worker_ident = None
131 else:
134 else:
132 completed_ev.wait()
135 completed_ev.wait()
133
136
134 return False
137 return False
135
138
136 def runcode(self):
139 def runcode(self):
137 """Execute a code object.
140 """Execute a code object.
138
141
139 Multithreaded wrapper around IPython's runcode()."""
142 Multithreaded wrapper around IPython's runcode()."""
140
143
141
144
142 # we are in worker thread, stash out the id for runsource()
145 # we are in worker thread, stash out the id for runsource()
143 self.worker_ident = thread.get_ident()
146 self.worker_ident = thread.get_ident()
144
147
145 if self._kill:
148 if self._kill:
146 print >>Term.cout, 'Closing threads...',
149 print >>Term.cout, 'Closing threads...',
147 Term.cout.flush()
150 Term.cout.flush()
148 for tokill in self.on_kill:
151 for tokill in self.on_kill:
149 tokill()
152 tokill()
150 print >>Term.cout, 'Done.'
153 print >>Term.cout, 'Done.'
151 # allow kill() to return
154 # allow kill() to return
152 self._kill.set()
155 self._kill.set()
153 return True
156 return True
154
157
155 # Install SIGINT handler. We do it every time to ensure that if user
158 # Install SIGINT handler. We do it every time to ensure that if user
156 # code modifies it, we restore our own handling.
159 # code modifies it, we restore our own handling.
157 try:
160 try:
158 pass
161 pass
159 signal(SIGINT,shellglobals.sigint_handler)
162 signal(SIGINT,shellglobals.sigint_handler)
160 except SystemError:
163 except SystemError:
161 # This happens under Windows, which seems to have all sorts
164 # This happens under Windows, which seems to have all sorts
162 # of problems with signal handling. Oh well...
165 # of problems with signal handling. Oh well...
163 pass
166 pass
164
167
165 # Flush queue of pending code by calling the run methood of the parent
168 # Flush queue of pending code by calling the run methood of the parent
166 # class with all items which may be in the queue.
169 # class with all items which may be in the queue.
167 code_to_run = None
170 code_to_run = None
168 while 1:
171 while 1:
169 try:
172 try:
170 code_to_run, completed_ev, received_ev = self.code_queue.get_nowait()
173 code_to_run, completed_ev, received_ev = self.code_queue.get_nowait()
171 except Queue.Empty:
174 except Queue.Empty:
172 break
175 break
173 received_ev.set()
176 received_ev.set()
174
177
175
178
176 # Exceptions need to be raised differently depending on which
179 # Exceptions need to be raised differently depending on which
177 # thread is active. This convoluted try/except is only there to
180 # thread is active. This convoluted try/except is only there to
178 # protect against asynchronous exceptions, to ensure that a shellglobals.KBINT
181 # protect against asynchronous exceptions, to ensure that a shellglobals.KBINT
179 # at the wrong time doesn't deadlock everything. The global
182 # at the wrong time doesn't deadlock everything. The global
180 # CODE_TO_RUN is set to true/false as close as possible to the
183 # CODE_TO_RUN is set to true/false as close as possible to the
181 # runcode() call, so that the KBINT handler is correctly informed.
184 # runcode() call, so that the KBINT handler is correctly informed.
182 try:
185 try:
183 try:
186 try:
184 shellglobals.CODE_RUN = True
187 shellglobals.CODE_RUN = True
185 InteractiveShell.runcode(self,code_to_run)
188 InteractiveShell.runcode(self,code_to_run)
186 except KeyboardInterrupt:
189 except KeyboardInterrupt:
187 print "Keyboard interrupted in mainloop"
190 print "Keyboard interrupted in mainloop"
188 while not self.code_queue.empty():
191 while not self.code_queue.empty():
189 code = self.code_queue.get_nowait()
192 code = self.code_queue.get_nowait()
190 break
193 break
191 finally:
194 finally:
192 shellglobals.CODE_RUN = False
195 shellglobals.CODE_RUN = False
193 # allow runsource() return from wait
196 # allow runsource() return from wait
194 completed_ev.set()
197 completed_ev.set()
195
198
196 # This MUST return true for gtk threading to work
199 # This MUST return true for gtk threading to work
197 return True
200 return True
198
201
199 def kill(self):
202 def kill(self):
200 """Kill the thread, returning when it has been shut down."""
203 """Kill the thread, returning when it has been shut down."""
201 self._kill = threading.Event()
204 self._kill = threading.Event()
202 reactor.callLater(0.0,self.runcode)
205 reactor.callLater(0.0,self.runcode)
203 self._kill.wait()
206 self._kill.wait()
204
207
205
208
206
209
207 class IPShellTwisted:
210 class IPShellTwisted:
208 """Run a Twisted reactor while in an IPython session.
211 """Run a Twisted reactor while in an IPython session.
209
212
210 Python commands can be passed to the thread where they will be
213 Python commands can be passed to the thread where they will be
211 executed. This is implemented by periodically checking for
214 executed. This is implemented by periodically checking for
212 passed code using a Twisted reactor callback.
215 passed code using a Twisted reactor callback.
213 """
216 """
214
217
215 TIMEOUT = 0.01 # Millisecond interval between reactor runs.
218 TIMEOUT = 0.01 # Millisecond interval between reactor runs.
216
219
217 def __init__(self, argv=None, user_ns=None, debug=1,
220 def __init__(self, argv=None, user_ns=None, debug=1,
218 shell_class=TwistedInteractiveShell):
221 shell_class=TwistedInteractiveShell):
219
222
220 from twisted.internet import reactor
223 from twisted.internet import reactor
221 self.reactor = hijack_reactor()
224 self.reactor = hijack_reactor()
222
225
223 mainquit = self.reactor.stop
226 mainquit = self.reactor.stop
224
227
225 # Make sure IPython keeps going after reactor stop.
228 # Make sure IPython keeps going after reactor stop.
226 def reactorstop():
229 def reactorstop():
227 pass
230 pass
228 self.reactor.stop = reactorstop
231 self.reactor.stop = reactorstop
229 reactorrun_orig = self.reactor.run
232 reactorrun_orig = self.reactor.run
230 self.quitting = False
233 self.quitting = False
231 def reactorrun():
234 def reactorrun():
232 while True and not self.quitting:
235 while True and not self.quitting:
233 reactorrun_orig()
236 reactorrun_orig()
234 self.reactor.run = reactorrun
237 self.reactor.run = reactorrun
235
238
236 self.IP = make_IPython(argv, user_ns=user_ns, debug=debug,
239 self.IP = make_IPython(argv, user_ns=user_ns, debug=debug,
237 shell_class=shell_class,
240 shell_class=shell_class,
238 on_kill=[mainquit])
241 on_kill=[mainquit])
239
242
240 # threading.Thread.__init__(self)
243 # threading.Thread.__init__(self)
241
244
242 def run(self):
245 def run(self):
243 self.IP.mainloop()
246 self.IP.mainloop()
244 self.quitting = True
247 self.quitting = True
245 self.IP.kill()
248 self.IP.kill()
246
249
247 def mainloop(self):
250 def mainloop(self):
248 def mainLoopThreadDeath(r):
251 def mainLoopThreadDeath(r):
249 print "mainLoopThreadDeath: ", str(r)
252 print "mainLoopThreadDeath: ", str(r)
250 def spawnMainloopThread():
253 def spawnMainloopThread():
251 d=threads.deferToThread(self.run)
254 d=threads.deferToThread(self.run)
252 d.addBoth(mainLoopThreadDeath)
255 d.addBoth(mainLoopThreadDeath)
253 reactor.callWhenRunning(spawnMainloopThread)
256 reactor.callWhenRunning(spawnMainloopThread)
254 self.IP.reactor_started = True
257 self.IP.reactor_started = True
255 self.reactor.run()
258 self.reactor.run()
256 print "mainloop ending...."
259 print "mainloop ending...."
257
260
258 exists = True
261 exists = True
259
262
260
263
261 if __name__ == '__main__':
264 if __name__ == '__main__':
262 # Sample usage.
265 # Sample usage.
263
266
264 # Create the shell object. This steals twisted.internet.reactor
267 # Create the shell object. This steals twisted.internet.reactor
265 # for its own purposes, to make sure you've already installed a
268 # for its own purposes, to make sure you've already installed a
266 # reactor of your choice.
269 # reactor of your choice.
267 shell = IPShellTwisted(
270 shell = IPShellTwisted(
268 argv=[],
271 argv=[],
269 user_ns={'__name__': '__example__',
272 user_ns={'__name__': '__example__',
270 'hello': 'world',
273 'hello': 'world',
271 },
274 },
272 )
275 )
273
276
274 # Run the mainloop. This runs the actual reactor.run() method.
277 # Run the mainloop. This runs the actual reactor.run() method.
275 # The twisted.internet.reactor object at this point is a dummy
278 # The twisted.internet.reactor object at this point is a dummy
276 # object that passes through to the actual reactor, but prevents
279 # object that passes through to the actual reactor, but prevents
277 # run() from being called on it again.
280 # run() from being called on it again.
278 shell.mainloop()
281 shell.mainloop()
279
282
280 # You must exit IPython to terminate your program.
283 # You must exit IPython to terminate your program.
281 print 'Goodbye!'
284 print 'Goodbye!'
282
285
@@ -1,77 +1,82 b''
1 """
1 """
2 Base front end class for all async frontends.
2 Base front end class for all async frontends.
3 """
3 """
4 __docformat__ = "restructuredtext en"
4 __docformat__ = "restructuredtext en"
5
5
6 # Tell nose to skip this module
7 __test__ = {}
8
6 #-------------------------------------------------------------------------------
9 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
10 # Copyright (C) 2008 The IPython Development Team
8 #
11 #
9 # 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
10 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
12
15
13
14 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
15 # Imports
17 # Imports
16 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
17
19
20 # Third-party
21 from twisted.python.failure import Failure
22 from zope.interface import implements, classProvides
23
24 # From IPython
18 from IPython.external import guid
25 from IPython.external import guid
19
26
20 from zope.interface import Interface, Attribute, implements, classProvides
27 from IPython.frontend.frontendbase import (FrontEndBase, IFrontEnd,
21 from twisted.python.failure import Failure
28 IFrontEndFactory)
22 from IPython.frontend.frontendbase import (
23 FrontEndBase, IFrontEnd, IFrontEndFactory)
24 from IPython.kernel.core.history import FrontEndHistory
29 from IPython.kernel.core.history import FrontEndHistory
25 from IPython.kernel.engineservice import IEngineCore
30 from IPython.kernel.engineservice import IEngineCore
26
31
32 #-----------------------------------------------------------------------------
33 # Classes and functions
34 #-----------------------------------------------------------------------------
27
35
28 class AsyncFrontEndBase(FrontEndBase):
36 class AsyncFrontEndBase(FrontEndBase):
29 """
37 """
30 Overrides FrontEndBase to wrap execute in a deferred result.
38 Overrides FrontEndBase to wrap execute in a deferred result.
31 All callbacks are made as callbacks on the deferred result.
39 All callbacks are made as callbacks on the deferred result.
32 """
40 """
33
41
34 implements(IFrontEnd)
42 implements(IFrontEnd)
35 classProvides(IFrontEndFactory)
43 classProvides(IFrontEndFactory)
36
44
37 def __init__(self, engine=None, history=None):
45 def __init__(self, engine=None, history=None):
38 assert(engine==None or IEngineCore.providedBy(engine))
46 assert(engine==None or IEngineCore.providedBy(engine))
39 self.engine = IEngineCore(engine)
47 self.engine = IEngineCore(engine)
40 if history is None:
48 if history is None:
41 self.history = FrontEndHistory(input_cache=[''])
49 self.history = FrontEndHistory(input_cache=[''])
42 else:
50 else:
43 self.history = history
51 self.history = history
44
52
45
46 def execute(self, block, blockID=None):
53 def execute(self, block, blockID=None):
47 """Execute the block and return the deferred result.
54 """Execute the block and return the deferred result.
48
55
49 Parameters:
56 Parameters:
50 block : {str, AST}
57 block : {str, AST}
51 blockID : any
58 blockID : any
52 Caller may provide an ID to identify this block.
59 Caller may provide an ID to identify this block.
53 result['blockID'] := blockID
60 result['blockID'] := blockID
54
61
55 Result:
62 Result:
56 Deferred result of self.interpreter.execute
63 Deferred result of self.interpreter.execute
57 """
64 """
58
65
59 if(not self.is_complete(block)):
66 if(not self.is_complete(block)):
60 return Failure(Exception("Block is not compilable"))
67 return Failure(Exception("Block is not compilable"))
61
68
62 if(blockID == None):
69 if(blockID == None):
63 blockID = guid.generate()
70 blockID = guid.generate()
64
71
65 d = self.engine.execute(block)
72 d = self.engine.execute(block)
66 d.addCallback(self._add_history, block=block)
73 d.addCallback(self._add_history, block=block)
67 d.addCallbacks(self._add_block_id_for_result,
74 d.addCallbacks(self._add_block_id_for_result,
68 errback=self._add_block_id_for_failure,
75 errback=self._add_block_id_for_failure,
69 callbackArgs=(blockID,),
76 callbackArgs=(blockID,),
70 errbackArgs=(blockID,))
77 errbackArgs=(blockID,))
71 d.addBoth(self.update_cell_prompt, blockID=blockID)
78 d.addBoth(self.update_cell_prompt, blockID=blockID)
72 d.addCallbacks(self.render_result,
79 d.addCallbacks(self.render_result,
73 errback=self.render_error)
80 errback=self.render_error)
74
81
75 return d
82 return d
76
77
@@ -1,109 +1,112 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the asyncfrontendbase module."""
3 """This file contains unittests for the asyncfrontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
7 # Tell nose to skip this module
8 __test__ = {}
6
9
7 #---------------------------------------------------------------------------
10 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
11 # Copyright (C) 2008 The IPython Development Team
9 #
12 #
10 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
15 #---------------------------------------------------------------------------
13
16
14 #---------------------------------------------------------------------------
17 #---------------------------------------------------------------------------
15 # Imports
18 # Imports
16 #---------------------------------------------------------------------------
19 #---------------------------------------------------------------------------
17
20
18 # Tell nose to skip this module
19 __test__ = {}
20
21 from twisted.trial import unittest
21 from twisted.trial import unittest
22
22 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
23 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
23 from IPython.frontend import frontendbase
24 from IPython.frontend import frontendbase
24 from IPython.kernel.engineservice import EngineService
25 from IPython.kernel.engineservice import EngineService
25 from IPython.testing.parametric import Parametric, parametric
26 from IPython.testing.parametric import Parametric, parametric
26
27
28 #-----------------------------------------------------------------------------
29 # Classes and functions
30 #-----------------------------------------------------------------------------
27
31
28 class FrontEndCallbackChecker(AsyncFrontEndBase):
32 class FrontEndCallbackChecker(AsyncFrontEndBase):
29 """FrontEndBase subclass for checking callbacks"""
33 """FrontEndBase subclass for checking callbacks"""
30 def __init__(self, engine=None, history=None):
34 def __init__(self, engine=None, history=None):
31 super(FrontEndCallbackChecker, self).__init__(engine=engine,
35 super(FrontEndCallbackChecker, self).__init__(engine=engine,
32 history=history)
36 history=history)
33 self.updateCalled = False
37 self.updateCalled = False
34 self.renderResultCalled = False
38 self.renderResultCalled = False
35 self.renderErrorCalled = False
39 self.renderErrorCalled = False
36
40
37 def update_cell_prompt(self, result, blockID=None):
41 def update_cell_prompt(self, result, blockID=None):
38 self.updateCalled = True
42 self.updateCalled = True
39 return result
43 return result
40
44
41 def render_result(self, result):
45 def render_result(self, result):
42 self.renderResultCalled = True
46 self.renderResultCalled = True
43 return result
47 return result
44
48
45 def render_error(self, failure):
49 def render_error(self, failure):
46 self.renderErrorCalled = True
50 self.renderErrorCalled = True
47 return failure
51 return failure
48
52
49
53
50 class TestAsyncFrontendBase(unittest.TestCase):
54 class TestAsyncFrontendBase(unittest.TestCase):
51 def setUp(self):
55 def setUp(self):
52 """Setup the EngineService and FrontEndBase"""
56 """Setup the EngineService and FrontEndBase"""
53
57
54 self.fb = FrontEndCallbackChecker(engine=EngineService())
58 self.fb = FrontEndCallbackChecker(engine=EngineService())
55
59
56 def test_implements_IFrontEnd(self):
60 def test_implements_IFrontEnd(self):
57 self.assert_(frontendbase.IFrontEnd.implementedBy(
61 self.assert_(frontendbase.IFrontEnd.implementedBy(
58 AsyncFrontEndBase))
62 AsyncFrontEndBase))
59
63
60 def test_is_complete_returns_False_for_incomplete_block(self):
64 def test_is_complete_returns_False_for_incomplete_block(self):
61 block = """def test(a):"""
65 block = """def test(a):"""
62 self.assert_(self.fb.is_complete(block) == False)
66 self.assert_(self.fb.is_complete(block) == False)
63
67
64 def test_is_complete_returns_True_for_complete_block(self):
68 def test_is_complete_returns_True_for_complete_block(self):
65 block = """def test(a): pass"""
69 block = """def test(a): pass"""
66 self.assert_(self.fb.is_complete(block))
70 self.assert_(self.fb.is_complete(block))
67 block = """a=3"""
71 block = """a=3"""
68 self.assert_(self.fb.is_complete(block))
72 self.assert_(self.fb.is_complete(block))
69
73
70 def test_blockID_added_to_result(self):
74 def test_blockID_added_to_result(self):
71 block = """3+3"""
75 block = """3+3"""
72 d = self.fb.execute(block, blockID='TEST_ID')
76 d = self.fb.execute(block, blockID='TEST_ID')
73 d.addCallback(lambda r: self.assert_(r['blockID']=='TEST_ID'))
77 d.addCallback(lambda r: self.assert_(r['blockID']=='TEST_ID'))
74 return d
78 return d
75
79
76 def test_blockID_added_to_failure(self):
80 def test_blockID_added_to_failure(self):
77 block = "raise Exception()"
81 block = "raise Exception()"
78 d = self.fb.execute(block,blockID='TEST_ID')
82 d = self.fb.execute(block,blockID='TEST_ID')
79 d.addErrback(lambda f: self.assert_(f.blockID=='TEST_ID'))
83 d.addErrback(lambda f: self.assert_(f.blockID=='TEST_ID'))
80 return d
84 return d
81
85
82 def test_callbacks_added_to_execute(self):
86 def test_callbacks_added_to_execute(self):
83 d = self.fb.execute("10+10")
87 d = self.fb.execute("10+10")
84 d.addCallback(lambda r: self.assert_(self.fb.updateCalled and self.fb.renderResultCalled))
88 d.addCallback(lambda r: self.assert_(self.fb.updateCalled and self.fb.renderResultCalled))
85 return d
89 return d
86
90
87 def test_error_callback_added_to_execute(self):
91 def test_error_callback_added_to_execute(self):
88 """Test that render_error called on execution error."""
92 """Test that render_error called on execution error."""
89
93
90 d = self.fb.execute("raise Exception()")
94 d = self.fb.execute("raise Exception()")
91 d.addErrback(lambda f: self.assert_(self.fb.renderErrorCalled))
95 d.addErrback(lambda f: self.assert_(self.fb.renderErrorCalled))
92 return d
96 return d
93
97
94 def test_history_returns_expected_block(self):
98 def test_history_returns_expected_block(self):
95 """Make sure history browsing doesn't fail."""
99 """Make sure history browsing doesn't fail."""
96
100
97 blocks = ["a=1","a=2","a=3"]
101 blocks = ["a=1","a=2","a=3"]
98 d = self.fb.execute(blocks[0])
102 d = self.fb.execute(blocks[0])
99 d.addCallback(lambda _: self.fb.execute(blocks[1]))
103 d.addCallback(lambda _: self.fb.execute(blocks[1]))
100 d.addCallback(lambda _: self.fb.execute(blocks[2]))
104 d.addCallback(lambda _: self.fb.execute(blocks[2]))
101 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-2]))
105 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-2]))
102 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-3]))
106 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-3]))
103 d.addCallback(lambda _: self.assert_(self.fb.get_history_next()==blocks[-2]))
107 d.addCallback(lambda _: self.assert_(self.fb.get_history_next()==blocks[-2]))
104 return d
108 return d
105
109
106 def test_history_returns_none_at_startup(self):
110 def test_history_returns_none_at_startup(self):
107 self.assert_(self.fb.get_history_previous("")==None)
111 self.assert_(self.fb.get_history_previous("")==None)
108 self.assert_(self.fb.get_history_next()==None)
112 self.assert_(self.fb.get_history_next()==None)
109
@@ -1,906 +1,903 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
3
3
4 """A Twisted Service Representation of the IPython core.
4 """A Twisted Service Representation of the IPython core.
5
5
6 The IPython Core exposed to the network is called the Engine. Its
6 The IPython Core exposed to the network is called the Engine. Its
7 representation in Twisted in the EngineService. Interfaces and adapters
7 representation in Twisted in the EngineService. Interfaces and adapters
8 are used to abstract out the details of the actual network protocol used.
8 are used to abstract out the details of the actual network protocol used.
9 The EngineService is an Engine that knows nothing about the actual protocol
9 The EngineService is an Engine that knows nothing about the actual protocol
10 used.
10 used.
11
11
12 The EngineService is exposed with various network protocols in modules like:
12 The EngineService is exposed with various network protocols in modules like:
13
13
14 enginepb.py
14 enginepb.py
15 enginevanilla.py
15 enginevanilla.py
16
16
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
18 felt that we had over-engineered things. To improve the maintainability of the
18 felt that we had over-engineered things. To improve the maintainability of the
19 code we have taken out the ICompleteEngine interface and the completeEngine
19 code we have taken out the ICompleteEngine interface and the completeEngine
20 method that automatically added methods to engines.
20 method that automatically added methods to engines.
21
21
22 """
22 """
23
23
24 __docformat__ = "restructuredtext en"
24 __docformat__ = "restructuredtext en"
25
25
26 # Tell nose to skip this module. I don't think we need this as nose
27 # shouldn't ever be run on this!
28 __test__ = {}
29
26 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
31 # Copyright (C) 2008 The IPython Development Team
28 #
32 #
29 # Distributed under the terms of the BSD License. The full license is in
33 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
34 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
32
36
33 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
34 # Imports
38 # Imports
35 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
36
40
37 # Tell nose to skip the testing of this module
41 import copy
38 __test__ = {}
42 import sys
39
40 import os, sys, copy
41 import cPickle as pickle
43 import cPickle as pickle
42 from new import instancemethod
43
44
44 from twisted.application import service
45 from twisted.application import service
45 from twisted.internet import defer, reactor
46 from twisted.internet import defer, reactor
46 from twisted.python import log, failure, components
47 from twisted.python import log, failure, components
47 import zope.interface as zi
48 import zope.interface as zi
48
49
49 from IPython.kernel.core.interpreter import Interpreter
50 from IPython.kernel.core.interpreter import Interpreter
50 from IPython.kernel import newserialized, error, util
51 from IPython.kernel import newserialized, error
51 from IPython.kernel.util import printer
52 from IPython.kernel.twistedutil import gatherBoth, DeferredList
53 from IPython.kernel import codeutil
54
55
52
56 #-------------------------------------------------------------------------------
53 #-------------------------------------------------------------------------------
57 # Interface specification for the Engine
54 # Interface specification for the Engine
58 #-------------------------------------------------------------------------------
55 #-------------------------------------------------------------------------------
59
56
60 class IEngineCore(zi.Interface):
57 class IEngineCore(zi.Interface):
61 """The minimal required interface for the IPython Engine.
58 """The minimal required interface for the IPython Engine.
62
59
63 This interface provides a formal specification of the IPython core.
60 This interface provides a formal specification of the IPython core.
64 All these methods should return deferreds regardless of what side of a
61 All these methods should return deferreds regardless of what side of a
65 network connection they are on.
62 network connection they are on.
66
63
67 In general, this class simply wraps a shell class and wraps its return
64 In general, this class simply wraps a shell class and wraps its return
68 values as Deferred objects. If the underlying shell class method raises
65 values as Deferred objects. If the underlying shell class method raises
69 an exception, this class should convert it to a twisted.failure.Failure
66 an exception, this class should convert it to a twisted.failure.Failure
70 that will be propagated along the Deferred's errback chain.
67 that will be propagated along the Deferred's errback chain.
71
68
72 In addition, Failures are aggressive. By this, we mean that if a method
69 In addition, Failures are aggressive. By this, we mean that if a method
73 is performing multiple actions (like pulling multiple object) if any
70 is performing multiple actions (like pulling multiple object) if any
74 single one fails, the entire method will fail with that Failure. It is
71 single one fails, the entire method will fail with that Failure. It is
75 all or nothing.
72 all or nothing.
76 """
73 """
77
74
78 id = zi.interface.Attribute("the id of the Engine object")
75 id = zi.interface.Attribute("the id of the Engine object")
79 properties = zi.interface.Attribute("A dict of properties of the Engine")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
80
77
81 def execute(lines):
78 def execute(lines):
82 """Execute lines of Python code.
79 """Execute lines of Python code.
83
80
84 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
85 upon success.
82 upon success.
86
83
87 Returns a failure object if the execution of lines raises an exception.
84 Returns a failure object if the execution of lines raises an exception.
88 """
85 """
89
86
90 def push(namespace):
87 def push(namespace):
91 """Push dict namespace into the user's namespace.
88 """Push dict namespace into the user's namespace.
92
89
93 Returns a deferred to None or a failure.
90 Returns a deferred to None or a failure.
94 """
91 """
95
92
96 def pull(keys):
93 def pull(keys):
97 """Pulls values out of the user's namespace by keys.
94 """Pulls values out of the user's namespace by keys.
98
95
99 Returns a deferred to a tuple objects or a single object.
96 Returns a deferred to a tuple objects or a single object.
100
97
101 Raises NameError if any one of objects doess not exist.
98 Raises NameError if any one of objects doess not exist.
102 """
99 """
103
100
104 def push_function(namespace):
101 def push_function(namespace):
105 """Push a dict of key, function pairs into the user's namespace.
102 """Push a dict of key, function pairs into the user's namespace.
106
103
107 Returns a deferred to None or a failure."""
104 Returns a deferred to None or a failure."""
108
105
109 def pull_function(keys):
106 def pull_function(keys):
110 """Pulls functions out of the user's namespace by keys.
107 """Pulls functions out of the user's namespace by keys.
111
108
112 Returns a deferred to a tuple of functions or a single function.
109 Returns a deferred to a tuple of functions or a single function.
113
110
114 Raises NameError if any one of the functions does not exist.
111 Raises NameError if any one of the functions does not exist.
115 """
112 """
116
113
117 def get_result(i=None):
114 def get_result(i=None):
118 """Get the stdin/stdout/stderr of command i.
115 """Get the stdin/stdout/stderr of command i.
119
116
120 Returns a deferred to a dict with keys
117 Returns a deferred to a dict with keys
121 (id, number, stdin, stdout, stderr).
118 (id, number, stdin, stdout, stderr).
122
119
123 Raises IndexError if command i does not exist.
120 Raises IndexError if command i does not exist.
124 Raises TypeError if i in not an int.
121 Raises TypeError if i in not an int.
125 """
122 """
126
123
127 def reset():
124 def reset():
128 """Reset the shell.
125 """Reset the shell.
129
126
130 This clears the users namespace. Won't cause modules to be
127 This clears the users namespace. Won't cause modules to be
131 reloaded. Should also re-initialize certain variables like id.
128 reloaded. Should also re-initialize certain variables like id.
132 """
129 """
133
130
134 def kill():
131 def kill():
135 """Kill the engine by stopping the reactor."""
132 """Kill the engine by stopping the reactor."""
136
133
137 def keys():
134 def keys():
138 """Return the top level variables in the users namspace.
135 """Return the top level variables in the users namspace.
139
136
140 Returns a deferred to a dict."""
137 Returns a deferred to a dict."""
141
138
142
139
143 class IEngineSerialized(zi.Interface):
140 class IEngineSerialized(zi.Interface):
144 """Push/Pull methods that take Serialized objects.
141 """Push/Pull methods that take Serialized objects.
145
142
146 All methods should return deferreds.
143 All methods should return deferreds.
147 """
144 """
148
145
149 def push_serialized(namespace):
146 def push_serialized(namespace):
150 """Push a dict of keys and Serialized objects into the user's namespace."""
147 """Push a dict of keys and Serialized objects into the user's namespace."""
151
148
152 def pull_serialized(keys):
149 def pull_serialized(keys):
153 """Pull objects by key from the user's namespace as Serialized.
150 """Pull objects by key from the user's namespace as Serialized.
154
151
155 Returns a list of or one Serialized.
152 Returns a list of or one Serialized.
156
153
157 Raises NameError is any one of the objects does not exist.
154 Raises NameError is any one of the objects does not exist.
158 """
155 """
159
156
160
157
161 class IEngineProperties(zi.Interface):
158 class IEngineProperties(zi.Interface):
162 """Methods for access to the properties object of an Engine"""
159 """Methods for access to the properties object of an Engine"""
163
160
164 properties = zi.Attribute("A StrictDict object, containing the properties")
161 properties = zi.Attribute("A StrictDict object, containing the properties")
165
162
166 def set_properties(properties):
163 def set_properties(properties):
167 """set properties by key and value"""
164 """set properties by key and value"""
168
165
169 def get_properties(keys=None):
166 def get_properties(keys=None):
170 """get a list of properties by `keys`, if no keys specified, get all"""
167 """get a list of properties by `keys`, if no keys specified, get all"""
171
168
172 def del_properties(keys):
169 def del_properties(keys):
173 """delete properties by `keys`"""
170 """delete properties by `keys`"""
174
171
175 def has_properties(keys):
172 def has_properties(keys):
176 """get a list of bool values for whether `properties` has `keys`"""
173 """get a list of bool values for whether `properties` has `keys`"""
177
174
178 def clear_properties():
175 def clear_properties():
179 """clear the properties dict"""
176 """clear the properties dict"""
180
177
181 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
182 """The basic engine interface that EngineService will implement.
179 """The basic engine interface that EngineService will implement.
183
180
184 This exists so it is easy to specify adapters that adapt to and from the
181 This exists so it is easy to specify adapters that adapt to and from the
185 API that the basic EngineService implements.
182 API that the basic EngineService implements.
186 """
183 """
187 pass
184 pass
188
185
189 class IEngineQueued(IEngineBase):
186 class IEngineQueued(IEngineBase):
190 """Interface for adding a queue to an IEngineBase.
187 """Interface for adding a queue to an IEngineBase.
191
188
192 This interface extends the IEngineBase interface to add methods for managing
189 This interface extends the IEngineBase interface to add methods for managing
193 the engine's queue. The implicit details of this interface are that the
190 the engine's queue. The implicit details of this interface are that the
194 execution of all methods declared in IEngineBase should appropriately be
191 execution of all methods declared in IEngineBase should appropriately be
195 put through a queue before execution.
192 put through a queue before execution.
196
193
197 All methods should return deferreds.
194 All methods should return deferreds.
198 """
195 """
199
196
200 def clear_queue():
197 def clear_queue():
201 """Clear the queue."""
198 """Clear the queue."""
202
199
203 def queue_status():
200 def queue_status():
204 """Get the queued and pending commands in the queue."""
201 """Get the queued and pending commands in the queue."""
205
202
206 def register_failure_observer(obs):
203 def register_failure_observer(obs):
207 """Register an observer of pending Failures.
204 """Register an observer of pending Failures.
208
205
209 The observer must implement IFailureObserver.
206 The observer must implement IFailureObserver.
210 """
207 """
211
208
212 def unregister_failure_observer(obs):
209 def unregister_failure_observer(obs):
213 """Unregister an observer of pending Failures."""
210 """Unregister an observer of pending Failures."""
214
211
215
212
216 class IEngineThreaded(zi.Interface):
213 class IEngineThreaded(zi.Interface):
217 """A place holder for threaded commands.
214 """A place holder for threaded commands.
218
215
219 All methods should return deferreds.
216 All methods should return deferreds.
220 """
217 """
221 pass
218 pass
222
219
223
220
224 #-------------------------------------------------------------------------------
221 #-------------------------------------------------------------------------------
225 # Functions and classes to implement the EngineService
222 # Functions and classes to implement the EngineService
226 #-------------------------------------------------------------------------------
223 #-------------------------------------------------------------------------------
227
224
228
225
229 class StrictDict(dict):
226 class StrictDict(dict):
230 """This is a strict copying dictionary for use as the interface to the
227 """This is a strict copying dictionary for use as the interface to the
231 properties of an Engine.
228 properties of an Engine.
232
229
233 :IMPORTANT:
230 :IMPORTANT:
234 This object copies the values you set to it, and returns copies to you
231 This object copies the values you set to it, and returns copies to you
235 when you request them. The only way to change properties os explicitly
232 when you request them. The only way to change properties os explicitly
236 through the setitem and getitem of the dictionary interface.
233 through the setitem and getitem of the dictionary interface.
237
234
238 Example:
235 Example:
239 >>> e = get_engine(id)
236 >>> e = get_engine(id)
240 >>> L = [1,2,3]
237 >>> L = [1,2,3]
241 >>> e.properties['L'] = L
238 >>> e.properties['L'] = L
242 >>> L == e.properties['L']
239 >>> L == e.properties['L']
243 True
240 True
244 >>> L.append(99)
241 >>> L.append(99)
245 >>> L == e.properties['L']
242 >>> L == e.properties['L']
246 False
243 False
247
244
248 Note that getitem copies, so calls to methods of objects do not affect
245 Note that getitem copies, so calls to methods of objects do not affect
249 the properties, as seen here:
246 the properties, as seen here:
250
247
251 >>> e.properties[1] = range(2)
248 >>> e.properties[1] = range(2)
252 >>> print e.properties[1]
249 >>> print e.properties[1]
253 [0, 1]
250 [0, 1]
254 >>> e.properties[1].append(2)
251 >>> e.properties[1].append(2)
255 >>> print e.properties[1]
252 >>> print e.properties[1]
256 [0, 1]
253 [0, 1]
257 """
254 """
258 def __init__(self, *args, **kwargs):
255 def __init__(self, *args, **kwargs):
259 dict.__init__(self, *args, **kwargs)
256 dict.__init__(self, *args, **kwargs)
260 self.modified = True
257 self.modified = True
261
258
262 def __getitem__(self, key):
259 def __getitem__(self, key):
263 return copy.deepcopy(dict.__getitem__(self, key))
260 return copy.deepcopy(dict.__getitem__(self, key))
264
261
265 def __setitem__(self, key, value):
262 def __setitem__(self, key, value):
266 # check if this entry is valid for transport around the network
263 # check if this entry is valid for transport around the network
267 # and copying
264 # and copying
268 try:
265 try:
269 pickle.dumps(key, 2)
266 pickle.dumps(key, 2)
270 pickle.dumps(value, 2)
267 pickle.dumps(value, 2)
271 newvalue = copy.deepcopy(value)
268 newvalue = copy.deepcopy(value)
272 except Exception, e:
269 except Exception, e:
273 raise error.InvalidProperty("can't be a value: %r" % value)
270 raise error.InvalidProperty("can't be a value: %r" % value)
274 dict.__setitem__(self, key, newvalue)
271 dict.__setitem__(self, key, newvalue)
275 self.modified = True
272 self.modified = True
276
273
277 def __delitem__(self, key):
274 def __delitem__(self, key):
278 dict.__delitem__(self, key)
275 dict.__delitem__(self, key)
279 self.modified = True
276 self.modified = True
280
277
281 def update(self, dikt):
278 def update(self, dikt):
282 for k,v in dikt.iteritems():
279 for k,v in dikt.iteritems():
283 self[k] = v
280 self[k] = v
284
281
285 def pop(self, key):
282 def pop(self, key):
286 self.modified = True
283 self.modified = True
287 return dict.pop(self, key)
284 return dict.pop(self, key)
288
285
289 def popitem(self):
286 def popitem(self):
290 self.modified = True
287 self.modified = True
291 return dict.popitem(self)
288 return dict.popitem(self)
292
289
293 def clear(self):
290 def clear(self):
294 self.modified = True
291 self.modified = True
295 dict.clear(self)
292 dict.clear(self)
296
293
297 def subDict(self, *keys):
294 def subDict(self, *keys):
298 d = {}
295 d = {}
299 for key in keys:
296 for key in keys:
300 d[key] = self[key]
297 d[key] = self[key]
301 return d
298 return d
302
299
303
300
304
301
305 class EngineAPI(object):
302 class EngineAPI(object):
306 """This is the object through which the user can edit the `properties`
303 """This is the object through which the user can edit the `properties`
307 attribute of an Engine.
304 attribute of an Engine.
308 The Engine Properties object copies all object in and out of itself.
305 The Engine Properties object copies all object in and out of itself.
309 See the EngineProperties object for details.
306 See the EngineProperties object for details.
310 """
307 """
311 _fix=False
308 _fix=False
312 def __init__(self, id):
309 def __init__(self, id):
313 self.id = id
310 self.id = id
314 self.properties = StrictDict()
311 self.properties = StrictDict()
315 self._fix=True
312 self._fix=True
316
313
317 def __setattr__(self, k,v):
314 def __setattr__(self, k,v):
318 if self._fix:
315 if self._fix:
319 raise error.KernelError("I am protected!")
316 raise error.KernelError("I am protected!")
320 else:
317 else:
321 object.__setattr__(self, k, v)
318 object.__setattr__(self, k, v)
322
319
323 def __delattr__(self, key):
320 def __delattr__(self, key):
324 raise error.KernelError("I am protected!")
321 raise error.KernelError("I am protected!")
325
322
326
323
327 _apiDict = {}
324 _apiDict = {}
328
325
329 def get_engine(id):
326 def get_engine(id):
330 """Get the Engine API object, whcih currently just provides the properties
327 """Get the Engine API object, whcih currently just provides the properties
331 object, by ID"""
328 object, by ID"""
332 global _apiDict
329 global _apiDict
333 if not _apiDict.get(id):
330 if not _apiDict.get(id):
334 _apiDict[id] = EngineAPI(id)
331 _apiDict[id] = EngineAPI(id)
335 return _apiDict[id]
332 return _apiDict[id]
336
333
337 def drop_engine(id):
334 def drop_engine(id):
338 """remove an engine"""
335 """remove an engine"""
339 global _apiDict
336 global _apiDict
340 if _apiDict.has_key(id):
337 if _apiDict.has_key(id):
341 del _apiDict[id]
338 del _apiDict[id]
342
339
343 class EngineService(object, service.Service):
340 class EngineService(object, service.Service):
344 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
341 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
345
342
346 zi.implements(IEngineBase)
343 zi.implements(IEngineBase)
347 name = 'EngineService'
344 name = 'EngineService'
348
345
349 def __init__(self, shellClass=Interpreter, mpi=None):
346 def __init__(self, shellClass=Interpreter, mpi=None):
350 """Create an EngineService.
347 """Create an EngineService.
351
348
352 shellClass: something that implements IInterpreter or core1
349 shellClass: something that implements IInterpreter or core1
353 mpi: an mpi module that has rank and size attributes
350 mpi: an mpi module that has rank and size attributes
354 """
351 """
355 self.shellClass = shellClass
352 self.shellClass = shellClass
356 self.shell = self.shellClass()
353 self.shell = self.shellClass()
357 self.mpi = mpi
354 self.mpi = mpi
358 self.id = None
355 self.id = None
359 self.properties = get_engine(self.id).properties
356 self.properties = get_engine(self.id).properties
360 if self.mpi is not None:
357 if self.mpi is not None:
361 log.msg("MPI started with rank = %i and size = %i" %
358 log.msg("MPI started with rank = %i and size = %i" %
362 (self.mpi.rank, self.mpi.size))
359 (self.mpi.rank, self.mpi.size))
363 self.id = self.mpi.rank
360 self.id = self.mpi.rank
364 self._seedNamespace()
361 self._seedNamespace()
365
362
366 # Make id a property so that the shell can get the updated id
363 # Make id a property so that the shell can get the updated id
367
364
368 def _setID(self, id):
365 def _setID(self, id):
369 self._id = id
366 self._id = id
370 self.properties = get_engine(id).properties
367 self.properties = get_engine(id).properties
371 self.shell.push({'id': id})
368 self.shell.push({'id': id})
372
369
373 def _getID(self):
370 def _getID(self):
374 return self._id
371 return self._id
375
372
376 id = property(_getID, _setID)
373 id = property(_getID, _setID)
377
374
378 def _seedNamespace(self):
375 def _seedNamespace(self):
379 self.shell.push({'mpi': self.mpi, 'id' : self.id})
376 self.shell.push({'mpi': self.mpi, 'id' : self.id})
380
377
381 def executeAndRaise(self, msg, callable, *args, **kwargs):
378 def executeAndRaise(self, msg, callable, *args, **kwargs):
382 """Call a method of self.shell and wrap any exception."""
379 """Call a method of self.shell and wrap any exception."""
383 d = defer.Deferred()
380 d = defer.Deferred()
384 try:
381 try:
385 result = callable(*args, **kwargs)
382 result = callable(*args, **kwargs)
386 except:
383 except:
387 # This gives the following:
384 # This gives the following:
388 # et=exception class
385 # et=exception class
389 # ev=exception class instance
386 # ev=exception class instance
390 # tb=traceback object
387 # tb=traceback object
391 et,ev,tb = sys.exc_info()
388 et,ev,tb = sys.exc_info()
392 # This call adds attributes to the exception value
389 # This call adds attributes to the exception value
393 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
390 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
394 # Add another attribute
391 # Add another attribute
395 ev._ipython_engine_info = msg
392 ev._ipython_engine_info = msg
396 f = failure.Failure(ev,et,None)
393 f = failure.Failure(ev,et,None)
397 d.errback(f)
394 d.errback(f)
398 else:
395 else:
399 d.callback(result)
396 d.callback(result)
400
397
401 return d
398 return d
402
399
403
400
404 # The IEngine methods. See the interface for documentation.
401 # The IEngine methods. See the interface for documentation.
405
402
406 def execute(self, lines):
403 def execute(self, lines):
407 msg = {'engineid':self.id,
404 msg = {'engineid':self.id,
408 'method':'execute',
405 'method':'execute',
409 'args':[lines]}
406 'args':[lines]}
410 d = self.executeAndRaise(msg, self.shell.execute, lines)
407 d = self.executeAndRaise(msg, self.shell.execute, lines)
411 d.addCallback(self.addIDToResult)
408 d.addCallback(self.addIDToResult)
412 return d
409 return d
413
410
414 def addIDToResult(self, result):
411 def addIDToResult(self, result):
415 result['id'] = self.id
412 result['id'] = self.id
416 return result
413 return result
417
414
418 def push(self, namespace):
415 def push(self, namespace):
419 msg = {'engineid':self.id,
416 msg = {'engineid':self.id,
420 'method':'push',
417 'method':'push',
421 'args':[repr(namespace.keys())]}
418 'args':[repr(namespace.keys())]}
422 d = self.executeAndRaise(msg, self.shell.push, namespace)
419 d = self.executeAndRaise(msg, self.shell.push, namespace)
423 return d
420 return d
424
421
425 def pull(self, keys):
422 def pull(self, keys):
426 msg = {'engineid':self.id,
423 msg = {'engineid':self.id,
427 'method':'pull',
424 'method':'pull',
428 'args':[repr(keys)]}
425 'args':[repr(keys)]}
429 d = self.executeAndRaise(msg, self.shell.pull, keys)
426 d = self.executeAndRaise(msg, self.shell.pull, keys)
430 return d
427 return d
431
428
432 def push_function(self, namespace):
429 def push_function(self, namespace):
433 msg = {'engineid':self.id,
430 msg = {'engineid':self.id,
434 'method':'push_function',
431 'method':'push_function',
435 'args':[repr(namespace.keys())]}
432 'args':[repr(namespace.keys())]}
436 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
433 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
437 return d
434 return d
438
435
439 def pull_function(self, keys):
436 def pull_function(self, keys):
440 msg = {'engineid':self.id,
437 msg = {'engineid':self.id,
441 'method':'pull_function',
438 'method':'pull_function',
442 'args':[repr(keys)]}
439 'args':[repr(keys)]}
443 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
440 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
444 return d
441 return d
445
442
446 def get_result(self, i=None):
443 def get_result(self, i=None):
447 msg = {'engineid':self.id,
444 msg = {'engineid':self.id,
448 'method':'get_result',
445 'method':'get_result',
449 'args':[repr(i)]}
446 'args':[repr(i)]}
450 d = self.executeAndRaise(msg, self.shell.getCommand, i)
447 d = self.executeAndRaise(msg, self.shell.getCommand, i)
451 d.addCallback(self.addIDToResult)
448 d.addCallback(self.addIDToResult)
452 return d
449 return d
453
450
454 def reset(self):
451 def reset(self):
455 msg = {'engineid':self.id,
452 msg = {'engineid':self.id,
456 'method':'reset',
453 'method':'reset',
457 'args':[]}
454 'args':[]}
458 del self.shell
455 del self.shell
459 self.shell = self.shellClass()
456 self.shell = self.shellClass()
460 self.properties.clear()
457 self.properties.clear()
461 d = self.executeAndRaise(msg, self._seedNamespace)
458 d = self.executeAndRaise(msg, self._seedNamespace)
462 return d
459 return d
463
460
464 def kill(self):
461 def kill(self):
465 drop_engine(self.id)
462 drop_engine(self.id)
466 try:
463 try:
467 reactor.stop()
464 reactor.stop()
468 except RuntimeError:
465 except RuntimeError:
469 log.msg('The reactor was not running apparently.')
466 log.msg('The reactor was not running apparently.')
470 return defer.fail()
467 return defer.fail()
471 else:
468 else:
472 return defer.succeed(None)
469 return defer.succeed(None)
473
470
474 def keys(self):
471 def keys(self):
475 """Return a list of variables names in the users top level namespace.
472 """Return a list of variables names in the users top level namespace.
476
473
477 This used to return a dict of all the keys/repr(values) in the
474 This used to return a dict of all the keys/repr(values) in the
478 user's namespace. This was too much info for the ControllerService
475 user's namespace. This was too much info for the ControllerService
479 to handle so it is now just a list of keys.
476 to handle so it is now just a list of keys.
480 """
477 """
481
478
482 remotes = []
479 remotes = []
483 for k in self.shell.user_ns.iterkeys():
480 for k in self.shell.user_ns.iterkeys():
484 if k not in ['__name__', '_ih', '_oh', '__builtins__',
481 if k not in ['__name__', '_ih', '_oh', '__builtins__',
485 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
482 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
486 remotes.append(k)
483 remotes.append(k)
487 return defer.succeed(remotes)
484 return defer.succeed(remotes)
488
485
489 def set_properties(self, properties):
486 def set_properties(self, properties):
490 msg = {'engineid':self.id,
487 msg = {'engineid':self.id,
491 'method':'set_properties',
488 'method':'set_properties',
492 'args':[repr(properties.keys())]}
489 'args':[repr(properties.keys())]}
493 return self.executeAndRaise(msg, self.properties.update, properties)
490 return self.executeAndRaise(msg, self.properties.update, properties)
494
491
495 def get_properties(self, keys=None):
492 def get_properties(self, keys=None):
496 msg = {'engineid':self.id,
493 msg = {'engineid':self.id,
497 'method':'get_properties',
494 'method':'get_properties',
498 'args':[repr(keys)]}
495 'args':[repr(keys)]}
499 if keys is None:
496 if keys is None:
500 keys = self.properties.keys()
497 keys = self.properties.keys()
501 return self.executeAndRaise(msg, self.properties.subDict, *keys)
498 return self.executeAndRaise(msg, self.properties.subDict, *keys)
502
499
503 def _doDel(self, keys):
500 def _doDel(self, keys):
504 for key in keys:
501 for key in keys:
505 del self.properties[key]
502 del self.properties[key]
506
503
507 def del_properties(self, keys):
504 def del_properties(self, keys):
508 msg = {'engineid':self.id,
505 msg = {'engineid':self.id,
509 'method':'del_properties',
506 'method':'del_properties',
510 'args':[repr(keys)]}
507 'args':[repr(keys)]}
511 return self.executeAndRaise(msg, self._doDel, keys)
508 return self.executeAndRaise(msg, self._doDel, keys)
512
509
513 def _doHas(self, keys):
510 def _doHas(self, keys):
514 return [self.properties.has_key(key) for key in keys]
511 return [self.properties.has_key(key) for key in keys]
515
512
516 def has_properties(self, keys):
513 def has_properties(self, keys):
517 msg = {'engineid':self.id,
514 msg = {'engineid':self.id,
518 'method':'has_properties',
515 'method':'has_properties',
519 'args':[repr(keys)]}
516 'args':[repr(keys)]}
520 return self.executeAndRaise(msg, self._doHas, keys)
517 return self.executeAndRaise(msg, self._doHas, keys)
521
518
522 def clear_properties(self):
519 def clear_properties(self):
523 msg = {'engineid':self.id,
520 msg = {'engineid':self.id,
524 'method':'clear_properties',
521 'method':'clear_properties',
525 'args':[]}
522 'args':[]}
526 return self.executeAndRaise(msg, self.properties.clear)
523 return self.executeAndRaise(msg, self.properties.clear)
527
524
528 def push_serialized(self, sNamespace):
525 def push_serialized(self, sNamespace):
529 msg = {'engineid':self.id,
526 msg = {'engineid':self.id,
530 'method':'push_serialized',
527 'method':'push_serialized',
531 'args':[repr(sNamespace.keys())]}
528 'args':[repr(sNamespace.keys())]}
532 ns = {}
529 ns = {}
533 for k,v in sNamespace.iteritems():
530 for k,v in sNamespace.iteritems():
534 try:
531 try:
535 unserialized = newserialized.IUnSerialized(v)
532 unserialized = newserialized.IUnSerialized(v)
536 ns[k] = unserialized.getObject()
533 ns[k] = unserialized.getObject()
537 except:
534 except:
538 return defer.fail()
535 return defer.fail()
539 return self.executeAndRaise(msg, self.shell.push, ns)
536 return self.executeAndRaise(msg, self.shell.push, ns)
540
537
541 def pull_serialized(self, keys):
538 def pull_serialized(self, keys):
542 msg = {'engineid':self.id,
539 msg = {'engineid':self.id,
543 'method':'pull_serialized',
540 'method':'pull_serialized',
544 'args':[repr(keys)]}
541 'args':[repr(keys)]}
545 if isinstance(keys, str):
542 if isinstance(keys, str):
546 keys = [keys]
543 keys = [keys]
547 if len(keys)==1:
544 if len(keys)==1:
548 d = self.executeAndRaise(msg, self.shell.pull, keys)
545 d = self.executeAndRaise(msg, self.shell.pull, keys)
549 d.addCallback(newserialized.serialize)
546 d.addCallback(newserialized.serialize)
550 return d
547 return d
551 elif len(keys)>1:
548 elif len(keys)>1:
552 d = self.executeAndRaise(msg, self.shell.pull, keys)
549 d = self.executeAndRaise(msg, self.shell.pull, keys)
553 @d.addCallback
550 @d.addCallback
554 def packThemUp(values):
551 def packThemUp(values):
555 serials = []
552 serials = []
556 for v in values:
553 for v in values:
557 try:
554 try:
558 serials.append(newserialized.serialize(v))
555 serials.append(newserialized.serialize(v))
559 except:
556 except:
560 return defer.fail(failure.Failure())
557 return defer.fail(failure.Failure())
561 return serials
558 return serials
562 return packThemUp
559 return packThemUp
563
560
564
561
565 def queue(methodToQueue):
562 def queue(methodToQueue):
566 def queuedMethod(this, *args, **kwargs):
563 def queuedMethod(this, *args, **kwargs):
567 name = methodToQueue.__name__
564 name = methodToQueue.__name__
568 return this.submitCommand(Command(name, *args, **kwargs))
565 return this.submitCommand(Command(name, *args, **kwargs))
569 return queuedMethod
566 return queuedMethod
570
567
571 class QueuedEngine(object):
568 class QueuedEngine(object):
572 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
569 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
573
570
574 The resulting object will implement IEngineQueued which extends
571 The resulting object will implement IEngineQueued which extends
575 IEngineCore which extends (IEngineBase, IEngineSerialized).
572 IEngineCore which extends (IEngineBase, IEngineSerialized).
576
573
577 This seems like the best way of handling it, but I am not sure. The
574 This seems like the best way of handling it, but I am not sure. The
578 other option is to have the various base interfaces be used like
575 other option is to have the various base interfaces be used like
579 mix-in intefaces. The problem I have with this is adpatation is
576 mix-in intefaces. The problem I have with this is adpatation is
580 more difficult and complicated because there can be can multiple
577 more difficult and complicated because there can be can multiple
581 original and final Interfaces.
578 original and final Interfaces.
582 """
579 """
583
580
584 zi.implements(IEngineQueued)
581 zi.implements(IEngineQueued)
585
582
586 def __init__(self, engine):
583 def __init__(self, engine):
587 """Create a QueuedEngine object from an engine
584 """Create a QueuedEngine object from an engine
588
585
589 engine: An implementor of IEngineCore and IEngineSerialized
586 engine: An implementor of IEngineCore and IEngineSerialized
590 keepUpToDate: whether to update the remote status when the
587 keepUpToDate: whether to update the remote status when the
591 queue is empty. Defaults to False.
588 queue is empty. Defaults to False.
592 """
589 """
593
590
594 # This is the right way to do these tests rather than
591 # This is the right way to do these tests rather than
595 # IEngineCore in list(zi.providedBy(engine)) which will only
592 # IEngineCore in list(zi.providedBy(engine)) which will only
596 # picks of the interfaces that are directly declared by engine.
593 # picks of the interfaces that are directly declared by engine.
597 assert IEngineBase.providedBy(engine), \
594 assert IEngineBase.providedBy(engine), \
598 "engine passed to QueuedEngine doesn't provide IEngineBase"
595 "engine passed to QueuedEngine doesn't provide IEngineBase"
599
596
600 self.engine = engine
597 self.engine = engine
601 self.id = engine.id
598 self.id = engine.id
602 self.queued = []
599 self.queued = []
603 self.history = {}
600 self.history = {}
604 self.engineStatus = {}
601 self.engineStatus = {}
605 self.currentCommand = None
602 self.currentCommand = None
606 self.failureObservers = []
603 self.failureObservers = []
607
604
608 def _get_properties(self):
605 def _get_properties(self):
609 return self.engine.properties
606 return self.engine.properties
610
607
611 properties = property(_get_properties, lambda self, _: None)
608 properties = property(_get_properties, lambda self, _: None)
612 # Queue management methods. You should not call these directly
609 # Queue management methods. You should not call these directly
613
610
614 def submitCommand(self, cmd):
611 def submitCommand(self, cmd):
615 """Submit command to queue."""
612 """Submit command to queue."""
616
613
617 d = defer.Deferred()
614 d = defer.Deferred()
618 cmd.setDeferred(d)
615 cmd.setDeferred(d)
619 if self.currentCommand is not None:
616 if self.currentCommand is not None:
620 if self.currentCommand.finished:
617 if self.currentCommand.finished:
621 # log.msg("Running command immediately: %r" % cmd)
618 # log.msg("Running command immediately: %r" % cmd)
622 self.currentCommand = cmd
619 self.currentCommand = cmd
623 self.runCurrentCommand()
620 self.runCurrentCommand()
624 else: # command is still running
621 else: # command is still running
625 # log.msg("Command is running: %r" % self.currentCommand)
622 # log.msg("Command is running: %r" % self.currentCommand)
626 # log.msg("Queueing: %r" % cmd)
623 # log.msg("Queueing: %r" % cmd)
627 self.queued.append(cmd)
624 self.queued.append(cmd)
628 else:
625 else:
629 # log.msg("No current commands, running: %r" % cmd)
626 # log.msg("No current commands, running: %r" % cmd)
630 self.currentCommand = cmd
627 self.currentCommand = cmd
631 self.runCurrentCommand()
628 self.runCurrentCommand()
632 return d
629 return d
633
630
634 def runCurrentCommand(self):
631 def runCurrentCommand(self):
635 """Run current command."""
632 """Run current command."""
636
633
637 cmd = self.currentCommand
634 cmd = self.currentCommand
638 f = getattr(self.engine, cmd.remoteMethod, None)
635 f = getattr(self.engine, cmd.remoteMethod, None)
639 if f:
636 if f:
640 d = f(*cmd.args, **cmd.kwargs)
637 d = f(*cmd.args, **cmd.kwargs)
641 if cmd.remoteMethod is 'execute':
638 if cmd.remoteMethod is 'execute':
642 d.addCallback(self.saveResult)
639 d.addCallback(self.saveResult)
643 d.addCallback(self.finishCommand)
640 d.addCallback(self.finishCommand)
644 d.addErrback(self.abortCommand)
641 d.addErrback(self.abortCommand)
645 else:
642 else:
646 return defer.fail(AttributeError(cmd.remoteMethod))
643 return defer.fail(AttributeError(cmd.remoteMethod))
647
644
648 def _flushQueue(self):
645 def _flushQueue(self):
649 """Pop next command in queue and run it."""
646 """Pop next command in queue and run it."""
650
647
651 if len(self.queued) > 0:
648 if len(self.queued) > 0:
652 self.currentCommand = self.queued.pop(0)
649 self.currentCommand = self.queued.pop(0)
653 self.runCurrentCommand()
650 self.runCurrentCommand()
654
651
655 def saveResult(self, result):
652 def saveResult(self, result):
656 """Put the result in the history."""
653 """Put the result in the history."""
657 self.history[result['number']] = result
654 self.history[result['number']] = result
658 return result
655 return result
659
656
660 def finishCommand(self, result):
657 def finishCommand(self, result):
661 """Finish currrent command."""
658 """Finish currrent command."""
662
659
663 # The order of these commands is absolutely critical.
660 # The order of these commands is absolutely critical.
664 self.currentCommand.handleResult(result)
661 self.currentCommand.handleResult(result)
665 self.currentCommand.finished = True
662 self.currentCommand.finished = True
666 self._flushQueue()
663 self._flushQueue()
667 return result
664 return result
668
665
669 def abortCommand(self, reason):
666 def abortCommand(self, reason):
670 """Abort current command.
667 """Abort current command.
671
668
672 This eats the Failure but first passes it onto the Deferred that the
669 This eats the Failure but first passes it onto the Deferred that the
673 user has.
670 user has.
674
671
675 It also clear out the queue so subsequence commands don't run.
672 It also clear out the queue so subsequence commands don't run.
676 """
673 """
677
674
678 # The order of these 3 commands is absolutely critical. The currentCommand
675 # The order of these 3 commands is absolutely critical. The currentCommand
679 # must first be marked as finished BEFORE the queue is cleared and before
676 # must first be marked as finished BEFORE the queue is cleared and before
680 # the current command is sent the failure.
677 # the current command is sent the failure.
681 # Also, the queue must be cleared BEFORE the current command is sent the Failure
678 # Also, the queue must be cleared BEFORE the current command is sent the Failure
682 # otherwise the errback chain could trigger new commands to be added to the
679 # otherwise the errback chain could trigger new commands to be added to the
683 # queue before we clear it. We should clear ONLY the commands that were in
680 # queue before we clear it. We should clear ONLY the commands that were in
684 # the queue when the error occured.
681 # the queue when the error occured.
685 self.currentCommand.finished = True
682 self.currentCommand.finished = True
686 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
683 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
687 self.clear_queue(msg=s)
684 self.clear_queue(msg=s)
688 self.currentCommand.handleError(reason)
685 self.currentCommand.handleError(reason)
689
686
690 return None
687 return None
691
688
692 #---------------------------------------------------------------------------
689 #---------------------------------------------------------------------------
693 # IEngineCore methods
690 # IEngineCore methods
694 #---------------------------------------------------------------------------
691 #---------------------------------------------------------------------------
695
692
696 @queue
693 @queue
697 def execute(self, lines):
694 def execute(self, lines):
698 pass
695 pass
699
696
700 @queue
697 @queue
701 def push(self, namespace):
698 def push(self, namespace):
702 pass
699 pass
703
700
704 @queue
701 @queue
705 def pull(self, keys):
702 def pull(self, keys):
706 pass
703 pass
707
704
708 @queue
705 @queue
709 def push_function(self, namespace):
706 def push_function(self, namespace):
710 pass
707 pass
711
708
712 @queue
709 @queue
713 def pull_function(self, keys):
710 def pull_function(self, keys):
714 pass
711 pass
715
712
716 def get_result(self, i=None):
713 def get_result(self, i=None):
717 if i is None:
714 if i is None:
718 i = max(self.history.keys()+[None])
715 i = max(self.history.keys()+[None])
719
716
720 cmd = self.history.get(i, None)
717 cmd = self.history.get(i, None)
721 # Uncomment this line to disable chaching of results
718 # Uncomment this line to disable chaching of results
722 #cmd = None
719 #cmd = None
723 if cmd is None:
720 if cmd is None:
724 return self.submitCommand(Command('get_result', i))
721 return self.submitCommand(Command('get_result', i))
725 else:
722 else:
726 return defer.succeed(cmd)
723 return defer.succeed(cmd)
727
724
728 def reset(self):
725 def reset(self):
729 self.clear_queue()
726 self.clear_queue()
730 self.history = {} # reset the cache - I am not sure we should do this
727 self.history = {} # reset the cache - I am not sure we should do this
731 return self.submitCommand(Command('reset'))
728 return self.submitCommand(Command('reset'))
732
729
733 def kill(self):
730 def kill(self):
734 self.clear_queue()
731 self.clear_queue()
735 return self.submitCommand(Command('kill'))
732 return self.submitCommand(Command('kill'))
736
733
737 @queue
734 @queue
738 def keys(self):
735 def keys(self):
739 pass
736 pass
740
737
741 #---------------------------------------------------------------------------
738 #---------------------------------------------------------------------------
742 # IEngineSerialized methods
739 # IEngineSerialized methods
743 #---------------------------------------------------------------------------
740 #---------------------------------------------------------------------------
744
741
745 @queue
742 @queue
746 def push_serialized(self, namespace):
743 def push_serialized(self, namespace):
747 pass
744 pass
748
745
749 @queue
746 @queue
750 def pull_serialized(self, keys):
747 def pull_serialized(self, keys):
751 pass
748 pass
752
749
753 #---------------------------------------------------------------------------
750 #---------------------------------------------------------------------------
754 # IEngineProperties methods
751 # IEngineProperties methods
755 #---------------------------------------------------------------------------
752 #---------------------------------------------------------------------------
756
753
757 @queue
754 @queue
758 def set_properties(self, namespace):
755 def set_properties(self, namespace):
759 pass
756 pass
760
757
761 @queue
758 @queue
762 def get_properties(self, keys=None):
759 def get_properties(self, keys=None):
763 pass
760 pass
764
761
765 @queue
762 @queue
766 def del_properties(self, keys):
763 def del_properties(self, keys):
767 pass
764 pass
768
765
769 @queue
766 @queue
770 def has_properties(self, keys):
767 def has_properties(self, keys):
771 pass
768 pass
772
769
773 @queue
770 @queue
774 def clear_properties(self):
771 def clear_properties(self):
775 pass
772 pass
776
773
777 #---------------------------------------------------------------------------
774 #---------------------------------------------------------------------------
778 # IQueuedEngine methods
775 # IQueuedEngine methods
779 #---------------------------------------------------------------------------
776 #---------------------------------------------------------------------------
780
777
781 def clear_queue(self, msg=''):
778 def clear_queue(self, msg=''):
782 """Clear the queue, but doesn't cancel the currently running commmand."""
779 """Clear the queue, but doesn't cancel the currently running commmand."""
783
780
784 for cmd in self.queued:
781 for cmd in self.queued:
785 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
782 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
786 self.queued = []
783 self.queued = []
787 return defer.succeed(None)
784 return defer.succeed(None)
788
785
789 def queue_status(self):
786 def queue_status(self):
790 if self.currentCommand is not None:
787 if self.currentCommand is not None:
791 if self.currentCommand.finished:
788 if self.currentCommand.finished:
792 pending = repr(None)
789 pending = repr(None)
793 else:
790 else:
794 pending = repr(self.currentCommand)
791 pending = repr(self.currentCommand)
795 else:
792 else:
796 pending = repr(None)
793 pending = repr(None)
797 dikt = {'queue':map(repr,self.queued), 'pending':pending}
794 dikt = {'queue':map(repr,self.queued), 'pending':pending}
798 return defer.succeed(dikt)
795 return defer.succeed(dikt)
799
796
800 def register_failure_observer(self, obs):
797 def register_failure_observer(self, obs):
801 self.failureObservers.append(obs)
798 self.failureObservers.append(obs)
802
799
803 def unregister_failure_observer(self, obs):
800 def unregister_failure_observer(self, obs):
804 self.failureObservers.remove(obs)
801 self.failureObservers.remove(obs)
805
802
806
803
807 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
804 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
808 # IEngineQueued.
805 # IEngineQueued.
809 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
806 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
810
807
811
808
812 class Command(object):
809 class Command(object):
813 """A command object that encapslates queued commands.
810 """A command object that encapslates queued commands.
814
811
815 This class basically keeps track of a command that has been queued
812 This class basically keeps track of a command that has been queued
816 in a QueuedEngine. It manages the deferreds and hold the method to be called
813 in a QueuedEngine. It manages the deferreds and hold the method to be called
817 and the arguments to that method.
814 and the arguments to that method.
818 """
815 """
819
816
820
817
821 def __init__(self, remoteMethod, *args, **kwargs):
818 def __init__(self, remoteMethod, *args, **kwargs):
822 """Build a new Command object."""
819 """Build a new Command object."""
823
820
824 self.remoteMethod = remoteMethod
821 self.remoteMethod = remoteMethod
825 self.args = args
822 self.args = args
826 self.kwargs = kwargs
823 self.kwargs = kwargs
827 self.finished = False
824 self.finished = False
828
825
829 def setDeferred(self, d):
826 def setDeferred(self, d):
830 """Sets the deferred attribute of the Command."""
827 """Sets the deferred attribute of the Command."""
831
828
832 self.deferred = d
829 self.deferred = d
833
830
834 def __repr__(self):
831 def __repr__(self):
835 if not self.args:
832 if not self.args:
836 args = ''
833 args = ''
837 else:
834 else:
838 args = str(self.args)[1:-2] #cut off (...,)
835 args = str(self.args)[1:-2] #cut off (...,)
839 for k,v in self.kwargs.iteritems():
836 for k,v in self.kwargs.iteritems():
840 if args:
837 if args:
841 args += ', '
838 args += ', '
842 args += '%s=%r' %(k,v)
839 args += '%s=%r' %(k,v)
843 return "%s(%s)" %(self.remoteMethod, args)
840 return "%s(%s)" %(self.remoteMethod, args)
844
841
845 def handleResult(self, result):
842 def handleResult(self, result):
846 """When the result is ready, relay it to self.deferred."""
843 """When the result is ready, relay it to self.deferred."""
847
844
848 self.deferred.callback(result)
845 self.deferred.callback(result)
849
846
850 def handleError(self, reason):
847 def handleError(self, reason):
851 """When an error has occured, relay it to self.deferred."""
848 """When an error has occured, relay it to self.deferred."""
852
849
853 self.deferred.errback(reason)
850 self.deferred.errback(reason)
854
851
855 class ThreadedEngineService(EngineService):
852 class ThreadedEngineService(EngineService):
856 """An EngineService subclass that defers execute commands to a separate
853 """An EngineService subclass that defers execute commands to a separate
857 thread.
854 thread.
858
855
859 ThreadedEngineService uses twisted.internet.threads.deferToThread to
856 ThreadedEngineService uses twisted.internet.threads.deferToThread to
860 defer execute requests to a separate thread. GUI frontends may want to
857 defer execute requests to a separate thread. GUI frontends may want to
861 use ThreadedEngineService as the engine in an
858 use ThreadedEngineService as the engine in an
862 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
859 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
863 block execution from blocking the GUI thread.
860 block execution from blocking the GUI thread.
864 """
861 """
865
862
866 zi.implements(IEngineBase)
863 zi.implements(IEngineBase)
867
864
868 def __init__(self, shellClass=Interpreter, mpi=None):
865 def __init__(self, shellClass=Interpreter, mpi=None):
869 EngineService.__init__(self, shellClass, mpi)
866 EngineService.__init__(self, shellClass, mpi)
870
867
871 def wrapped_execute(self, msg, lines):
868 def wrapped_execute(self, msg, lines):
872 """Wrap self.shell.execute to add extra information to tracebacks"""
869 """Wrap self.shell.execute to add extra information to tracebacks"""
873
870
874 try:
871 try:
875 result = self.shell.execute(lines)
872 result = self.shell.execute(lines)
876 except Exception,e:
873 except Exception,e:
877 # This gives the following:
874 # This gives the following:
878 # et=exception class
875 # et=exception class
879 # ev=exception class instance
876 # ev=exception class instance
880 # tb=traceback object
877 # tb=traceback object
881 et,ev,tb = sys.exc_info()
878 et,ev,tb = sys.exc_info()
882 # This call adds attributes to the exception value
879 # This call adds attributes to the exception value
883 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
880 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
884 # Add another attribute
881 # Add another attribute
885
882
886 # Create a new exception with the new attributes
883 # Create a new exception with the new attributes
887 e = et(ev._ipython_traceback_text)
884 e = et(ev._ipython_traceback_text)
888 e._ipython_engine_info = msg
885 e._ipython_engine_info = msg
889
886
890 # Re-raise
887 # Re-raise
891 raise e
888 raise e
892
889
893 return result
890 return result
894
891
895
892
896 def execute(self, lines):
893 def execute(self, lines):
897 # Only import this if we are going to use this class
894 # Only import this if we are going to use this class
898 from twisted.internet import threads
895 from twisted.internet import threads
899
896
900 msg = {'engineid':self.id,
897 msg = {'engineid':self.id,
901 'method':'execute',
898 'method':'execute',
902 'args':[lines]}
899 'args':[lines]}
903
900
904 d = threads.deferToThread(self.wrapped_execute, msg, lines)
901 d = threads.deferToThread(self.wrapped_execute, msg, lines)
905 d.addCallback(self.addIDToResult)
902 d.addCallback(self.addIDToResult)
906 return d
903 return d
@@ -1,202 +1,205 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Classes and functions for kernel related errors and exceptions."""
3 """Classes and functions for kernel related errors and exceptions."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 # Tell nose to skip this module
8 __test__ = {}
9
7 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
11 # Copyright (C) 2008 The IPython Development Team
9 #
12 #
10 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
13
16
14 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
15 # Imports
18 # Imports
16 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 from twisted.python import failure
17
21
18 from IPython.kernel.core import error
22 from IPython.kernel.core import error
19 from twisted.python import failure
20
23
21 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
22 # Error classes
25 # Error classes
23 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
24
27
25 class KernelError(error.IPythonError):
28 class KernelError(error.IPythonError):
26 pass
29 pass
27
30
28 class NotDefined(KernelError):
31 class NotDefined(KernelError):
29 def __init__(self, name):
32 def __init__(self, name):
30 self.name = name
33 self.name = name
31 self.args = (name,)
34 self.args = (name,)
32
35
33 def __repr__(self):
36 def __repr__(self):
34 return '<NotDefined: %s>' % self.name
37 return '<NotDefined: %s>' % self.name
35
38
36 __str__ = __repr__
39 __str__ = __repr__
37
40
38 class QueueCleared(KernelError):
41 class QueueCleared(KernelError):
39 pass
42 pass
40
43
41 class IdInUse(KernelError):
44 class IdInUse(KernelError):
42 pass
45 pass
43
46
44 class ProtocolError(KernelError):
47 class ProtocolError(KernelError):
45 pass
48 pass
46
49
47 class ConnectionError(KernelError):
50 class ConnectionError(KernelError):
48 pass
51 pass
49
52
50 class InvalidEngineID(KernelError):
53 class InvalidEngineID(KernelError):
51 pass
54 pass
52
55
53 class NoEnginesRegistered(KernelError):
56 class NoEnginesRegistered(KernelError):
54 pass
57 pass
55
58
56 class InvalidClientID(KernelError):
59 class InvalidClientID(KernelError):
57 pass
60 pass
58
61
59 class InvalidDeferredID(KernelError):
62 class InvalidDeferredID(KernelError):
60 pass
63 pass
61
64
62 class SerializationError(KernelError):
65 class SerializationError(KernelError):
63 pass
66 pass
64
67
65 class MessageSizeError(KernelError):
68 class MessageSizeError(KernelError):
66 pass
69 pass
67
70
68 class PBMessageSizeError(MessageSizeError):
71 class PBMessageSizeError(MessageSizeError):
69 pass
72 pass
70
73
71 class ResultNotCompleted(KernelError):
74 class ResultNotCompleted(KernelError):
72 pass
75 pass
73
76
74 class ResultAlreadyRetrieved(KernelError):
77 class ResultAlreadyRetrieved(KernelError):
75 pass
78 pass
76
79
77 class ClientError(KernelError):
80 class ClientError(KernelError):
78 pass
81 pass
79
82
80 class TaskAborted(KernelError):
83 class TaskAborted(KernelError):
81 pass
84 pass
82
85
83 class TaskTimeout(KernelError):
86 class TaskTimeout(KernelError):
84 pass
87 pass
85
88
86 class NotAPendingResult(KernelError):
89 class NotAPendingResult(KernelError):
87 pass
90 pass
88
91
89 class UnpickleableException(KernelError):
92 class UnpickleableException(KernelError):
90 pass
93 pass
91
94
92 class AbortedPendingDeferredError(KernelError):
95 class AbortedPendingDeferredError(KernelError):
93 pass
96 pass
94
97
95 class InvalidProperty(KernelError):
98 class InvalidProperty(KernelError):
96 pass
99 pass
97
100
98 class MissingBlockArgument(KernelError):
101 class MissingBlockArgument(KernelError):
99 pass
102 pass
100
103
101 class StopLocalExecution(KernelError):
104 class StopLocalExecution(KernelError):
102 pass
105 pass
103
106
104 class SecurityError(KernelError):
107 class SecurityError(KernelError):
105 pass
108 pass
106
109
107 class FileTimeoutError(KernelError):
110 class FileTimeoutError(KernelError):
108 pass
111 pass
109
112
110 class TaskRejectError(KernelError):
113 class TaskRejectError(KernelError):
111 """Exception to raise when a task should be rejected by an engine.
114 """Exception to raise when a task should be rejected by an engine.
112
115
113 This exception can be used to allow a task running on an engine to test
116 This exception can be used to allow a task running on an engine to test
114 if the engine (or the user's namespace on the engine) has the needed
117 if the engine (or the user's namespace on the engine) has the needed
115 task dependencies. If not, the task should raise this exception. For
118 task dependencies. If not, the task should raise this exception. For
116 the task to be retried on another engine, the task should be created
119 the task to be retried on another engine, the task should be created
117 with the `retries` argument > 1.
120 with the `retries` argument > 1.
118
121
119 The advantage of this approach over our older properties system is that
122 The advantage of this approach over our older properties system is that
120 tasks have full access to the user's namespace on the engines and the
123 tasks have full access to the user's namespace on the engines and the
121 properties don't have to be managed or tested by the controller.
124 properties don't have to be managed or tested by the controller.
122 """
125 """
123
126
124 class CompositeError(KernelError):
127 class CompositeError(KernelError):
125 def __init__(self, message, elist):
128 def __init__(self, message, elist):
126 Exception.__init__(self, *(message, elist))
129 Exception.__init__(self, *(message, elist))
127 self.message = message
130 self.message = message
128 self.elist = elist
131 self.elist = elist
129
132
130 def _get_engine_str(self, ev):
133 def _get_engine_str(self, ev):
131 try:
134 try:
132 ei = ev._ipython_engine_info
135 ei = ev._ipython_engine_info
133 except AttributeError:
136 except AttributeError:
134 return '[Engine Exception]'
137 return '[Engine Exception]'
135 else:
138 else:
136 return '[%i:%s]: ' % (ei['engineid'], ei['method'])
139 return '[%i:%s]: ' % (ei['engineid'], ei['method'])
137
140
138 def _get_traceback(self, ev):
141 def _get_traceback(self, ev):
139 try:
142 try:
140 tb = ev._ipython_traceback_text
143 tb = ev._ipython_traceback_text
141 except AttributeError:
144 except AttributeError:
142 return 'No traceback available'
145 return 'No traceback available'
143 else:
146 else:
144 return tb
147 return tb
145
148
146 def __str__(self):
149 def __str__(self):
147 s = str(self.message)
150 s = str(self.message)
148 for et, ev, etb in self.elist:
151 for et, ev, etb in self.elist:
149 engine_str = self._get_engine_str(ev)
152 engine_str = self._get_engine_str(ev)
150 s = s + '\n' + engine_str + str(et.__name__) + ': ' + str(ev)
153 s = s + '\n' + engine_str + str(et.__name__) + ': ' + str(ev)
151 return s
154 return s
152
155
153 def print_tracebacks(self, excid=None):
156 def print_tracebacks(self, excid=None):
154 if excid is None:
157 if excid is None:
155 for (et,ev,etb) in self.elist:
158 for (et,ev,etb) in self.elist:
156 print self._get_engine_str(ev)
159 print self._get_engine_str(ev)
157 print self._get_traceback(ev)
160 print self._get_traceback(ev)
158 print
161 print
159 else:
162 else:
160 try:
163 try:
161 et,ev,etb = self.elist[excid]
164 et,ev,etb = self.elist[excid]
162 except:
165 except:
163 raise IndexError("an exception with index %i does not exist"%excid)
166 raise IndexError("an exception with index %i does not exist"%excid)
164 else:
167 else:
165 print self._get_engine_str(ev)
168 print self._get_engine_str(ev)
166 print self._get_traceback(ev)
169 print self._get_traceback(ev)
167
170
168 def raise_exception(self, excid=0):
171 def raise_exception(self, excid=0):
169 try:
172 try:
170 et,ev,etb = self.elist[excid]
173 et,ev,etb = self.elist[excid]
171 except:
174 except:
172 raise IndexError("an exception with index %i does not exist"%excid)
175 raise IndexError("an exception with index %i does not exist"%excid)
173 else:
176 else:
174 raise et, ev, etb
177 raise et, ev, etb
175
178
176 def collect_exceptions(rlist, method):
179 def collect_exceptions(rlist, method):
177 elist = []
180 elist = []
178 for r in rlist:
181 for r in rlist:
179 if isinstance(r, failure.Failure):
182 if isinstance(r, failure.Failure):
180 r.cleanFailure()
183 r.cleanFailure()
181 et, ev, etb = r.type, r.value, r.tb
184 et, ev, etb = r.type, r.value, r.tb
182 # Sometimes we could have CompositeError in our list. Just take
185 # Sometimes we could have CompositeError in our list. Just take
183 # the errors out of them and put them in our new list. This
186 # the errors out of them and put them in our new list. This
184 # has the effect of flattening lists of CompositeErrors into one
187 # has the effect of flattening lists of CompositeErrors into one
185 # CompositeError
188 # CompositeError
186 if et==CompositeError:
189 if et==CompositeError:
187 for e in ev.elist:
190 for e in ev.elist:
188 elist.append(e)
191 elist.append(e)
189 else:
192 else:
190 elist.append((et, ev, etb))
193 elist.append((et, ev, etb))
191 if len(elist)==0:
194 if len(elist)==0:
192 return rlist
195 return rlist
193 else:
196 else:
194 msg = "one or more exceptions from call to method: %s" % (method)
197 msg = "one or more exceptions from call to method: %s" % (method)
195 # This silliness is needed so the debugger has access to the exception
198 # This silliness is needed so the debugger has access to the exception
196 # instance (e in this case)
199 # instance (e in this case)
197 try:
200 try:
198 raise CompositeError(msg, elist)
201 raise CompositeError(msg, elist)
199 except CompositeError, e:
202 except CompositeError, e:
200 raise e
203 raise e
201
204
202
205
@@ -1,163 +1,170 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_newserialized -*-
2 # -*- test-case-name: IPython.kernel.test.test_newserialized -*-
3
3
4 """Refactored serialization classes and interfaces."""
4 """Refactored serialization classes and interfaces."""
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 # Tell nose to skip this module
9 __test__ = {}
10
8 #-------------------------------------------------------------------------------
11 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
12 # Copyright (C) 2008 The IPython Development Team
10 #
13 #
11 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
14
17
15 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
16 # Imports
19 # Imports
17 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
18
21
19 import cPickle as pickle
22 import cPickle as pickle
20
23
21 from zope.interface import Interface, implements
22 from twisted.python import components
24 from twisted.python import components
25 from zope.interface import Interface, implements
23
26
24 try:
27 try:
25 import numpy
28 import numpy
26 except ImportError:
29 except ImportError:
27 pass
30 pass
28
31
29 from IPython.kernel.error import SerializationError
32 from IPython.kernel.error import SerializationError
30
33
34 #-----------------------------------------------------------------------------
35 # Classes and functions
36 #-----------------------------------------------------------------------------
37
31 class ISerialized(Interface):
38 class ISerialized(Interface):
32
39
33 def getData():
40 def getData():
34 """"""
41 """"""
35
42
36 def getDataSize(units=10.0**6):
43 def getDataSize(units=10.0**6):
37 """"""
44 """"""
38
45
39 def getTypeDescriptor():
46 def getTypeDescriptor():
40 """"""
47 """"""
41
48
42 def getMetadata():
49 def getMetadata():
43 """"""
50 """"""
44
51
45
52
46 class IUnSerialized(Interface):
53 class IUnSerialized(Interface):
47
54
48 def getObject():
55 def getObject():
49 """"""
56 """"""
50
57
51 class Serialized(object):
58 class Serialized(object):
52
59
53 implements(ISerialized)
60 implements(ISerialized)
54
61
55 def __init__(self, data, typeDescriptor, metadata={}):
62 def __init__(self, data, typeDescriptor, metadata={}):
56 self.data = data
63 self.data = data
57 self.typeDescriptor = typeDescriptor
64 self.typeDescriptor = typeDescriptor
58 self.metadata = metadata
65 self.metadata = metadata
59
66
60 def getData(self):
67 def getData(self):
61 return self.data
68 return self.data
62
69
63 def getDataSize(self, units=10.0**6):
70 def getDataSize(self, units=10.0**6):
64 return len(self.data)/units
71 return len(self.data)/units
65
72
66 def getTypeDescriptor(self):
73 def getTypeDescriptor(self):
67 return self.typeDescriptor
74 return self.typeDescriptor
68
75
69 def getMetadata(self):
76 def getMetadata(self):
70 return self.metadata
77 return self.metadata
71
78
72
79
73 class UnSerialized(object):
80 class UnSerialized(object):
74
81
75 implements(IUnSerialized)
82 implements(IUnSerialized)
76
83
77 def __init__(self, obj):
84 def __init__(self, obj):
78 self.obj = obj
85 self.obj = obj
79
86
80 def getObject(self):
87 def getObject(self):
81 return self.obj
88 return self.obj
82
89
83
90
84 class SerializeIt(object):
91 class SerializeIt(object):
85
92
86 implements(ISerialized)
93 implements(ISerialized)
87
94
88 def __init__(self, unSerialized):
95 def __init__(self, unSerialized):
89 self.data = None
96 self.data = None
90 self.obj = unSerialized.getObject()
97 self.obj = unSerialized.getObject()
91 if globals().has_key('numpy'):
98 if globals().has_key('numpy'):
92 if isinstance(self.obj, numpy.ndarray):
99 if isinstance(self.obj, numpy.ndarray):
93 if len(self.obj) == 0: # length 0 arrays can't be reconstructed
100 if len(self.obj) == 0: # length 0 arrays can't be reconstructed
94 raise SerializationError("You cannot send a length 0 array")
101 raise SerializationError("You cannot send a length 0 array")
95 self.obj = numpy.ascontiguousarray(self.obj, dtype=None)
102 self.obj = numpy.ascontiguousarray(self.obj, dtype=None)
96 self.typeDescriptor = 'ndarray'
103 self.typeDescriptor = 'ndarray'
97 self.metadata = {'shape':self.obj.shape,
104 self.metadata = {'shape':self.obj.shape,
98 'dtype':self.obj.dtype.str}
105 'dtype':self.obj.dtype.str}
99 else:
106 else:
100 self.typeDescriptor = 'pickle'
107 self.typeDescriptor = 'pickle'
101 self.metadata = {}
108 self.metadata = {}
102 else:
109 else:
103 self.typeDescriptor = 'pickle'
110 self.typeDescriptor = 'pickle'
104 self.metadata = {}
111 self.metadata = {}
105 self._generateData()
112 self._generateData()
106
113
107 def _generateData(self):
114 def _generateData(self):
108 if self.typeDescriptor == 'ndarray':
115 if self.typeDescriptor == 'ndarray':
109 self.data = numpy.getbuffer(self.obj)
116 self.data = numpy.getbuffer(self.obj)
110 elif self.typeDescriptor == 'pickle':
117 elif self.typeDescriptor == 'pickle':
111 self.data = pickle.dumps(self.obj, 2)
118 self.data = pickle.dumps(self.obj, 2)
112 else:
119 else:
113 raise SerializationError("Really wierd serialization error.")
120 raise SerializationError("Really wierd serialization error.")
114 del self.obj
121 del self.obj
115
122
116 def getData(self):
123 def getData(self):
117 return self.data
124 return self.data
118
125
119 def getDataSize(self, units=10.0**6):
126 def getDataSize(self, units=10.0**6):
120 return len(self.data)/units
127 return len(self.data)/units
121
128
122 def getTypeDescriptor(self):
129 def getTypeDescriptor(self):
123 return self.typeDescriptor
130 return self.typeDescriptor
124
131
125 def getMetadata(self):
132 def getMetadata(self):
126 return self.metadata
133 return self.metadata
127
134
128
135
129 class UnSerializeIt(UnSerialized):
136 class UnSerializeIt(UnSerialized):
130
137
131 implements(IUnSerialized)
138 implements(IUnSerialized)
132
139
133 def __init__(self, serialized):
140 def __init__(self, serialized):
134 self.serialized = serialized
141 self.serialized = serialized
135
142
136 def getObject(self):
143 def getObject(self):
137 typeDescriptor = self.serialized.getTypeDescriptor()
144 typeDescriptor = self.serialized.getTypeDescriptor()
138 if globals().has_key('numpy'):
145 if globals().has_key('numpy'):
139 if typeDescriptor == 'ndarray':
146 if typeDescriptor == 'ndarray':
140 result = numpy.frombuffer(self.serialized.getData(), dtype = self.serialized.metadata['dtype'])
147 result = numpy.frombuffer(self.serialized.getData(), dtype = self.serialized.metadata['dtype'])
141 result.shape = self.serialized.metadata['shape']
148 result.shape = self.serialized.metadata['shape']
142 # This is a hack to make the array writable. We are working with
149 # This is a hack to make the array writable. We are working with
143 # the numpy folks to address this issue.
150 # the numpy folks to address this issue.
144 result = result.copy()
151 result = result.copy()
145 elif typeDescriptor == 'pickle':
152 elif typeDescriptor == 'pickle':
146 result = pickle.loads(self.serialized.getData())
153 result = pickle.loads(self.serialized.getData())
147 else:
154 else:
148 raise SerializationError("Really wierd serialization error.")
155 raise SerializationError("Really wierd serialization error.")
149 elif typeDescriptor == 'pickle':
156 elif typeDescriptor == 'pickle':
150 result = pickle.loads(self.serialized.getData())
157 result = pickle.loads(self.serialized.getData())
151 else:
158 else:
152 raise SerializationError("Really wierd serialization error.")
159 raise SerializationError("Really wierd serialization error.")
153 return result
160 return result
154
161
155 components.registerAdapter(UnSerializeIt, ISerialized, IUnSerialized)
162 components.registerAdapter(UnSerializeIt, ISerialized, IUnSerialized)
156
163
157 components.registerAdapter(SerializeIt, IUnSerialized, ISerialized)
164 components.registerAdapter(SerializeIt, IUnSerialized, ISerialized)
158
165
159 def serialize(obj):
166 def serialize(obj):
160 return ISerialized(UnSerialized(obj))
167 return ISerialized(UnSerialized(obj))
161
168
162 def unserialize(serialized):
169 def unserialize(serialized):
163 return IUnSerialized(serialized).getObject()
170 return IUnSerialized(serialized).getObject()
@@ -1,306 +1,327 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) or trial recursively. This
8 calling this script (with different arguments) or trial recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 For now, this script requires that both nose and twisted are installed. This
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
16 will change in the future.
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Module imports
20 # Module imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import os.path as path
24 import os.path as path
25 import sys
25 import sys
26 import subprocess
26 import subprocess
27 import tempfile
27 import tempfile
28 import time
28 import time
29 import warnings
29 import warnings
30
30
31 import nose.plugins.builtin
31 import nose.plugins.builtin
32 from nose.core import TestProgram
32 from nose.core import TestProgram
33
33
34 from IPython.utils.platutils import find_cmd
34 from IPython.utils.platutils import find_cmd
35 from IPython.testing.plugin.ipdoctest import IPythonDoctest
35 from IPython.testing.plugin.ipdoctest import IPythonDoctest
36
36
37 pjoin = path.join
37 pjoin = path.join
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Logic for skipping doctests
40 # Logic for skipping doctests
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 def test_for(mod):
43 def test_for(mod):
44 """Test to see if mod is importable."""
44 """Test to see if mod is importable."""
45 try:
45 try:
46 __import__(mod)
46 __import__(mod)
47 except ImportError:
47 except ImportError:
48 return False
48 return False
49 else:
49 else:
50 return True
50 return True
51
51
52 have_curses = test_for('_curses')
52 have_curses = test_for('_curses')
53 have_wx = test_for('wx')
53 have_wx = test_for('wx')
54 have_wx_aui = test_for('wx.aui')
54 have_wx_aui = test_for('wx.aui')
55 have_zi = test_for('zope.interface')
55 have_zi = test_for('zope.interface')
56 have_twisted = test_for('twisted')
56 have_twisted = test_for('twisted')
57 have_foolscap = test_for('foolscap')
57 have_foolscap = test_for('foolscap')
58 have_objc = test_for('objc')
58 have_objc = test_for('objc')
59 have_pexpect = test_for('pexpect')
59 have_pexpect = test_for('pexpect')
60
60
61
61
62 def make_exclude():
62 def make_exclude():
63
63
64 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
64 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
65 # testing problems. We should strive to minimize the number of skipped
65 # testing problems. We should strive to minimize the number of skipped
66 # modules, since this means untested code. As the testing machinery
66 # modules, since this means untested code. As the testing machinery
67 # solidifies, this list should eventually become empty.
67 # solidifies, this list should eventually become empty.
68 EXCLUDE = [pjoin('IPython', 'external'),
68 EXCLUDE = [pjoin('IPython', 'external'),
69 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
69 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
70 pjoin('IPython_doctest_plugin'),
70 pjoin('IPython_doctest_plugin'),
71 pjoin('IPython', 'extensions', 'ipy_'),
71 pjoin('IPython', 'extensions', 'ipy_'),
72 pjoin('IPython', 'extensions', 'PhysicalQInput'),
72 pjoin('IPython', 'extensions', 'PhysicalQInput'),
73 pjoin('IPython', 'extensions', 'PhysicalQInteractive'),
73 pjoin('IPython', 'extensions', 'PhysicalQInteractive'),
74 pjoin('IPython', 'extensions', 'InterpreterPasteInput'),
74 pjoin('IPython', 'extensions', 'InterpreterPasteInput'),
75 pjoin('IPython', 'extensions', 'scitedirector'),
75 pjoin('IPython', 'extensions', 'scitedirector'),
76 pjoin('IPython', 'extensions', 'numeric_formats'),
76 pjoin('IPython', 'extensions', 'numeric_formats'),
77 pjoin('IPython', 'testing', 'attic'),
77 pjoin('IPython', 'testing', 'attic'),
78 pjoin('IPython', 'testing', 'tools'),
78 pjoin('IPython', 'testing', 'tools'),
79 pjoin('IPython', 'testing', 'mkdoctests')
79 pjoin('IPython', 'testing', 'mkdoctests')
80 ]
80 ]
81
81
82 if not have_wx:
82 if not have_wx:
83 EXCLUDE.append(pjoin('IPython', 'extensions', 'igrid'))
83 EXCLUDE.append(pjoin('IPython', 'extensions', 'igrid'))
84 EXCLUDE.append(pjoin('IPython', 'gui'))
84 EXCLUDE.append(pjoin('IPython', 'gui'))
85 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
85 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
86
86
87 if not have_wx_aui:
87 if not have_wx_aui:
88 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
88 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
89
89
90 if not have_objc:
90 if not have_objc:
91 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
91 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
92
92
93 if not have_curses:
93 if not have_curses:
94 EXCLUDE.append(pjoin('IPython', 'extensions', 'ibrowse'))
94 EXCLUDE.append(pjoin('IPython', 'extensions', 'ibrowse'))
95
95
96 if not sys.platform == 'win32':
96 if not sys.platform == 'win32':
97 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32'))
97 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32'))
98
98
99 # These have to be skipped on win32 because the use echo, rm, cd, etc.
99 # These have to be skipped on win32 because the use echo, rm, cd, etc.
100 # See ticket https://bugs.launchpad.net/bugs/366982
100 # See ticket https://bugs.launchpad.net/bugs/366982
101 if sys.platform == 'win32':
101 if sys.platform == 'win32':
102 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
102 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
103 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
103 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
104
104
105 if not os.name == 'posix':
105 if not os.name == 'posix':
106 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix'))
106 EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix'))
107
107
108 if not have_pexpect:
108 if not have_pexpect:
109 EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner'))
109 EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner'))
110
110
111 # This is scary. We still have things in frontend and testing that
112 # are being tested by nose that use twisted. We need to rethink
113 # how we are isolating dependencies in testing.
114 if not (have_twisted and have_zi and have_foolscap):
115 EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
116 EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
117 EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase'))
118 EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
119 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
120 'test_linefrontend'))
121 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
122 'test_frontendbase'))
123 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
124 'test_prefilterfrontend'))
125 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
126 'test_asyncfrontendbase')),
127 EXCLUDE.append(pjoin('IPython', 'testing', 'parametric'))
128 EXCLUDE.append(pjoin('IPython', 'testing', 'util'))
129 EXCLUDE.append(pjoin('IPython', 'testing', 'tests',
130 'test_decorators_trial'))
131
111 # Skip shell always because of a bug in FakeModule.
132 # Skip shell always because of a bug in FakeModule.
112 EXCLUDE.append(pjoin('IPython', 'core', 'shell'))
133 EXCLUDE.append(pjoin('IPython', 'core', 'shell'))
113
134
114 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
135 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
115 if sys.platform == 'win32':
136 if sys.platform == 'win32':
116 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
137 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
117
138
118 return EXCLUDE
139 return EXCLUDE
119
140
120
141
121 #-----------------------------------------------------------------------------
142 #-----------------------------------------------------------------------------
122 # Functions and classes
143 # Functions and classes
123 #-----------------------------------------------------------------------------
144 #-----------------------------------------------------------------------------
124
145
125 def run_iptest():
146 def run_iptest():
126 """Run the IPython test suite using nose.
147 """Run the IPython test suite using nose.
127
148
128 This function is called when this script is **not** called with the form
149 This function is called when this script is **not** called with the form
129 `iptest all`. It simply calls nose with appropriate command line flags
150 `iptest all`. It simply calls nose with appropriate command line flags
130 and accepts all of the standard nose arguments.
151 and accepts all of the standard nose arguments.
131 """
152 """
132
153
133 warnings.filterwarnings('ignore',
154 warnings.filterwarnings('ignore',
134 'This will be removed soon. Use IPython.testing.util instead')
155 'This will be removed soon. Use IPython.testing.util instead')
135
156
136 argv = sys.argv + [
157 argv = sys.argv + [
137 # Loading ipdoctest causes problems with Twisted.
158 # Loading ipdoctest causes problems with Twisted.
138 # I am removing this as a temporary fix to get the
159 # I am removing this as a temporary fix to get the
139 # test suite back into working shape. Our nose
160 # test suite back into working shape. Our nose
140 # plugin needs to be gone through with a fine
161 # plugin needs to be gone through with a fine
141 # toothed comb to find what is causing the problem.
162 # toothed comb to find what is causing the problem.
142 '--with-ipdoctest',
163 '--with-ipdoctest',
143 '--ipdoctest-tests','--ipdoctest-extension=txt',
164 '--ipdoctest-tests','--ipdoctest-extension=txt',
144 '--detailed-errors',
165 '--detailed-errors',
145
166
146 # We add --exe because of setuptools' imbecility (it
167 # We add --exe because of setuptools' imbecility (it
147 # blindly does chmod +x on ALL files). Nose does the
168 # blindly does chmod +x on ALL files). Nose does the
148 # right thing and it tries to avoid executables,
169 # right thing and it tries to avoid executables,
149 # setuptools unfortunately forces our hand here. This
170 # setuptools unfortunately forces our hand here. This
150 # has been discussed on the distutils list and the
171 # has been discussed on the distutils list and the
151 # setuptools devs refuse to fix this problem!
172 # setuptools devs refuse to fix this problem!
152 '--exe',
173 '--exe',
153 ]
174 ]
154
175
155 # Detect if any tests were required by explicitly calling an IPython
176 # Detect if any tests were required by explicitly calling an IPython
156 # submodule or giving a specific path
177 # submodule or giving a specific path
157 has_tests = False
178 has_tests = False
158 for arg in sys.argv:
179 for arg in sys.argv:
159 if 'IPython' in arg or arg.endswith('.py') or \
180 if 'IPython' in arg or arg.endswith('.py') or \
160 (':' in arg and '.py' in arg):
181 (':' in arg and '.py' in arg):
161 has_tests = True
182 has_tests = True
162 break
183 break
163
184
164 # If nothing was specifically requested, test full IPython
185 # If nothing was specifically requested, test full IPython
165 if not has_tests:
186 if not has_tests:
166 argv.append('IPython')
187 argv.append('IPython')
167
188
168 # Construct list of plugins, omitting the existing doctest plugin, which
189 # Construct list of plugins, omitting the existing doctest plugin, which
169 # ours replaces (and extends).
190 # ours replaces (and extends).
170 EXCLUDE = make_exclude()
191 EXCLUDE = make_exclude()
171 plugins = [IPythonDoctest(EXCLUDE)]
192 plugins = [IPythonDoctest(EXCLUDE)]
172 for p in nose.plugins.builtin.plugins:
193 for p in nose.plugins.builtin.plugins:
173 plug = p()
194 plug = p()
174 if plug.name == 'doctest':
195 if plug.name == 'doctest':
175 continue
196 continue
176 plugins.append(plug)
197 plugins.append(plug)
177
198
178 TestProgram(argv=argv,plugins=plugins)
199 TestProgram(argv=argv,plugins=plugins)
179
200
180
201
181 class IPTester(object):
202 class IPTester(object):
182 """Call that calls iptest or trial in a subprocess.
203 """Call that calls iptest or trial in a subprocess.
183 """
204 """
184 def __init__(self,runner='iptest',params=None):
205 def __init__(self,runner='iptest',params=None):
185 """ """
206 """ """
186 if runner == 'iptest':
207 if runner == 'iptest':
187 self.runner = ['iptest','-v']
208 self.runner = ['iptest','-v']
188 else:
209 else:
189 self.runner = [find_cmd('trial')]
210 self.runner = [find_cmd('trial')]
190 if params is None:
211 if params is None:
191 params = []
212 params = []
192 if isinstance(params,str):
213 if isinstance(params,str):
193 params = [params]
214 params = [params]
194 self.params = params
215 self.params = params
195
216
196 # Assemble call
217 # Assemble call
197 self.call_args = self.runner+self.params
218 self.call_args = self.runner+self.params
198
219
199 if sys.platform == 'win32':
220 if sys.platform == 'win32':
200 def run(self):
221 def run(self):
201 """Run the stored commands"""
222 """Run the stored commands"""
202 # On Windows, cd to temporary directory to run tests. Otherwise,
223 # On Windows, cd to temporary directory to run tests. Otherwise,
203 # Twisted's trial may not be able to execute 'trial IPython', since
224 # Twisted's trial may not be able to execute 'trial IPython', since
204 # it will confuse the IPython module name with the ipython
225 # it will confuse the IPython module name with the ipython
205 # execution scripts, because the windows file system isn't case
226 # execution scripts, because the windows file system isn't case
206 # sensitive.
227 # sensitive.
207 # We also use os.system instead of subprocess.call, because I was
228 # We also use os.system instead of subprocess.call, because I was
208 # having problems with subprocess and I just don't know enough
229 # having problems with subprocess and I just don't know enough
209 # about win32 to debug this reliably. Os.system may be the 'old
230 # about win32 to debug this reliably. Os.system may be the 'old
210 # fashioned' way to do it, but it works just fine. If someone
231 # fashioned' way to do it, but it works just fine. If someone
211 # later can clean this up that's fine, as long as the tests run
232 # later can clean this up that's fine, as long as the tests run
212 # reliably in win32.
233 # reliably in win32.
213 curdir = os.getcwd()
234 curdir = os.getcwd()
214 os.chdir(tempfile.gettempdir())
235 os.chdir(tempfile.gettempdir())
215 stat = os.system(' '.join(self.call_args))
236 stat = os.system(' '.join(self.call_args))
216 os.chdir(curdir)
237 os.chdir(curdir)
217 return stat
238 return stat
218 else:
239 else:
219 def run(self):
240 def run(self):
220 """Run the stored commands"""
241 """Run the stored commands"""
221 return subprocess.call(self.call_args)
242 return subprocess.call(self.call_args)
222
243
223
244
224 def make_runners():
245 def make_runners():
225 """Define the top-level packages that need to be tested.
246 """Define the top-level packages that need to be tested.
226 """
247 """
227
248
228 nose_packages = ['config', 'core', 'extensions',
249 nose_packages = ['config', 'core', 'extensions',
229 'frontend', 'lib', 'quarantine',
250 'frontend', 'lib', 'quarantine',
230 'scripts', 'testing', 'utils']
251 'scripts', 'testing', 'utils']
231 trial_packages = ['kernel']
252 trial_packages = ['kernel']
232
253
233 if have_wx:
254 if have_wx:
234 nose_packages.append('gui')
255 nose_packages.append('gui')
235
256
236 nose_packages = ['IPython.%s' % m for m in nose_packages ]
257 nose_packages = ['IPython.%s' % m for m in nose_packages ]
237 trial_packages = ['IPython.%s' % m for m in trial_packages ]
258 trial_packages = ['IPython.%s' % m for m in trial_packages ]
238
259
239 # Make runners
260 # Make runners
240 runners = dict()
261 runners = dict()
241
262
242 nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages]))
263 nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages]))
243 if have_zi and have_twisted and have_foolscap:
264 if have_zi and have_twisted and have_foolscap:
244 trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages]))
265 trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages]))
245 runners.update(nose_runners)
266 runners.update(nose_runners)
246 runners.update(trial_runners)
267 runners.update(trial_runners)
247
268
248 return runners
269 return runners
249
270
250
271
251 def run_iptestall():
272 def run_iptestall():
252 """Run the entire IPython test suite by calling nose and trial.
273 """Run the entire IPython test suite by calling nose and trial.
253
274
254 This function constructs :class:`IPTester` instances for all IPython
275 This function constructs :class:`IPTester` instances for all IPython
255 modules and package and then runs each of them. This causes the modules
276 modules and package and then runs each of them. This causes the modules
256 and packages of IPython to be tested each in their own subprocess using
277 and packages of IPython to be tested each in their own subprocess using
257 nose or twisted.trial appropriately.
278 nose or twisted.trial appropriately.
258 """
279 """
259
280
260 runners = make_runners()
281 runners = make_runners()
261
282
262 # Run all test runners, tracking execution time
283 # Run all test runners, tracking execution time
263 failed = {}
284 failed = {}
264 t_start = time.time()
285 t_start = time.time()
265 for name,runner in runners.iteritems():
286 for name,runner in runners.iteritems():
266 print '*'*77
287 print '*'*77
267 print 'IPython test group:',name
288 print 'IPython test group:',name
268 res = runner.run()
289 res = runner.run()
269 if res:
290 if res:
270 failed[name] = res
291 failed[name] = res
271 t_end = time.time()
292 t_end = time.time()
272 t_tests = t_end - t_start
293 t_tests = t_end - t_start
273 nrunners = len(runners)
294 nrunners = len(runners)
274 nfail = len(failed)
295 nfail = len(failed)
275 # summarize results
296 # summarize results
276 print
297 print
277 print '*'*77
298 print '*'*77
278 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
299 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
279 print
300 print
280 if not failed:
301 if not failed:
281 print 'OK'
302 print 'OK'
282 else:
303 else:
283 # If anything went wrong, point out what command to rerun manually to
304 # If anything went wrong, point out what command to rerun manually to
284 # see the actual errors and individual summary
305 # see the actual errors and individual summary
285 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
306 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
286 for name in failed:
307 for name in failed:
287 failed_runner = runners[name]
308 failed_runner = runners[name]
288 print '-'*40
309 print '-'*40
289 print 'Runner failed:',name
310 print 'Runner failed:',name
290 print 'You may wish to rerun this one individually, with:'
311 print 'You may wish to rerun this one individually, with:'
291 print ' '.join(failed_runner.call_args)
312 print ' '.join(failed_runner.call_args)
292 print
313 print
293
314
294
315
295 def main():
316 def main():
296 if len(sys.argv) == 1:
317 if len(sys.argv) == 1:
297 run_iptestall()
318 run_iptestall()
298 else:
319 else:
299 if sys.argv[1] == 'all':
320 if sys.argv[1] == 'all':
300 run_iptestall()
321 run_iptestall()
301 else:
322 else:
302 run_iptest()
323 run_iptest()
303
324
304
325
305 if __name__ == '__main__':
326 if __name__ == '__main__':
306 main()
327 main()
General Comments 0
You need to be logged in to leave comments. Login now