Skip to content

Commit

Permalink
Replace readthedocs with mkdocs (#282)
Browse files Browse the repository at this point in the history
* Add docs back

* Replace readthedocs with mkdocs
  • Loading branch information
bcb authored Aug 16, 2024
1 parent 389959d commit 8c23c46
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 15 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<img
alt="jsonrpcserver"
style="margin: 0 auto;"
src="https://github.com/explodinglabs/jsonrpcserver/blob/main/docs/logo.png?raw=true"
src="https://github.com/explodinglabs/jsonrpcserver/blob/main/logo.png?raw=true"
/>

![PyPI](https://img.shields.io/pypi/v/jsonrpcserver.svg)
Expand All @@ -16,7 +16,7 @@ pip install jsonrpcserver
```

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

@method
def ping() -> Result:
Expand Down
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.
```
84 changes: 84 additions & 0 deletions docs/dispatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Dispatch

The `dispatch` function processes a JSON-RPC request, attempting to call the method(s)
and gives a JSON-RPC response.

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

It's a pure function; it will always give you a JSON-RPC response. No exceptions will be
raised.

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

## Optional parameters

The `dispatch` function takes a request as its argument, and also 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`.
37 changes: 37 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Jsonrpcserver

Jsonrpcserver processes JSON-RPC requests.

## 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
$ python server.py
```

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

`serve` starts a basic development server. Do not use it in a production deployment. Use
a production WSGI server instead, with jsonrpcserver's [dispatch](dispatch) function.
70 changes: 70 additions & 0 deletions docs/methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Methods

Methods are functions that can be called by a JSON-RPC request.

## Writing methods

To write a method, 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")
```

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")
```
18 changes: 5 additions & 13 deletions jsonrpcserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,27 @@
http.server module.
"""

import logging
from http.server import BaseHTTPRequestHandler, HTTPServer

from .main import dispatch


class RequestHandler(BaseHTTPRequestHandler):
"""Handle HTTP requests"""

def do_POST(self) -> None: # pylint: disable=invalid-name
"""Handle POST request"""
response = dispatch(
self.rfile.read(int(str(self.headers["Content-Length"]))).decode()
)
request = self.rfile.read(int(str(self.headers["Content-Length"]))).decode()
response = dispatch(request)
if response is not None:
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(str(response).encode())
self.wfile.write(response.encode())


def serve(name: str = "", port: int = 5000) -> None:
"""A simple function to serve HTTP requests"""
logging.info(" * Listening on port %s", port)
httpd = HTTPServer((name, port), RequestHandler)
try:
httpd = HTTPServer((name, port), RequestHandler)
httpd.serve_forever()
except KeyboardInterrupt:
pass
except Exception:
finally:
httpd.shutdown()
raise
Binary file added logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
markdown_extensions:
- pymdownx.highlight:
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.details
- pymdownx.superfences
- pymdownx.mark
nav:
- Home: 'index.md'
- 'methods.md'
- 'dispatch.md'
- 'async.md'
- 'faq.md'
- 'examples.md'
repo_name: jsonrpcserver
repo_url: https://github.com/explodinglabs/jsonrpcserver
site_author: Exploding Labs
site_description: Welcome to the documentation for Jsonrcpcserver.
site_name: Jsonrpcserver
site_url: https://www.jsonrpcserver.com/
theme:
features:
- content.code.copy
- navigation.footer
- navigation.tabs
- toc.integrate
name: material
palette:
# Palette toggle for automatic mode
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to system preference
extra:
version:
provider: mike

0 comments on commit 8c23c46

Please sign in to comment.