aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/manhole/service.py
blob: c9d467900b7ed600a5cea62b414229096d7152e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.


"""L{twisted.manhole} L{PB<twisted.spread.pb>} service implementation.
"""

# twisted imports
from twisted import copyright
from twisted.spread import pb
from twisted.python import log, failure
from twisted.cred import portal
from twisted.application import service
from zope.interface import implements, Interface

# sibling imports
import explorer

# system imports
from cStringIO import StringIO

import string
import sys
import traceback
import types


class FakeStdIO:
    def __init__(self, type_, list):
        self.type = type_
        self.list = list

    def write(self, text):
        log.msg("%s: %s" % (self.type, string.strip(str(text))))
        self.list.append((self.type, text))

    def flush(self):
        pass

    def consolidate(self):
        """Concatenate adjacent messages of same type into one.

        Greatly cuts down on the number of elements, increasing
        network transport friendliness considerably.
        """
        if not self.list:
            return

        inlist = self.list
        outlist = []
        last_type = inlist[0]
        block_begin = 0
        for i in xrange(1, len(self.list)):
            (mtype, message) = inlist[i]
            if mtype == last_type:
                continue
            else:
                if (i - block_begin) == 1:
                    outlist.append(inlist[block_begin])
                else:
                    messages = map(lambda l: l[1],
                                   inlist[block_begin:i])
                    message = string.join(messages, '')
                    outlist.append((last_type, message))
                last_type = mtype
                block_begin = i


class IManholeClient(Interface):
    def console(list_of_messages):
        """Takes a list of (type, message) pairs to display.

        Types include:
            - \"stdout\" -- string sent to sys.stdout

            - \"stderr\" -- string sent to sys.stderr

            - \"result\" -- string repr of the resulting value
                 of the expression

            - \"exception\" -- a L{failure.Failure}
        """

    def receiveExplorer(xplorer):
        """Receives an explorer.Explorer
        """

    def listCapabilities():
        """List what manholey things I am capable of doing.

        i.e. C{\"Explorer\"}, C{\"Failure\"}
        """

def runInConsole(command, console, globalNS=None, localNS=None,
                 filename=None, args=None, kw=None, unsafeTracebacks=False):
    """Run this, directing all output to the specified console.

    If command is callable, it will be called with the args and keywords
    provided.  Otherwise, command will be compiled and eval'd.
    (Wouldn't you like a macro?)

    Returns the command's return value.

    The console is called with a list of (type, message) pairs for
    display, see L{IManholeClient.console}.
    """
    output = []
    fakeout = FakeStdIO("stdout", output)
    fakeerr = FakeStdIO("stderr", output)
    errfile = FakeStdIO("exception", output)
    code = None
    val = None
    if filename is None:
        filename = str(console)
    if args is None:
        args = ()
    if kw is None:
        kw = {}
    if localNS is None:
        localNS = globalNS
    if (globalNS is None) and (not callable(command)):
        raise ValueError("Need a namespace to evaluate the command in.")

    try:
        out = sys.stdout
        err = sys.stderr
        sys.stdout = fakeout
        sys.stderr = fakeerr
        try:
            if callable(command):
                val = apply(command, args, kw)
            else:
                try:
                    code = compile(command, filename, 'eval')
                except:
                    code = compile(command, filename, 'single')

                if code:
                    val = eval(code, globalNS, localNS)
        finally:
            sys.stdout = out
            sys.stderr = err
    except:
        (eType, eVal, tb) = sys.exc_info()
        fail = failure.Failure(eVal, eType, tb)
        del tb
        # In CVS reversion 1.35, there was some code here to fill in the
        # source lines in the traceback for frames in the local command
        # buffer.  But I can't figure out when that's triggered, so it's
        # going away in the conversion to Failure, until you bring it back.
        errfile.write(pb.failure2Copyable(fail, unsafeTracebacks))

    if console:
        fakeout.consolidate()
        console(output)

    return val

def _failureOldStyle(fail):
    """Pre-Failure manhole representation of exceptions.

    For compatibility with manhole clients without the \"Failure\"
    capability.

    A dictionary with two members:
        - \'traceback\' -- traceback.extract_tb output; a list of tuples
             (filename, line number, function name, text) suitable for
             feeding to traceback.format_list.

        - \'exception\' -- a list of one or more strings, each
             ending in a newline. (traceback.format_exception_only output)
    """
    import linecache
    tb = []
    for f in fail.frames:
        # (filename, line number, function name, text)
        tb.append((f[1], f[2], f[0], linecache.getline(f[1], f[2])))

    return {
        'traceback': tb,
        'exception': traceback.format_exception_only(fail.type, fail.value)
        }

# Capabilities clients are likely to have before they knew how to answer a
# "listCapabilities" query.
_defaultCapabilities = {
    "Explorer": 'Set'
    }

class Perspective(pb.Avatar):
    lastDeferred = 0
    def __init__(self, service):
        self.localNamespace = {
            "service": service,
            "avatar": self,
            "_": None,
            }
        self.clients = {}
        self.service = service

    def __getstate__(self):
        state = self.__dict__.copy()
        state['clients'] = {}
        if state['localNamespace'].has_key("__builtins__"):
            del state['localNamespace']['__builtins__']
        return state

    def attached(self, client, identity):
        """A client has attached -- welcome them and add them to the list.
        """
        self.clients[client] = identity

        host = ':'.join(map(str, client.broker.transport.getHost()[1:]))

        msg = self.service.welcomeMessage % {
            'you': getattr(identity, 'name', str(identity)),
            'host': host,
            'longversion': copyright.longversion,
            }

        client.callRemote('console', [("stdout", msg)])

        client.capabilities = _defaultCapabilities
        client.callRemote('listCapabilities').addCallbacks(
            self._cbClientCapable, self._ebClientCapable,
            callbackArgs=(client,),errbackArgs=(client,))

    def detached(self, client, identity):
        try:
            del self.clients[client]
        except KeyError:
            pass

    def runInConsole(self, command, *args, **kw):
        """Convience method to \"runInConsole with my stuff\".
        """
        return runInConsole(command,
                            self.console,
                            self.service.namespace,
                            self.localNamespace,
                            str(self.service),
                            args=args,
                            kw=kw,
                            unsafeTracebacks=self.service.unsafeTracebacks)


    ### Methods for communicating to my clients.

    def console(self, message):
        """Pass a message to my clients' console.
        """
        clients = self.clients.keys()
        origMessage = message
        compatMessage = None
        for client in clients:
            try:
                if "Failure" not in client.capabilities:
                    if compatMessage is None:
                        compatMessage = origMessage[:]
                        for i in xrange(len(message)):
                            if ((message[i][0] == "exception") and
                                isinstance(message[i][1], failure.Failure)):
                                compatMessage[i] = (
                                    message[i][0],
                                    _failureOldStyle(message[i][1]))
                    client.callRemote('console', compatMessage)
                else:
                    client.callRemote('console', message)
            except pb.ProtocolError:
                # Stale broker.
                self.detached(client, None)

    def receiveExplorer(self, objectLink):
        """Pass an Explorer on to my clients.
        """
        clients = self.clients.keys()
        for client in clients:
            try:
                client.callRemote('receiveExplorer', objectLink)
            except pb.ProtocolError:
                # Stale broker.
                self.detached(client, None)


    def _cbResult(self, val, dnum):
        self.console([('result', "Deferred #%s Result: %r\n" %(dnum, val))])
        return val

    def _cbClientCapable(self, capabilities, client):
        log.msg("client %x has %s" % (id(client), capabilities))
        client.capabilities = capabilities

    def _ebClientCapable(self, reason, client):
        reason.trap(AttributeError)
        log.msg("Couldn't get capabilities from %s, assuming defaults." %
                (client,))

    ### perspective_ methods, commands used by the client.

    def perspective_do(self, expr):
        """Evaluate the given expression, with output to the console.

        The result is stored in the local variable '_', and its repr()
        string is sent to the console as a \"result\" message.
        """
        log.msg(">>> %s" % expr)
        val = self.runInConsole(expr)
        if val is not None:
            self.localNamespace["_"] = val
            from twisted.internet.defer import Deferred
            # TODO: client support for Deferred.
            if isinstance(val, Deferred):
                self.lastDeferred += 1
                self.console([('result', "Waiting for Deferred #%s...\n" % self.lastDeferred)])
                val.addBoth(self._cbResult, self.lastDeferred)
            else:
                self.console([("result", repr(val) + '\n')])
        log.msg("<<<")

    def perspective_explore(self, identifier):
        """Browse the object obtained by evaluating the identifier.

        The resulting ObjectLink is passed back through the client's
        receiveBrowserObject method.
        """
        object = self.runInConsole(identifier)
        if object:
            expl = explorer.explorerPool.getExplorer(object, identifier)
            self.receiveExplorer(expl)

    def perspective_watch(self, identifier):
        """Watch the object obtained by evaluating the identifier.

        Whenever I think this object might have changed, I will pass
        an ObjectLink of it back to the client's receiveBrowserObject
        method.
        """
        raise NotImplementedError
        object = self.runInConsole(identifier)
        if object:
            # Return an ObjectLink of this right away, before the watch.
            oLink = self.runInConsole(self.browser.browseObject,
                                      object, identifier)
            self.receiveExplorer(oLink)

            self.runInConsole(self.browser.watchObject,
                              object, identifier,
                              self.receiveExplorer)


class Realm:

    implements(portal.IRealm)

    def __init__(self, service):
        self.service = service
        self._cache = {}

    def requestAvatar(self, avatarId, mind, *interfaces):
        if pb.IPerspective not in interfaces:
            raise NotImplementedError("no interface")
        if avatarId in self._cache:
            p = self._cache[avatarId]
        else:
            p = Perspective(self.service)
        p.attached(mind, avatarId)
        def detached():
            p.detached(mind, avatarId)
        return (pb.IPerspective, p, detached)


class Service(service.Service):

    welcomeMessage = (
        "\nHello %(you)s, welcome to Manhole "
        "on %(host)s.\n"
        "%(longversion)s.\n\n")

    def __init__(self, unsafeTracebacks=False, namespace=None):
        self.unsafeTracebacks = unsafeTracebacks
        self.namespace = {
            '__name__': '__manhole%x__' % (id(self),),
            'sys': sys
            }
        if namespace:
            self.namespace.update(namespace)

    def __getstate__(self):
        """This returns the persistent state of this shell factory.
        """
        # TODO -- refactor this and twisted.reality.author.Author to
        # use common functionality (perhaps the 'code' module?)
        dict = self.__dict__.copy()
        ns = dict['namespace'].copy()
        dict['namespace'] = ns
        if ns.has_key('__builtins__'):
            del ns['__builtins__']
        return dict