Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add docs back
  • Loading branch information
bcb committed Aug 2, 2024
commit 184f361edcd686baffea6ff4ff660f93455e338f
41 changes: 41 additions & 0 deletions docs/Async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
Async dispatch is supported.

```python
from jsonrpcserver import async_dispatch, async_method, Ok, Result

@async_method
async def ping() -> Result:
return Ok("pong")

await async_dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
```

Some reasons to use this:

- Use it with an asynchronous protocol like sockets or message queues.
- `await` long-running functions from your method.
- Batch requests are dispatched concurrently.

## Notifications

Notifications are requests without an `id`. We should not respond to
notifications, so jsonrpcserver gives an empty string to signify there is *no
response*.

```python
>>> await async_dispatch('{"jsonrpc": "2.0", "method": "ping"}')
''
```

If the response is an empty string, don't send it.

```python
if response := dispatch(request):
send(response)
```

```{note}
A synchronous protocol like HTTP requires a response no matter what, so we can
send back the empty string. However with async protocols, we have the choice of
responding or not.
```
79 changes: 79 additions & 0 deletions docs/Dispatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
The `dispatch` function takes a JSON-RPC request, attempts to call a method and gives a
JSON-RPC response.

```python
>>> dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
'{"jsonrpc": "2.0", "result": "pong", "id": 1}'
```

[See how dispatch is used in different frameworks.](examples)

## Optional parameters

The `dispatch` function has some optional parameters that allow you to
customise how it works.

### methods

This lets you specify the methods to dispatch to. It's an alternative to using
the `@method` decorator. The value should be a dict mapping function names to
functions.

```python
def ping():
return Ok("pong")

dispatch(request, methods={"ping": ping})
```

Default is `global_methods`, which is an internal dict populated by the
`@method` decorator.

### context

If specified, this will be the first argument to all methods.

```python
@method
def greet(context, name):
return Ok(f"Hello {context}")

>>> dispatch('{"jsonrpc": "2.0", "method": "greet", "params": ["Beau"], "id": 1}', context="Beau")
'{"jsonrpc": "2.0", "result": "Hello Beau", "id": 1}'
```

### deserializer

A function that parses the JSON request string. Default is `json.loads`.

```python
dispatch(request, deserializer=ujson.loads)
```

### jsonrpc_validator

A function that validates the request once the JSON string has been parsed. The
function should raise an exception (any exception) if the request doesn't match
the JSON-RPC spec (https://www.jsonrpc.org/specification). Default is
`default_jsonrpc_validator` which uses Jsonschema to validate requests against
a schema.

To disable JSON-RPC validation, pass `jsonrpc_validator=lambda _: None`, which
will improve performance because this validation takes around half the dispatch
time.

### args_validator

A function that validates a request's parameters against the signature of the
Python function that will be called for it. Note this should not validate the
_values_ of the parameters, it should simply ensure the parameters match the
Python function's signature. For reference, see the `validate_args` function in
`dispatcher.py`, which is the default `args_validator`.

### serializer

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

```python
dispatch(request, serializer=ujson.dumps)
```
39 changes: 39 additions & 0 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
## How to disable schema validation?

Validating requests is costly - roughly 40% of dispatching time is spent on schema validation.
If you know the incoming requests are valid, you can disable the validation for better
performance.

```python
dispatch(request, validator=lambda _: None)
```

## Which HTTP status code to respond with?

I suggest:

```python
200 if response else 204
```

If the request was a notification, `dispatch` will give you an empty string. So
since there's no http body, use status code 204 - no content.

## How to rename a method

Use `@method(name="new_name")`.

Or use the dispatch function's [methods
parameter](https://www.jsonrpcserver.com/en/latest/dispatch.html#methods).

## How to get the response in other forms?

Instead of `dispatch`, use:

- `dispatch_to_serializable` to get the response as a dict.
- `dispatch_to_response` to get the response as a namedtuple (either a
`SuccessResponse` or `ErrorResponse`, these are defined in
[response.py](https://github.com/explodinglabs/jsonrpcserver/blob/main/jsonrpcserver/response.py)).

For these functions, if the request was a batch, you'll get a list of
responses. If the request was a notification, you'll get `None`.
67 changes: 67 additions & 0 deletions docs/Methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Methods are functions that can be called by a JSON-RPC request. To write one,
decorate a function with `@method`:

```python
from jsonrpcserver import method, Error, Ok, Result

@method
def ping() -> Result:
return Ok("pong")
```

If you don't need to respond with any value simply `return Ok()`.

## Responses

Methods return either `Ok` or `Error`. These are the [JSON-RPC response
objects](https://www.jsonrpc.org/specification#response_object) (excluding the
`jsonrpc` and `id` parts). `Error` takes a code, message, and optionally
'data'.

```python
@method
def test() -> Result:
return Error(1, "There was a problem")
```

```{note}
Alternatively, raise a `JsonRpcError`, which takes the same arguments as `Error`.
```

## Parameters

Methods can accept arguments.

```python
@method
def hello(name: str) -> Result:
return Ok("Hello " + name)
```

Testing it:

```sh
$ curl -X POST http://localhost:5000 -d '{"jsonrpc": "2.0", "method": "hello", "params": ["Beau"], "id": 1}'
{"jsonrpc": "2.0", "result": "Hello Beau", "id": 1}
```

## Invalid params

A common error response is *invalid params*.
The JSON-RPC error code for this is **-32602**. A shortcut, *InvalidParams*, is
included so you don't need to remember that.

```python
from jsonrpcserver import dispatch, method, InvalidParams, Ok, Result

@method
def within_range(num: int) -> Result:
if num not in range(1, 5):
return InvalidParams("Value must be 1-5")
return Ok()
```

This is the same as saying
```python
return Error(-32602, "Invalid params", "Value must be 1-5")
```
37 changes: 37 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Quickstart

Install jsonrpcserver:
```python
pip install jsonrpcserver
```

Create a `server.py`:

```python
from jsonrpcserver import method, serve, Ok

@method
def ping():
return Ok("pong")

if __name__ == "__main__":
serve()
```

Start the server:

```sh
$ pip install jsonrpcserver
$ python server.py
* Listening on port 5000
```

Test the server:

```sh
$ curl -X POST http://localhost:5000 -d '{"jsonrpc": "2.0", "method": "ping", "id": 1}'
{"jsonrpc": "2.0", "result": "pong", "id": 1}
```

`serve` is good for serving methods in development, but for production use
`dispatch` instead.