Skip to content

Replace Response class with a NamedTuple #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# jsonrpcserver Change Log

## 5.0.0 (Coming Soon)

- Methods now take a "context" as the first param. It includes the request
object, plus an "extra" value (None if not passed to dispatch).
- dispatch's context param renamed to "extra". This value is included in the
context object passed to every method.
- Removed "convert camel case" option.

Refactoring/internal changes:

- Changed all classes (Request, Response, Methods) to NamedTuples.

## 4.2.0 (Nov 9, 2020)

- Add ability to use custom serializer and deserializer ([#125](https://github.com/bcb/jsonrpcserver/pull/125))
Expand Down
6 changes: 0 additions & 6 deletions doc/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,6 @@ below):

Adds log handlers, to log all requests and responses to stderr.

**convert_camel_case**

Attempts to clean up requests before processing, by changing the method and
parameter names to snake case. Default is *False*.

**debug**

If True, more information is included in error responses, such as an exception
Expand All @@ -138,7 +133,6 @@ placed in the current or home directory:
```ini
[general]
basic_logging = yes
convert_camel_case = yes
debug = yes
trim_log_values = yes
```
Expand Down
11 changes: 3 additions & 8 deletions jsonrpcserver/async_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
validate,
)
from .methods import Method, Methods, global_methods, validate_args, lookup
from .request import NOCONTEXT, Request
from .request import Request
from .response import (
BatchResponse,
InvalidJSONResponse,
Expand Down Expand Up @@ -69,7 +69,6 @@ async def dispatch_pure(
methods: Methods,
*,
context: Any,
convert_camel_case: bool,
debug: bool,
serialize: Callable,
deserialize: Callable,
Expand All @@ -81,9 +80,7 @@ async def dispatch_pure(
except ValidationError as exc:
return InvalidJSONRPCResponse(data=None, debug=debug)
return await call_requests(
create_requests(
deserialized, context=context, convert_camel_case=convert_camel_case
),
create_requests(deserialized, context=context),
methods,
debug=debug,
serialize=serialize,
Expand All @@ -96,8 +93,7 @@ async def dispatch(
methods: Optional[Methods] = None,
*,
basic_logging: bool = False,
convert_camel_case: bool = False,
context: Any = NOCONTEXT,
context: Optional[dict] = None,
debug: bool = False,
trim_log_values: bool = False,
serialize: Callable = default_serialize,
Expand All @@ -115,7 +111,6 @@ async def dispatch(
methods,
debug=debug,
context=context,
convert_camel_case=convert_camel_case,
serialize=serialize,
deserialize=deserialize,
)
Expand Down
99 changes: 63 additions & 36 deletions jsonrpcserver/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
from types import SimpleNamespace
from typing import (
Any,
Callable,
Dict,
Generator,
Iterable,
List,
NamedTuple,
Optional,
Set,
Tuple,
Union,
Callable,
)

from apply_defaults import apply_config # type: ignore
Expand All @@ -33,7 +34,7 @@

from .log import log_
from .methods import Method, Methods, global_methods, validate_args, lookup
from .request import NOCONTEXT, Request
from .request import Request, is_notification, NOID
from .response import (
ApiErrorResponse,
BatchResponse,
Expand Down Expand Up @@ -63,6 +64,11 @@
config = ConfigParser(default_section="dispatch")
config.read([".jsonrpcserverrc", os.path.expanduser("~/.jsonrpcserverrc")])

Context = NamedTuple(
"Context",
[("request", Request), ("extra", Any)],
)


def add_handlers() -> Tuple[logging.Handler, logging.Handler]:
# Request handler
Expand Down Expand Up @@ -159,12 +165,12 @@ def handle_exceptions(request: Request, debug: bool) -> Generator:
logging.exception(exc)
handler.response = ExceptionResponse(exc, id=request.id, debug=debug)
finally:
if request.is_notification:
if is_notification(request):
handler.response = NotificationResponse()


def safe_call(
request: Request, methods: Methods, *, debug: bool, serialize: Callable
request: Request, methods: Methods, *, debug: bool, extra: Any, serialize: Callable
) -> Response:
"""
Call a Request, catching exceptions to ensure we always return a Response.
Expand All @@ -179,7 +185,17 @@ def safe_call(
A Response object.
"""
with handle_exceptions(request, debug) as handler:
result = call(lookup(methods, request.method), *request.args, **request.kwargs)
if isinstance(request.params, list):
result = call(
lookup(methods, request.method),
*([Context(request=request, extra=extra)] + request.params),
)
else:
result = call(
lookup(methods, request.method),
Context(request=request, extra=extra),
**request.params,
)
# Ensure value returned from the method is JSON-serializable. If not,
# handle_exception will set handler.response to an ExceptionResponse
serialize(result)
Expand All @@ -192,6 +208,8 @@ def safe_call(
def call_requests(
requests: Union[Request, Iterable[Request]],
methods: Methods,
*,
extra: Any,
debug: bool,
serialize: Callable,
) -> Response:
Expand All @@ -204,44 +222,59 @@ def call_requests(
debug: Include more information in error responses.
serialize: Function that is used to serialize data.
"""
if isinstance(requests, Iterable):
return BatchResponse(
[safe_call(r, methods, debug=debug, serialize=serialize) for r in requests],
return (
BatchResponse(
[
safe_call(
r, methods=methods, debug=debug, extra=extra, serialize=serialize
)
for r in requests
],
serialize_func=serialize,
)
return safe_call(requests, methods, debug=debug, serialize=serialize)
if isinstance(requests, list)
else safe_call(
requests, methods=methods, debug=debug, extra=extra, serialize=serialize
)
)


def create_requests(
requests: Union[Dict, List], *, context: Any = NOCONTEXT, convert_camel_case: bool
requests: Union[Dict, List[Dict]],
) -> Union[Request, Set[Request]]:
"""
Create a Request object from a dictionary (or list of them).
Converts a raw deserialized request dictionary to a Request (namedtuple).

Args:
requests: Request object, or a collection of them.
methods: The list of methods that can be called.
context: If specified, will be the first positional argument in all requests.
convert_camel_case: Will convert the method name/any named params to snake case.
requests: Request dict, or a list of dicts.

Returns:
A Request object, or a collection of them.
A Request object, or a list of them.
"""
if isinstance(requests, list):
return {
Request(context=context, convert_camel_case=convert_camel_case, **request)
return (
[
Request(
method=request["method"],
params=request.get("params", []),
id=request.get("id", NOID),
)
for request in requests
}
return Request(context=context, convert_camel_case=convert_camel_case, **requests)
]
if isinstance(requests, list)
else Request(
method=requests["method"],
params=requests.get("params", []),
id=requests.get("id", NOID),
)
)


def dispatch_pure(
request: str,
methods: Methods,
*,
context: Any,
convert_camel_case: bool,
debug: bool,
extra: Any,
serialize: Callable,
deserialize: Callable,
) -> Response:
Expand All @@ -255,9 +288,8 @@ def dispatch_pure(
Args:
request: The incoming request string.
methods: Collection of methods that can be called.
context: If specified, will be the first positional argument in all requests.
convert_camel_case: Will convert the method name/any named params to snake case.
debug: Include more information in error responses.
extra: Will be included in the context dictionary passed to methods.
serialize: Function that is used to serialize data.
deserialize: Function that is used to deserialize data.
Returns:
Expand All @@ -270,10 +302,9 @@ def dispatch_pure(
except ValidationError as exc:
return InvalidJSONRPCResponse(data=None, debug=debug)
return call_requests(
create_requests(
deserialized, context=context, convert_camel_case=convert_camel_case
),
methods,
create_requests(deserialized),
methods=methods,
extra=extra,
debug=debug,
serialize=serialize,
)
Expand All @@ -285,8 +316,7 @@ def dispatch(
methods: Optional[Methods] = None,
*,
basic_logging: bool = False,
convert_camel_case: bool = False,
context: Any = NOCONTEXT,
extra: Optional[dict] = None,
debug: bool = False,
trim_log_values: bool = False,
serialize: Callable = default_serialize,
Expand All @@ -303,9 +333,7 @@ def dispatch(
request: The incoming request string.
methods: Collection of methods that can be called. If not passed, uses the
internal methods object.
context: If specified, will be the first positional argument in all requests.
convert_camel_case: Convert keys in params dictionary from camel case to snake
case.
extra: Extra data available inside methods (as context.extra).
debug: Include more information in error responses.
trim_log_values: Show abbreviated requests and responses in log.
serialize: Function that is used to serialize data.
Expand All @@ -327,8 +355,7 @@ def dispatch(
request,
methods,
debug=debug,
context=context,
convert_camel_case=convert_camel_case,
extra=extra,
serialize=serialize,
deserialize=deserialize,
)
Expand Down
Loading