2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
"""L{twisted.manhole} L{PB<twisted.spread.pb>} service implementation.
10
from twisted import copyright
11
from twisted.spread import pb
12
from twisted.python import log, failure
13
from twisted.cred import portal
14
from twisted.application import service
15
from zope.interface import implements, Interface
21
from cStringIO import StringIO
30
def __init__(self, type_, list):
34
def write(self, text):
35
log.msg("%s: %s" % (self.type, string.strip(str(text))))
36
self.list.append((self.type, text))
41
def consolidate(self):
42
"""Concatenate adjacent messages of same type into one.
44
Greatly cuts down on the number of elements, increasing
45
network transport friendliness considerably.
54
for i in xrange(1, len(self.list)):
55
(mtype, message) = inlist[i]
56
if mtype == last_type:
59
if (i - block_begin) == 1:
60
outlist.append(inlist[block_begin])
62
messages = map(lambda l: l[1],
63
inlist[block_begin:i])
64
message = string.join(messages, '')
65
outlist.append((last_type, message))
70
class IManholeClient(Interface):
71
def console(list_of_messages):
72
"""Takes a list of (type, message) pairs to display.
75
- \"stdout\" -- string sent to sys.stdout
77
- \"stderr\" -- string sent to sys.stderr
79
- \"result\" -- string repr of the resulting value
82
- \"exception\" -- a L{failure.Failure}
85
def receiveExplorer(xplorer):
86
"""Receives an explorer.Explorer
89
def listCapabilities():
90
"""List what manholey things I am capable of doing.
92
i.e. C{\"Explorer\"}, C{\"Failure\"}
95
def runInConsole(command, console, globalNS=None, localNS=None,
96
filename=None, args=None, kw=None, unsafeTracebacks=False):
97
"""Run this, directing all output to the specified console.
99
If command is callable, it will be called with the args and keywords
100
provided. Otherwise, command will be compiled and eval'd.
101
(Wouldn't you like a macro?)
103
Returns the command's return value.
105
The console is called with a list of (type, message) pairs for
106
display, see L{IManholeClient.console}.
109
fakeout = FakeStdIO("stdout", output)
110
fakeerr = FakeStdIO("stderr", output)
111
errfile = FakeStdIO("exception", output)
115
filename = str(console)
122
if (globalNS is None) and (not callable(command)):
123
raise ValueError("Need a namespace to evaluate the command in.")
131
if callable(command):
132
val = apply(command, args, kw)
135
code = compile(command, filename, 'eval')
137
code = compile(command, filename, 'single')
140
val = eval(code, globalNS, localNS)
145
(eType, eVal, tb) = sys.exc_info()
146
fail = failure.Failure(eVal, eType, tb)
148
# In CVS reversion 1.35, there was some code here to fill in the
149
# source lines in the traceback for frames in the local command
150
# buffer. But I can't figure out when that's triggered, so it's
151
# going away in the conversion to Failure, until you bring it back.
152
errfile.write(pb.failure2Copyable(fail, unsafeTracebacks))
155
fakeout.consolidate()
160
def _failureOldStyle(fail):
161
"""Pre-Failure manhole representation of exceptions.
163
For compatibility with manhole clients without the \"Failure\"
166
A dictionary with two members:
167
- \'traceback\' -- traceback.extract_tb output; a list of tuples
168
(filename, line number, function name, text) suitable for
169
feeding to traceback.format_list.
171
- \'exception\' -- a list of one or more strings, each
172
ending in a newline. (traceback.format_exception_only output)
176
for f in fail.frames:
177
# (filename, line number, function name, text)
178
tb.append((f[1], f[2], f[0], linecache.getline(f[1], f[2])))
182
'exception': traceback.format_exception_only(fail.type, fail.value)
185
# Capabilities clients are likely to have before they knew how to answer a
186
# "listCapabilities" query.
187
_defaultCapabilities = {
191
class Perspective(pb.Avatar):
193
def __init__(self, service):
194
self.localNamespace = {
200
self.service = service
202
def __getstate__(self):
203
state = self.__dict__.copy()
204
state['clients'] = {}
205
if state['localNamespace'].has_key("__builtins__"):
206
del state['localNamespace']['__builtins__']
209
def attached(self, client, identity):
210
"""A client has attached -- welcome them and add them to the list.
212
self.clients[client] = identity
214
host = ':'.join(map(str, client.broker.transport.getHost()[1:]))
216
msg = self.service.welcomeMessage % {
217
'you': getattr(identity, 'name', str(identity)),
219
'longversion': copyright.longversion,
222
client.callRemote('console', [("stdout", msg)])
224
client.capabilities = _defaultCapabilities
225
client.callRemote('listCapabilities').addCallbacks(
226
self._cbClientCapable, self._ebClientCapable,
227
callbackArgs=(client,),errbackArgs=(client,))
229
def detached(self, client, identity):
231
del self.clients[client]
235
def runInConsole(self, command, *args, **kw):
236
"""Convience method to \"runInConsole with my stuff\".
238
return runInConsole(command,
240
self.service.namespace,
245
unsafeTracebacks=self.service.unsafeTracebacks)
248
### Methods for communicating to my clients.
250
def console(self, message):
251
"""Pass a message to my clients' console.
253
clients = self.clients.keys()
254
origMessage = message
256
for client in clients:
258
if not client.capabilities.has_key("Failure"):
259
if compatMessage is None:
260
compatMessage = origMessage[:]
261
for i in xrange(len(message)):
262
if ((message[i][0] == "exception") and
263
isinstance(message[i][1], failure.Failure)):
266
_failureOldStyle(message[i][1]))
267
client.callRemote('console', compatMessage)
269
client.callRemote('console', message)
270
except pb.ProtocolError:
272
self.detached(client, None)
274
def receiveExplorer(self, objectLink):
275
"""Pass an Explorer on to my clients.
277
clients = self.clients.keys()
278
for client in clients:
280
client.callRemote('receiveExplorer', objectLink)
281
except pb.ProtocolError:
283
self.detached(client, None)
286
def _cbResult(self, val, dnum):
287
self.console([('result', "Deferred #%s Result: %r\n" %(dnum, val))])
290
def _cbClientCapable(self, capabilities, client):
291
log.msg("client %x has %s" % (id(client), capabilities))
292
client.capabilities = capabilities
294
def _ebClientCapable(self, reason, client):
295
reason.trap(AttributeError)
296
log.msg("Couldn't get capabilities from %s, assuming defaults." %
299
### perspective_ methods, commands used by the client.
301
def perspective_do(self, expr):
302
"""Evaluate the given expression, with output to the console.
304
The result is stored in the local variable '_', and its repr()
305
string is sent to the console as a \"result\" message.
307
log.msg(">>> %s" % expr)
308
val = self.runInConsole(expr)
310
self.localNamespace["_"] = val
311
from twisted.internet.defer import Deferred
312
# TODO: client support for Deferred.
313
if isinstance(val, Deferred):
314
self.lastDeferred += 1
315
self.console([('result', "Waiting for Deferred #%s...\n" % self.lastDeferred)])
316
val.addBoth(self._cbResult, self.lastDeferred)
318
self.console([("result", repr(val) + '\n')])
321
def perspective_explore(self, identifier):
322
"""Browse the object obtained by evaluating the identifier.
324
The resulting ObjectLink is passed back through the client's
325
receiveBrowserObject method.
327
object = self.runInConsole(identifier)
329
expl = explorer.explorerPool.getExplorer(object, identifier)
330
self.receiveExplorer(expl)
332
def perspective_watch(self, identifier):
333
"""Watch the object obtained by evaluating the identifier.
335
Whenever I think this object might have changed, I will pass
336
an ObjectLink of it back to the client's receiveBrowserObject
339
raise NotImplementedError
340
object = self.runInConsole(identifier)
342
# Return an ObjectLink of this right away, before the watch.
343
oLink = self.runInConsole(self.browser.browseObject,
345
self.receiveExplorer(oLink)
347
self.runInConsole(self.browser.watchObject,
349
self.receiveExplorer)
354
implements(portal.IRealm)
356
def __init__(self, service):
357
self.service = service
360
def requestAvatar(self, avatarId, mind, *interfaces):
361
if pb.IPerspective not in interfaces:
362
raise NotImplementedError("no interface")
363
if avatarId in self._cache:
364
p = self._cache[avatarId]
366
p = Perspective(self.service)
367
p.attached(mind, avatarId)
369
p.detached(mind, avatarId)
370
return (pb.IPerspective, p, detached)
373
class Service(service.Service):
376
"\nHello %(you)s, welcome to Manhole "
378
"%(longversion)s.\n\n")
380
def __init__(self, unsafeTracebacks=False, namespace=None):
381
self.unsafeTracebacks = unsafeTracebacks
383
'__name__': '__manhole%x__' % (id(self),),
387
self.namespace.update(namespace)
389
def __getstate__(self):
390
"""This returns the persistent state of this shell factory.
392
# TODO -- refactor this and twisted.reality.author.Author to
393
# use common functionality (perhaps the 'code' module?)
394
dict = self.__dict__.copy()
395
ns = dict['namespace'].copy()
396
dict['namespace'] = ns
397
if ns.has_key('__builtins__'):
398
del ns['__builtins__']