##// END OF EJS Templates
Fixes so the test suite runs when Twisted is not available....
Fernando Perez -
Show More
@@ -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,902 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
27 __test__ = {}
28
26 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
30 # Copyright (C) 2008 The IPython Development Team
28 #
31 #
29 # Distributed under the terms of the BSD License. The full license is in
32 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
33 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
32
35
33 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
34 # Imports
37 # Imports
35 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
36
39
37 # Tell nose to skip the testing of this module
40 import copy
38 __test__ = {}
41 import sys
39
40 import os, sys, copy
41 import cPickle as pickle
42 import cPickle as pickle
42 from new import instancemethod
43
43
44 from twisted.application import service
44 from twisted.application import service
45 from twisted.internet import defer, reactor
45 from twisted.internet import defer, reactor
46 from twisted.python import log, failure, components
46 from twisted.python import log, failure, components
47 import zope.interface as zi
47 import zope.interface as zi
48
48
49 from IPython.kernel.core.interpreter import Interpreter
49 from IPython.kernel.core.interpreter import Interpreter
50 from IPython.kernel import newserialized, error, util
50 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
51
56 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
57 # Interface specification for the Engine
53 # Interface specification for the Engine
58 #-------------------------------------------------------------------------------
54 #-------------------------------------------------------------------------------
59
55
60 class IEngineCore(zi.Interface):
56 class IEngineCore(zi.Interface):
61 """The minimal required interface for the IPython Engine.
57 """The minimal required interface for the IPython Engine.
62
58
63 This interface provides a formal specification of the IPython core.
59 This interface provides a formal specification of the IPython core.
64 All these methods should return deferreds regardless of what side of a
60 All these methods should return deferreds regardless of what side of a
65 network connection they are on.
61 network connection they are on.
66
62
67 In general, this class simply wraps a shell class and wraps its return
63 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
64 values as Deferred objects. If the underlying shell class method raises
69 an exception, this class should convert it to a twisted.failure.Failure
65 an exception, this class should convert it to a twisted.failure.Failure
70 that will be propagated along the Deferred's errback chain.
66 that will be propagated along the Deferred's errback chain.
71
67
72 In addition, Failures are aggressive. By this, we mean that if a method
68 In addition, Failures are aggressive. By this, we mean that if a method
73 is performing multiple actions (like pulling multiple object) if any
69 is performing multiple actions (like pulling multiple object) if any
74 single one fails, the entire method will fail with that Failure. It is
70 single one fails, the entire method will fail with that Failure. It is
75 all or nothing.
71 all or nothing.
76 """
72 """
77
73
78 id = zi.interface.Attribute("the id of the Engine object")
74 id = zi.interface.Attribute("the id of the Engine object")
79 properties = zi.interface.Attribute("A dict of properties of the Engine")
75 properties = zi.interface.Attribute("A dict of properties of the Engine")
80
76
81 def execute(lines):
77 def execute(lines):
82 """Execute lines of Python code.
78 """Execute lines of Python code.
83
79
84 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
80 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
85 upon success.
81 upon success.
86
82
87 Returns a failure object if the execution of lines raises an exception.
83 Returns a failure object if the execution of lines raises an exception.
88 """
84 """
89
85
90 def push(namespace):
86 def push(namespace):
91 """Push dict namespace into the user's namespace.
87 """Push dict namespace into the user's namespace.
92
88
93 Returns a deferred to None or a failure.
89 Returns a deferred to None or a failure.
94 """
90 """
95
91
96 def pull(keys):
92 def pull(keys):
97 """Pulls values out of the user's namespace by keys.
93 """Pulls values out of the user's namespace by keys.
98
94
99 Returns a deferred to a tuple objects or a single object.
95 Returns a deferred to a tuple objects or a single object.
100
96
101 Raises NameError if any one of objects doess not exist.
97 Raises NameError if any one of objects doess not exist.
102 """
98 """
103
99
104 def push_function(namespace):
100 def push_function(namespace):
105 """Push a dict of key, function pairs into the user's namespace.
101 """Push a dict of key, function pairs into the user's namespace.
106
102
107 Returns a deferred to None or a failure."""
103 Returns a deferred to None or a failure."""
108
104
109 def pull_function(keys):
105 def pull_function(keys):
110 """Pulls functions out of the user's namespace by keys.
106 """Pulls functions out of the user's namespace by keys.
111
107
112 Returns a deferred to a tuple of functions or a single function.
108 Returns a deferred to a tuple of functions or a single function.
113
109
114 Raises NameError if any one of the functions does not exist.
110 Raises NameError if any one of the functions does not exist.
115 """
111 """
116
112
117 def get_result(i=None):
113 def get_result(i=None):
118 """Get the stdin/stdout/stderr of command i.
114 """Get the stdin/stdout/stderr of command i.
119
115
120 Returns a deferred to a dict with keys
116 Returns a deferred to a dict with keys
121 (id, number, stdin, stdout, stderr).
117 (id, number, stdin, stdout, stderr).
122
118
123 Raises IndexError if command i does not exist.
119 Raises IndexError if command i does not exist.
124 Raises TypeError if i in not an int.
120 Raises TypeError if i in not an int.
125 """
121 """
126
122
127 def reset():
123 def reset():
128 """Reset the shell.
124 """Reset the shell.
129
125
130 This clears the users namespace. Won't cause modules to be
126 This clears the users namespace. Won't cause modules to be
131 reloaded. Should also re-initialize certain variables like id.
127 reloaded. Should also re-initialize certain variables like id.
132 """
128 """
133
129
134 def kill():
130 def kill():
135 """Kill the engine by stopping the reactor."""
131 """Kill the engine by stopping the reactor."""
136
132
137 def keys():
133 def keys():
138 """Return the top level variables in the users namspace.
134 """Return the top level variables in the users namspace.
139
135
140 Returns a deferred to a dict."""
136 Returns a deferred to a dict."""
141
137
142
138
143 class IEngineSerialized(zi.Interface):
139 class IEngineSerialized(zi.Interface):
144 """Push/Pull methods that take Serialized objects.
140 """Push/Pull methods that take Serialized objects.
145
141
146 All methods should return deferreds.
142 All methods should return deferreds.
147 """
143 """
148
144
149 def push_serialized(namespace):
145 def push_serialized(namespace):
150 """Push a dict of keys and Serialized objects into the user's namespace."""
146 """Push a dict of keys and Serialized objects into the user's namespace."""
151
147
152 def pull_serialized(keys):
148 def pull_serialized(keys):
153 """Pull objects by key from the user's namespace as Serialized.
149 """Pull objects by key from the user's namespace as Serialized.
154
150
155 Returns a list of or one Serialized.
151 Returns a list of or one Serialized.
156
152
157 Raises NameError is any one of the objects does not exist.
153 Raises NameError is any one of the objects does not exist.
158 """
154 """
159
155
160
156
161 class IEngineProperties(zi.Interface):
157 class IEngineProperties(zi.Interface):
162 """Methods for access to the properties object of an Engine"""
158 """Methods for access to the properties object of an Engine"""
163
159
164 properties = zi.Attribute("A StrictDict object, containing the properties")
160 properties = zi.Attribute("A StrictDict object, containing the properties")
165
161
166 def set_properties(properties):
162 def set_properties(properties):
167 """set properties by key and value"""
163 """set properties by key and value"""
168
164
169 def get_properties(keys=None):
165 def get_properties(keys=None):
170 """get a list of properties by `keys`, if no keys specified, get all"""
166 """get a list of properties by `keys`, if no keys specified, get all"""
171
167
172 def del_properties(keys):
168 def del_properties(keys):
173 """delete properties by `keys`"""
169 """delete properties by `keys`"""
174
170
175 def has_properties(keys):
171 def has_properties(keys):
176 """get a list of bool values for whether `properties` has `keys`"""
172 """get a list of bool values for whether `properties` has `keys`"""
177
173
178 def clear_properties():
174 def clear_properties():
179 """clear the properties dict"""
175 """clear the properties dict"""
180
176
181 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
177 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
182 """The basic engine interface that EngineService will implement.
178 """The basic engine interface that EngineService will implement.
183
179
184 This exists so it is easy to specify adapters that adapt to and from the
180 This exists so it is easy to specify adapters that adapt to and from the
185 API that the basic EngineService implements.
181 API that the basic EngineService implements.
186 """
182 """
187 pass
183 pass
188
184
189 class IEngineQueued(IEngineBase):
185 class IEngineQueued(IEngineBase):
190 """Interface for adding a queue to an IEngineBase.
186 """Interface for adding a queue to an IEngineBase.
191
187
192 This interface extends the IEngineBase interface to add methods for managing
188 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
189 the engine's queue. The implicit details of this interface are that the
194 execution of all methods declared in IEngineBase should appropriately be
190 execution of all methods declared in IEngineBase should appropriately be
195 put through a queue before execution.
191 put through a queue before execution.
196
192
197 All methods should return deferreds.
193 All methods should return deferreds.
198 """
194 """
199
195
200 def clear_queue():
196 def clear_queue():
201 """Clear the queue."""
197 """Clear the queue."""
202
198
203 def queue_status():
199 def queue_status():
204 """Get the queued and pending commands in the queue."""
200 """Get the queued and pending commands in the queue."""
205
201
206 def register_failure_observer(obs):
202 def register_failure_observer(obs):
207 """Register an observer of pending Failures.
203 """Register an observer of pending Failures.
208
204
209 The observer must implement IFailureObserver.
205 The observer must implement IFailureObserver.
210 """
206 """
211
207
212 def unregister_failure_observer(obs):
208 def unregister_failure_observer(obs):
213 """Unregister an observer of pending Failures."""
209 """Unregister an observer of pending Failures."""
214
210
215
211
216 class IEngineThreaded(zi.Interface):
212 class IEngineThreaded(zi.Interface):
217 """A place holder for threaded commands.
213 """A place holder for threaded commands.
218
214
219 All methods should return deferreds.
215 All methods should return deferreds.
220 """
216 """
221 pass
217 pass
222
218
223
219
224 #-------------------------------------------------------------------------------
220 #-------------------------------------------------------------------------------
225 # Functions and classes to implement the EngineService
221 # Functions and classes to implement the EngineService
226 #-------------------------------------------------------------------------------
222 #-------------------------------------------------------------------------------
227
223
228
224
229 class StrictDict(dict):
225 class StrictDict(dict):
230 """This is a strict copying dictionary for use as the interface to the
226 """This is a strict copying dictionary for use as the interface to the
231 properties of an Engine.
227 properties of an Engine.
232
228
233 :IMPORTANT:
229 :IMPORTANT:
234 This object copies the values you set to it, and returns copies to you
230 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
231 when you request them. The only way to change properties os explicitly
236 through the setitem and getitem of the dictionary interface.
232 through the setitem and getitem of the dictionary interface.
237
233
238 Example:
234 Example:
239 >>> e = get_engine(id)
235 >>> e = get_engine(id)
240 >>> L = [1,2,3]
236 >>> L = [1,2,3]
241 >>> e.properties['L'] = L
237 >>> e.properties['L'] = L
242 >>> L == e.properties['L']
238 >>> L == e.properties['L']
243 True
239 True
244 >>> L.append(99)
240 >>> L.append(99)
245 >>> L == e.properties['L']
241 >>> L == e.properties['L']
246 False
242 False
247
243
248 Note that getitem copies, so calls to methods of objects do not affect
244 Note that getitem copies, so calls to methods of objects do not affect
249 the properties, as seen here:
245 the properties, as seen here:
250
246
251 >>> e.properties[1] = range(2)
247 >>> e.properties[1] = range(2)
252 >>> print e.properties[1]
248 >>> print e.properties[1]
253 [0, 1]
249 [0, 1]
254 >>> e.properties[1].append(2)
250 >>> e.properties[1].append(2)
255 >>> print e.properties[1]
251 >>> print e.properties[1]
256 [0, 1]
252 [0, 1]
257 """
253 """
258 def __init__(self, *args, **kwargs):
254 def __init__(self, *args, **kwargs):
259 dict.__init__(self, *args, **kwargs)
255 dict.__init__(self, *args, **kwargs)
260 self.modified = True
256 self.modified = True
261
257
262 def __getitem__(self, key):
258 def __getitem__(self, key):
263 return copy.deepcopy(dict.__getitem__(self, key))
259 return copy.deepcopy(dict.__getitem__(self, key))
264
260
265 def __setitem__(self, key, value):
261 def __setitem__(self, key, value):
266 # check if this entry is valid for transport around the network
262 # check if this entry is valid for transport around the network
267 # and copying
263 # and copying
268 try:
264 try:
269 pickle.dumps(key, 2)
265 pickle.dumps(key, 2)
270 pickle.dumps(value, 2)
266 pickle.dumps(value, 2)
271 newvalue = copy.deepcopy(value)
267 newvalue = copy.deepcopy(value)
272 except Exception, e:
268 except Exception, e:
273 raise error.InvalidProperty("can't be a value: %r" % value)
269 raise error.InvalidProperty("can't be a value: %r" % value)
274 dict.__setitem__(self, key, newvalue)
270 dict.__setitem__(self, key, newvalue)
275 self.modified = True
271 self.modified = True
276
272
277 def __delitem__(self, key):
273 def __delitem__(self, key):
278 dict.__delitem__(self, key)
274 dict.__delitem__(self, key)
279 self.modified = True
275 self.modified = True
280
276
281 def update(self, dikt):
277 def update(self, dikt):
282 for k,v in dikt.iteritems():
278 for k,v in dikt.iteritems():
283 self[k] = v
279 self[k] = v
284
280
285 def pop(self, key):
281 def pop(self, key):
286 self.modified = True
282 self.modified = True
287 return dict.pop(self, key)
283 return dict.pop(self, key)
288
284
289 def popitem(self):
285 def popitem(self):
290 self.modified = True
286 self.modified = True
291 return dict.popitem(self)
287 return dict.popitem(self)
292
288
293 def clear(self):
289 def clear(self):
294 self.modified = True
290 self.modified = True
295 dict.clear(self)
291 dict.clear(self)
296
292
297 def subDict(self, *keys):
293 def subDict(self, *keys):
298 d = {}
294 d = {}
299 for key in keys:
295 for key in keys:
300 d[key] = self[key]
296 d[key] = self[key]
301 return d
297 return d
302
298
303
299
304
300
305 class EngineAPI(object):
301 class EngineAPI(object):
306 """This is the object through which the user can edit the `properties`
302 """This is the object through which the user can edit the `properties`
307 attribute of an Engine.
303 attribute of an Engine.
308 The Engine Properties object copies all object in and out of itself.
304 The Engine Properties object copies all object in and out of itself.
309 See the EngineProperties object for details.
305 See the EngineProperties object for details.
310 """
306 """
311 _fix=False
307 _fix=False
312 def __init__(self, id):
308 def __init__(self, id):
313 self.id = id
309 self.id = id
314 self.properties = StrictDict()
310 self.properties = StrictDict()
315 self._fix=True
311 self._fix=True
316
312
317 def __setattr__(self, k,v):
313 def __setattr__(self, k,v):
318 if self._fix:
314 if self._fix:
319 raise error.KernelError("I am protected!")
315 raise error.KernelError("I am protected!")
320 else:
316 else:
321 object.__setattr__(self, k, v)
317 object.__setattr__(self, k, v)
322
318
323 def __delattr__(self, key):
319 def __delattr__(self, key):
324 raise error.KernelError("I am protected!")
320 raise error.KernelError("I am protected!")
325
321
326
322
327 _apiDict = {}
323 _apiDict = {}
328
324
329 def get_engine(id):
325 def get_engine(id):
330 """Get the Engine API object, whcih currently just provides the properties
326 """Get the Engine API object, whcih currently just provides the properties
331 object, by ID"""
327 object, by ID"""
332 global _apiDict
328 global _apiDict
333 if not _apiDict.get(id):
329 if not _apiDict.get(id):
334 _apiDict[id] = EngineAPI(id)
330 _apiDict[id] = EngineAPI(id)
335 return _apiDict[id]
331 return _apiDict[id]
336
332
337 def drop_engine(id):
333 def drop_engine(id):
338 """remove an engine"""
334 """remove an engine"""
339 global _apiDict
335 global _apiDict
340 if _apiDict.has_key(id):
336 if _apiDict.has_key(id):
341 del _apiDict[id]
337 del _apiDict[id]
342
338
343 class EngineService(object, service.Service):
339 class EngineService(object, service.Service):
344 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
340 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
345
341
346 zi.implements(IEngineBase)
342 zi.implements(IEngineBase)
347 name = 'EngineService'
343 name = 'EngineService'
348
344
349 def __init__(self, shellClass=Interpreter, mpi=None):
345 def __init__(self, shellClass=Interpreter, mpi=None):
350 """Create an EngineService.
346 """Create an EngineService.
351
347
352 shellClass: something that implements IInterpreter or core1
348 shellClass: something that implements IInterpreter or core1
353 mpi: an mpi module that has rank and size attributes
349 mpi: an mpi module that has rank and size attributes
354 """
350 """
355 self.shellClass = shellClass
351 self.shellClass = shellClass
356 self.shell = self.shellClass()
352 self.shell = self.shellClass()
357 self.mpi = mpi
353 self.mpi = mpi
358 self.id = None
354 self.id = None
359 self.properties = get_engine(self.id).properties
355 self.properties = get_engine(self.id).properties
360 if self.mpi is not None:
356 if self.mpi is not None:
361 log.msg("MPI started with rank = %i and size = %i" %
357 log.msg("MPI started with rank = %i and size = %i" %
362 (self.mpi.rank, self.mpi.size))
358 (self.mpi.rank, self.mpi.size))
363 self.id = self.mpi.rank
359 self.id = self.mpi.rank
364 self._seedNamespace()
360 self._seedNamespace()
365
361
366 # Make id a property so that the shell can get the updated id
362 # Make id a property so that the shell can get the updated id
367
363
368 def _setID(self, id):
364 def _setID(self, id):
369 self._id = id
365 self._id = id
370 self.properties = get_engine(id).properties
366 self.properties = get_engine(id).properties
371 self.shell.push({'id': id})
367 self.shell.push({'id': id})
372
368
373 def _getID(self):
369 def _getID(self):
374 return self._id
370 return self._id
375
371
376 id = property(_getID, _setID)
372 id = property(_getID, _setID)
377
373
378 def _seedNamespace(self):
374 def _seedNamespace(self):
379 self.shell.push({'mpi': self.mpi, 'id' : self.id})
375 self.shell.push({'mpi': self.mpi, 'id' : self.id})
380
376
381 def executeAndRaise(self, msg, callable, *args, **kwargs):
377 def executeAndRaise(self, msg, callable, *args, **kwargs):
382 """Call a method of self.shell and wrap any exception."""
378 """Call a method of self.shell and wrap any exception."""
383 d = defer.Deferred()
379 d = defer.Deferred()
384 try:
380 try:
385 result = callable(*args, **kwargs)
381 result = callable(*args, **kwargs)
386 except:
382 except:
387 # This gives the following:
383 # This gives the following:
388 # et=exception class
384 # et=exception class
389 # ev=exception class instance
385 # ev=exception class instance
390 # tb=traceback object
386 # tb=traceback object
391 et,ev,tb = sys.exc_info()
387 et,ev,tb = sys.exc_info()
392 # This call adds attributes to the exception value
388 # This call adds attributes to the exception value
393 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
389 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
394 # Add another attribute
390 # Add another attribute
395 ev._ipython_engine_info = msg
391 ev._ipython_engine_info = msg
396 f = failure.Failure(ev,et,None)
392 f = failure.Failure(ev,et,None)
397 d.errback(f)
393 d.errback(f)
398 else:
394 else:
399 d.callback(result)
395 d.callback(result)
400
396
401 return d
397 return d
402
398
403
399
404 # The IEngine methods. See the interface for documentation.
400 # The IEngine methods. See the interface for documentation.
405
401
406 def execute(self, lines):
402 def execute(self, lines):
407 msg = {'engineid':self.id,
403 msg = {'engineid':self.id,
408 'method':'execute',
404 'method':'execute',
409 'args':[lines]}
405 'args':[lines]}
410 d = self.executeAndRaise(msg, self.shell.execute, lines)
406 d = self.executeAndRaise(msg, self.shell.execute, lines)
411 d.addCallback(self.addIDToResult)
407 d.addCallback(self.addIDToResult)
412 return d
408 return d
413
409
414 def addIDToResult(self, result):
410 def addIDToResult(self, result):
415 result['id'] = self.id
411 result['id'] = self.id
416 return result
412 return result
417
413
418 def push(self, namespace):
414 def push(self, namespace):
419 msg = {'engineid':self.id,
415 msg = {'engineid':self.id,
420 'method':'push',
416 'method':'push',
421 'args':[repr(namespace.keys())]}
417 'args':[repr(namespace.keys())]}
422 d = self.executeAndRaise(msg, self.shell.push, namespace)
418 d = self.executeAndRaise(msg, self.shell.push, namespace)
423 return d
419 return d
424
420
425 def pull(self, keys):
421 def pull(self, keys):
426 msg = {'engineid':self.id,
422 msg = {'engineid':self.id,
427 'method':'pull',
423 'method':'pull',
428 'args':[repr(keys)]}
424 'args':[repr(keys)]}
429 d = self.executeAndRaise(msg, self.shell.pull, keys)
425 d = self.executeAndRaise(msg, self.shell.pull, keys)
430 return d
426 return d
431
427
432 def push_function(self, namespace):
428 def push_function(self, namespace):
433 msg = {'engineid':self.id,
429 msg = {'engineid':self.id,
434 'method':'push_function',
430 'method':'push_function',
435 'args':[repr(namespace.keys())]}
431 'args':[repr(namespace.keys())]}
436 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
432 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
437 return d
433 return d
438
434
439 def pull_function(self, keys):
435 def pull_function(self, keys):
440 msg = {'engineid':self.id,
436 msg = {'engineid':self.id,
441 'method':'pull_function',
437 'method':'pull_function',
442 'args':[repr(keys)]}
438 'args':[repr(keys)]}
443 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
439 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
444 return d
440 return d
445
441
446 def get_result(self, i=None):
442 def get_result(self, i=None):
447 msg = {'engineid':self.id,
443 msg = {'engineid':self.id,
448 'method':'get_result',
444 'method':'get_result',
449 'args':[repr(i)]}
445 'args':[repr(i)]}
450 d = self.executeAndRaise(msg, self.shell.getCommand, i)
446 d = self.executeAndRaise(msg, self.shell.getCommand, i)
451 d.addCallback(self.addIDToResult)
447 d.addCallback(self.addIDToResult)
452 return d
448 return d
453
449
454 def reset(self):
450 def reset(self):
455 msg = {'engineid':self.id,
451 msg = {'engineid':self.id,
456 'method':'reset',
452 'method':'reset',
457 'args':[]}
453 'args':[]}
458 del self.shell
454 del self.shell
459 self.shell = self.shellClass()
455 self.shell = self.shellClass()
460 self.properties.clear()
456 self.properties.clear()
461 d = self.executeAndRaise(msg, self._seedNamespace)
457 d = self.executeAndRaise(msg, self._seedNamespace)
462 return d
458 return d
463
459
464 def kill(self):
460 def kill(self):
465 drop_engine(self.id)
461 drop_engine(self.id)
466 try:
462 try:
467 reactor.stop()
463 reactor.stop()
468 except RuntimeError:
464 except RuntimeError:
469 log.msg('The reactor was not running apparently.')
465 log.msg('The reactor was not running apparently.')
470 return defer.fail()
466 return defer.fail()
471 else:
467 else:
472 return defer.succeed(None)
468 return defer.succeed(None)
473
469
474 def keys(self):
470 def keys(self):
475 """Return a list of variables names in the users top level namespace.
471 """Return a list of variables names in the users top level namespace.
476
472
477 This used to return a dict of all the keys/repr(values) in the
473 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
474 user's namespace. This was too much info for the ControllerService
479 to handle so it is now just a list of keys.
475 to handle so it is now just a list of keys.
480 """
476 """
481
477
482 remotes = []
478 remotes = []
483 for k in self.shell.user_ns.iterkeys():
479 for k in self.shell.user_ns.iterkeys():
484 if k not in ['__name__', '_ih', '_oh', '__builtins__',
480 if k not in ['__name__', '_ih', '_oh', '__builtins__',
485 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
481 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
486 remotes.append(k)
482 remotes.append(k)
487 return defer.succeed(remotes)
483 return defer.succeed(remotes)
488
484
489 def set_properties(self, properties):
485 def set_properties(self, properties):
490 msg = {'engineid':self.id,
486 msg = {'engineid':self.id,
491 'method':'set_properties',
487 'method':'set_properties',
492 'args':[repr(properties.keys())]}
488 'args':[repr(properties.keys())]}
493 return self.executeAndRaise(msg, self.properties.update, properties)
489 return self.executeAndRaise(msg, self.properties.update, properties)
494
490
495 def get_properties(self, keys=None):
491 def get_properties(self, keys=None):
496 msg = {'engineid':self.id,
492 msg = {'engineid':self.id,
497 'method':'get_properties',
493 'method':'get_properties',
498 'args':[repr(keys)]}
494 'args':[repr(keys)]}
499 if keys is None:
495 if keys is None:
500 keys = self.properties.keys()
496 keys = self.properties.keys()
501 return self.executeAndRaise(msg, self.properties.subDict, *keys)
497 return self.executeAndRaise(msg, self.properties.subDict, *keys)
502
498
503 def _doDel(self, keys):
499 def _doDel(self, keys):
504 for key in keys:
500 for key in keys:
505 del self.properties[key]
501 del self.properties[key]
506
502
507 def del_properties(self, keys):
503 def del_properties(self, keys):
508 msg = {'engineid':self.id,
504 msg = {'engineid':self.id,
509 'method':'del_properties',
505 'method':'del_properties',
510 'args':[repr(keys)]}
506 'args':[repr(keys)]}
511 return self.executeAndRaise(msg, self._doDel, keys)
507 return self.executeAndRaise(msg, self._doDel, keys)
512
508
513 def _doHas(self, keys):
509 def _doHas(self, keys):
514 return [self.properties.has_key(key) for key in keys]
510 return [self.properties.has_key(key) for key in keys]
515
511
516 def has_properties(self, keys):
512 def has_properties(self, keys):
517 msg = {'engineid':self.id,
513 msg = {'engineid':self.id,
518 'method':'has_properties',
514 'method':'has_properties',
519 'args':[repr(keys)]}
515 'args':[repr(keys)]}
520 return self.executeAndRaise(msg, self._doHas, keys)
516 return self.executeAndRaise(msg, self._doHas, keys)
521
517
522 def clear_properties(self):
518 def clear_properties(self):
523 msg = {'engineid':self.id,
519 msg = {'engineid':self.id,
524 'method':'clear_properties',
520 'method':'clear_properties',
525 'args':[]}
521 'args':[]}
526 return self.executeAndRaise(msg, self.properties.clear)
522 return self.executeAndRaise(msg, self.properties.clear)
527
523
528 def push_serialized(self, sNamespace):
524 def push_serialized(self, sNamespace):
529 msg = {'engineid':self.id,
525 msg = {'engineid':self.id,
530 'method':'push_serialized',
526 'method':'push_serialized',
531 'args':[repr(sNamespace.keys())]}
527 'args':[repr(sNamespace.keys())]}
532 ns = {}
528 ns = {}
533 for k,v in sNamespace.iteritems():
529 for k,v in sNamespace.iteritems():
534 try:
530 try:
535 unserialized = newserialized.IUnSerialized(v)
531 unserialized = newserialized.IUnSerialized(v)
536 ns[k] = unserialized.getObject()
532 ns[k] = unserialized.getObject()
537 except:
533 except:
538 return defer.fail()
534 return defer.fail()
539 return self.executeAndRaise(msg, self.shell.push, ns)
535 return self.executeAndRaise(msg, self.shell.push, ns)
540
536
541 def pull_serialized(self, keys):
537 def pull_serialized(self, keys):
542 msg = {'engineid':self.id,
538 msg = {'engineid':self.id,
543 'method':'pull_serialized',
539 'method':'pull_serialized',
544 'args':[repr(keys)]}
540 'args':[repr(keys)]}
545 if isinstance(keys, str):
541 if isinstance(keys, str):
546 keys = [keys]
542 keys = [keys]
547 if len(keys)==1:
543 if len(keys)==1:
548 d = self.executeAndRaise(msg, self.shell.pull, keys)
544 d = self.executeAndRaise(msg, self.shell.pull, keys)
549 d.addCallback(newserialized.serialize)
545 d.addCallback(newserialized.serialize)
550 return d
546 return d
551 elif len(keys)>1:
547 elif len(keys)>1:
552 d = self.executeAndRaise(msg, self.shell.pull, keys)
548 d = self.executeAndRaise(msg, self.shell.pull, keys)
553 @d.addCallback
549 @d.addCallback
554 def packThemUp(values):
550 def packThemUp(values):
555 serials = []
551 serials = []
556 for v in values:
552 for v in values:
557 try:
553 try:
558 serials.append(newserialized.serialize(v))
554 serials.append(newserialized.serialize(v))
559 except:
555 except:
560 return defer.fail(failure.Failure())
556 return defer.fail(failure.Failure())
561 return serials
557 return serials
562 return packThemUp
558 return packThemUp
563
559
564
560
565 def queue(methodToQueue):
561 def queue(methodToQueue):
566 def queuedMethod(this, *args, **kwargs):
562 def queuedMethod(this, *args, **kwargs):
567 name = methodToQueue.__name__
563 name = methodToQueue.__name__
568 return this.submitCommand(Command(name, *args, **kwargs))
564 return this.submitCommand(Command(name, *args, **kwargs))
569 return queuedMethod
565 return queuedMethod
570
566
571 class QueuedEngine(object):
567 class QueuedEngine(object):
572 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
568 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
573
569
574 The resulting object will implement IEngineQueued which extends
570 The resulting object will implement IEngineQueued which extends
575 IEngineCore which extends (IEngineBase, IEngineSerialized).
571 IEngineCore which extends (IEngineBase, IEngineSerialized).
576
572
577 This seems like the best way of handling it, but I am not sure. The
573 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
574 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
575 mix-in intefaces. The problem I have with this is adpatation is
580 more difficult and complicated because there can be can multiple
576 more difficult and complicated because there can be can multiple
581 original and final Interfaces.
577 original and final Interfaces.
582 """
578 """
583
579
584 zi.implements(IEngineQueued)
580 zi.implements(IEngineQueued)
585
581
586 def __init__(self, engine):
582 def __init__(self, engine):
587 """Create a QueuedEngine object from an engine
583 """Create a QueuedEngine object from an engine
588
584
589 engine: An implementor of IEngineCore and IEngineSerialized
585 engine: An implementor of IEngineCore and IEngineSerialized
590 keepUpToDate: whether to update the remote status when the
586 keepUpToDate: whether to update the remote status when the
591 queue is empty. Defaults to False.
587 queue is empty. Defaults to False.
592 """
588 """
593
589
594 # This is the right way to do these tests rather than
590 # This is the right way to do these tests rather than
595 # IEngineCore in list(zi.providedBy(engine)) which will only
591 # IEngineCore in list(zi.providedBy(engine)) which will only
596 # picks of the interfaces that are directly declared by engine.
592 # picks of the interfaces that are directly declared by engine.
597 assert IEngineBase.providedBy(engine), \
593 assert IEngineBase.providedBy(engine), \
598 "engine passed to QueuedEngine doesn't provide IEngineBase"
594 "engine passed to QueuedEngine doesn't provide IEngineBase"
599
595
600 self.engine = engine
596 self.engine = engine
601 self.id = engine.id
597 self.id = engine.id
602 self.queued = []
598 self.queued = []
603 self.history = {}
599 self.history = {}
604 self.engineStatus = {}
600 self.engineStatus = {}
605 self.currentCommand = None
601 self.currentCommand = None
606 self.failureObservers = []
602 self.failureObservers = []
607
603
608 def _get_properties(self):
604 def _get_properties(self):
609 return self.engine.properties
605 return self.engine.properties
610
606
611 properties = property(_get_properties, lambda self, _: None)
607 properties = property(_get_properties, lambda self, _: None)
612 # Queue management methods. You should not call these directly
608 # Queue management methods. You should not call these directly
613
609
614 def submitCommand(self, cmd):
610 def submitCommand(self, cmd):
615 """Submit command to queue."""
611 """Submit command to queue."""
616
612
617 d = defer.Deferred()
613 d = defer.Deferred()
618 cmd.setDeferred(d)
614 cmd.setDeferred(d)
619 if self.currentCommand is not None:
615 if self.currentCommand is not None:
620 if self.currentCommand.finished:
616 if self.currentCommand.finished:
621 # log.msg("Running command immediately: %r" % cmd)
617 # log.msg("Running command immediately: %r" % cmd)
622 self.currentCommand = cmd
618 self.currentCommand = cmd
623 self.runCurrentCommand()
619 self.runCurrentCommand()
624 else: # command is still running
620 else: # command is still running
625 # log.msg("Command is running: %r" % self.currentCommand)
621 # log.msg("Command is running: %r" % self.currentCommand)
626 # log.msg("Queueing: %r" % cmd)
622 # log.msg("Queueing: %r" % cmd)
627 self.queued.append(cmd)
623 self.queued.append(cmd)
628 else:
624 else:
629 # log.msg("No current commands, running: %r" % cmd)
625 # log.msg("No current commands, running: %r" % cmd)
630 self.currentCommand = cmd
626 self.currentCommand = cmd
631 self.runCurrentCommand()
627 self.runCurrentCommand()
632 return d
628 return d
633
629
634 def runCurrentCommand(self):
630 def runCurrentCommand(self):
635 """Run current command."""
631 """Run current command."""
636
632
637 cmd = self.currentCommand
633 cmd = self.currentCommand
638 f = getattr(self.engine, cmd.remoteMethod, None)
634 f = getattr(self.engine, cmd.remoteMethod, None)
639 if f:
635 if f:
640 d = f(*cmd.args, **cmd.kwargs)
636 d = f(*cmd.args, **cmd.kwargs)
641 if cmd.remoteMethod is 'execute':
637 if cmd.remoteMethod is 'execute':
642 d.addCallback(self.saveResult)
638 d.addCallback(self.saveResult)
643 d.addCallback(self.finishCommand)
639 d.addCallback(self.finishCommand)
644 d.addErrback(self.abortCommand)
640 d.addErrback(self.abortCommand)
645 else:
641 else:
646 return defer.fail(AttributeError(cmd.remoteMethod))
642 return defer.fail(AttributeError(cmd.remoteMethod))
647
643
648 def _flushQueue(self):
644 def _flushQueue(self):
649 """Pop next command in queue and run it."""
645 """Pop next command in queue and run it."""
650
646
651 if len(self.queued) > 0:
647 if len(self.queued) > 0:
652 self.currentCommand = self.queued.pop(0)
648 self.currentCommand = self.queued.pop(0)
653 self.runCurrentCommand()
649 self.runCurrentCommand()
654
650
655 def saveResult(self, result):
651 def saveResult(self, result):
656 """Put the result in the history."""
652 """Put the result in the history."""
657 self.history[result['number']] = result
653 self.history[result['number']] = result
658 return result
654 return result
659
655
660 def finishCommand(self, result):
656 def finishCommand(self, result):
661 """Finish currrent command."""
657 """Finish currrent command."""
662
658
663 # The order of these commands is absolutely critical.
659 # The order of these commands is absolutely critical.
664 self.currentCommand.handleResult(result)
660 self.currentCommand.handleResult(result)
665 self.currentCommand.finished = True
661 self.currentCommand.finished = True
666 self._flushQueue()
662 self._flushQueue()
667 return result
663 return result
668
664
669 def abortCommand(self, reason):
665 def abortCommand(self, reason):
670 """Abort current command.
666 """Abort current command.
671
667
672 This eats the Failure but first passes it onto the Deferred that the
668 This eats the Failure but first passes it onto the Deferred that the
673 user has.
669 user has.
674
670
675 It also clear out the queue so subsequence commands don't run.
671 It also clear out the queue so subsequence commands don't run.
676 """
672 """
677
673
678 # The order of these 3 commands is absolutely critical. The currentCommand
674 # 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
675 # must first be marked as finished BEFORE the queue is cleared and before
680 # the current command is sent the failure.
676 # the current command is sent the failure.
681 # Also, the queue must be cleared BEFORE the current command is sent the Failure
677 # 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
678 # 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
679 # queue before we clear it. We should clear ONLY the commands that were in
684 # the queue when the error occured.
680 # the queue when the error occured.
685 self.currentCommand.finished = True
681 self.currentCommand.finished = True
686 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
682 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
687 self.clear_queue(msg=s)
683 self.clear_queue(msg=s)
688 self.currentCommand.handleError(reason)
684 self.currentCommand.handleError(reason)
689
685
690 return None
686 return None
691
687
692 #---------------------------------------------------------------------------
688 #---------------------------------------------------------------------------
693 # IEngineCore methods
689 # IEngineCore methods
694 #---------------------------------------------------------------------------
690 #---------------------------------------------------------------------------
695
691
696 @queue
692 @queue
697 def execute(self, lines):
693 def execute(self, lines):
698 pass
694 pass
699
695
700 @queue
696 @queue
701 def push(self, namespace):
697 def push(self, namespace):
702 pass
698 pass
703
699
704 @queue
700 @queue
705 def pull(self, keys):
701 def pull(self, keys):
706 pass
702 pass
707
703
708 @queue
704 @queue
709 def push_function(self, namespace):
705 def push_function(self, namespace):
710 pass
706 pass
711
707
712 @queue
708 @queue
713 def pull_function(self, keys):
709 def pull_function(self, keys):
714 pass
710 pass
715
711
716 def get_result(self, i=None):
712 def get_result(self, i=None):
717 if i is None:
713 if i is None:
718 i = max(self.history.keys()+[None])
714 i = max(self.history.keys()+[None])
719
715
720 cmd = self.history.get(i, None)
716 cmd = self.history.get(i, None)
721 # Uncomment this line to disable chaching of results
717 # Uncomment this line to disable chaching of results
722 #cmd = None
718 #cmd = None
723 if cmd is None:
719 if cmd is None:
724 return self.submitCommand(Command('get_result', i))
720 return self.submitCommand(Command('get_result', i))
725 else:
721 else:
726 return defer.succeed(cmd)
722 return defer.succeed(cmd)
727
723
728 def reset(self):
724 def reset(self):
729 self.clear_queue()
725 self.clear_queue()
730 self.history = {} # reset the cache - I am not sure we should do this
726 self.history = {} # reset the cache - I am not sure we should do this
731 return self.submitCommand(Command('reset'))
727 return self.submitCommand(Command('reset'))
732
728
733 def kill(self):
729 def kill(self):
734 self.clear_queue()
730 self.clear_queue()
735 return self.submitCommand(Command('kill'))
731 return self.submitCommand(Command('kill'))
736
732
737 @queue
733 @queue
738 def keys(self):
734 def keys(self):
739 pass
735 pass
740
736
741 #---------------------------------------------------------------------------
737 #---------------------------------------------------------------------------
742 # IEngineSerialized methods
738 # IEngineSerialized methods
743 #---------------------------------------------------------------------------
739 #---------------------------------------------------------------------------
744
740
745 @queue
741 @queue
746 def push_serialized(self, namespace):
742 def push_serialized(self, namespace):
747 pass
743 pass
748
744
749 @queue
745 @queue
750 def pull_serialized(self, keys):
746 def pull_serialized(self, keys):
751 pass
747 pass
752
748
753 #---------------------------------------------------------------------------
749 #---------------------------------------------------------------------------
754 # IEngineProperties methods
750 # IEngineProperties methods
755 #---------------------------------------------------------------------------
751 #---------------------------------------------------------------------------
756
752
757 @queue
753 @queue
758 def set_properties(self, namespace):
754 def set_properties(self, namespace):
759 pass
755 pass
760
756
761 @queue
757 @queue
762 def get_properties(self, keys=None):
758 def get_properties(self, keys=None):
763 pass
759 pass
764
760
765 @queue
761 @queue
766 def del_properties(self, keys):
762 def del_properties(self, keys):
767 pass
763 pass
768
764
769 @queue
765 @queue
770 def has_properties(self, keys):
766 def has_properties(self, keys):
771 pass
767 pass
772
768
773 @queue
769 @queue
774 def clear_properties(self):
770 def clear_properties(self):
775 pass
771 pass
776
772
777 #---------------------------------------------------------------------------
773 #---------------------------------------------------------------------------
778 # IQueuedEngine methods
774 # IQueuedEngine methods
779 #---------------------------------------------------------------------------
775 #---------------------------------------------------------------------------
780
776
781 def clear_queue(self, msg=''):
777 def clear_queue(self, msg=''):
782 """Clear the queue, but doesn't cancel the currently running commmand."""
778 """Clear the queue, but doesn't cancel the currently running commmand."""
783
779
784 for cmd in self.queued:
780 for cmd in self.queued:
785 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
781 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
786 self.queued = []
782 self.queued = []
787 return defer.succeed(None)
783 return defer.succeed(None)
788
784
789 def queue_status(self):
785 def queue_status(self):
790 if self.currentCommand is not None:
786 if self.currentCommand is not None:
791 if self.currentCommand.finished:
787 if self.currentCommand.finished:
792 pending = repr(None)
788 pending = repr(None)
793 else:
789 else:
794 pending = repr(self.currentCommand)
790 pending = repr(self.currentCommand)
795 else:
791 else:
796 pending = repr(None)
792 pending = repr(None)
797 dikt = {'queue':map(repr,self.queued), 'pending':pending}
793 dikt = {'queue':map(repr,self.queued), 'pending':pending}
798 return defer.succeed(dikt)
794 return defer.succeed(dikt)
799
795
800 def register_failure_observer(self, obs):
796 def register_failure_observer(self, obs):
801 self.failureObservers.append(obs)
797 self.failureObservers.append(obs)
802
798
803 def unregister_failure_observer(self, obs):
799 def unregister_failure_observer(self, obs):
804 self.failureObservers.remove(obs)
800 self.failureObservers.remove(obs)
805
801
806
802
807 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
803 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
808 # IEngineQueued.
804 # IEngineQueued.
809 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
805 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
810
806
811
807
812 class Command(object):
808 class Command(object):
813 """A command object that encapslates queued commands.
809 """A command object that encapslates queued commands.
814
810
815 This class basically keeps track of a command that has been queued
811 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
812 in a QueuedEngine. It manages the deferreds and hold the method to be called
817 and the arguments to that method.
813 and the arguments to that method.
818 """
814 """
819
815
820
816
821 def __init__(self, remoteMethod, *args, **kwargs):
817 def __init__(self, remoteMethod, *args, **kwargs):
822 """Build a new Command object."""
818 """Build a new Command object."""
823
819
824 self.remoteMethod = remoteMethod
820 self.remoteMethod = remoteMethod
825 self.args = args
821 self.args = args
826 self.kwargs = kwargs
822 self.kwargs = kwargs
827 self.finished = False
823 self.finished = False
828
824
829 def setDeferred(self, d):
825 def setDeferred(self, d):
830 """Sets the deferred attribute of the Command."""
826 """Sets the deferred attribute of the Command."""
831
827
832 self.deferred = d
828 self.deferred = d
833
829
834 def __repr__(self):
830 def __repr__(self):
835 if not self.args:
831 if not self.args:
836 args = ''
832 args = ''
837 else:
833 else:
838 args = str(self.args)[1:-2] #cut off (...,)
834 args = str(self.args)[1:-2] #cut off (...,)
839 for k,v in self.kwargs.iteritems():
835 for k,v in self.kwargs.iteritems():
840 if args:
836 if args:
841 args += ', '
837 args += ', '
842 args += '%s=%r' %(k,v)
838 args += '%s=%r' %(k,v)
843 return "%s(%s)" %(self.remoteMethod, args)
839 return "%s(%s)" %(self.remoteMethod, args)
844
840
845 def handleResult(self, result):
841 def handleResult(self, result):
846 """When the result is ready, relay it to self.deferred."""
842 """When the result is ready, relay it to self.deferred."""
847
843
848 self.deferred.callback(result)
844 self.deferred.callback(result)
849
845
850 def handleError(self, reason):
846 def handleError(self, reason):
851 """When an error has occured, relay it to self.deferred."""
847 """When an error has occured, relay it to self.deferred."""
852
848
853 self.deferred.errback(reason)
849 self.deferred.errback(reason)
854
850
855 class ThreadedEngineService(EngineService):
851 class ThreadedEngineService(EngineService):
856 """An EngineService subclass that defers execute commands to a separate
852 """An EngineService subclass that defers execute commands to a separate
857 thread.
853 thread.
858
854
859 ThreadedEngineService uses twisted.internet.threads.deferToThread to
855 ThreadedEngineService uses twisted.internet.threads.deferToThread to
860 defer execute requests to a separate thread. GUI frontends may want to
856 defer execute requests to a separate thread. GUI frontends may want to
861 use ThreadedEngineService as the engine in an
857 use ThreadedEngineService as the engine in an
862 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
858 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
863 block execution from blocking the GUI thread.
859 block execution from blocking the GUI thread.
864 """
860 """
865
861
866 zi.implements(IEngineBase)
862 zi.implements(IEngineBase)
867
863
868 def __init__(self, shellClass=Interpreter, mpi=None):
864 def __init__(self, shellClass=Interpreter, mpi=None):
869 EngineService.__init__(self, shellClass, mpi)
865 EngineService.__init__(self, shellClass, mpi)
870
866
871 def wrapped_execute(self, msg, lines):
867 def wrapped_execute(self, msg, lines):
872 """Wrap self.shell.execute to add extra information to tracebacks"""
868 """Wrap self.shell.execute to add extra information to tracebacks"""
873
869
874 try:
870 try:
875 result = self.shell.execute(lines)
871 result = self.shell.execute(lines)
876 except Exception,e:
872 except Exception,e:
877 # This gives the following:
873 # This gives the following:
878 # et=exception class
874 # et=exception class
879 # ev=exception class instance
875 # ev=exception class instance
880 # tb=traceback object
876 # tb=traceback object
881 et,ev,tb = sys.exc_info()
877 et,ev,tb = sys.exc_info()
882 # This call adds attributes to the exception value
878 # This call adds attributes to the exception value
883 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
879 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
884 # Add another attribute
880 # Add another attribute
885
881
886 # Create a new exception with the new attributes
882 # Create a new exception with the new attributes
887 e = et(ev._ipython_traceback_text)
883 e = et(ev._ipython_traceback_text)
888 e._ipython_engine_info = msg
884 e._ipython_engine_info = msg
889
885
890 # Re-raise
886 # Re-raise
891 raise e
887 raise e
892
888
893 return result
889 return result
894
890
895
891
896 def execute(self, lines):
892 def execute(self, lines):
897 # Only import this if we are going to use this class
893 # Only import this if we are going to use this class
898 from twisted.internet import threads
894 from twisted.internet import threads
899
895
900 msg = {'engineid':self.id,
896 msg = {'engineid':self.id,
901 'method':'execute',
897 'method':'execute',
902 'args':[lines]}
898 'args':[lines]}
903
899
904 d = threads.deferToThread(self.wrapped_execute, msg, lines)
900 d = threads.deferToThread(self.wrapped_execute, msg, lines)
905 d.addCallback(self.addIDToResult)
901 d.addCallback(self.addIDToResult)
906 return d
902 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,326 +1,346 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.platutils import find_cmd
34 from IPython.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 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
61 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
62 # testing problems. We should strive to minimize the number of skipped
62 # testing problems. We should strive to minimize the number of skipped
63 # modules, since this means untested code. As the testing machinery
63 # modules, since this means untested code. As the testing machinery
64 # solidifies, this list should eventually become empty.
64 # solidifies, this list should eventually become empty.
65 EXCLUDE = [pjoin('IPython', 'external'),
65 EXCLUDE = [pjoin('IPython', 'external'),
66 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
66 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
67 pjoin('IPython_doctest_plugin'),
67 pjoin('IPython_doctest_plugin'),
68 pjoin('IPython', 'Gnuplot'),
68 pjoin('IPython', 'Gnuplot'),
69 pjoin('IPython', 'Extensions', 'ipy_'),
69 pjoin('IPython', 'Extensions', 'ipy_'),
70 pjoin('IPython', 'Extensions', 'PhysicalQInput'),
70 pjoin('IPython', 'Extensions', 'PhysicalQInput'),
71 pjoin('IPython', 'Extensions', 'PhysicalQInteractive'),
71 pjoin('IPython', 'Extensions', 'PhysicalQInteractive'),
72 pjoin('IPython', 'Extensions', 'InterpreterPasteInput'),
72 pjoin('IPython', 'Extensions', 'InterpreterPasteInput'),
73 pjoin('IPython', 'Extensions', 'scitedirector'),
73 pjoin('IPython', 'Extensions', 'scitedirector'),
74 pjoin('IPython', 'Extensions', 'numeric_formats'),
74 pjoin('IPython', 'Extensions', 'numeric_formats'),
75 pjoin('IPython', 'testing', 'attic'),
75 pjoin('IPython', 'testing', 'attic'),
76 pjoin('IPython', 'testing', 'tutils'),
76 pjoin('IPython', 'testing', 'tutils'),
77 pjoin('IPython', 'testing', 'tools'),
77 pjoin('IPython', 'testing', 'tools'),
78 pjoin('IPython', 'testing', 'mkdoctests'),
78 pjoin('IPython', 'testing', 'mkdoctests'),
79 ]
79 ]
80
80
81 if not have_wx:
81 if not have_wx:
82 EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid'))
82 EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid'))
83 EXCLUDE.append(pjoin('IPython', 'gui'))
83 EXCLUDE.append(pjoin('IPython', 'gui'))
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
85
85
86 if not have_wx_aui:
86 if not have_wx_aui:
87 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
87 EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython'))
88
88
89 if not have_objc:
89 if not have_objc:
90 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
90 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
91
91
92 if not have_curses:
92 if not have_curses:
93 EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse'))
93 EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse'))
94
94
95 if not sys.platform == 'win32':
95 if not sys.platform == 'win32':
96 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
96 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
97
97
98 # These have to be skipped on win32 because the use echo, rm, cd, etc.
98 # These have to be skipped on win32 because the use echo, rm, cd, etc.
99 # See ticket https://bugs.launchpad.net/bugs/366982
99 # See ticket https://bugs.launchpad.net/bugs/366982
100 if sys.platform == 'win32':
100 if sys.platform == 'win32':
101 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
101 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
102 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
102 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
103
103
104 if not os.name == 'posix':
104 if not os.name == 'posix':
105 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
105 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
106
106
107 if not have_pexpect:
107 if not have_pexpect:
108 EXCLUDE.append(pjoin('IPython', 'irunner'))
108 EXCLUDE.append(pjoin('IPython', 'irunner'))
109
109
110 if not have_twisted:
111 EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase'))
112 EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend'))
113 EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase'))
114 EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase'))
115 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', 'test_linefrontend'))
116 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', 'test_frontendbase'))
117 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
118 'test_prefilterfrontend'))
119 EXCLUDE.append(pjoin('IPython', 'frontend', 'tests',
120 'test_asyncfrontendbase'))
121 EXCLUDE.append(pjoin('IPython', 'kernel', 'error'))
122 EXCLUDE.append(pjoin('IPython', 'testing', 'parametric'))
123 EXCLUDE.append(pjoin('IPython', 'testing', 'util'))
124 EXCLUDE.append(pjoin('IPython', 'testing', 'tests', 'test_decorators_trial'))
125
126
110 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
127 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
111 if sys.platform == 'win32':
128 if sys.platform == 'win32':
112 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
129 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
113
130
114
131
115 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
116 # Functions and classes
133 # Functions and classes
117 #-----------------------------------------------------------------------------
134 #-----------------------------------------------------------------------------
118
135
119 def run_iptest():
136 def run_iptest():
120 """Run the IPython test suite using nose.
137 """Run the IPython test suite using nose.
121
138
122 This function is called when this script is **not** called with the form
139 This function is called when this script is **not** called with the form
123 `iptest all`. It simply calls nose with appropriate command line flags
140 `iptest all`. It simply calls nose with appropriate command line flags
124 and accepts all of the standard nose arguments.
141 and accepts all of the standard nose arguments.
125 """
142 """
126
143
127 warnings.filterwarnings('ignore',
144 warnings.filterwarnings('ignore',
128 'This will be removed soon. Use IPython.testing.util instead')
145 'This will be removed soon. Use IPython.testing.util instead')
129
146
130 argv = sys.argv + [
147 argv = sys.argv + [
131 # Loading ipdoctest causes problems with Twisted.
148 # Loading ipdoctest causes problems with Twisted.
132 # I am removing this as a temporary fix to get the
149 # I am removing this as a temporary fix to get the
133 # test suite back into working shape. Our nose
150 # test suite back into working shape. Our nose
134 # plugin needs to be gone through with a fine
151 # plugin needs to be gone through with a fine
135 # toothed comb to find what is causing the problem.
152 # toothed comb to find what is causing the problem.
136 '--with-ipdoctest',
153 '--with-ipdoctest',
137 '--ipdoctest-tests','--ipdoctest-extension=txt',
154 '--ipdoctest-tests','--ipdoctest-extension=txt',
138 '--detailed-errors',
155 '--detailed-errors',
139
156
140 # We add --exe because of setuptools' imbecility (it
157 # We add --exe because of setuptools' imbecility (it
141 # blindly does chmod +x on ALL files). Nose does the
158 # blindly does chmod +x on ALL files). Nose does the
142 # right thing and it tries to avoid executables,
159 # right thing and it tries to avoid executables,
143 # setuptools unfortunately forces our hand here. This
160 # setuptools unfortunately forces our hand here. This
144 # has been discussed on the distutils list and the
161 # has been discussed on the distutils list and the
145 # setuptools devs refuse to fix this problem!
162 # setuptools devs refuse to fix this problem!
146 '--exe',
163 '--exe',
147 ]
164 ]
148
165
149 # Detect if any tests were required by explicitly calling an IPython
166 # Detect if any tests were required by explicitly calling an IPython
150 # submodule or giving a specific path
167 # submodule or giving a specific path
151 has_tests = False
168 has_tests = False
152 for arg in sys.argv:
169 for arg in sys.argv:
153 if 'IPython' in arg or arg.endswith('.py') or \
170 if 'IPython' in arg or arg.endswith('.py') or \
154 (':' in arg and '.py' in arg):
171 (':' in arg and '.py' in arg):
155 has_tests = True
172 has_tests = True
156 break
173 break
157
174
158 # If nothing was specifically requested, test full IPython
175 # If nothing was specifically requested, test full IPython
159 if not has_tests:
176 if not has_tests:
160 argv.append('IPython')
177 argv.append('IPython')
161
178
162 # Construct list of plugins, omitting the existing doctest plugin, which
179 # Construct list of plugins, omitting the existing doctest plugin, which
163 # ours replaces (and extends).
180 # ours replaces (and extends).
164 plugins = [IPythonDoctest(EXCLUDE)]
181 plugins = [IPythonDoctest(EXCLUDE)]
165 for p in nose.plugins.builtin.plugins:
182 for p in nose.plugins.builtin.plugins:
166 plug = p()
183 plug = p()
167 if plug.name == 'doctest':
184 if plug.name == 'doctest':
168 continue
185 continue
169
186
170 #print '*** adding plugin:',plug.name # dbg
187 #print '*** adding plugin:',plug.name # dbg
171 plugins.append(plug)
188 plugins.append(plug)
172
189
173 TestProgram(argv=argv,plugins=plugins)
190 TestProgram(argv=argv,plugins=plugins)
174
191
175
192
176 class IPTester(object):
193 class IPTester(object):
177 """Call that calls iptest or trial in a subprocess.
194 """Call that calls iptest or trial in a subprocess.
178 """
195 """
179 def __init__(self,runner='iptest',params=None):
196 def __init__(self,runner='iptest',params=None):
180 """ """
197 """ """
181 if runner == 'iptest':
198 if runner == 'iptest':
182 self.runner = ['iptest','-v']
199 self.runner = ['iptest','-v']
183 else:
200 else:
184 self.runner = [find_cmd('trial')]
201 self.runner = [find_cmd('trial')]
185 if params is None:
202 if params is None:
186 params = []
203 params = []
187 if isinstance(params,str):
204 if isinstance(params,str):
188 params = [params]
205 params = [params]
189 self.params = params
206 self.params = params
190
207
191 # Assemble call
208 # Assemble call
192 self.call_args = self.runner+self.params
209 self.call_args = self.runner+self.params
193
210
194 if sys.platform == 'win32':
211 if sys.platform == 'win32':
195 def run(self):
212 def run(self):
196 """Run the stored commands"""
213 """Run the stored commands"""
197 # On Windows, cd to temporary directory to run tests. Otherwise,
214 # On Windows, cd to temporary directory to run tests. Otherwise,
198 # Twisted's trial may not be able to execute 'trial IPython', since
215 # Twisted's trial may not be able to execute 'trial IPython', since
199 # it will confuse the IPython module name with the ipython
216 # it will confuse the IPython module name with the ipython
200 # execution scripts, because the windows file system isn't case
217 # execution scripts, because the windows file system isn't case
201 # sensitive.
218 # sensitive.
202 # We also use os.system instead of subprocess.call, because I was
219 # We also use os.system instead of subprocess.call, because I was
203 # having problems with subprocess and I just don't know enough
220 # having problems with subprocess and I just don't know enough
204 # about win32 to debug this reliably. Os.system may be the 'old
221 # about win32 to debug this reliably. Os.system may be the 'old
205 # fashioned' way to do it, but it works just fine. If someone
222 # fashioned' way to do it, but it works just fine. If someone
206 # later can clean this up that's fine, as long as the tests run
223 # later can clean this up that's fine, as long as the tests run
207 # reliably in win32.
224 # reliably in win32.
208 curdir = os.getcwd()
225 curdir = os.getcwd()
209 os.chdir(tempfile.gettempdir())
226 os.chdir(tempfile.gettempdir())
210 stat = os.system(' '.join(self.call_args))
227 stat = os.system(' '.join(self.call_args))
211 os.chdir(curdir)
228 os.chdir(curdir)
212 return stat
229 return stat
213 else:
230 else:
214 def run(self):
231 def run(self):
215 """Run the stored commands"""
232 """Run the stored commands"""
216 return subprocess.call(self.call_args)
233 return subprocess.call(self.call_args)
217
234
218
235
219 def make_runners():
236 def make_runners():
220 """Define the modules and packages that need to be tested.
237 """Define the modules and packages that need to be tested.
221 """
238 """
222
239
223 # This omits additional top-level modules that should not be doctested.
240 # This omits additional top-level modules that should not be doctested.
224 # XXX: Shell.py is also ommited because of a bug in the skip_doctest
241 # XXX: Shell.py is also ommited because of a bug in the skip_doctest
225 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
242 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
226 top_mod = \
243 top_mod = \
227 ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py',
244 ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py',
228 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py',
245 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py',
229 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py',
246 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py',
230 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
247 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
231 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
248 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
232 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py',
249 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py',
233 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py',
250 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py',
234 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py',
251 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py',
235 'shellglobals.py', 'strdispatch.py', 'twshell.py',
252 'shellglobals.py', 'strdispatch.py',
236 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py',
253 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py',
237 # See note above for why this is skipped
254 # See note above for why this is skipped
238 # 'Shell.py',
255 # 'Shell.py',
239 'winconsole.py']
256 'winconsole.py']
240
257
258 if have_twisted:
259 top_mod.append('twshell.py')
260
241 if have_pexpect:
261 if have_pexpect:
242 top_mod.append('irunner.py')
262 top_mod.append('irunner.py')
243
263
244 if sys.platform == 'win32':
264 if sys.platform == 'win32':
245 top_mod.append('platutils_win32.py')
265 top_mod.append('platutils_win32.py')
246 elif os.name == 'posix':
266 elif os.name == 'posix':
247 top_mod.append('platutils_posix.py')
267 top_mod.append('platutils_posix.py')
248 else:
268 else:
249 top_mod.append('platutils_dummy.py')
269 top_mod.append('platutils_dummy.py')
250
270
251 # These are tested by nose, so skip IPython.kernel
271 # These are tested by nose, so skip IPython.kernel
252 top_pack = ['config','Extensions','frontend',
272 top_pack = ['config','Extensions','frontend',
253 'testing','tests','tools','UserConfig']
273 'testing','tests','tools','UserConfig']
254
274
255 if have_wx:
275 if have_wx:
256 top_pack.append('gui')
276 top_pack.append('gui')
257
277
258 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
278 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
259 packages = ['IPython.%s' % m for m in top_pack ]
279 packages = ['IPython.%s' % m for m in top_pack ]
260
280
261 # Make runners
281 # Make runners
262 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
282 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
263
283
264 # Test IPython.kernel using trial if twisted is installed
284 # Test IPython.kernel using trial if twisted is installed
265 if have_zi and have_twisted and have_foolscap:
285 if have_zi and have_twisted and have_foolscap:
266 runners['trial'] = IPTester('trial',['IPython'])
286 runners['trial'] = IPTester('trial',['IPython'])
267
287
268 runners['modules'] = IPTester(params=modules)
288 runners['modules'] = IPTester(params=modules)
269
289
270 return runners
290 return runners
271
291
272
292
273 def run_iptestall():
293 def run_iptestall():
274 """Run the entire IPython test suite by calling nose and trial.
294 """Run the entire IPython test suite by calling nose and trial.
275
295
276 This function constructs :class:`IPTester` instances for all IPython
296 This function constructs :class:`IPTester` instances for all IPython
277 modules and package and then runs each of them. This causes the modules
297 modules and package and then runs each of them. This causes the modules
278 and packages of IPython to be tested each in their own subprocess using
298 and packages of IPython to be tested each in their own subprocess using
279 nose or twisted.trial appropriately.
299 nose or twisted.trial appropriately.
280 """
300 """
281 runners = make_runners()
301 runners = make_runners()
282 # Run all test runners, tracking execution time
302 # Run all test runners, tracking execution time
283 failed = {}
303 failed = {}
284 t_start = time.time()
304 t_start = time.time()
285 for name,runner in runners.iteritems():
305 for name,runner in runners.iteritems():
286 print '*'*77
306 print '*'*77
287 print 'IPython test group:',name
307 print 'IPython test group:',name
288 res = runner.run()
308 res = runner.run()
289 if res:
309 if res:
290 failed[name] = res
310 failed[name] = res
291 t_end = time.time()
311 t_end = time.time()
292 t_tests = t_end - t_start
312 t_tests = t_end - t_start
293 nrunners = len(runners)
313 nrunners = len(runners)
294 nfail = len(failed)
314 nfail = len(failed)
295 # summarize results
315 # summarize results
296 print
316 print
297 print '*'*77
317 print '*'*77
298 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
318 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
299 print
319 print
300 if not failed:
320 if not failed:
301 print 'OK'
321 print 'OK'
302 else:
322 else:
303 # If anything went wrong, point out what command to rerun manually to
323 # If anything went wrong, point out what command to rerun manually to
304 # see the actual errors and individual summary
324 # see the actual errors and individual summary
305 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
325 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
306 for name in failed:
326 for name in failed:
307 failed_runner = runners[name]
327 failed_runner = runners[name]
308 print '-'*40
328 print '-'*40
309 print 'Runner failed:',name
329 print 'Runner failed:',name
310 print 'You may wish to rerun this one individually, with:'
330 print 'You may wish to rerun this one individually, with:'
311 print ' '.join(failed_runner.call_args)
331 print ' '.join(failed_runner.call_args)
312 print
332 print
313
333
314
334
315 def main():
335 def main():
316 if len(sys.argv) == 1:
336 if len(sys.argv) == 1:
317 run_iptestall()
337 run_iptestall()
318 else:
338 else:
319 if sys.argv[1] == 'all':
339 if sys.argv[1] == 'all':
320 run_iptestall()
340 run_iptestall()
321 else:
341 else:
322 run_iptest()
342 run_iptest()
323
343
324
344
325 if __name__ == '__main__':
345 if __name__ == '__main__':
326 main()
346 main()
@@ -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.platutils import find_cmd, get_long_path_name
13 from IPython.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.history import ShadowHist
80 from IPython.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.ipmaker import make_IPython
12 from IPython.ipmaker import make_IPython
10 from IPython.iplib import InteractiveShell
13 from IPython.iplib import InteractiveShell
11 from IPython.ipstruct import Struct
14 from IPython.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.genutils import Term,warn,error,flag_calls, ask_yes_no
17 from IPython.genutils import Term,warn,error,flag_calls, ask_yes_no
15 import shellglobals
18 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
General Comments 0
You need to be logged in to leave comments. Login now