From c6b05eb8e1edc2b23937dd595f657b0d1f1d1b07 Mon Sep 17 00:00:00 2001 From: Beau Barker Date: Thu, 1 Apr 2021 00:02:36 +1100 Subject: [PATCH] Replace Response class with a NamedTuple Also removes convert_camel_case option, and rename dispatch's "context" param to "extra". --- CHANGELOG.md | 12 +++ doc/api.md | 6 -- jsonrpcserver/async_dispatcher.py | 11 +-- jsonrpcserver/dispatcher.py | 99 ++++++++++++++-------- jsonrpcserver/request.py | 119 +++----------------------- jsonrpcserver/response.py | 28 ++++--- tests/test_dispatcher.py | 134 +++++++++++++++--------------- tests/test_request.py | 114 ++----------------------- 8 files changed, 177 insertions(+), 346 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a0061f..91b4129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/doc/api.md b/doc/api.md index ccb7e89..d3e66c3 100644 --- a/doc/api.md +++ b/doc/api.md @@ -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 @@ -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 ``` diff --git a/jsonrpcserver/async_dispatcher.py b/jsonrpcserver/async_dispatcher.py index 0195184..18dc2a0 100644 --- a/jsonrpcserver/async_dispatcher.py +++ b/jsonrpcserver/async_dispatcher.py @@ -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, @@ -69,7 +69,6 @@ async def dispatch_pure( methods: Methods, *, context: Any, - convert_camel_case: bool, debug: bool, serialize: Callable, deserialize: Callable, @@ -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, @@ -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, @@ -115,7 +111,6 @@ async def dispatch( methods, debug=debug, context=context, - convert_camel_case=convert_camel_case, serialize=serialize, deserialize=deserialize, ) diff --git a/jsonrpcserver/dispatcher.py b/jsonrpcserver/dispatcher.py index d032ba2..c6a9723 100644 --- a/jsonrpcserver/dispatcher.py +++ b/jsonrpcserver/dispatcher.py @@ -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 @@ -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, @@ -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 @@ -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. @@ -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) @@ -192,6 +208,8 @@ def safe_call( def call_requests( requests: Union[Request, Iterable[Request]], methods: Methods, + *, + extra: Any, debug: bool, serialize: Callable, ) -> Response: @@ -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: @@ -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: @@ -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, ) @@ -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, @@ -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. @@ -327,8 +355,7 @@ def dispatch( request, methods, debug=debug, - context=context, - convert_camel_case=convert_camel_case, + extra=extra, serialize=serialize, deserialize=deserialize, ) diff --git a/jsonrpcserver/request.py b/jsonrpcserver/request.py index 7835c4a..cc3fcc6 100644 --- a/jsonrpcserver/request.py +++ b/jsonrpcserver/request.py @@ -3,118 +3,25 @@ Represents a JSON-RPC request object. """ -import re -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, NamedTuple, Union -NOCONTEXT = object() -NOPARAMS = object() - - -# NOID is used as a request's id attribute to signify request is a Notification. We -# can't use None which is a valid ID. NOID = object() -def convert_camel_case_string(name: str) -> str: - """Convert camel case string to snake case""" - string = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) - return re.sub("([a-z0-9])([A-Z])", r"\1_\2", string).lower() - +Request = NamedTuple( + "Request", + [ + ("method", str), + ("params", Union[list, dict]), + ("id", Any), # Use NOID for a Notification. + ], +) -def convert_camel_case_keys(original_dict: Dict[str, Any]) -> Dict[str, Any]: - """Converts all keys of a dict from camel case to snake case, recursively""" - new_dict = dict() - for key, val in original_dict.items(): - if isinstance(val, dict): - # Recurse - new_dict[convert_camel_case_string(key)] = convert_camel_case_keys(val) - else: - new_dict[convert_camel_case_string(key)] = val - return new_dict - -def get_arguments( - params: Union[List, Dict, object] = NOPARAMS, context: Any = NOCONTEXT -) -> Tuple[List, Dict]: +def is_notification(request: Request) -> bool: """ - Get the positional and keyword arguments from a request. - - Takes the 'params' part of a JSON-RPC request and converts it to either positional - or named arguments usable in a Python function call. Note that a JSON-RPC request - can only have positional _or_ named arguments, but not both. See - http://www.jsonrpc.org/specification#parameter_structures - - Args: - params: The 'params' part of the JSON-RPC request (should be a list or dict). - The 'params' value can be a JSON array (Python list), object (Python dict), - or None. - context: Optionally include some context data, which will be included as the - first positional arguments passed to the method. - Returns: - A two-tuple containing the positional (in a list, or None) and named (in a dict, - or None) arguments, extracted from the 'params' part of the request. - """ - positionals, nameds = [], {} # type: list, dict - if params is not NOPARAMS: - assert isinstance(params, (list, dict)) - if isinstance(params, list): - positionals, nameds = (params, {}) - elif isinstance(params, dict): - positionals, nameds = ([], params) - - # If context data was passed, include it as the first positional argument. - if context is not NOCONTEXT: - positionals = [context] + positionals - - return (positionals, nameds) - - -class Request: + True if the request is a JSON-RPC Notification (ie. No id attribute is + included). False if it doesn't, meaning it's a JSON-RPC "Request". """ - Represents a JSON-RPC Request object. - - Encapsulates a JSON-RPC request, providing details such as the method name, - arguments, and whether it's a request or a notification, and provides a `process` - method to execute the request. - - Note: There's no need to validate here, because the schema should have validated the - data already. - """ - - def __init__( - self, - method: str, - *, - params: Any = None, - id: Any = NOID, - jsonrpc: Optional[str] = None, # ignored - context: Any = NOCONTEXT, - convert_camel_case: bool = False, - ) -> None: - """ - Args: - method, params, id, jsonrpc: Parts of the JSON-RPC request. - context: If passed, will be the first positional argument passed to the - method. - convert_camel_case: Will convert the method name and any keyword parameter - names to snake_case. - """ - self.jsonrpc = jsonrpc - self.method = method - self.args, self.kwargs = get_arguments(params or NOPARAMS, context=context) - self.id = id - - if convert_camel_case: - self.method = convert_camel_case_string(self.method) - if self.kwargs: - self.kwargs = convert_camel_case_keys(self.kwargs) - - @property - def is_notification(self) -> bool: - """ - Returns: - True if the request is a JSON-RPC Notification (ie. No id attribute is - included). False if it doesn't, meaning it's a JSON-RPC "Request". - """ - return self.id is NOID + return request.id is NOID diff --git a/jsonrpcserver/response.py b/jsonrpcserver/response.py index d503c5d..1d9c614 100644 --- a/jsonrpcserver/response.py +++ b/jsonrpcserver/response.py @@ -53,13 +53,13 @@ def __init__( @abstractmethod def wanted(self) -> bool: """ - Indicates that this response is wanted by the request; the request had an "id" - member, and was therefore not a JSON-RPC Notification object. All responses are - wanted, except NotificationResponse. + Indicates that this response is wanted by the request; the request had + an "id" member, and was therefore not a JSON-RPC Notification object. + All responses are wanted, except NotificationResponse. - Note that blocking/synchronous transfer protocols require a response to every - request no matter what, in which case this property should be ignored and - str(response) returned regardless. + Note that blocking/synchronous transfer protocols require a response to + every request no matter what, in which case this property should be + ignored and str(response) returned regardless. """ @@ -68,8 +68,8 @@ class NotificationResponse(Response): Notification response. Returned from processing a successful - [notification](http://www.jsonrpc.org/specification#notification) (i.e. a request - with no `id` member). + [notification](http://www.jsonrpc.org/specification#notification) (i.e. a + request with no `id` member). """ def __init__(self, http_status: int = status.HTTP_NO_CONTENT) -> None: @@ -87,8 +87,9 @@ def sort_dict_response(response: Dict[str, Any]) -> OrderedDict: """ Sort the keys of a dict, returning an OrderedDict. - This has no effect other than making it nicer to read. It's also only useful with - Python 3.5, since from 3.6 onwards, dictionaries hold their order. + This has no effect other than making it nicer to read. It's also only + useful with Python 3.5, since from 3.6 onwards, dictionaries hold their + order. Args: response: The JSON-RPC response to sort. @@ -116,9 +117,10 @@ class DictResponse(Response): def __init__(self, *args: Any, id: Any, **kwargs: Any) -> None: """ Args: - id: Must be the same as the value as the id member in the Request Object. If - there was an error in detecting the id in the Request object (e.g. Parse - error/Invalid Request), it MUST be Null. + id: Must be the same as the value as the id member in the Request + Object. If there was an error in detecting the id in the + Request object (e.g. Parse error/Invalid Request), it MUST be + Null. """ super().__init__(*args, **kwargs) self.id = id diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index e0d1a3e..93ccb9f 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -3,6 +3,7 @@ from unittest.mock import sentinel from jsonrpcserver.dispatcher import ( + Context, add_handlers, call_requests, create_requests, @@ -16,7 +17,7 @@ default_serialize, ) from jsonrpcserver.methods import Methods, global_methods -from jsonrpcserver.request import NOCONTEXT, Request +from jsonrpcserver.request import Request, NOID from jsonrpcserver.response import ( BatchResponse, ErrorResponse, @@ -30,7 +31,7 @@ from jsonrpcserver.exceptions import ApiError -def ping(): +def ping(context: Context): return "pong" @@ -55,9 +56,10 @@ def test_log_response(): def test_safe_call_success_response(): response = safe_call( - Request(method="ping", id=1), + Request(method="ping", params=[], id=1), Methods(ping), debug=True, + extra=None, serialize=default_serialize, ) assert isinstance(response, SuccessResponse) @@ -67,7 +69,11 @@ def test_safe_call_success_response(): def test_safe_call_notification(): response = safe_call( - Request(method="ping"), Methods(ping), debug=True, serialize=default_serialize + Request(method="ping", params=[], id=NOID), + Methods(ping), + debug=True, + extra=None, + serialize=default_serialize, ) assert isinstance(response, NotificationResponse) @@ -77,16 +83,21 @@ def fail(): raise ValueError() response = safe_call( - Request(method="foo"), Methods(fail), debug=True, serialize=default_serialize + Request(method="foo", params=[], id=NOID), + Methods(fail), + debug=True, + extra=None, + serialize=default_serialize, ) assert isinstance(response, NotificationResponse) def test_safe_call_method_not_found(): response = safe_call( - Request(method="nonexistant", id=1), + Request(method="nonexistant", params=[], id=1), Methods(ping), debug=True, + extra=None, serialize=default_serialize, ) assert isinstance(response, MethodNotFoundResponse) @@ -97,19 +108,21 @@ def test_safe_call_invalid_args(): Request(method="ping", params=[1], id=1), Methods(ping), debug=True, + extra=None, serialize=default_serialize, ) assert isinstance(response, InvalidParamsResponse) def test_safe_call_api_error(): - def error(): + def error(context: Context): raise ApiError("Client Error", code=123, data={"data": 42}) response = safe_call( - Request(method="error", id=1), + Request(method="error", params=[], id=1), Methods(error), debug=True, + extra=None, serialize=default_serialize, ) assert isinstance(response, ErrorResponse) @@ -120,13 +133,14 @@ def error(): def test_safe_call_api_error_minimal(): - def error(): + def error(context: Context): raise ApiError("Client Error") response = safe_call( - Request(method="error", id=1), + Request(method="error", params=[], id=1), Methods(error), debug=True, + extra=None, serialize=default_serialize, ) assert isinstance(response, ErrorResponse) @@ -138,13 +152,14 @@ def error(): def test_non_json_encodable_resonse(): - def method(): + def method(context: Context): return b"Hello, World" response = safe_call( - Request(method="method", id=1), + Request(method="method", params=[], id=1), Methods(method), debug=False, + extra=None, serialize=default_serialize, ) # response must be serializable here @@ -160,14 +175,15 @@ def method(): # call_requests -def test_call_requests_with_context(): - def ping_with_context(context=None): - assert context is sentinel.context +def test_call_requests_with_extra(): + def ping_with_extra(context: Context): + assert context.extra is sentinel.extra call_requests( - Request("ping_with_context", convert_camel_case=False), - Methods(ping_with_context), + Request(method="ping_with_extra", params=[], id=1), + Methods(ping_with_extra), debug=True, + extra=sentinel.extra, serialize=default_serialize, ) # Assert is in the method @@ -176,12 +192,13 @@ def ping_with_context(context=None): def test_call_requests_batch_all_notifications(): """Should return a BatchResponse response, an empty list""" response = call_requests( - { - Request(**{"jsonrpc": "2.0", "method": "notify_sum", "params": [1, 2, 4]}), - Request(**{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}), - }, + [ + Request(method="notify_sum", params=[1, 2, 4], id=NOID), + Request(method="notify_hello", params=[7], id=NOID), + ], Methods(ping), debug=True, + extra=None, serialize=default_serialize, ) assert str(response) == "" @@ -191,16 +208,13 @@ def test_call_requests_batch_all_notifications(): def test_create_requests(): - requests = create_requests( - {"jsonrpc": "2.0", "method": "ping"}, convert_camel_case=False - ) + requests = create_requests({"jsonrpc": "2.0", "method": "ping"}) assert isinstance(requests, Request) def test_create_requests_batch(): requests = create_requests( [{"jsonrpc": "2.0", "method": "ping"}, {"jsonrpc": "2.0", "method": "ping"}], - convert_camel_case=False, ) assert iter(requests) @@ -212,9 +226,8 @@ def test_dispatch_pure_request(): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "ping", "id": 1}', Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -227,9 +240,8 @@ def test_dispatch_pure_notification(): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "ping"}', Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -240,9 +252,8 @@ def test_dispatch_pure_notification_invalid_jsonrpc(): response = dispatch_pure( '{"jsonrpc": "0", "method": "notify"}', Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -254,9 +265,8 @@ def test_dispatch_pure_invalid_json(): response = dispatch_pure( "{", Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -268,9 +278,8 @@ def test_dispatch_pure_invalid_jsonrpc(): response = dispatch_pure( "{}", Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -278,15 +287,14 @@ def test_dispatch_pure_invalid_jsonrpc(): def test_dispatch_pure_invalid_params(): - def foo(colour): + def foo(context: Context, colour): assert colour in ("orange", "red", "yellow"), "Invalid colour" response = dispatch_pure( '{"jsonrpc": "2.0", "method": "foo", "params": ["blue"], "id": 1}', Methods(foo), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -294,15 +302,14 @@ def foo(colour): def test_dispatch_pure_invalid_params_count(): - def foo(colour: str, size: str): + def foo(context: Context, colour: str, size: str): pass response = dispatch_pure( '{"jsonrpc": "2.0", "method": "foo", "params": {"colour":"blue"}, "id": 1}', Methods(foo), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -344,15 +351,14 @@ def test_dispatch_basic_logging(): def test_examples_positionals(): - def subtract(minuend, subtrahend): + def subtract(context: Context, minuend, subtrahend): return minuend - subtrahend response = dispatch_pure( '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}', Methods(subtract), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -363,9 +369,8 @@ def subtract(minuend, subtrahend): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}', Methods(subtract), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -374,15 +379,14 @@ def subtract(minuend, subtrahend): def test_examples_nameds(): - def subtract(**kwargs): + def subtract(context: Context, **kwargs): return kwargs["minuend"] - kwargs["subtrahend"] response = dispatch_pure( '{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}', Methods(subtract), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -393,9 +397,8 @@ def subtract(**kwargs): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}', Methods(subtract), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -408,9 +411,8 @@ def test_examples_notification(): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]}', methods, - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -420,9 +422,8 @@ def test_examples_notification(): response = dispatch_pure( '{"jsonrpc": "2.0", "method": "foobar"}', methods, - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -433,9 +434,8 @@ def test_examples_invalid_json(): response = dispatch_pure( '[{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]', Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -451,9 +451,8 @@ def test_examples_empty_array(): response = dispatch_pure( "[]", Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -472,9 +471,8 @@ def test_examples_invalid_jsonrpc_batch(): response = dispatch_pure( "[1]", Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -493,9 +491,8 @@ def test_examples_multiple_invalid_jsonrpc(): response = dispatch_pure( "[1, 2, 3]", Methods(ping), - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) @@ -517,10 +514,10 @@ def test_examples_mixed_requests_and_notifications(): """ methods = Methods( **{ - "sum": lambda *args: sum(args), - "notify_hello": lambda *args: 19, - "subtract": lambda *args: args[0] - sum(args[1:]), - "get_data": lambda: ["hello", 5], + "sum": lambda _, *args: sum(args), + "notify_hello": lambda _, *args: 19, + "subtract": lambda _, *args: args[0] - sum(args[1:]), + "get_data": lambda _: ["hello", 5], } ) requests = serialize( @@ -540,9 +537,8 @@ def test_examples_mixed_requests_and_notifications(): response = dispatch_pure( requests, methods, - convert_camel_case=False, - context=NOCONTEXT, debug=True, + extra=None, serialize=default_serialize, deserialize=default_deserialize, ) diff --git a/tests/test_request.py b/tests/test_request.py index 9c0720f..daa657f 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -2,69 +2,11 @@ import pytest -from jsonrpcserver.request import ( - NOID, - Request, - convert_camel_case_keys, - convert_camel_case_string, - get_arguments, -) - - -def test_convert_camel_case_string(): - assert convert_camel_case_string("fooBar") == "foo_bar" - - -def test_convert_camel_case_keys(): - dictionary = {"fooKey": 1, "aDict": {"fooKey": 1, "barKey": 2}} - assert convert_camel_case_keys(dictionary) == { - "foo_key": 1, - "a_dict": {"foo_key": 1, "bar_key": 2}, - } - - -def test_get_arguments_none(): - with pytest.raises(AssertionError): - get_arguments(None) - - -def test_get_arguments_invalid(): - with pytest.raises(AssertionError): - assert get_arguments(5) - - -def test_get_arguments_positional(): - assert get_arguments([2, 3]) == ([2, 3], {}) - - -def test_get_arguments_keyword(): - assert get_arguments({"foo": "bar"}) == ([], {"foo": "bar"}) - - -def test_get_arguments_invalid_string(): - with pytest.raises(AssertionError): - get_arguments("str") - - -# With the "context" argument -def test_get_arguments_positional_with_context(): - args = get_arguments(["foo"], context="bar") - assert args == (["bar", "foo"], {}) - - -def test_get_arguments_keyword_with_context(): - args = get_arguments({"foo": "bar"}, context="baz") - assert args == (["baz"], {"foo": "bar"}) - - -# With the "context" argument, but without params -def test_get_arguments_no_params_with_context(): - args = get_arguments(context="bar") - assert args == (["bar"], {}) +from jsonrpcserver.request import NOID, Request, is_notification def test_request(): - assert Request(method="foo").method == "foo" + assert Request(method="foo", params=[], id=1).method == "foo" def test_request_invalid(): @@ -73,53 +15,9 @@ def test_request_invalid(): pass -def test_notification_true(): - request = Request(method="foo") - assert request.is_notification is True - - -def test_notification_false(): - request = Request(method="foo", id=99) - assert request.is_notification is False - - -def test_request_no_args(): - req = Request(method="foo") - assert req.args == [] - assert req.kwargs == {} - - -def test_request_positional_args(): - req = Request(method="foo", params=[2, 3]) - assert req.args == [2, 3] - assert req.kwargs == {} - - -def test_request_keyword_args(): - req = Request(method="foo", params={"foo": "bar"}) - assert req.args == [] - assert req.kwargs == {"foo": "bar"} - - -def test_request_id(): - assert Request(method="foo", id=99).id == 99 - - -def test_request_no_id(): - request = Request({"jsonrpc": "2.0", "method": "foo"}) - assert request.id is NOID - - -def test_request_from_string(): - request = Request(**json.loads('{"jsonrpc": "2.0", "method": "foo", "id": 1}')) - assert request.jsonrpc == "2.0" - assert request.method == "foo" - assert request.id == 1 +def test_is_notification_true(): + assert is_notification(Request(method="foo", params=[], id=NOID)) is True -def test_request_convert_camel_case(): - request = Request( - **{"jsonrpc": "2.0", "method": "fooBar", "params": {"fooBar": 1}}, - convert_camel_case=True - ) - assert request.method == "foo_bar" +def test_is_notification_false(): + assert is_notification(Request(method="foo", params=[], id=1)) is False