jsonrpcserver 5.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ """Process incoming JSON-RPC requests in Python.
2
+
3
+ Write methods with @method, then pass each request string to dispatch, or to
4
+ async_dispatch in an asyncio server. Documentation:
5
+ https://bensynapse.github.io/jsonrpcserver/
6
+ """
7
+
8
+ # __all__ also tells type checkers that these names are re-exported.
9
+ __all__ = [
10
+ "Error",
11
+ "InvalidParams",
12
+ "JsonRpcError",
13
+ "Result",
14
+ "Success",
15
+ "async_dispatch",
16
+ "async_dispatch_to_response",
17
+ "async_dispatch_to_serializable",
18
+ "dispatch",
19
+ "dispatch_to_response",
20
+ "dispatch_to_serializable",
21
+ "method",
22
+ "serve",
23
+ ]
24
+
25
+
26
+ from .async_main import (
27
+ dispatch as async_dispatch,
28
+ )
29
+ from .async_main import (
30
+ dispatch_to_response as async_dispatch_to_response,
31
+ )
32
+ from .async_main import (
33
+ dispatch_to_serializable as async_dispatch_to_serializable,
34
+ )
35
+ from .exceptions import JsonRpcError
36
+ from .main import dispatch, dispatch_to_response, dispatch_to_serializable
37
+ from .methods import method
38
+ from .result import Error, InvalidParams, Result, Success
39
+ from .server import serve
40
+
41
+ __version__ = "5.0.10"
42
+ """The version of jsonrpcserver, as a string. Added in 5.0.10."""
@@ -0,0 +1,187 @@
1
+ """Async version of dispatcher.py"""
2
+
3
+ import asyncio
4
+ import logging
5
+ from functools import partial
6
+ from inspect import isawaitable
7
+ from itertools import starmap
8
+ from typing import Any, Callable, Optional, Tuple, cast
9
+
10
+ from oslash.either import Left
11
+
12
+ from .dispatcher import (
13
+ NORESPONSE,
14
+ BatchTooLargeResponse,
15
+ Deserialized,
16
+ InvalidResultError,
17
+ create_request,
18
+ deserialize_request,
19
+ exception_data,
20
+ extract_args,
21
+ extract_kwargs,
22
+ extract_list,
23
+ get_method,
24
+ log_invalid_result,
25
+ member_id,
26
+ not_notification,
27
+ to_response,
28
+ validate_args,
29
+ validate_request,
30
+ validate_result,
31
+ )
32
+ from .exceptions import JsonRpcError
33
+ from .methods import AnyMethod, MethodsArgument
34
+
35
+ # Importable from here in 5.0.9. The "as" form marks a re-export.
36
+ from .methods import Method as Method
37
+ from .methods import Methods as Methods
38
+ from .request import Request
39
+ from .response import InvalidRequestResponse, Response, ServerErrorResponse
40
+ from .result import ErrorResult, InternalErrorResult, Result
41
+ from .utils import identity, make_list, unwrap
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ async def call(
47
+ request: Request, context: Any, method: AnyMethod, debug: bool = False
48
+ ) -> Result:
49
+ try:
50
+ result = method(*extract_args(request, context), **extract_kwargs(request))
51
+ # Plain (sync) methods work too. Their result is used as it is.
52
+ if isawaitable(result):
53
+ result = await result
54
+ validate_result(result)
55
+ except JsonRpcError as exc:
56
+ return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
57
+ except InvalidResultError as exc:
58
+ log_invalid_result(request.method, exc, logger)
59
+ return Left(InternalErrorResult(exception_data(exc, debug)))
60
+ except Exception as exc:
61
+ # Other error inside method - Internal error
62
+ logger.exception("Method %r raised an exception", request.method)
63
+ return Left(InternalErrorResult(exception_data(exc, debug)))
64
+ # validate_result has checked it.
65
+ return cast(Result, result)
66
+
67
+
68
+ async def dispatch_request(
69
+ methods: MethodsArgument, context: Any, request: Request, debug: bool = False
70
+ ) -> Tuple[Request, Result]:
71
+ method = get_method(methods, request.method).bind(
72
+ partial(validate_args, request, context)
73
+ )
74
+ if isinstance(method, Left):
75
+ return request, cast(Result, method)
76
+ return request, await call(request, context, unwrap(method), debug=debug)
77
+
78
+
79
+ async def dispatch_deserialized(
80
+ methods: MethodsArgument,
81
+ context: Any,
82
+ post_process: Callable[[Response], Any],
83
+ deserialized: Deserialized,
84
+ debug: bool = False,
85
+ ) -> Any:
86
+ results = await asyncio.gather(
87
+ *(
88
+ dispatch_request(methods, context, r, debug=debug)
89
+ for r in map(create_request, make_list(deserialized))
90
+ )
91
+ )
92
+ return extract_list(
93
+ isinstance(deserialized, list),
94
+ map(
95
+ post_process,
96
+ starmap(to_response, filter(not_notification, results)),
97
+ ),
98
+ )
99
+
100
+
101
+ async def dispatch_single(
102
+ validator: Callable[[Deserialized], object],
103
+ methods: MethodsArgument,
104
+ context: Any,
105
+ deserialized: Deserialized,
106
+ debug: bool = False,
107
+ ) -> Optional[Response]:
108
+ """Validate and dispatch one member of a batch."""
109
+ if isinstance(deserialized, list):
110
+ return Left(InvalidRequestResponse("The request failed schema validation"))
111
+ result = validate_request(validator, deserialized)
112
+ if isinstance(result, Left):
113
+ return Left(result._error)
114
+ return cast(
115
+ Optional[Response],
116
+ await dispatch_deserialized(
117
+ methods, context, identity, unwrap(result), debug=debug
118
+ ),
119
+ )
120
+
121
+
122
+ async def dispatch_member(
123
+ validator: Callable[[Deserialized], object],
124
+ methods: MethodsArgument,
125
+ context: Any,
126
+ post_process: Callable[[Response], Any],
127
+ member: Any,
128
+ debug: bool = False,
129
+ ) -> Any:
130
+ """Dispatch one member of a batch, keeping any failure inside that member.
131
+
132
+ Returns: The post-processed response, or NORESPONSE for a notification.
133
+ """
134
+ response: Optional[Response]
135
+ try:
136
+ response = await dispatch_single(
137
+ validator, methods, context, member, debug=debug
138
+ )
139
+ except Exception as exc:
140
+ logger.exception("Error while dispatching a member of a batch")
141
+ response = Left(
142
+ ServerErrorResponse(exception_data(exc, debug), member_id(member))
143
+ )
144
+ return NORESPONSE if response is None else post_process(response)
145
+
146
+
147
+ async def dispatch_to_response_pure(
148
+ *,
149
+ deserializer: Callable[[str], Deserialized],
150
+ validator: Callable[[Deserialized], object],
151
+ methods: MethodsArgument,
152
+ context: Any,
153
+ post_process: Callable[[Response], Any],
154
+ request: str,
155
+ debug: bool = False,
156
+ max_batch_size: Optional[int] = None,
157
+ ) -> Any:
158
+ try:
159
+ parsed = deserialize_request(deserializer, request)
160
+ if isinstance(parsed, Left):
161
+ return post_process(Left(parsed._error))
162
+ deserialized = unwrap(parsed)
163
+ if isinstance(deserialized, list) and deserialized:
164
+ if max_batch_size is not None and len(deserialized) > max_batch_size:
165
+ return post_process(
166
+ Left(BatchTooLargeResponse(len(deserialized), max_batch_size))
167
+ )
168
+ responses = await asyncio.gather(
169
+ *(
170
+ dispatch_member(
171
+ validator, methods, context, post_process, member, debug=debug
172
+ )
173
+ for member in deserialized
174
+ )
175
+ )
176
+ return extract_list(
177
+ True, [response for response in responses if response is not NORESPONSE]
178
+ )
179
+ validated = validate_request(validator, deserialized)
180
+ if isinstance(validated, Left):
181
+ return post_process(Left(validated._error))
182
+ return await dispatch_deserialized(
183
+ methods, context, post_process, unwrap(validated), debug=debug
184
+ )
185
+ except Exception as exc:
186
+ logger.exception("Error while dispatching the request")
187
+ return post_process(Left(ServerErrorResponse(exception_data(exc, debug), None)))
@@ -0,0 +1,180 @@
1
+ """The async dispatch functions, for asyncio servers.
2
+
3
+ They take the same arguments as the functions in main.py, and are imported from
4
+ the package as async_dispatch, async_dispatch_to_serializable and
5
+ async_dispatch_to_response. Methods can be async functions or plain ones. The
6
+ requests in a batch run concurrently.
7
+ """
8
+
9
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
10
+
11
+ from .async_dispatcher import dispatch_to_response_pure
12
+ from .dispatcher import Deserialized, check_max_batch_size
13
+ from .main import (
14
+ default_deserializer,
15
+ default_serializer,
16
+ default_validator,
17
+ serialize,
18
+ )
19
+
20
+ # Importable from here in 5.0.9. The "as" form marks a re-export.
21
+ from .methods import Methods as Methods
22
+ from .methods import MethodsArgument, global_methods
23
+ from .response import Response, to_serializable
24
+ from .sentinels import NOCONTEXT
25
+ from .utils import identity
26
+
27
+
28
+ async def dispatch_to_response(
29
+ request: str,
30
+ methods: Optional[MethodsArgument] = None,
31
+ *,
32
+ context: Any = NOCONTEXT,
33
+ deserializer: Callable[[str], Deserialized] = default_deserializer,
34
+ validator: Callable[[Deserialized], object] = default_validator,
35
+ post_process: Callable[[Response], Any] = identity,
36
+ debug: bool = False,
37
+ max_batch_size: Optional[int] = None,
38
+ ) -> Union[Response, Iterable[Response], None]:
39
+ """Dispatch a request and give the response as Response objects. Async.
40
+
41
+ The async version of `dispatch_to_response`, imported from the package as
42
+ `async_dispatch_to_response`. It takes the same arguments.
43
+
44
+ Args:
45
+ request: The JSON-RPC request string.
46
+ methods: The same as for `dispatch_to_response`.
47
+ context: The same as for `dispatch_to_response`.
48
+ deserializer: The same as for `dispatch_to_response`.
49
+ validator: The same as for `dispatch_to_response`.
50
+ post_process: The same as for `dispatch_to_response`.
51
+ debug: The same as for `dispatch_to_response`.
52
+ max_batch_size: The same as for `dispatch_to_response`. Every request in a
53
+ batch runs at the same time, so a limit matters even more here.
54
+
55
+ Returns:
56
+ A Response for a single request, a list of Responses for a batch, or None
57
+ if there's nothing to send back. The type hint says Iterable for a
58
+ batch, but it's a list.
59
+
60
+ Raises:
61
+ ValueError: If `max_batch_size` isn't None or a positive int.
62
+ """
63
+ check_max_batch_size(max_batch_size)
64
+ response = await dispatch_to_response_pure(
65
+ deserializer=deserializer,
66
+ validator=validator,
67
+ post_process=post_process,
68
+ context=context,
69
+ methods=global_methods if methods is None else methods,
70
+ request=request,
71
+ debug=debug,
72
+ max_batch_size=max_batch_size,
73
+ )
74
+ return cast(Union[Response, Iterable[Response], None], response)
75
+
76
+
77
+ async def dispatch_to_serializable(
78
+ request: str,
79
+ methods: Optional[MethodsArgument] = None,
80
+ *,
81
+ context: Any = NOCONTEXT,
82
+ deserializer: Callable[[str], Deserialized] = default_deserializer,
83
+ validator: Callable[[Deserialized], object] = default_validator,
84
+ debug: bool = False,
85
+ max_batch_size: Optional[int] = None,
86
+ ) -> Union[Dict[str, Any], List[Dict[str, Any]], None]:
87
+ """Dispatch a request and give the response as a dict. Async.
88
+
89
+ The async version of `dispatch_to_serializable`, imported from the package as
90
+ `async_dispatch_to_serializable`.
91
+
92
+ Args:
93
+ request: The JSON-RPC request string.
94
+ methods: The same as for `dispatch_to_response`.
95
+ context: The same as for `dispatch_to_response`.
96
+ deserializer: The same as for `dispatch_to_response`.
97
+ validator: The same as for `dispatch_to_response`.
98
+ debug: The same as for `dispatch_to_response`.
99
+ max_batch_size: The same as for `dispatch_to_response`.
100
+
101
+ Returns:
102
+ The response as a dict, a list of dicts for a batch, or None if there's
103
+ nothing to send back.
104
+
105
+ Raises:
106
+ ValueError: If `max_batch_size` isn't None or a positive int.
107
+ """
108
+ return cast(
109
+ Union[Dict[str, Any], List[Dict[str, Any]], None],
110
+ await dispatch_to_response(
111
+ request,
112
+ methods,
113
+ context=context,
114
+ deserializer=deserializer,
115
+ validator=validator,
116
+ debug=debug,
117
+ max_batch_size=max_batch_size,
118
+ post_process=to_serializable,
119
+ ),
120
+ )
121
+
122
+
123
+ async def dispatch_to_json(
124
+ request: str,
125
+ methods: Optional[MethodsArgument] = None,
126
+ *,
127
+ context: Any = NOCONTEXT,
128
+ deserializer: Callable[[str], Deserialized] = default_deserializer,
129
+ validator: Callable[[Deserialized], object] = default_validator,
130
+ debug: bool = False,
131
+ max_batch_size: Optional[int] = None,
132
+ serializer: Callable[
133
+ [Union[Dict[str, Any], List[Dict[str, Any]], None]], str
134
+ ] = default_serializer,
135
+ ) -> str:
136
+ """Dispatch a request and give the response as a JSON string. Async.
137
+
138
+ This is `async_dispatch`, the async version of `dispatch`. Methods can be async
139
+ functions or plain ones (plain ones work from 5.0.10). A plain method runs on
140
+ the event loop, so a slow one holds up every other request.
141
+
142
+ Args:
143
+ request: The JSON-RPC request string.
144
+ methods: The same as for `dispatch_to_response`.
145
+ context: The same as for `dispatch_to_response`.
146
+ deserializer: The same as for `dispatch_to_response`.
147
+ validator: The same as for `dispatch_to_response`.
148
+ debug: The same as for `dispatch_to_response`.
149
+ max_batch_size: The same as for `dispatch_to_response`.
150
+ serializer: The same as for `dispatch`.
151
+
152
+ Returns:
153
+ The response as a JSON string, or "" if there's nothing to send back.
154
+
155
+ Raises:
156
+ ValueError: If `max_batch_size` isn't None or a positive int.
157
+
158
+ Example:
159
+ >>> import asyncio
160
+ >>> from jsonrpcserver import Result, Success, async_dispatch
161
+ >>> async def ping() -> Result:
162
+ ... return Success("pong")
163
+ >>> request = '{"jsonrpc": "2.0", "method": "ping", "id": 1}'
164
+ >>> asyncio.run(async_dispatch(request, {"ping": ping}))
165
+ '{"jsonrpc": "2.0", "result": "pong", "id": 1}'
166
+ """
167
+ response = await dispatch_to_serializable(
168
+ request,
169
+ methods,
170
+ context=context,
171
+ deserializer=deserializer,
172
+ validator=validator,
173
+ debug=debug,
174
+ max_batch_size=max_batch_size,
175
+ )
176
+ return "" if response is None else serialize(serializer, response, debug)
177
+
178
+
179
+ dispatch = dispatch_to_json
180
+ """Another name for `dispatch_to_json`. The package exports it as `async_dispatch`."""
jsonrpcserver/codes.py ADDED
@@ -0,0 +1,20 @@
1
+ """The error codes jsonrpcserver sends.
2
+
3
+ The first five are defined by the spec:
4
+ https://www.jsonrpc.org/specification#error_object
5
+ """
6
+
7
+ ERROR_PARSE_ERROR = -32700
8
+ """The request isn't valid JSON (or the deserializer raised)."""
9
+ ERROR_INVALID_REQUEST = -32600
10
+ """The JSON isn't a valid request object, or a batch is empty or too big."""
11
+ ERROR_METHOD_NOT_FOUND = -32601
12
+ """No method has that name."""
13
+ ERROR_INVALID_PARAMS = -32602
14
+ """The params don't fit the method's signature, or the method said they're invalid."""
15
+ ERROR_INTERNAL_ERROR = -32603
16
+ """The method raised, returned something other than a Result, or its result
17
+ could not be serialized.
18
+ """
19
+ ERROR_SERVER_ERROR = -32000
20
+ """Something failed in jsonrpcserver itself, outside any method."""