Skip to content

Commit f4e8761

Browse files
committed
Fix parameters
1 parent 0e67522 commit f4e8761

File tree

9 files changed

+353
-304
lines changed

9 files changed

+353
-304
lines changed

.pre-commit-config.yaml

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,14 @@ repos:
99
- id: black
1010
args: [--diff, --check]
1111

12-
- repo: https://github.com/PyCQA/pylint
13-
rev: v2.15.5
12+
- repo: local
1413
hooks:
15-
- id: pylint
16-
stages: [manual]
17-
additional_dependencies:
18-
- returns<1
19-
- aiohttp<4
20-
- aiozmq<1
21-
- django<5
22-
- fastapi<1
23-
- flask<3
24-
- flask-socketio<5.3.1
25-
- jsonschema<5
26-
- pytest
27-
- pyzmq
28-
- sanic
29-
- tornado<7
30-
- uvicorn<1
31-
- websockets<11
14+
- id: pylint
15+
name: pylint
16+
entry: pylint
17+
language: system
18+
types: [python]
19+
require_serial: true
3220

3321
- repo: https://github.com/pre-commit/mirrors-mypy
3422
rev: v0.971

docs/dispatch.md

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and gives a JSON-RPC response.
1212

1313
## Optional parameters
1414

15+
The `dispatch` function has some optional parameters that allow you to
16+
customise how it works.
17+
1518
### methods
1619

1720
This lets you specify the methods to dispatch to. It's an alternative to using
@@ -43,23 +46,36 @@ def greet(context, name):
4346

4447
### deserializer
4548

46-
A function that parses the request string. Default is `json.loads`.
49+
A function that parses the JSON request string. Default is `json.loads`.
4750

4851
```python
4952
dispatch(request, deserializer=ujson.loads)
5053
```
5154

55+
### jsonrpc_validator
56+
57+
A function that validates the request once the JSON string has been parsed. The
58+
function should raise an exception (any exception) if the request doesn't match
59+
the JSON-RPC spec (https://www.jsonrpc.org/specification). Default is
60+
`default_jsonrpc_validator` which uses Jsonschema to validate requests against
61+
a schema.
62+
63+
To disable JSON-RPC validation, pass `jsonrpc_validator=lambda _: None`, which
64+
will improve performance because this validation takes around half the dispatch
65+
time.
66+
67+
### args_validator
68+
69+
A function that validates a request's parameters against the signature of the
70+
Python function that will be called for it. Note this should not validate the
71+
_values_ of the parameters, it should simply ensure the parameters match the
72+
Python function's signature. For reference, see the `validate_args` function in
73+
`dispatcher.py`, which is the default `args_validator`.
74+
5275
### serializer
5376

5477
A function that serializes the response string. Default is `json.dumps`.
5578

5679
```python
5780
dispatch(request, serializer=ujson.dumps)
5881
```
59-
60-
### validator
61-
62-
A function that validates the request once the json has been parsed. The
63-
function should raise an exception (any exception) if the request doesn't match
64-
the JSON-RPC spec. Default is `default_validator` which validates the request
65-
against a schema.

examples/fastapi_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""FastAPI server"""
22
from fastapi import FastAPI, Request, Response
3-
import uvicorn # type: ignore
3+
import uvicorn
44
from jsonrpcserver import dispatch, method, Ok, Result
55

66
app = FastAPI()

jsonrpcserver/async_main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .async_dispatcher import dispatch_to_response_pure
66
from .async_methods import Methods, global_methods
77
from .dispatcher import Deserialized
8-
from .main import default_validator, default_deserializer
8+
from .main import default_jsonrpc_validator, default_deserializer
99
from .response import Response, to_serializable
1010
from .sentinels import NOCONTEXT
1111
from .utils import identity
@@ -20,7 +20,7 @@ async def dispatch_to_response(
2020
*,
2121
context: Any = NOCONTEXT,
2222
deserializer: Callable[[str], Deserialized] = default_deserializer,
23-
validator: Callable[[Deserialized], Deserialized] = default_validator,
23+
validator: Callable[[Deserialized], Deserialized] = default_jsonrpc_validator,
2424
post_process: Callable[[Response], Any] = identity,
2525
) -> Union[Response, Iterable[Response], None]:
2626
return await dispatch_to_response_pure(

jsonrpcserver/dispatcher.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from .sentinels import NOCONTEXT, NOID
3232
from .utils import compose, make_list
3333

34+
ArgsValidator = Callable[[Any, Request, Method], Result[Method, ErrorResult]]
3435
Deserialized = Union[Dict[str, Any], List[Dict[str, Any]]]
3536

3637
logger = logging.getLogger(__name__)
@@ -120,7 +121,9 @@ def validate_result(result: Result[SuccessResult, ErrorResult]) -> None:
120121

121122

122123
def call(
123-
request: Request, context: Any, method: Method
124+
request: Request,
125+
context: Any,
126+
method: Method,
124127
) -> Result[SuccessResult, ErrorResult]:
125128
"""Call the method.
126129
@@ -146,7 +149,9 @@ def call(
146149

147150

148151
def validate_args(
149-
request: Request, context: Any, func: Method
152+
request: Request,
153+
context: Any,
154+
func: Method,
150155
) -> Result[Method, ErrorResult]:
151156
"""Ensure the method can be called with the arguments given.
152157
@@ -171,7 +176,10 @@ def get_method(methods: Methods, method_name: str) -> Result[Method, ErrorResult
171176

172177

173178
def dispatch_request(
174-
methods: Methods, context: Any, request: Request
179+
args_validator: ArgsValidator,
180+
methods: Methods,
181+
context: Any,
182+
request: Request,
175183
) -> Tuple[Request, Result[SuccessResult, ErrorResult]]:
176184
"""Get the method, validates the arguments and calls the method.
177185
@@ -182,7 +190,7 @@ def dispatch_request(
182190
return (
183191
request,
184192
get_method(methods, request.method)
185-
.bind(partial(validate_args, request, context))
193+
.bind(partial(args_validator, request, context))
186194
.bind(partial(call, request, context)),
187195
)
188196

@@ -203,9 +211,10 @@ def not_notification(request_result: Any) -> bool:
203211

204212

205213
def dispatch_deserialized(
214+
args_validator: ArgsValidator,
215+
post_process: Callable[[Response], Response],
206216
methods: Methods,
207217
context: Any,
208-
post_process: Callable[[Response], Response],
209218
deserialized: Deserialized,
210219
) -> Union[Response, List[Response], None]:
211220
"""This is simply continuing the pipeline from dispatch_to_response_pure. It exists
@@ -216,15 +225,17 @@ def dispatch_deserialized(
216225
applied to the Response(s).
217226
"""
218227
results = map(
219-
compose(partial(dispatch_request, methods, context), create_request),
228+
compose(
229+
partial(dispatch_request, args_validator, methods, context), create_request
230+
),
220231
make_list(deserialized),
221232
)
222233
responses = starmap(to_response, filter(not_notification, results))
223234
return extract_list(isinstance(deserialized, list), map(post_process, responses))
224235

225236

226237
def validate_request(
227-
validator: Callable[[Deserialized], Deserialized], request: Deserialized
238+
jsonrpc_validator: Callable[[Deserialized], Deserialized], request: Deserialized
228239
) -> Result[Deserialized, ErrorResponse]:
229240
"""Validate the request against a JSON-RPC schema.
230241
@@ -233,9 +244,9 @@ def validate_request(
233244
Returns: Either the same request passed in or an Invalid request response.
234245
"""
235246
try:
236-
validator(request)
247+
jsonrpc_validator(request)
237248
# Since the validator is unknown, the specific exception that will be raised is also
238-
# unknown. Any exception raised we assume the request is invalid and return an
249+
# unknown. Any exception raised we assume the request is invalid and return an
239250
# "invalid request" response.
240251
except Exception: # pylint: disable=broad-except
241252
return Failure(InvalidRequestResponse("The request failed schema validation"))
@@ -259,29 +270,31 @@ def deserialize_request(
259270

260271

261272
def dispatch_to_response_pure(
262-
*,
273+
args_validator: ArgsValidator,
263274
deserializer: Callable[[str], Deserialized],
264-
validator: Callable[[Deserialized], Deserialized],
275+
jsonrpc_validator: Callable[[Deserialized], Deserialized],
276+
post_process: Callable[[Response], Response],
265277
methods: Methods,
266278
context: Any,
267-
post_process: Callable[[Response], Response],
268279
request: str,
269280
) -> Union[Response, List[Response], None]:
270281
"""A function from JSON-RPC request string to Response namedtuple(s), (yet to be
271282
serialized to json).
272283
273284
Returns: A single Response, a list of Responses, or None. None is given for
274-
notifications or batches of notifications, to indicate that we should not
275-
respond.
285+
notifications or batches of notifications, to indicate that we should
286+
not respond.
276287
"""
277288
try:
278289
result = deserialize_request(deserializer, request).bind(
279-
partial(validate_request, validator)
290+
partial(validate_request, jsonrpc_validator)
280291
)
281292
return (
282293
post_process(result)
283294
if isinstance(result, Failure)
284-
else dispatch_deserialized(methods, context, post_process, result.unwrap())
295+
else dispatch_deserialized(
296+
args_validator, post_process, methods, context, result.unwrap()
297+
)
285298
)
286299
except Exception as exc: # pylint: disable=broad-except
287300
# There was an error with the jsonrpcserver library.

jsonrpcserver/main.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,38 @@
1515

1616
from jsonschema.validators import validator_for # type: ignore
1717

18-
from .dispatcher import dispatch_to_response_pure, Deserialized
18+
from .dispatcher import (
19+
ArgsValidator,
20+
Deserialized,
21+
dispatch_to_response_pure,
22+
validate_args,
23+
)
1924
from .methods import Methods, global_methods
2025
from .response import Response, to_dict
2126
from .sentinels import NOCONTEXT
2227
from .utils import identity
2328

2429

30+
default_args_validator = validate_args
2531
default_deserializer = json.loads
2632

27-
# Prepare the jsonschema validator. This is global so it loads only once, not every
28-
# time dispatch is called.
33+
# Prepare the jsonschema validator. This is global so it loads only once, not every time
34+
# dispatch is called.
2935
schema = json.loads(read_text(__package__, "request-schema.json"))
3036
klass = validator_for(schema)
3137
klass.check_schema(schema)
32-
default_validator = klass(schema).validate
38+
default_jsonrpc_validator = klass(schema).validate
3339

3440

3541
def dispatch_to_response(
3642
request: str,
37-
methods: Optional[Methods] = None,
38-
*,
43+
methods: Methods = global_methods,
3944
context: Any = NOCONTEXT,
45+
args_validator: ArgsValidator = default_args_validator,
4046
deserializer: Callable[[str], Deserialized] = json.loads,
41-
validator: Callable[[Deserialized], Deserialized] = default_validator,
47+
jsonrpc_validator: Callable[
48+
[Deserialized], Deserialized
49+
] = default_jsonrpc_validator,
4250
post_process: Callable[[Response], Any] = identity,
4351
) -> Union[Response, List[Response], None]:
4452
"""Takes a JSON-RPC request string and dispatches it to method(s), giving Response
@@ -54,9 +62,11 @@ def dispatch_to_response(
5462
populated with the @method decorator.
5563
context: If given, will be passed as the first argument to methods.
5664
deserializer: Function that deserializes the request string.
57-
validator: Function that validates the JSON-RPC request. The function should
58-
raise an exception if the request is invalid. To disable validation, pass
59-
lambda _: None.
65+
args_validator: Function that validates that the parameters in the request match
66+
the Python function being called.
67+
jsonrpc_validator: Function that validates the JSON-RPC request. The function
68+
should raise an exception if the request is invalid. To disable validation,
69+
pass lambda _: None.
6070
post_process: Function that will be applied to Responses.
6171
6272
Returns:
@@ -67,12 +77,13 @@ def dispatch_to_response(
6777
'{"jsonrpc": "2.0", "result": "pong", "id": 1}'
6878
"""
6979
return dispatch_to_response_pure(
70-
deserializer=deserializer,
71-
validator=validator,
72-
post_process=post_process,
73-
context=context,
74-
methods=global_methods if methods is None else methods,
75-
request=request,
80+
args_validator,
81+
deserializer,
82+
jsonrpc_validator,
83+
post_process,
84+
methods,
85+
context,
86+
request,
7687
)
7788

7889

tests/test_async_dispatcher.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
dispatch_request,
1111
dispatch_to_response_pure,
1212
)
13-
from jsonrpcserver.main import default_deserializer, default_validator
13+
from jsonrpcserver.main import default_deserializer, default_jsonrpc_validator
1414
from jsonrpcserver.codes import ERROR_INTERNAL_ERROR, ERROR_SERVER_ERROR
1515
from jsonrpcserver.exceptions import JsonRpcError
1616
from jsonrpcserver.request import Request
@@ -76,7 +76,7 @@ async def test_dispatch_deserialized() -> None:
7676
async def test_dispatch_to_response_pure_success() -> None:
7777
assert await dispatch_to_response_pure(
7878
deserializer=default_deserializer,
79-
validator=default_validator,
79+
validator=default_jsonrpc_validator,
8080
post_process=identity,
8181
context=NOCONTEXT,
8282
methods={"ping": ping},
@@ -92,7 +92,7 @@ async def ping() -> Result:
9292

9393
assert await dispatch_to_response_pure(
9494
deserializer=default_deserializer,
95-
validator=default_validator,
95+
validator=default_jsonrpc_validator,
9696
post_process=identity,
9797
context=NOCONTEXT,
9898
methods={"ping": ping},

0 commit comments

Comments
 (0)