4
4
The dispatch() function takes a JSON-RPC request, logs it, calls the appropriate method,
5
5
then logs and returns the response.
6
6
"""
7
- import asyncio
8
7
import logging
9
8
import os
10
9
from collections .abc import Iterable
11
10
from configparser import ConfigParser
12
- from contextlib import contextmanager
13
11
from json import JSONDecodeError
14
12
from json import dumps as default_serialize , loads as default_deserialize
15
- from types import SimpleNamespace
16
13
from typing import (
17
14
Any ,
18
15
Callable ,
19
16
Dict ,
20
- Generator ,
21
17
Iterable ,
22
18
List ,
23
19
NamedTuple ,
33
29
from pkg_resources import resource_string
34
30
35
31
from .log import log_
36
- from .methods import Method , Methods , global_methods , validate_args , lookup
32
+ from .methods import Methods , global_methods , validate_args
37
33
from .request import Request , is_notification , NOID
38
34
from .response import (
39
- ApiErrorResponse ,
40
35
BatchResponse ,
41
36
ExceptionResponse ,
42
37
InvalidJSONResponse ,
45
40
MethodNotFoundResponse ,
46
41
NotificationResponse ,
47
42
Response ,
48
- SuccessResponse ,
49
43
)
50
- from .exceptions import MethodNotFoundError , InvalidParamsError , ApiError
44
+
45
+ Context = NamedTuple (
46
+ "Context" ,
47
+ [("request" , Request ), ("extra" , Any )],
48
+ )
51
49
52
50
request_logger = logging .getLogger (__name__ + ".request" )
53
51
response_logger = logging .getLogger (__name__ + ".response" )
54
52
53
+ DEFAULT_REQUEST_LOG_FORMAT = "--> %(message)s"
54
+ DEFAULT_RESPONSE_LOG_FORMAT = "<-- %(message)s"
55
+
55
56
# Prepare the jsonschema validator
56
57
schema = default_deserialize (resource_string (__name__ , "request-schema.json" ))
57
58
klass = validator_for (schema )
58
59
klass .check_schema (schema )
59
60
validator = klass (schema )
60
61
61
- DEFAULT_REQUEST_LOG_FORMAT = "--> %(message)s"
62
- DEFAULT_RESPONSE_LOG_FORMAT = "<-- %(message)s"
63
-
62
+ # Read configuration file
64
63
config = ConfigParser (default_section = "dispatch" )
65
64
config .read ([".jsonrpcserverrc" , os .path .expanduser ("~/.jsonrpcserverrc" )])
66
65
67
- Context = NamedTuple (
68
- "Context" ,
69
- [("request" , Request ), ("extra" , Any )],
70
- )
71
-
72
66
73
67
def add_handlers () -> Tuple [logging .Handler , logging .Handler ]:
74
68
# Request handler
@@ -120,49 +114,30 @@ def validate(request: Union[Dict, List], schema: dict) -> Union[Dict, List]:
120
114
return request
121
115
122
116
123
- def call (method : Method , * args : Any , ** kwargs : Any ) -> Any :
124
- """
125
- Validates arguments and then calls the method.
126
-
127
- Args:
128
- method: The method to call.
129
- *args, **kwargs: Arguments to the method.
130
-
131
- Returns:
132
- The "result" part of the JSON-RPC response (the return value from the method).
133
- """
134
- return validate_args (method , * args , ** kwargs )(* args , ** kwargs )
117
+ def c (request , method , * args , ** kwargs ) -> Response :
118
+ errors = validate_args (method , * args , ** kwargs )
119
+ return (
120
+ method (* args , ** kwargs )
121
+ if not errors
122
+ else InvalidParamsResponse (data = errors , id = request .id )
123
+ )
135
124
136
125
137
- @contextmanager
138
- def handle_exceptions (request : Request ) -> Generator :
139
- handler = SimpleNamespace (response = None )
140
- try :
141
- yield handler
142
- except MethodNotFoundError :
143
- handler .response = MethodNotFoundResponse (id = request .id , data = request .method )
144
- except (InvalidParamsError , AssertionError ) as exc :
145
- # InvalidParamsError is raised by validate_args. AssertionError is raised inside
146
- # the methods, however it's better to raise InvalidParamsError inside methods.
147
- # AssertionError will be removed in the next major release.
148
- handler .response = InvalidParamsResponse (id = request .id , data = str (exc ))
149
- except ApiError as exc : # Method signals custom error
150
- handler .response = ApiErrorResponse (
151
- str (exc ), code = exc .code , data = exc .data , id = request .id
126
+ def call (request : Request , method : Callable , * , extra : Any ) -> Response :
127
+ return (
128
+ c (
129
+ request ,
130
+ method ,
131
+ * ([Context (request = request , extra = extra )] + request .params ),
132
+ )
133
+ if isinstance (request .params , list )
134
+ else c (
135
+ request ,
136
+ method ,
137
+ Context (request = request , extra = extra ),
138
+ ** request .params ,
152
139
)
153
- except asyncio .CancelledError :
154
- # Allow CancelledError from asyncio task cancellation to bubble up. Without
155
- # this, CancelledError is caught and handled, resulting in a "Server error"
156
- # response object from the dispatcher, but because the CancelledError doesn't
157
- # bubble up the rpc_server task doesn't exit. See PR
158
- # https://github.com/bcb/jsonrpcserver/pull/132
159
- raise
160
- except Exception as exc : # Other error inside method - server error
161
- logging .exception (exc )
162
- handler .response = ExceptionResponse (exc , id = request .id )
163
- finally :
164
- if is_notification (request ):
165
- handler .response = NotificationResponse ()
140
+ )
166
141
167
142
168
143
def safe_call (
@@ -179,28 +154,19 @@ def safe_call(
179
154
Returns:
180
155
A Response object.
181
156
"""
182
- with handle_exceptions ( request ) as handler :
183
- if isinstance ( request . params , list ) :
184
- result = call (
185
- lookup ( methods , request . method ),
186
- * ([ Context ( request = request , extra = extra )] + request . params ),
187
- )
157
+ if request . method in methods . items :
158
+ try :
159
+ response = call (request , methods . items [ request . method ], extra = extra )
160
+ except Exception as exc : # Other error inside method - server error
161
+ logging . exception ( exc )
162
+ return ExceptionResponse ( exc , id = request . id )
188
163
else :
189
- result = call (
190
- lookup (methods , request .method ),
191
- Context (request = request , extra = extra ),
192
- ** request .params ,
193
- )
194
- # Ensure value returned from the method is JSON-serializable. If not,
195
- # handle_exception will set handler.response to an ExceptionResponse
196
- serialize (result )
197
- handler .response = SuccessResponse (
198
- result = result , id = request .id , serialize_func = serialize
199
- )
200
- return handler .response
164
+ return NotificationResponse () if is_notification (request ) else response
165
+ else :
166
+ return MethodNotFoundResponse (data = request .method , id = request .id )
201
167
202
168
203
- def call_requests (
169
+ def dispatch_requests_pure (
204
170
requests : Union [Request , Iterable [Request ]],
205
171
methods : Methods ,
206
172
* ,
@@ -233,6 +199,19 @@ def call_requests(
233
199
)
234
200
235
201
202
+ def dispatch_requests (
203
+ requests : Union [Request , Iterable [Request ]],
204
+ methods : Methods ,
205
+ * ,
206
+ extra : Optional [Any ] = None ,
207
+ serialize : Callable = default_serialize ,
208
+ ) -> Response :
209
+ """
210
+ Impure (public) version of dispatch_requests_pure - has default values.
211
+ """
212
+ return dispatch_requests_pure (requests , methods , extra = extra , serialize = serialize )
213
+
214
+
236
215
def create_requests (
237
216
requests : Union [Dict , List [Dict ]],
238
217
) -> Union [Request , List [Request ]]:
@@ -293,12 +272,13 @@ def dispatch_pure(
293
272
return InvalidJSONResponse (data = str (exc ))
294
273
except ValidationError as exc :
295
274
return InvalidJSONRPCResponse (data = None )
296
- return call_requests (
297
- create_requests (deserialized ),
298
- methods = methods ,
299
- extra = extra ,
300
- serialize = serialize ,
301
- )
275
+ else :
276
+ return dispatch_requests_pure (
277
+ create_requests (deserialized ),
278
+ methods = methods ,
279
+ extra = extra ,
280
+ serialize = serialize ,
281
+ )
302
282
303
283
304
284
@apply_config (config )
0 commit comments