~oubiwann/txjsonrpc/456441-version1-spec

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
from uuid import uuid4

from twisted.internet import defer, protocol
from twisted.python import reflect

from txjsonrpc import jsonrpclib


class BaseSubhandler:
    """
    Sub-handlers for prefixed methods (e.g., system.listMethods)
    can be added with putSubHandler. By default, prefixes are
    separated with a '.'. Override self.separator to change this.
    """
    separator = '.'

    def __init__(self):
        self.subHandlers = {}

    def putSubHandler(self, prefix, handler):
        self.subHandlers[prefix] = handler

    def getSubHandler(self, prefix):
        return self.subHandlers.get(prefix, None)

    def getSubHandlerPrefixes(self):
        return self.subHandlers.keys()

    def _getFunction(self, functionPath):
        """
        Given a string, return a function, or raise jsonrpclib.NoSuchFunction.

        This returned function will be called, and should return the result
        of the call, a Deferred, or a Fault instance.

        Override in subclasses if you want your own policy. The default
        policy is that given functionPath 'foo', return the method at
        self.jsonrpc_foo, i.e. getattr(self, "jsonrpc_" + functionPath).
        If functionPath contains self.separator, the sub-handler for
        the initial prefix is used to search for the remaining path.
        """
        if functionPath.find(self.separator) != -1:
            prefix, functionPath = functionPath.split(self.separator, 1)
            handler = self.getSubHandler(prefix)
            if handler is None:
                raise jsonrpclib.NoSuchFunction(jsonrpclib.METHOD_NOT_FOUND,
                    "no such sub-handler %s" % prefix)
            return handler._getFunction(functionPath)

        f = getattr(self, "jsonrpc_%s" % functionPath, None)
        if not f:
            raise jsonrpclib.NoSuchFunction(jsonrpclib.METHOD_NOT_FOUND,
                "function %s not found" % functionPath)
        elif not callable(f):
            raise jsonrpclib.NoSuchFunction(jsonrpclib.METHOD_NOT_CALLABLE,
                "function %s not callable" % functionPath)
        else:
            return f

    def _listFunctions(self):
        """
        Return a list of the names of all jsonrpc methods.
        """
        return reflect.prefixedMethodNames(self.__class__, 'jsonrpc_')


class BaseQueryFactory(protocol.ClientFactory):

    deferred = None
    protocol = None

    def __init__(self, method, version=jsonrpclib.VERSION_PRE1, id=None,
                 *args):
        self.version = version
        self.id = id
        self.payload = self._buildVersionedPayload(method, args)
        self.deferred = defer.Deferred()

    def _buildVersionedPayload(self, *args):
        if self.version == jsonrpclib.VERSION_PRE1:
            return jsonrpclib._preV1Request(*args)
        # Inject the id into the request payload.
        elif self.version == jsonrpclib.VERSION_1:
            return jsonrpclib._v1Request(id=self.id, *args)
        elif self.version == jsonrpclib.VERSION_2:
            return jsonrpclib._v2Request(id=self.id, *args)

    def parseResponse(self, contents):
        if not self.deferred:
            return
        try:
            # Convert the response from JSON-RPC to python.
            result = jsonrpclib.loads(contents)
            # Inject the id into the response payload.
            if self.version > jsonrpclib.VERSION_PRE1:
                result["id"] = self.id
            if isinstance(result, list):
                result = result[0]
        except Exception, error:
            self.deferred.errback(error)
            self.deferred = None
        else:
            self.deferred.callback(result)
            self.deferred = None

    def clientConnectionFailed(self, _, reason):
        if self.deferred is not None:
            self.deferred.errback(reason)
            self.deferred = None

    clientConnectionLost = clientConnectionFailed

    def badStatus(self, status, message):
        self.deferred.errback(ValueError(status, message))
        self.deferred = None


class BaseProxy:
    """
    A Proxy base class for making remote JSON-RPC calls.
    """
    def __init__(self, version=jsonrpclib.VERSION_PRE1, factoryClass=None):
        self.version = version
        self.factoryClass = factoryClass

    def _getVersion(self, keywords):
        version = keywords.get("version")
        if version == None:
            version = self.version
        return version

    def _getFactoryClass(self, keywords):
        factoryClass = keywords.get("factoryClass")
        if not factoryClass:
            factoryClass = self.factoryClass
        return factoryClass

    def _getUUID(self):
        return uuid4().get_urn().split(":")[-1]


class Introspection(BaseSubhandler):
    """
    Implement the JSON-RPC Introspection API.

    By default, the methodHelp method returns the 'help' method attribute,
    if it exists, otherwise the __doc__ method attribute, if it exists,
    otherwise the empty string.

    To enable the methodSignature method, add a 'signature' method attribute
    containing a list of lists. See methodSignature's documentation for the
    format. Note the type strings should be JSON-RPC types, not Python types.
    """

    def __init__(self, parent):
        """
        Implement Introspection support for an JSONRPC server.

        @param parent: the JSONRPC server to add Introspection support to.
        """
        BaseSubhandler.__init__(self)
        self._jsonrpc_parent = parent

    def jsonrpc_listMethods(self):
        """
        Return a list of the method names implemented by this server.
        """
        functions = []
        todo = [(self._jsonrpc_parent, '')]
        while todo:
            obj, prefix = todo.pop(0)
            functions.extend([prefix + name for name in obj._listFunctions()])
            todo.extend([(obj.getSubHandler(name),
                          prefix + name + obj.separator)
                         for name in obj.getSubHandlerPrefixes()])
        functions.sort()
        return functions

    jsonrpc_listMethods.signature = [['array']]

    def jsonrpc_methodHelp(self, method):
        """
        Return a documentation string describing the use of the given method.
        """
        method = self._jsonrpc_parent._getFunction(method)
        return (getattr(method, 'help', None)
                or getattr(method, '__doc__', None) or '').strip()

    jsonrpc_methodHelp.signature = [['string', 'string']]

    def jsonrpc_methodSignature(self, method):
        """
        Return a list of type signatures.

        Each type signature is a list of the form [rtype, type1, type2, ...]
        where rtype is the return type and typeN is the type of the Nth
        argument. If no signature information is available, the empty
        string is returned.
        """
        method = self._jsonrpc_parent._getFunction(method)
        return getattr(method, 'signature', None) or ''

    jsonrpc_methodSignature.signature = [['array', 'string'],
                                        ['string', 'string']]


def addIntrospection(jsonrpc):
    """
    Add Introspection support to an JSONRPC server.

    @param jsonrpc: The jsonrpc server to add Introspection support to.
    """
    #jsonrpc.putSubHandler('system', Introspection, ('protocol',))
    jsonrpc.putSubHandler('system', Introspection(jsonrpc))