##// END OF EJS Templates
Fixing mysterious bug in IEnginePropertiesTestCase.strictDict....
Brian Granger -
Show More
@@ -1,906 +1,906 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 #-------------------------------------------------------------------------------
26 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
27 # Copyright (C) 2008 The IPython Development Team
28 #
28 #
29 # Distributed under the terms of the BSD License. The full license is in
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
30 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Imports
34 # Imports
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # Tell nose to skip the testing of this module
37 # Tell nose to skip the testing of this module
38 __test__ = {}
38 __test__ = {}
39
39
40 import os, sys, copy
40 import os, sys, copy
41 import cPickle as pickle
41 import cPickle as pickle
42 from new import instancemethod
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, util
51 from IPython.kernel.util import printer
51 from IPython.kernel.util import printer
52 from IPython.kernel.twistedutil import gatherBoth, DeferredList
52 from IPython.kernel.twistedutil import gatherBoth, DeferredList
53 from IPython.kernel import codeutil
53 from IPython.kernel import codeutil
54
54
55
55
56 #-------------------------------------------------------------------------------
56 #-------------------------------------------------------------------------------
57 # Interface specification for the Engine
57 # Interface specification for the Engine
58 #-------------------------------------------------------------------------------
58 #-------------------------------------------------------------------------------
59
59
60 class IEngineCore(zi.Interface):
60 class IEngineCore(zi.Interface):
61 """The minimal required interface for the IPython Engine.
61 """The minimal required interface for the IPython Engine.
62
62
63 This interface provides a formal specification of the IPython core.
63 This interface provides a formal specification of the IPython core.
64 All these methods should return deferreds regardless of what side of a
64 All these methods should return deferreds regardless of what side of a
65 network connection they are on.
65 network connection they are on.
66
66
67 In general, this class simply wraps a shell class and wraps its return
67 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
68 values as Deferred objects. If the underlying shell class method raises
69 an exception, this class should convert it to a twisted.failure.Failure
69 an exception, this class should convert it to a twisted.failure.Failure
70 that will be propagated along the Deferred's errback chain.
70 that will be propagated along the Deferred's errback chain.
71
71
72 In addition, Failures are aggressive. By this, we mean that if a method
72 In addition, Failures are aggressive. By this, we mean that if a method
73 is performing multiple actions (like pulling multiple object) if any
73 is performing multiple actions (like pulling multiple object) if any
74 single one fails, the entire method will fail with that Failure. It is
74 single one fails, the entire method will fail with that Failure. It is
75 all or nothing.
75 all or nothing.
76 """
76 """
77
77
78 id = zi.interface.Attribute("the id of the Engine object")
78 id = zi.interface.Attribute("the id of the Engine object")
79 properties = zi.interface.Attribute("A dict of properties of the Engine")
79 properties = zi.interface.Attribute("A dict of properties of the Engine")
80
80
81 def execute(lines):
81 def execute(lines):
82 """Execute lines of Python code.
82 """Execute lines of Python code.
83
83
84 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
84 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
85 upon success.
85 upon success.
86
86
87 Returns a failure object if the execution of lines raises an exception.
87 Returns a failure object if the execution of lines raises an exception.
88 """
88 """
89
89
90 def push(namespace):
90 def push(namespace):
91 """Push dict namespace into the user's namespace.
91 """Push dict namespace into the user's namespace.
92
92
93 Returns a deferred to None or a failure.
93 Returns a deferred to None or a failure.
94 """
94 """
95
95
96 def pull(keys):
96 def pull(keys):
97 """Pulls values out of the user's namespace by keys.
97 """Pulls values out of the user's namespace by keys.
98
98
99 Returns a deferred to a tuple objects or a single object.
99 Returns a deferred to a tuple objects or a single object.
100
100
101 Raises NameError if any one of objects doess not exist.
101 Raises NameError if any one of objects doess not exist.
102 """
102 """
103
103
104 def push_function(namespace):
104 def push_function(namespace):
105 """Push a dict of key, function pairs into the user's namespace.
105 """Push a dict of key, function pairs into the user's namespace.
106
106
107 Returns a deferred to None or a failure."""
107 Returns a deferred to None or a failure."""
108
108
109 def pull_function(keys):
109 def pull_function(keys):
110 """Pulls functions out of the user's namespace by keys.
110 """Pulls functions out of the user's namespace by keys.
111
111
112 Returns a deferred to a tuple of functions or a single function.
112 Returns a deferred to a tuple of functions or a single function.
113
113
114 Raises NameError if any one of the functions does not exist.
114 Raises NameError if any one of the functions does not exist.
115 """
115 """
116
116
117 def get_result(i=None):
117 def get_result(i=None):
118 """Get the stdin/stdout/stderr of command i.
118 """Get the stdin/stdout/stderr of command i.
119
119
120 Returns a deferred to a dict with keys
120 Returns a deferred to a dict with keys
121 (id, number, stdin, stdout, stderr).
121 (id, number, stdin, stdout, stderr).
122
122
123 Raises IndexError if command i does not exist.
123 Raises IndexError if command i does not exist.
124 Raises TypeError if i in not an int.
124 Raises TypeError if i in not an int.
125 """
125 """
126
126
127 def reset():
127 def reset():
128 """Reset the shell.
128 """Reset the shell.
129
129
130 This clears the users namespace. Won't cause modules to be
130 This clears the users namespace. Won't cause modules to be
131 reloaded. Should also re-initialize certain variables like id.
131 reloaded. Should also re-initialize certain variables like id.
132 """
132 """
133
133
134 def kill():
134 def kill():
135 """Kill the engine by stopping the reactor."""
135 """Kill the engine by stopping the reactor."""
136
136
137 def keys():
137 def keys():
138 """Return the top level variables in the users namspace.
138 """Return the top level variables in the users namspace.
139
139
140 Returns a deferred to a dict."""
140 Returns a deferred to a dict."""
141
141
142
142
143 class IEngineSerialized(zi.Interface):
143 class IEngineSerialized(zi.Interface):
144 """Push/Pull methods that take Serialized objects.
144 """Push/Pull methods that take Serialized objects.
145
145
146 All methods should return deferreds.
146 All methods should return deferreds.
147 """
147 """
148
148
149 def push_serialized(namespace):
149 def push_serialized(namespace):
150 """Push a dict of keys and Serialized objects into the user's namespace."""
150 """Push a dict of keys and Serialized objects into the user's namespace."""
151
151
152 def pull_serialized(keys):
152 def pull_serialized(keys):
153 """Pull objects by key from the user's namespace as Serialized.
153 """Pull objects by key from the user's namespace as Serialized.
154
154
155 Returns a list of or one Serialized.
155 Returns a list of or one Serialized.
156
156
157 Raises NameError is any one of the objects does not exist.
157 Raises NameError is any one of the objects does not exist.
158 """
158 """
159
159
160
160
161 class IEngineProperties(zi.Interface):
161 class IEngineProperties(zi.Interface):
162 """Methods for access to the properties object of an Engine"""
162 """Methods for access to the properties object of an Engine"""
163
163
164 properties = zi.Attribute("A StrictDict object, containing the properties")
164 properties = zi.Attribute("A StrictDict object, containing the properties")
165
165
166 def set_properties(properties):
166 def set_properties(properties):
167 """set properties by key and value"""
167 """set properties by key and value"""
168
168
169 def get_properties(keys=None):
169 def get_properties(keys=None):
170 """get a list of properties by `keys`, if no keys specified, get all"""
170 """get a list of properties by `keys`, if no keys specified, get all"""
171
171
172 def del_properties(keys):
172 def del_properties(keys):
173 """delete properties by `keys`"""
173 """delete properties by `keys`"""
174
174
175 def has_properties(keys):
175 def has_properties(keys):
176 """get a list of bool values for whether `properties` has `keys`"""
176 """get a list of bool values for whether `properties` has `keys`"""
177
177
178 def clear_properties():
178 def clear_properties():
179 """clear the properties dict"""
179 """clear the properties dict"""
180
180
181 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
181 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
182 """The basic engine interface that EngineService will implement.
182 """The basic engine interface that EngineService will implement.
183
183
184 This exists so it is easy to specify adapters that adapt to and from the
184 This exists so it is easy to specify adapters that adapt to and from the
185 API that the basic EngineService implements.
185 API that the basic EngineService implements.
186 """
186 """
187 pass
187 pass
188
188
189 class IEngineQueued(IEngineBase):
189 class IEngineQueued(IEngineBase):
190 """Interface for adding a queue to an IEngineBase.
190 """Interface for adding a queue to an IEngineBase.
191
191
192 This interface extends the IEngineBase interface to add methods for managing
192 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
193 the engine's queue. The implicit details of this interface are that the
194 execution of all methods declared in IEngineBase should appropriately be
194 execution of all methods declared in IEngineBase should appropriately be
195 put through a queue before execution.
195 put through a queue before execution.
196
196
197 All methods should return deferreds.
197 All methods should return deferreds.
198 """
198 """
199
199
200 def clear_queue():
200 def clear_queue():
201 """Clear the queue."""
201 """Clear the queue."""
202
202
203 def queue_status():
203 def queue_status():
204 """Get the queued and pending commands in the queue."""
204 """Get the queued and pending commands in the queue."""
205
205
206 def register_failure_observer(obs):
206 def register_failure_observer(obs):
207 """Register an observer of pending Failures.
207 """Register an observer of pending Failures.
208
208
209 The observer must implement IFailureObserver.
209 The observer must implement IFailureObserver.
210 """
210 """
211
211
212 def unregister_failure_observer(obs):
212 def unregister_failure_observer(obs):
213 """Unregister an observer of pending Failures."""
213 """Unregister an observer of pending Failures."""
214
214
215
215
216 class IEngineThreaded(zi.Interface):
216 class IEngineThreaded(zi.Interface):
217 """A place holder for threaded commands.
217 """A place holder for threaded commands.
218
218
219 All methods should return deferreds.
219 All methods should return deferreds.
220 """
220 """
221 pass
221 pass
222
222
223
223
224 #-------------------------------------------------------------------------------
224 #-------------------------------------------------------------------------------
225 # Functions and classes to implement the EngineService
225 # Functions and classes to implement the EngineService
226 #-------------------------------------------------------------------------------
226 #-------------------------------------------------------------------------------
227
227
228
228
229 class StrictDict(dict):
229 class StrictDict(dict):
230 """This is a strict copying dictionary for use as the interface to the
230 """This is a strict copying dictionary for use as the interface to the
231 properties of an Engine.
231 properties of an Engine.
232
232
233 :IMPORTANT:
233 :IMPORTANT:
234 This object copies the values you set to it, and returns copies to you
234 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
235 when you request them. The only way to change properties os explicitly
236 through the setitem and getitem of the dictionary interface.
236 through the setitem and getitem of the dictionary interface.
237
237
238 Example:
238 Example:
239 >>> e = get_engine(id)
239 >>> e = get_engine(id)
240 >>> L = [1,2,3]
240 >>> L = [1,2,3]
241 >>> e.properties['L'] = L
241 >>> e.properties['L'] = L
242 >>> L == e.properties['L']
242 >>> L == e.properties['L']
243 True
243 True
244 >>> L.append(99)
244 >>> L.append(99)
245 >>> L == e.properties['L']
245 >>> L == e.properties['L']
246 False
246 False
247
247
248 Note that getitem copies, so calls to methods of objects do not affect
248 Note that getitem copies, so calls to methods of objects do not affect
249 the properties, as seen here:
249 the properties, as seen here:
250
250
251 >>> e.properties[1] = range(2)
251 >>> e.properties[1] = range(2)
252 >>> print e.properties[1]
252 >>> print e.properties[1]
253 [0, 1]
253 [0, 1]
254 >>> e.properties[1].append(2)
254 >>> e.properties[1].append(2)
255 >>> print e.properties[1]
255 >>> print e.properties[1]
256 [0, 1]
256 [0, 1]
257 """
257 """
258 def __init__(self, *args, **kwargs):
258 def __init__(self, *args, **kwargs):
259 dict.__init__(self, *args, **kwargs)
259 dict.__init__(self, *args, **kwargs)
260 self.modified = True
260 self.modified = True
261
261
262 def __getitem__(self, key):
262 def __getitem__(self, key):
263 return copy.deepcopy(dict.__getitem__(self, key))
263 return copy.deepcopy(dict.__getitem__(self, key))
264
264
265 def __setitem__(self, key, value):
265 def __setitem__(self, key, value):
266 # check if this entry is valid for transport around the network
266 # check if this entry is valid for transport around the network
267 # and copying
267 # and copying
268 try:
268 try:
269 pickle.dumps(key, 2)
269 pickle.dumps(key, 2)
270 pickle.dumps(value, 2)
270 pickle.dumps(value, 2)
271 newvalue = copy.deepcopy(value)
271 newvalue = copy.deepcopy(value)
272 except:
272 except Exception, e:
273 raise error.InvalidProperty(value)
273 raise error.InvalidProperty("can't be a value: %r" % value)
274 dict.__setitem__(self, key, newvalue)
274 dict.__setitem__(self, key, newvalue)
275 self.modified = True
275 self.modified = True
276
276
277 def __delitem__(self, key):
277 def __delitem__(self, key):
278 dict.__delitem__(self, key)
278 dict.__delitem__(self, key)
279 self.modified = True
279 self.modified = True
280
280
281 def update(self, dikt):
281 def update(self, dikt):
282 for k,v in dikt.iteritems():
282 for k,v in dikt.iteritems():
283 self[k] = v
283 self[k] = v
284
284
285 def pop(self, key):
285 def pop(self, key):
286 self.modified = True
286 self.modified = True
287 return dict.pop(self, key)
287 return dict.pop(self, key)
288
288
289 def popitem(self):
289 def popitem(self):
290 self.modified = True
290 self.modified = True
291 return dict.popitem(self)
291 return dict.popitem(self)
292
292
293 def clear(self):
293 def clear(self):
294 self.modified = True
294 self.modified = True
295 dict.clear(self)
295 dict.clear(self)
296
296
297 def subDict(self, *keys):
297 def subDict(self, *keys):
298 d = {}
298 d = {}
299 for key in keys:
299 for key in keys:
300 d[key] = self[key]
300 d[key] = self[key]
301 return d
301 return d
302
302
303
303
304
304
305 class EngineAPI(object):
305 class EngineAPI(object):
306 """This is the object through which the user can edit the `properties`
306 """This is the object through which the user can edit the `properties`
307 attribute of an Engine.
307 attribute of an Engine.
308 The Engine Properties object copies all object in and out of itself.
308 The Engine Properties object copies all object in and out of itself.
309 See the EngineProperties object for details.
309 See the EngineProperties object for details.
310 """
310 """
311 _fix=False
311 _fix=False
312 def __init__(self, id):
312 def __init__(self, id):
313 self.id = id
313 self.id = id
314 self.properties = StrictDict()
314 self.properties = StrictDict()
315 self._fix=True
315 self._fix=True
316
316
317 def __setattr__(self, k,v):
317 def __setattr__(self, k,v):
318 if self._fix:
318 if self._fix:
319 raise error.KernelError("I am protected!")
319 raise error.KernelError("I am protected!")
320 else:
320 else:
321 object.__setattr__(self, k, v)
321 object.__setattr__(self, k, v)
322
322
323 def __delattr__(self, key):
323 def __delattr__(self, key):
324 raise error.KernelError("I am protected!")
324 raise error.KernelError("I am protected!")
325
325
326
326
327 _apiDict = {}
327 _apiDict = {}
328
328
329 def get_engine(id):
329 def get_engine(id):
330 """Get the Engine API object, whcih currently just provides the properties
330 """Get the Engine API object, whcih currently just provides the properties
331 object, by ID"""
331 object, by ID"""
332 global _apiDict
332 global _apiDict
333 if not _apiDict.get(id):
333 if not _apiDict.get(id):
334 _apiDict[id] = EngineAPI(id)
334 _apiDict[id] = EngineAPI(id)
335 return _apiDict[id]
335 return _apiDict[id]
336
336
337 def drop_engine(id):
337 def drop_engine(id):
338 """remove an engine"""
338 """remove an engine"""
339 global _apiDict
339 global _apiDict
340 if _apiDict.has_key(id):
340 if _apiDict.has_key(id):
341 del _apiDict[id]
341 del _apiDict[id]
342
342
343 class EngineService(object, service.Service):
343 class EngineService(object, service.Service):
344 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
344 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
345
345
346 zi.implements(IEngineBase)
346 zi.implements(IEngineBase)
347 name = 'EngineService'
347 name = 'EngineService'
348
348
349 def __init__(self, shellClass=Interpreter, mpi=None):
349 def __init__(self, shellClass=Interpreter, mpi=None):
350 """Create an EngineService.
350 """Create an EngineService.
351
351
352 shellClass: something that implements IInterpreter or core1
352 shellClass: something that implements IInterpreter or core1
353 mpi: an mpi module that has rank and size attributes
353 mpi: an mpi module that has rank and size attributes
354 """
354 """
355 self.shellClass = shellClass
355 self.shellClass = shellClass
356 self.shell = self.shellClass()
356 self.shell = self.shellClass()
357 self.mpi = mpi
357 self.mpi = mpi
358 self.id = None
358 self.id = None
359 self.properties = get_engine(self.id).properties
359 self.properties = get_engine(self.id).properties
360 if self.mpi is not None:
360 if self.mpi is not None:
361 log.msg("MPI started with rank = %i and size = %i" %
361 log.msg("MPI started with rank = %i and size = %i" %
362 (self.mpi.rank, self.mpi.size))
362 (self.mpi.rank, self.mpi.size))
363 self.id = self.mpi.rank
363 self.id = self.mpi.rank
364 self._seedNamespace()
364 self._seedNamespace()
365
365
366 # Make id a property so that the shell can get the updated id
366 # Make id a property so that the shell can get the updated id
367
367
368 def _setID(self, id):
368 def _setID(self, id):
369 self._id = id
369 self._id = id
370 self.properties = get_engine(id).properties
370 self.properties = get_engine(id).properties
371 self.shell.push({'id': id})
371 self.shell.push({'id': id})
372
372
373 def _getID(self):
373 def _getID(self):
374 return self._id
374 return self._id
375
375
376 id = property(_getID, _setID)
376 id = property(_getID, _setID)
377
377
378 def _seedNamespace(self):
378 def _seedNamespace(self):
379 self.shell.push({'mpi': self.mpi, 'id' : self.id})
379 self.shell.push({'mpi': self.mpi, 'id' : self.id})
380
380
381 def executeAndRaise(self, msg, callable, *args, **kwargs):
381 def executeAndRaise(self, msg, callable, *args, **kwargs):
382 """Call a method of self.shell and wrap any exception."""
382 """Call a method of self.shell and wrap any exception."""
383 d = defer.Deferred()
383 d = defer.Deferred()
384 try:
384 try:
385 result = callable(*args, **kwargs)
385 result = callable(*args, **kwargs)
386 except:
386 except:
387 # This gives the following:
387 # This gives the following:
388 # et=exception class
388 # et=exception class
389 # ev=exception class instance
389 # ev=exception class instance
390 # tb=traceback object
390 # tb=traceback object
391 et,ev,tb = sys.exc_info()
391 et,ev,tb = sys.exc_info()
392 # This call adds attributes to the exception value
392 # This call adds attributes to the exception value
393 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
393 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
394 # Add another attribute
394 # Add another attribute
395 ev._ipython_engine_info = msg
395 ev._ipython_engine_info = msg
396 f = failure.Failure(ev,et,None)
396 f = failure.Failure(ev,et,None)
397 d.errback(f)
397 d.errback(f)
398 else:
398 else:
399 d.callback(result)
399 d.callback(result)
400
400
401 return d
401 return d
402
402
403
403
404 # The IEngine methods. See the interface for documentation.
404 # The IEngine methods. See the interface for documentation.
405
405
406 def execute(self, lines):
406 def execute(self, lines):
407 msg = {'engineid':self.id,
407 msg = {'engineid':self.id,
408 'method':'execute',
408 'method':'execute',
409 'args':[lines]}
409 'args':[lines]}
410 d = self.executeAndRaise(msg, self.shell.execute, lines)
410 d = self.executeAndRaise(msg, self.shell.execute, lines)
411 d.addCallback(self.addIDToResult)
411 d.addCallback(self.addIDToResult)
412 return d
412 return d
413
413
414 def addIDToResult(self, result):
414 def addIDToResult(self, result):
415 result['id'] = self.id
415 result['id'] = self.id
416 return result
416 return result
417
417
418 def push(self, namespace):
418 def push(self, namespace):
419 msg = {'engineid':self.id,
419 msg = {'engineid':self.id,
420 'method':'push',
420 'method':'push',
421 'args':[repr(namespace.keys())]}
421 'args':[repr(namespace.keys())]}
422 d = self.executeAndRaise(msg, self.shell.push, namespace)
422 d = self.executeAndRaise(msg, self.shell.push, namespace)
423 return d
423 return d
424
424
425 def pull(self, keys):
425 def pull(self, keys):
426 msg = {'engineid':self.id,
426 msg = {'engineid':self.id,
427 'method':'pull',
427 'method':'pull',
428 'args':[repr(keys)]}
428 'args':[repr(keys)]}
429 d = self.executeAndRaise(msg, self.shell.pull, keys)
429 d = self.executeAndRaise(msg, self.shell.pull, keys)
430 return d
430 return d
431
431
432 def push_function(self, namespace):
432 def push_function(self, namespace):
433 msg = {'engineid':self.id,
433 msg = {'engineid':self.id,
434 'method':'push_function',
434 'method':'push_function',
435 'args':[repr(namespace.keys())]}
435 'args':[repr(namespace.keys())]}
436 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
436 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
437 return d
437 return d
438
438
439 def pull_function(self, keys):
439 def pull_function(self, keys):
440 msg = {'engineid':self.id,
440 msg = {'engineid':self.id,
441 'method':'pull_function',
441 'method':'pull_function',
442 'args':[repr(keys)]}
442 'args':[repr(keys)]}
443 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
443 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
444 return d
444 return d
445
445
446 def get_result(self, i=None):
446 def get_result(self, i=None):
447 msg = {'engineid':self.id,
447 msg = {'engineid':self.id,
448 'method':'get_result',
448 'method':'get_result',
449 'args':[repr(i)]}
449 'args':[repr(i)]}
450 d = self.executeAndRaise(msg, self.shell.getCommand, i)
450 d = self.executeAndRaise(msg, self.shell.getCommand, i)
451 d.addCallback(self.addIDToResult)
451 d.addCallback(self.addIDToResult)
452 return d
452 return d
453
453
454 def reset(self):
454 def reset(self):
455 msg = {'engineid':self.id,
455 msg = {'engineid':self.id,
456 'method':'reset',
456 'method':'reset',
457 'args':[]}
457 'args':[]}
458 del self.shell
458 del self.shell
459 self.shell = self.shellClass()
459 self.shell = self.shellClass()
460 self.properties.clear()
460 self.properties.clear()
461 d = self.executeAndRaise(msg, self._seedNamespace)
461 d = self.executeAndRaise(msg, self._seedNamespace)
462 return d
462 return d
463
463
464 def kill(self):
464 def kill(self):
465 drop_engine(self.id)
465 drop_engine(self.id)
466 try:
466 try:
467 reactor.stop()
467 reactor.stop()
468 except RuntimeError:
468 except RuntimeError:
469 log.msg('The reactor was not running apparently.')
469 log.msg('The reactor was not running apparently.')
470 return defer.fail()
470 return defer.fail()
471 else:
471 else:
472 return defer.succeed(None)
472 return defer.succeed(None)
473
473
474 def keys(self):
474 def keys(self):
475 """Return a list of variables names in the users top level namespace.
475 """Return a list of variables names in the users top level namespace.
476
476
477 This used to return a dict of all the keys/repr(values) in the
477 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
478 user's namespace. This was too much info for the ControllerService
479 to handle so it is now just a list of keys.
479 to handle so it is now just a list of keys.
480 """
480 """
481
481
482 remotes = []
482 remotes = []
483 for k in self.shell.user_ns.iterkeys():
483 for k in self.shell.user_ns.iterkeys():
484 if k not in ['__name__', '_ih', '_oh', '__builtins__',
484 if k not in ['__name__', '_ih', '_oh', '__builtins__',
485 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
485 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
486 remotes.append(k)
486 remotes.append(k)
487 return defer.succeed(remotes)
487 return defer.succeed(remotes)
488
488
489 def set_properties(self, properties):
489 def set_properties(self, properties):
490 msg = {'engineid':self.id,
490 msg = {'engineid':self.id,
491 'method':'set_properties',
491 'method':'set_properties',
492 'args':[repr(properties.keys())]}
492 'args':[repr(properties.keys())]}
493 return self.executeAndRaise(msg, self.properties.update, properties)
493 return self.executeAndRaise(msg, self.properties.update, properties)
494
494
495 def get_properties(self, keys=None):
495 def get_properties(self, keys=None):
496 msg = {'engineid':self.id,
496 msg = {'engineid':self.id,
497 'method':'get_properties',
497 'method':'get_properties',
498 'args':[repr(keys)]}
498 'args':[repr(keys)]}
499 if keys is None:
499 if keys is None:
500 keys = self.properties.keys()
500 keys = self.properties.keys()
501 return self.executeAndRaise(msg, self.properties.subDict, *keys)
501 return self.executeAndRaise(msg, self.properties.subDict, *keys)
502
502
503 def _doDel(self, keys):
503 def _doDel(self, keys):
504 for key in keys:
504 for key in keys:
505 del self.properties[key]
505 del self.properties[key]
506
506
507 def del_properties(self, keys):
507 def del_properties(self, keys):
508 msg = {'engineid':self.id,
508 msg = {'engineid':self.id,
509 'method':'del_properties',
509 'method':'del_properties',
510 'args':[repr(keys)]}
510 'args':[repr(keys)]}
511 return self.executeAndRaise(msg, self._doDel, keys)
511 return self.executeAndRaise(msg, self._doDel, keys)
512
512
513 def _doHas(self, keys):
513 def _doHas(self, keys):
514 return [self.properties.has_key(key) for key in keys]
514 return [self.properties.has_key(key) for key in keys]
515
515
516 def has_properties(self, keys):
516 def has_properties(self, keys):
517 msg = {'engineid':self.id,
517 msg = {'engineid':self.id,
518 'method':'has_properties',
518 'method':'has_properties',
519 'args':[repr(keys)]}
519 'args':[repr(keys)]}
520 return self.executeAndRaise(msg, self._doHas, keys)
520 return self.executeAndRaise(msg, self._doHas, keys)
521
521
522 def clear_properties(self):
522 def clear_properties(self):
523 msg = {'engineid':self.id,
523 msg = {'engineid':self.id,
524 'method':'clear_properties',
524 'method':'clear_properties',
525 'args':[]}
525 'args':[]}
526 return self.executeAndRaise(msg, self.properties.clear)
526 return self.executeAndRaise(msg, self.properties.clear)
527
527
528 def push_serialized(self, sNamespace):
528 def push_serialized(self, sNamespace):
529 msg = {'engineid':self.id,
529 msg = {'engineid':self.id,
530 'method':'push_serialized',
530 'method':'push_serialized',
531 'args':[repr(sNamespace.keys())]}
531 'args':[repr(sNamespace.keys())]}
532 ns = {}
532 ns = {}
533 for k,v in sNamespace.iteritems():
533 for k,v in sNamespace.iteritems():
534 try:
534 try:
535 unserialized = newserialized.IUnSerialized(v)
535 unserialized = newserialized.IUnSerialized(v)
536 ns[k] = unserialized.getObject()
536 ns[k] = unserialized.getObject()
537 except:
537 except:
538 return defer.fail()
538 return defer.fail()
539 return self.executeAndRaise(msg, self.shell.push, ns)
539 return self.executeAndRaise(msg, self.shell.push, ns)
540
540
541 def pull_serialized(self, keys):
541 def pull_serialized(self, keys):
542 msg = {'engineid':self.id,
542 msg = {'engineid':self.id,
543 'method':'pull_serialized',
543 'method':'pull_serialized',
544 'args':[repr(keys)]}
544 'args':[repr(keys)]}
545 if isinstance(keys, str):
545 if isinstance(keys, str):
546 keys = [keys]
546 keys = [keys]
547 if len(keys)==1:
547 if len(keys)==1:
548 d = self.executeAndRaise(msg, self.shell.pull, keys)
548 d = self.executeAndRaise(msg, self.shell.pull, keys)
549 d.addCallback(newserialized.serialize)
549 d.addCallback(newserialized.serialize)
550 return d
550 return d
551 elif len(keys)>1:
551 elif len(keys)>1:
552 d = self.executeAndRaise(msg, self.shell.pull, keys)
552 d = self.executeAndRaise(msg, self.shell.pull, keys)
553 @d.addCallback
553 @d.addCallback
554 def packThemUp(values):
554 def packThemUp(values):
555 serials = []
555 serials = []
556 for v in values:
556 for v in values:
557 try:
557 try:
558 serials.append(newserialized.serialize(v))
558 serials.append(newserialized.serialize(v))
559 except:
559 except:
560 return defer.fail(failure.Failure())
560 return defer.fail(failure.Failure())
561 return serials
561 return serials
562 return packThemUp
562 return packThemUp
563
563
564
564
565 def queue(methodToQueue):
565 def queue(methodToQueue):
566 def queuedMethod(this, *args, **kwargs):
566 def queuedMethod(this, *args, **kwargs):
567 name = methodToQueue.__name__
567 name = methodToQueue.__name__
568 return this.submitCommand(Command(name, *args, **kwargs))
568 return this.submitCommand(Command(name, *args, **kwargs))
569 return queuedMethod
569 return queuedMethod
570
570
571 class QueuedEngine(object):
571 class QueuedEngine(object):
572 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
572 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
573
573
574 The resulting object will implement IEngineQueued which extends
574 The resulting object will implement IEngineQueued which extends
575 IEngineCore which extends (IEngineBase, IEngineSerialized).
575 IEngineCore which extends (IEngineBase, IEngineSerialized).
576
576
577 This seems like the best way of handling it, but I am not sure. The
577 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
578 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
579 mix-in intefaces. The problem I have with this is adpatation is
580 more difficult and complicated because there can be can multiple
580 more difficult and complicated because there can be can multiple
581 original and final Interfaces.
581 original and final Interfaces.
582 """
582 """
583
583
584 zi.implements(IEngineQueued)
584 zi.implements(IEngineQueued)
585
585
586 def __init__(self, engine):
586 def __init__(self, engine):
587 """Create a QueuedEngine object from an engine
587 """Create a QueuedEngine object from an engine
588
588
589 engine: An implementor of IEngineCore and IEngineSerialized
589 engine: An implementor of IEngineCore and IEngineSerialized
590 keepUpToDate: whether to update the remote status when the
590 keepUpToDate: whether to update the remote status when the
591 queue is empty. Defaults to False.
591 queue is empty. Defaults to False.
592 """
592 """
593
593
594 # This is the right way to do these tests rather than
594 # This is the right way to do these tests rather than
595 # IEngineCore in list(zi.providedBy(engine)) which will only
595 # IEngineCore in list(zi.providedBy(engine)) which will only
596 # picks of the interfaces that are directly declared by engine.
596 # picks of the interfaces that are directly declared by engine.
597 assert IEngineBase.providedBy(engine), \
597 assert IEngineBase.providedBy(engine), \
598 "engine passed to QueuedEngine doesn't provide IEngineBase"
598 "engine passed to QueuedEngine doesn't provide IEngineBase"
599
599
600 self.engine = engine
600 self.engine = engine
601 self.id = engine.id
601 self.id = engine.id
602 self.queued = []
602 self.queued = []
603 self.history = {}
603 self.history = {}
604 self.engineStatus = {}
604 self.engineStatus = {}
605 self.currentCommand = None
605 self.currentCommand = None
606 self.failureObservers = []
606 self.failureObservers = []
607
607
608 def _get_properties(self):
608 def _get_properties(self):
609 return self.engine.properties
609 return self.engine.properties
610
610
611 properties = property(_get_properties, lambda self, _: None)
611 properties = property(_get_properties, lambda self, _: None)
612 # Queue management methods. You should not call these directly
612 # Queue management methods. You should not call these directly
613
613
614 def submitCommand(self, cmd):
614 def submitCommand(self, cmd):
615 """Submit command to queue."""
615 """Submit command to queue."""
616
616
617 d = defer.Deferred()
617 d = defer.Deferred()
618 cmd.setDeferred(d)
618 cmd.setDeferred(d)
619 if self.currentCommand is not None:
619 if self.currentCommand is not None:
620 if self.currentCommand.finished:
620 if self.currentCommand.finished:
621 # log.msg("Running command immediately: %r" % cmd)
621 # log.msg("Running command immediately: %r" % cmd)
622 self.currentCommand = cmd
622 self.currentCommand = cmd
623 self.runCurrentCommand()
623 self.runCurrentCommand()
624 else: # command is still running
624 else: # command is still running
625 # log.msg("Command is running: %r" % self.currentCommand)
625 # log.msg("Command is running: %r" % self.currentCommand)
626 # log.msg("Queueing: %r" % cmd)
626 # log.msg("Queueing: %r" % cmd)
627 self.queued.append(cmd)
627 self.queued.append(cmd)
628 else:
628 else:
629 # log.msg("No current commands, running: %r" % cmd)
629 # log.msg("No current commands, running: %r" % cmd)
630 self.currentCommand = cmd
630 self.currentCommand = cmd
631 self.runCurrentCommand()
631 self.runCurrentCommand()
632 return d
632 return d
633
633
634 def runCurrentCommand(self):
634 def runCurrentCommand(self):
635 """Run current command."""
635 """Run current command."""
636
636
637 cmd = self.currentCommand
637 cmd = self.currentCommand
638 f = getattr(self.engine, cmd.remoteMethod, None)
638 f = getattr(self.engine, cmd.remoteMethod, None)
639 if f:
639 if f:
640 d = f(*cmd.args, **cmd.kwargs)
640 d = f(*cmd.args, **cmd.kwargs)
641 if cmd.remoteMethod is 'execute':
641 if cmd.remoteMethod is 'execute':
642 d.addCallback(self.saveResult)
642 d.addCallback(self.saveResult)
643 d.addCallback(self.finishCommand)
643 d.addCallback(self.finishCommand)
644 d.addErrback(self.abortCommand)
644 d.addErrback(self.abortCommand)
645 else:
645 else:
646 return defer.fail(AttributeError(cmd.remoteMethod))
646 return defer.fail(AttributeError(cmd.remoteMethod))
647
647
648 def _flushQueue(self):
648 def _flushQueue(self):
649 """Pop next command in queue and run it."""
649 """Pop next command in queue and run it."""
650
650
651 if len(self.queued) > 0:
651 if len(self.queued) > 0:
652 self.currentCommand = self.queued.pop(0)
652 self.currentCommand = self.queued.pop(0)
653 self.runCurrentCommand()
653 self.runCurrentCommand()
654
654
655 def saveResult(self, result):
655 def saveResult(self, result):
656 """Put the result in the history."""
656 """Put the result in the history."""
657 self.history[result['number']] = result
657 self.history[result['number']] = result
658 return result
658 return result
659
659
660 def finishCommand(self, result):
660 def finishCommand(self, result):
661 """Finish currrent command."""
661 """Finish currrent command."""
662
662
663 # The order of these commands is absolutely critical.
663 # The order of these commands is absolutely critical.
664 self.currentCommand.handleResult(result)
664 self.currentCommand.handleResult(result)
665 self.currentCommand.finished = True
665 self.currentCommand.finished = True
666 self._flushQueue()
666 self._flushQueue()
667 return result
667 return result
668
668
669 def abortCommand(self, reason):
669 def abortCommand(self, reason):
670 """Abort current command.
670 """Abort current command.
671
671
672 This eats the Failure but first passes it onto the Deferred that the
672 This eats the Failure but first passes it onto the Deferred that the
673 user has.
673 user has.
674
674
675 It also clear out the queue so subsequence commands don't run.
675 It also clear out the queue so subsequence commands don't run.
676 """
676 """
677
677
678 # The order of these 3 commands is absolutely critical. The currentCommand
678 # 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
679 # must first be marked as finished BEFORE the queue is cleared and before
680 # the current command is sent the failure.
680 # the current command is sent the failure.
681 # Also, the queue must be cleared BEFORE the current command is sent the Failure
681 # 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
682 # 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
683 # queue before we clear it. We should clear ONLY the commands that were in
684 # the queue when the error occured.
684 # the queue when the error occured.
685 self.currentCommand.finished = True
685 self.currentCommand.finished = True
686 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
686 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
687 self.clear_queue(msg=s)
687 self.clear_queue(msg=s)
688 self.currentCommand.handleError(reason)
688 self.currentCommand.handleError(reason)
689
689
690 return None
690 return None
691
691
692 #---------------------------------------------------------------------------
692 #---------------------------------------------------------------------------
693 # IEngineCore methods
693 # IEngineCore methods
694 #---------------------------------------------------------------------------
694 #---------------------------------------------------------------------------
695
695
696 @queue
696 @queue
697 def execute(self, lines):
697 def execute(self, lines):
698 pass
698 pass
699
699
700 @queue
700 @queue
701 def push(self, namespace):
701 def push(self, namespace):
702 pass
702 pass
703
703
704 @queue
704 @queue
705 def pull(self, keys):
705 def pull(self, keys):
706 pass
706 pass
707
707
708 @queue
708 @queue
709 def push_function(self, namespace):
709 def push_function(self, namespace):
710 pass
710 pass
711
711
712 @queue
712 @queue
713 def pull_function(self, keys):
713 def pull_function(self, keys):
714 pass
714 pass
715
715
716 def get_result(self, i=None):
716 def get_result(self, i=None):
717 if i is None:
717 if i is None:
718 i = max(self.history.keys()+[None])
718 i = max(self.history.keys()+[None])
719
719
720 cmd = self.history.get(i, None)
720 cmd = self.history.get(i, None)
721 # Uncomment this line to disable chaching of results
721 # Uncomment this line to disable chaching of results
722 #cmd = None
722 #cmd = None
723 if cmd is None:
723 if cmd is None:
724 return self.submitCommand(Command('get_result', i))
724 return self.submitCommand(Command('get_result', i))
725 else:
725 else:
726 return defer.succeed(cmd)
726 return defer.succeed(cmd)
727
727
728 def reset(self):
728 def reset(self):
729 self.clear_queue()
729 self.clear_queue()
730 self.history = {} # reset the cache - I am not sure we should do this
730 self.history = {} # reset the cache - I am not sure we should do this
731 return self.submitCommand(Command('reset'))
731 return self.submitCommand(Command('reset'))
732
732
733 def kill(self):
733 def kill(self):
734 self.clear_queue()
734 self.clear_queue()
735 return self.submitCommand(Command('kill'))
735 return self.submitCommand(Command('kill'))
736
736
737 @queue
737 @queue
738 def keys(self):
738 def keys(self):
739 pass
739 pass
740
740
741 #---------------------------------------------------------------------------
741 #---------------------------------------------------------------------------
742 # IEngineSerialized methods
742 # IEngineSerialized methods
743 #---------------------------------------------------------------------------
743 #---------------------------------------------------------------------------
744
744
745 @queue
745 @queue
746 def push_serialized(self, namespace):
746 def push_serialized(self, namespace):
747 pass
747 pass
748
748
749 @queue
749 @queue
750 def pull_serialized(self, keys):
750 def pull_serialized(self, keys):
751 pass
751 pass
752
752
753 #---------------------------------------------------------------------------
753 #---------------------------------------------------------------------------
754 # IEngineProperties methods
754 # IEngineProperties methods
755 #---------------------------------------------------------------------------
755 #---------------------------------------------------------------------------
756
756
757 @queue
757 @queue
758 def set_properties(self, namespace):
758 def set_properties(self, namespace):
759 pass
759 pass
760
760
761 @queue
761 @queue
762 def get_properties(self, keys=None):
762 def get_properties(self, keys=None):
763 pass
763 pass
764
764
765 @queue
765 @queue
766 def del_properties(self, keys):
766 def del_properties(self, keys):
767 pass
767 pass
768
768
769 @queue
769 @queue
770 def has_properties(self, keys):
770 def has_properties(self, keys):
771 pass
771 pass
772
772
773 @queue
773 @queue
774 def clear_properties(self):
774 def clear_properties(self):
775 pass
775 pass
776
776
777 #---------------------------------------------------------------------------
777 #---------------------------------------------------------------------------
778 # IQueuedEngine methods
778 # IQueuedEngine methods
779 #---------------------------------------------------------------------------
779 #---------------------------------------------------------------------------
780
780
781 def clear_queue(self, msg=''):
781 def clear_queue(self, msg=''):
782 """Clear the queue, but doesn't cancel the currently running commmand."""
782 """Clear the queue, but doesn't cancel the currently running commmand."""
783
783
784 for cmd in self.queued:
784 for cmd in self.queued:
785 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
785 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
786 self.queued = []
786 self.queued = []
787 return defer.succeed(None)
787 return defer.succeed(None)
788
788
789 def queue_status(self):
789 def queue_status(self):
790 if self.currentCommand is not None:
790 if self.currentCommand is not None:
791 if self.currentCommand.finished:
791 if self.currentCommand.finished:
792 pending = repr(None)
792 pending = repr(None)
793 else:
793 else:
794 pending = repr(self.currentCommand)
794 pending = repr(self.currentCommand)
795 else:
795 else:
796 pending = repr(None)
796 pending = repr(None)
797 dikt = {'queue':map(repr,self.queued), 'pending':pending}
797 dikt = {'queue':map(repr,self.queued), 'pending':pending}
798 return defer.succeed(dikt)
798 return defer.succeed(dikt)
799
799
800 def register_failure_observer(self, obs):
800 def register_failure_observer(self, obs):
801 self.failureObservers.append(obs)
801 self.failureObservers.append(obs)
802
802
803 def unregister_failure_observer(self, obs):
803 def unregister_failure_observer(self, obs):
804 self.failureObservers.remove(obs)
804 self.failureObservers.remove(obs)
805
805
806
806
807 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
807 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
808 # IEngineQueued.
808 # IEngineQueued.
809 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
809 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
810
810
811
811
812 class Command(object):
812 class Command(object):
813 """A command object that encapslates queued commands.
813 """A command object that encapslates queued commands.
814
814
815 This class basically keeps track of a command that has been queued
815 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
816 in a QueuedEngine. It manages the deferreds and hold the method to be called
817 and the arguments to that method.
817 and the arguments to that method.
818 """
818 """
819
819
820
820
821 def __init__(self, remoteMethod, *args, **kwargs):
821 def __init__(self, remoteMethod, *args, **kwargs):
822 """Build a new Command object."""
822 """Build a new Command object."""
823
823
824 self.remoteMethod = remoteMethod
824 self.remoteMethod = remoteMethod
825 self.args = args
825 self.args = args
826 self.kwargs = kwargs
826 self.kwargs = kwargs
827 self.finished = False
827 self.finished = False
828
828
829 def setDeferred(self, d):
829 def setDeferred(self, d):
830 """Sets the deferred attribute of the Command."""
830 """Sets the deferred attribute of the Command."""
831
831
832 self.deferred = d
832 self.deferred = d
833
833
834 def __repr__(self):
834 def __repr__(self):
835 if not self.args:
835 if not self.args:
836 args = ''
836 args = ''
837 else:
837 else:
838 args = str(self.args)[1:-2] #cut off (...,)
838 args = str(self.args)[1:-2] #cut off (...,)
839 for k,v in self.kwargs.iteritems():
839 for k,v in self.kwargs.iteritems():
840 if args:
840 if args:
841 args += ', '
841 args += ', '
842 args += '%s=%r' %(k,v)
842 args += '%s=%r' %(k,v)
843 return "%s(%s)" %(self.remoteMethod, args)
843 return "%s(%s)" %(self.remoteMethod, args)
844
844
845 def handleResult(self, result):
845 def handleResult(self, result):
846 """When the result is ready, relay it to self.deferred."""
846 """When the result is ready, relay it to self.deferred."""
847
847
848 self.deferred.callback(result)
848 self.deferred.callback(result)
849
849
850 def handleError(self, reason):
850 def handleError(self, reason):
851 """When an error has occured, relay it to self.deferred."""
851 """When an error has occured, relay it to self.deferred."""
852
852
853 self.deferred.errback(reason)
853 self.deferred.errback(reason)
854
854
855 class ThreadedEngineService(EngineService):
855 class ThreadedEngineService(EngineService):
856 """An EngineService subclass that defers execute commands to a separate
856 """An EngineService subclass that defers execute commands to a separate
857 thread.
857 thread.
858
858
859 ThreadedEngineService uses twisted.internet.threads.deferToThread to
859 ThreadedEngineService uses twisted.internet.threads.deferToThread to
860 defer execute requests to a separate thread. GUI frontends may want to
860 defer execute requests to a separate thread. GUI frontends may want to
861 use ThreadedEngineService as the engine in an
861 use ThreadedEngineService as the engine in an
862 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
862 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
863 block execution from blocking the GUI thread.
863 block execution from blocking the GUI thread.
864 """
864 """
865
865
866 zi.implements(IEngineBase)
866 zi.implements(IEngineBase)
867
867
868 def __init__(self, shellClass=Interpreter, mpi=None):
868 def __init__(self, shellClass=Interpreter, mpi=None):
869 EngineService.__init__(self, shellClass, mpi)
869 EngineService.__init__(self, shellClass, mpi)
870
870
871 def wrapped_execute(self, msg, lines):
871 def wrapped_execute(self, msg, lines):
872 """Wrap self.shell.execute to add extra information to tracebacks"""
872 """Wrap self.shell.execute to add extra information to tracebacks"""
873
873
874 try:
874 try:
875 result = self.shell.execute(lines)
875 result = self.shell.execute(lines)
876 except Exception,e:
876 except Exception,e:
877 # This gives the following:
877 # This gives the following:
878 # et=exception class
878 # et=exception class
879 # ev=exception class instance
879 # ev=exception class instance
880 # tb=traceback object
880 # tb=traceback object
881 et,ev,tb = sys.exc_info()
881 et,ev,tb = sys.exc_info()
882 # This call adds attributes to the exception value
882 # This call adds attributes to the exception value
883 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
883 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
884 # Add another attribute
884 # Add another attribute
885
885
886 # Create a new exception with the new attributes
886 # Create a new exception with the new attributes
887 e = et(ev._ipython_traceback_text)
887 e = et(ev._ipython_traceback_text)
888 e._ipython_engine_info = msg
888 e._ipython_engine_info = msg
889
889
890 # Re-raise
890 # Re-raise
891 raise e
891 raise e
892
892
893 return result
893 return result
894
894
895
895
896 def execute(self, lines):
896 def execute(self, lines):
897 # Only import this if we are going to use this class
897 # Only import this if we are going to use this class
898 from twisted.internet import threads
898 from twisted.internet import threads
899
899
900 msg = {'engineid':self.id,
900 msg = {'engineid':self.id,
901 'method':'execute',
901 'method':'execute',
902 'args':[lines]}
902 'args':[lines]}
903
903
904 d = threads.deferToThread(self.wrapped_execute, msg, lines)
904 d = threads.deferToThread(self.wrapped_execute, msg, lines)
905 d.addCallback(self.addIDToResult)
905 d.addCallback(self.addIDToResult)
906 return d
906 return d
@@ -1,373 +1,374 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """Test template for complete engine object"""
3 """Test template for complete engine object"""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import cPickle as pickle
18 import cPickle as pickle
19
19
20 from twisted.internet import defer, reactor
20 from twisted.internet import defer, reactor
21 from twisted.python import failure
21 from twisted.python import failure
22 from twisted.application import service
22 from twisted.application import service
23 import zope.interface as zi
23 import zope.interface as zi
24
24
25 from IPython.kernel import newserialized
25 from IPython.kernel import newserialized
26 from IPython.kernel import error
26 from IPython.kernel import error
27 from IPython.kernel.pickleutil import can, uncan
27 from IPython.kernel.pickleutil import can, uncan
28 import IPython.kernel.engineservice as es
28 import IPython.kernel.engineservice as es
29 from IPython.kernel.core.interpreter import Interpreter
29 from IPython.kernel.core.interpreter import Interpreter
30 from IPython.testing.parametric import Parametric, parametric
30 from IPython.testing.parametric import Parametric, parametric
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Tests
33 # Tests
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36
36
37 # A sequence of valid commands run through execute
37 # A sequence of valid commands run through execute
38 validCommands = ['a=5',
38 validCommands = ['a=5',
39 'b=10',
39 'b=10',
40 'a=5; b=10; c=a+b',
40 'a=5; b=10; c=a+b',
41 'import math; 2.0*math.pi',
41 'import math; 2.0*math.pi',
42 """def f():
42 """def f():
43 result = 0.0
43 result = 0.0
44 for i in range(10):
44 for i in range(10):
45 result += i
45 result += i
46 """,
46 """,
47 'if 1<2: a=5',
47 'if 1<2: a=5',
48 """import time
48 """import time
49 time.sleep(0.1)""",
49 time.sleep(0.1)""",
50 """from math import cos;
50 """from math import cos;
51 x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded
51 x = 1.0*cos(0.5)""", # Semicolons lead to Discard ast nodes that should be discarded
52 """s = 1
52 """s = 1
53 s = set()
53 s = set()
54 """, # Trailing whitespace should be allowed.
54 """, # Trailing whitespace should be allowed.
55 """import math
55 """import math
56 math.cos(1.0)""", # Test a method call with a discarded return value
56 math.cos(1.0)""", # Test a method call with a discarded return value
57 """x=1.0234
57 """x=1.0234
58 a=5; b=10""", # Test an embedded semicolon
58 a=5; b=10""", # Test an embedded semicolon
59 """x=1.0234
59 """x=1.0234
60 a=5; b=10;""" # Test both an embedded and trailing semicolon
60 a=5; b=10;""" # Test both an embedded and trailing semicolon
61 ]
61 ]
62
62
63 # A sequence of commands that raise various exceptions
63 # A sequence of commands that raise various exceptions
64 invalidCommands = [('a=1/0',ZeroDivisionError),
64 invalidCommands = [('a=1/0',ZeroDivisionError),
65 ('print v',NameError),
65 ('print v',NameError),
66 ('l=[];l[0]',IndexError),
66 ('l=[];l[0]',IndexError),
67 ("d={};d['a']",KeyError),
67 ("d={};d['a']",KeyError),
68 ("assert 1==0",AssertionError),
68 ("assert 1==0",AssertionError),
69 ("import abababsdbfsbaljasdlja",ImportError),
69 ("import abababsdbfsbaljasdlja",ImportError),
70 ("raise Exception()",Exception)]
70 ("raise Exception()",Exception)]
71
71
72 def testf(x):
72 def testf(x):
73 return 2.0*x
73 return 2.0*x
74
74
75 globala = 99
75 globala = 99
76
76
77 def testg(x):
77 def testg(x):
78 return globala*x
78 return globala*x
79
79
80 class IEngineCoreTestCase(object):
80 class IEngineCoreTestCase(object):
81 """Test an IEngineCore implementer."""
81 """Test an IEngineCore implementer."""
82
82
83 def createShell(self):
83 def createShell(self):
84 return Interpreter()
84 return Interpreter()
85
85
86 def catchQueueCleared(self, f):
86 def catchQueueCleared(self, f):
87 try:
87 try:
88 f.raiseException()
88 f.raiseException()
89 except error.QueueCleared:
89 except error.QueueCleared:
90 pass
90 pass
91
91
92 def testIEngineCoreInterface(self):
92 def testIEngineCoreInterface(self):
93 """Does self.engine claim to implement IEngineCore?"""
93 """Does self.engine claim to implement IEngineCore?"""
94 self.assert_(es.IEngineCore.providedBy(self.engine))
94 self.assert_(es.IEngineCore.providedBy(self.engine))
95
95
96 def testIEngineCoreInterfaceMethods(self):
96 def testIEngineCoreInterfaceMethods(self):
97 """Does self.engine have the methods and attributes in IEngineCore."""
97 """Does self.engine have the methods and attributes in IEngineCore."""
98 for m in list(es.IEngineCore):
98 for m in list(es.IEngineCore):
99 self.assert_(hasattr(self.engine, m))
99 self.assert_(hasattr(self.engine, m))
100
100
101 def testIEngineCoreDeferreds(self):
101 def testIEngineCoreDeferreds(self):
102 d = self.engine.execute('a=5')
102 d = self.engine.execute('a=5')
103 d.addCallback(lambda _: self.engine.pull('a'))
103 d.addCallback(lambda _: self.engine.pull('a'))
104 d.addCallback(lambda _: self.engine.get_result())
104 d.addCallback(lambda _: self.engine.get_result())
105 d.addCallback(lambda _: self.engine.keys())
105 d.addCallback(lambda _: self.engine.keys())
106 d.addCallback(lambda _: self.engine.push(dict(a=10)))
106 d.addCallback(lambda _: self.engine.push(dict(a=10)))
107 return d
107 return d
108
108
109 def runTestExecute(self, cmd):
109 def runTestExecute(self, cmd):
110 self.shell = Interpreter()
110 self.shell = Interpreter()
111 actual = self.shell.execute(cmd)
111 actual = self.shell.execute(cmd)
112 def compare(computed):
112 def compare(computed):
113 actual['id'] = computed['id']
113 actual['id'] = computed['id']
114 self.assertEquals(actual, computed)
114 self.assertEquals(actual, computed)
115 d = self.engine.execute(cmd)
115 d = self.engine.execute(cmd)
116 d.addCallback(compare)
116 d.addCallback(compare)
117 return d
117 return d
118
118
119 @parametric
119 @parametric
120 def testExecute(cls):
120 def testExecute(cls):
121 return [(cls.runTestExecute, cmd) for cmd in validCommands]
121 return [(cls.runTestExecute, cmd) for cmd in validCommands]
122
122
123 def runTestExecuteFailures(self, cmd, exc):
123 def runTestExecuteFailures(self, cmd, exc):
124 def compare(f):
124 def compare(f):
125 self.assertRaises(exc, f.raiseException)
125 self.assertRaises(exc, f.raiseException)
126 d = self.engine.execute(cmd)
126 d = self.engine.execute(cmd)
127 d.addErrback(compare)
127 d.addErrback(compare)
128 return d
128 return d
129
129
130 @parametric
130 @parametric
131 def testExecuteFailuresEngineService(cls):
131 def testExecuteFailuresEngineService(cls):
132 return [(cls.runTestExecuteFailures, cmd, exc)
132 return [(cls.runTestExecuteFailures, cmd, exc)
133 for cmd, exc in invalidCommands]
133 for cmd, exc in invalidCommands]
134
134
135 def runTestPushPull(self, o):
135 def runTestPushPull(self, o):
136 d = self.engine.push(dict(a=o))
136 d = self.engine.push(dict(a=o))
137 d.addCallback(lambda r: self.engine.pull('a'))
137 d.addCallback(lambda r: self.engine.pull('a'))
138 d.addCallback(lambda r: self.assertEquals(o,r))
138 d.addCallback(lambda r: self.assertEquals(o,r))
139 return d
139 return d
140
140
141 @parametric
141 @parametric
142 def testPushPull(cls):
142 def testPushPull(cls):
143 objs = [10,"hi there",1.2342354,{"p":(1,2)},None]
143 objs = [10,"hi there",1.2342354,{"p":(1,2)},None]
144 return [(cls.runTestPushPull, o) for o in objs]
144 return [(cls.runTestPushPull, o) for o in objs]
145
145
146 def testPullNameError(self):
146 def testPullNameError(self):
147 d = self.engine.push(dict(a=5))
147 d = self.engine.push(dict(a=5))
148 d.addCallback(lambda _:self.engine.reset())
148 d.addCallback(lambda _:self.engine.reset())
149 d.addCallback(lambda _: self.engine.pull("a"))
149 d.addCallback(lambda _: self.engine.pull("a"))
150 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
150 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
151 return d
151 return d
152
152
153 def testPushPullFailures(self):
153 def testPushPullFailures(self):
154 d = self.engine.pull('a')
154 d = self.engine.pull('a')
155 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
155 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
156 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
156 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
157 d.addCallback(lambda _: self.engine.pull('l'))
157 d.addCallback(lambda _: self.engine.pull('l'))
158 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
158 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
159 d.addCallback(lambda _: self.engine.push(dict(l=lambda x: x)))
159 d.addCallback(lambda _: self.engine.push(dict(l=lambda x: x)))
160 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
160 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
161 return d
161 return d
162
162
163 def testPushPullArray(self):
163 def testPushPullArray(self):
164 try:
164 try:
165 import numpy
165 import numpy
166 except:
166 except:
167 return
167 return
168 a = numpy.random.random(1000)
168 a = numpy.random.random(1000)
169 d = self.engine.push(dict(a=a))
169 d = self.engine.push(dict(a=a))
170 d.addCallback(lambda _: self.engine.pull('a'))
170 d.addCallback(lambda _: self.engine.pull('a'))
171 d.addCallback(lambda b: b==a)
171 d.addCallback(lambda b: b==a)
172 d.addCallback(lambda c: c.all())
172 d.addCallback(lambda c: c.all())
173 return self.assertDeferredEquals(d, True)
173 return self.assertDeferredEquals(d, True)
174
174
175 def testPushFunction(self):
175 def testPushFunction(self):
176
176
177 d = self.engine.push_function(dict(f=testf))
177 d = self.engine.push_function(dict(f=testf))
178 d.addCallback(lambda _: self.engine.execute('result = f(10)'))
178 d.addCallback(lambda _: self.engine.execute('result = f(10)'))
179 d.addCallback(lambda _: self.engine.pull('result'))
179 d.addCallback(lambda _: self.engine.pull('result'))
180 d.addCallback(lambda r: self.assertEquals(r, testf(10)))
180 d.addCallback(lambda r: self.assertEquals(r, testf(10)))
181 return d
181 return d
182
182
183 def testPullFunction(self):
183 def testPullFunction(self):
184 d = self.engine.push_function(dict(f=testf, g=testg))
184 d = self.engine.push_function(dict(f=testf, g=testg))
185 d.addCallback(lambda _: self.engine.pull_function(('f','g')))
185 d.addCallback(lambda _: self.engine.pull_function(('f','g')))
186 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
186 d.addCallback(lambda r: self.assertEquals(r[0](10), testf(10)))
187 return d
187 return d
188
188
189 def testPushFunctionGlobal(self):
189 def testPushFunctionGlobal(self):
190 """Make sure that pushed functions pick up the user's namespace for globals."""
190 """Make sure that pushed functions pick up the user's namespace for globals."""
191 d = self.engine.push(dict(globala=globala))
191 d = self.engine.push(dict(globala=globala))
192 d.addCallback(lambda _: self.engine.push_function(dict(g=testg)))
192 d.addCallback(lambda _: self.engine.push_function(dict(g=testg)))
193 d.addCallback(lambda _: self.engine.execute('result = g(10)'))
193 d.addCallback(lambda _: self.engine.execute('result = g(10)'))
194 d.addCallback(lambda _: self.engine.pull('result'))
194 d.addCallback(lambda _: self.engine.pull('result'))
195 d.addCallback(lambda r: self.assertEquals(r, testg(10)))
195 d.addCallback(lambda r: self.assertEquals(r, testg(10)))
196 return d
196 return d
197
197
198 def testGetResultFailure(self):
198 def testGetResultFailure(self):
199 d = self.engine.get_result(None)
199 d = self.engine.get_result(None)
200 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
200 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
201 d.addCallback(lambda _: self.engine.get_result(10))
201 d.addCallback(lambda _: self.engine.get_result(10))
202 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
202 d.addErrback(lambda f: self.assertRaises(IndexError, f.raiseException))
203 return d
203 return d
204
204
205 def runTestGetResult(self, cmd):
205 def runTestGetResult(self, cmd):
206 self.shell = Interpreter()
206 self.shell = Interpreter()
207 actual = self.shell.execute(cmd)
207 actual = self.shell.execute(cmd)
208 def compare(computed):
208 def compare(computed):
209 actual['id'] = computed['id']
209 actual['id'] = computed['id']
210 self.assertEquals(actual, computed)
210 self.assertEquals(actual, computed)
211 d = self.engine.execute(cmd)
211 d = self.engine.execute(cmd)
212 d.addCallback(lambda r: self.engine.get_result(r['number']))
212 d.addCallback(lambda r: self.engine.get_result(r['number']))
213 d.addCallback(compare)
213 d.addCallback(compare)
214 return d
214 return d
215
215
216 @parametric
216 @parametric
217 def testGetResult(cls):
217 def testGetResult(cls):
218 return [(cls.runTestGetResult, cmd) for cmd in validCommands]
218 return [(cls.runTestGetResult, cmd) for cmd in validCommands]
219
219
220 def testGetResultDefault(self):
220 def testGetResultDefault(self):
221 cmd = 'a=5'
221 cmd = 'a=5'
222 shell = self.createShell()
222 shell = self.createShell()
223 shellResult = shell.execute(cmd)
223 shellResult = shell.execute(cmd)
224 def popit(dikt, key):
224 def popit(dikt, key):
225 dikt.pop(key)
225 dikt.pop(key)
226 return dikt
226 return dikt
227 d = self.engine.execute(cmd)
227 d = self.engine.execute(cmd)
228 d.addCallback(lambda _: self.engine.get_result())
228 d.addCallback(lambda _: self.engine.get_result())
229 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r,'id')))
229 d.addCallback(lambda r: self.assertEquals(shellResult, popit(r,'id')))
230 return d
230 return d
231
231
232 def testKeys(self):
232 def testKeys(self):
233 d = self.engine.keys()
233 d = self.engine.keys()
234 d.addCallback(lambda s: isinstance(s, list))
234 d.addCallback(lambda s: isinstance(s, list))
235 d.addCallback(lambda r: self.assertEquals(r, True))
235 d.addCallback(lambda r: self.assertEquals(r, True))
236 return d
236 return d
237
237
238 Parametric(IEngineCoreTestCase)
238 Parametric(IEngineCoreTestCase)
239
239
240 class IEngineSerializedTestCase(object):
240 class IEngineSerializedTestCase(object):
241 """Test an IEngineCore implementer."""
241 """Test an IEngineCore implementer."""
242
242
243 def testIEngineSerializedInterface(self):
243 def testIEngineSerializedInterface(self):
244 """Does self.engine claim to implement IEngineCore?"""
244 """Does self.engine claim to implement IEngineCore?"""
245 self.assert_(es.IEngineSerialized.providedBy(self.engine))
245 self.assert_(es.IEngineSerialized.providedBy(self.engine))
246
246
247 def testIEngineSerializedInterfaceMethods(self):
247 def testIEngineSerializedInterfaceMethods(self):
248 """Does self.engine have the methods and attributes in IEngineCore."""
248 """Does self.engine have the methods and attributes in IEngineCore."""
249 for m in list(es.IEngineSerialized):
249 for m in list(es.IEngineSerialized):
250 self.assert_(hasattr(self.engine, m))
250 self.assert_(hasattr(self.engine, m))
251
251
252 def testIEngineSerializedDeferreds(self):
252 def testIEngineSerializedDeferreds(self):
253 dList = []
253 dList = []
254 d = self.engine.push_serialized(dict(key=newserialized.serialize(12345)))
254 d = self.engine.push_serialized(dict(key=newserialized.serialize(12345)))
255 self.assert_(isinstance(d, defer.Deferred))
255 self.assert_(isinstance(d, defer.Deferred))
256 dList.append(d)
256 dList.append(d)
257 d = self.engine.pull_serialized('key')
257 d = self.engine.pull_serialized('key')
258 self.assert_(isinstance(d, defer.Deferred))
258 self.assert_(isinstance(d, defer.Deferred))
259 dList.append(d)
259 dList.append(d)
260 D = defer.DeferredList(dList)
260 D = defer.DeferredList(dList)
261 return D
261 return D
262
262
263 def testPushPullSerialized(self):
263 def testPushPullSerialized(self):
264 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
264 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
265 d = defer.succeed(None)
265 d = defer.succeed(None)
266 for o in objs:
266 for o in objs:
267 self.engine.push_serialized(dict(key=newserialized.serialize(o)))
267 self.engine.push_serialized(dict(key=newserialized.serialize(o)))
268 value = self.engine.pull_serialized('key')
268 value = self.engine.pull_serialized('key')
269 value.addCallback(lambda serial: newserialized.IUnSerialized(serial).getObject())
269 value.addCallback(lambda serial: newserialized.IUnSerialized(serial).getObject())
270 d = self.assertDeferredEquals(value,o,d)
270 d = self.assertDeferredEquals(value,o,d)
271 return d
271 return d
272
272
273 def testPullSerializedFailures(self):
273 def testPullSerializedFailures(self):
274 d = self.engine.pull_serialized('a')
274 d = self.engine.pull_serialized('a')
275 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
275 d.addErrback(lambda f: self.assertRaises(NameError, f.raiseException))
276 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
276 d.addCallback(lambda _: self.engine.execute('l = lambda x: x'))
277 d.addCallback(lambda _: self.engine.pull_serialized('l'))
277 d.addCallback(lambda _: self.engine.pull_serialized('l'))
278 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
278 d.addErrback(lambda f: self.assertRaises(pickle.PicklingError, f.raiseException))
279 return d
279 return d
280
280
281 Parametric(IEngineSerializedTestCase)
281 Parametric(IEngineSerializedTestCase)
282
282
283 class IEngineQueuedTestCase(object):
283 class IEngineQueuedTestCase(object):
284 """Test an IEngineQueued implementer."""
284 """Test an IEngineQueued implementer."""
285
285
286 def testIEngineQueuedInterface(self):
286 def testIEngineQueuedInterface(self):
287 """Does self.engine claim to implement IEngineQueued?"""
287 """Does self.engine claim to implement IEngineQueued?"""
288 self.assert_(es.IEngineQueued.providedBy(self.engine))
288 self.assert_(es.IEngineQueued.providedBy(self.engine))
289
289
290 def testIEngineQueuedInterfaceMethods(self):
290 def testIEngineQueuedInterfaceMethods(self):
291 """Does self.engine have the methods and attributes in IEngineQueued."""
291 """Does self.engine have the methods and attributes in IEngineQueued."""
292 for m in list(es.IEngineQueued):
292 for m in list(es.IEngineQueued):
293 self.assert_(hasattr(self.engine, m))
293 self.assert_(hasattr(self.engine, m))
294
294
295 def testIEngineQueuedDeferreds(self):
295 def testIEngineQueuedDeferreds(self):
296 dList = []
296 dList = []
297 d = self.engine.clear_queue()
297 d = self.engine.clear_queue()
298 self.assert_(isinstance(d, defer.Deferred))
298 self.assert_(isinstance(d, defer.Deferred))
299 dList.append(d)
299 dList.append(d)
300 d = self.engine.queue_status()
300 d = self.engine.queue_status()
301 self.assert_(isinstance(d, defer.Deferred))
301 self.assert_(isinstance(d, defer.Deferred))
302 dList.append(d)
302 dList.append(d)
303 D = defer.DeferredList(dList)
303 D = defer.DeferredList(dList)
304 return D
304 return D
305
305
306 def testClearQueue(self):
306 def testClearQueue(self):
307 result = self.engine.clear_queue()
307 result = self.engine.clear_queue()
308 d1 = self.assertDeferredEquals(result, None)
308 d1 = self.assertDeferredEquals(result, None)
309 d1.addCallback(lambda _: self.engine.queue_status())
309 d1.addCallback(lambda _: self.engine.queue_status())
310 d2 = self.assertDeferredEquals(d1, {'queue':[], 'pending':'None'})
310 d2 = self.assertDeferredEquals(d1, {'queue':[], 'pending':'None'})
311 return d2
311 return d2
312
312
313 def testQueueStatus(self):
313 def testQueueStatus(self):
314 result = self.engine.queue_status()
314 result = self.engine.queue_status()
315 result.addCallback(lambda r: 'queue' in r and 'pending' in r)
315 result.addCallback(lambda r: 'queue' in r and 'pending' in r)
316 d = self.assertDeferredEquals(result, True)
316 d = self.assertDeferredEquals(result, True)
317 return d
317 return d
318
318
319 Parametric(IEngineQueuedTestCase)
319 Parametric(IEngineQueuedTestCase)
320
320
321 class IEnginePropertiesTestCase(object):
321 class IEnginePropertiesTestCase(object):
322 """Test an IEngineProperties implementor."""
322 """Test an IEngineProperties implementor."""
323
323
324 def testIEnginePropertiesInterface(self):
324 def testIEnginePropertiesInterface(self):
325 """Does self.engine claim to implement IEngineProperties?"""
325 """Does self.engine claim to implement IEngineProperties?"""
326 self.assert_(es.IEngineProperties.providedBy(self.engine))
326 self.assert_(es.IEngineProperties.providedBy(self.engine))
327
327
328 def testIEnginePropertiesInterfaceMethods(self):
328 def testIEnginePropertiesInterfaceMethods(self):
329 """Does self.engine have the methods and attributes in IEngineProperties."""
329 """Does self.engine have the methods and attributes in IEngineProperties."""
330 for m in list(es.IEngineProperties):
330 for m in list(es.IEngineProperties):
331 self.assert_(hasattr(self.engine, m))
331 self.assert_(hasattr(self.engine, m))
332
332
333 def testGetSetProperties(self):
333 def testGetSetProperties(self):
334 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
334 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
335 d = self.engine.set_properties(dikt)
335 d = self.engine.set_properties(dikt)
336 d.addCallback(lambda r: self.engine.get_properties())
336 d.addCallback(lambda r: self.engine.get_properties())
337 d = self.assertDeferredEquals(d, dikt)
337 d = self.assertDeferredEquals(d, dikt)
338 d.addCallback(lambda r: self.engine.get_properties(('c',)))
338 d.addCallback(lambda r: self.engine.get_properties(('c',)))
339 d = self.assertDeferredEquals(d, {'c': dikt['c']})
339 d = self.assertDeferredEquals(d, {'c': dikt['c']})
340 d.addCallback(lambda r: self.engine.set_properties(dict(c=False)))
340 d.addCallback(lambda r: self.engine.set_properties(dict(c=False)))
341 d.addCallback(lambda r: self.engine.get_properties(('c', 'd')))
341 d.addCallback(lambda r: self.engine.get_properties(('c', 'd')))
342 d = self.assertDeferredEquals(d, dict(c=False, d=None))
342 d = self.assertDeferredEquals(d, dict(c=False, d=None))
343 return d
343 return d
344
344
345 def testClearProperties(self):
345 def testClearProperties(self):
346 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
346 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
347 d = self.engine.set_properties(dikt)
347 d = self.engine.set_properties(dikt)
348 d.addCallback(lambda r: self.engine.clear_properties())
348 d.addCallback(lambda r: self.engine.clear_properties())
349 d.addCallback(lambda r: self.engine.get_properties())
349 d.addCallback(lambda r: self.engine.get_properties())
350 d = self.assertDeferredEquals(d, {})
350 d = self.assertDeferredEquals(d, {})
351 return d
351 return d
352
352
353 def testDelHasProperties(self):
353 def testDelHasProperties(self):
354 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
354 dikt = dict(a=5, b='asdf', c=True, d=None, e=range(5))
355 d = self.engine.set_properties(dikt)
355 d = self.engine.set_properties(dikt)
356 d.addCallback(lambda r: self.engine.del_properties(('b','e')))
356 d.addCallback(lambda r: self.engine.del_properties(('b','e')))
357 d.addCallback(lambda r: self.engine.has_properties(('a','b','c','d','e')))
357 d.addCallback(lambda r: self.engine.has_properties(('a','b','c','d','e')))
358 d = self.assertDeferredEquals(d, [True, False, True, True, False])
358 d = self.assertDeferredEquals(d, [True, False, True, True, False])
359 return d
359 return d
360
360
361 def testStrictDict(self):
361 def testStrictDict(self):
362 s = """from IPython.kernel.engineservice import get_engine
362 s = """from IPython.kernel.engineservice import get_engine
363 p = get_engine(%s).properties"""%self.engine.id
363 p = get_engine(%s).properties"""%self.engine.id
364 d = self.engine.execute(s)
364 d = self.engine.execute(s)
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
366 d = self.assertDeferredRaises(d, error.InvalidProperty)
366 d.addErrback(lambda f: self.assertRaises(error.InvalidProperty,
367 f.raiseException))
367 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.get_properties('a'))
370 d.addCallback(lambda r: self.engine.get_properties('a'))
370 d = self.assertDeferredEquals(d, dict(a=range(5)))
371 d = self.assertDeferredEquals(d, dict(a=range(5)))
371 return d
372 return d
372
373
373 Parametric(IEnginePropertiesTestCase)
374 Parametric(IEnginePropertiesTestCase)
General Comments 0
You need to be logged in to leave comments. Login now