diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 99187f6..abc349c 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -10,7 +10,7 @@ jobs:
with:
python-version: 3.8
- run: pip install --upgrade pip
- - run: pip install types-setuptools "black<23" "pylint<3" "mypy<1" "jsonschema<5" pytest "oslash<1" "aiohttp<4" "aiozmq<1" "django<5" "fastapi<1" "flask<3" "flask-socketio<5.3.1" "pyzmq" "sanic" "tornado<7" "uvicorn<1" "websockets<11"
- - run: black --diff --check $(git ls-files -- '*.py' ':!:docs/*')
- - run: pylint $(git ls-files -- '*.py' ':!:docs/*')
+ - run: pip install types-setuptools "black<23" ruff "mypy<2" "jsonschema<5" pytest "returns<1" "aiohttp<4" "aiozmq<1" "django<5" "fastapi<1" "flask<3" "flask-socketio<5.3.1" "pyzmq" "sanic" "tornado<7" "uvicorn<1" "websockets<11"
+ - run: ruff check --select I $(git ls-files -- '*.py' ':!:docs/*')
+ - run: ruff format --check $(git ls-files -- '*.py' ':!:docs/*')
- run: mypy --strict $(git ls-files -- '*.py' ':!:docs/*')
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e05ac27..f884896 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,38 +3,24 @@ default_language_version:
exclude: (^docs)
fail_fast: true
repos:
- - repo: https://github.com/ambv/black
- rev: 22.8.0
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.4.1
hooks:
- - id: black
- args: [--diff, --check]
-
- - repo: https://github.com/PyCQA/pylint
- rev: v2.15.3
- hooks:
- - id: pylint
- additional_dependencies:
- - oslash<1
- - aiohttp<4
- - aiozmq<1
- - django<5
- - fastapi<1
- - flask<3
- - flask-socketio<5.3.1
- - jsonschema<5
- - pytest
- - pyzmq
- - sanic
- - tornado<7
- - uvicorn<1
- - websockets<11
+ - id: ruff
+ name: lint with ruff
+ - id: ruff
+ name: sort imports with ruff
+ args: [--select, I, --fix]
+ - id: ruff-format
+ name: format with ruff
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.971
+ rev: v1.2.0
hooks:
- id: mypy
args: [--strict]
additional_dependencies:
+ - returns<1
- aiohttp<4
- aiozmq<1
- django<5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92e4296..036af4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,25 @@
# jsonrpcserver Change Log
+## 6.0.0 (May 18, 2022)
+
+A small release but incrementing the major release number due to a breaking
+change for async use.
+
+Breaking changes:
+
+- Async methods should be decorated with `@async_method` instead of `@method`. The
+ reason is due to the _type_ of the decorated function - async functions have a
+ different type to other functions (`Awaitable`).
+
+Other changes:
+
+- Replaced the Oslash dependency with [Returns](https://github.com/dry-python/returns).
+ Oslash is not meant for production use, and doesn't work in Python 3.12.
+- Use `Ok` instead of `Success` when returning a response. This is to avoid confusion
+ with the Returns library's `Success` class. It also matches Jsonrpcclient's `Ok` type.
+ This is not a breaking change, `Success` will still work for now.
+- Docs moved to https://github.com/explodinglabs/jsonrpcserver/wiki
+
## 5.0.9 (Sep 15, 2022)
- Remove unncessary `package_data` from setup.py (#243)
diff --git a/README.md b/README.md
index 6d050cb..a295785 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@




+
Process incoming JSON-RPC requests in Python.
@@ -16,24 +17,18 @@ pip install jsonrpcserver
```
```python
-from jsonrpcserver import method, serve, Success
+from jsonrpcserver import method, Result, Ok
@method
-def ping():
- return Success("pong")
+def ping() -> Result:
+ return Ok("pong")
-if __name__ == "__main__":
- serve()
-```
-
-Or use `dispatch` instead of `serve`:
-```python
response = dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
# => '{"jsonrpc": "2.0", "result": "pong", "id": 1}'
```
[Watch a video on how to use it.](https://www.youtube.com/watch?v=3_BMmgJaFHQ)
-Full documentation is at [jsonrpcserver.com](https://www.jsonrpcserver.com/).
+Full documentation is in the [wiki](https://github.com/explodinglabs/jsonrpcserver/wiki).
See also: [jsonrpcclient](https://github.com/explodinglabs/jsonrpcclient)
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index fa0c384..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,177 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
- @echo "Please use \`make ' where is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " texinfo to make Texinfo files"
- @echo " info to make Texinfo files and run them through makeinfo"
- @echo " gettext to make PO message catalogs"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " xml to make Docutils-native XML files"
- @echo " pseudoxml to make pseudoxml-XML files for display purposes"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/jsonrpcserver.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/jsonrpcserver.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/jsonrpcserver"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/jsonrpcserver"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-latexpdfja:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through platex and dvipdfmx..."
- $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo
- @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
- @echo "Run \`make' in that directory to run these through makeinfo" \
- "(use \`make info' here to do that automatically)."
-
-info:
- $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
- @echo "Running Texinfo files through makeinfo..."
- make -C $(BUILDDIR)/texinfo info
- @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
- $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
- @echo
- @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
-
-xml:
- $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
- @echo
- @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-
-pseudoxml:
- $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
- @echo
- @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
diff --git a/docs/async.md b/docs/async.md
index 296a74f..5954d07 100644
--- a/docs/async.md
+++ b/docs/async.md
@@ -1,13 +1,11 @@
-# Async
-
Async dispatch is supported.
```python
-from jsonrpcserver import method, Success, async_dispatch
+from jsonrpcserver import async_dispatch, async_method, Ok, Result
-@method
+@async_method
async def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
await async_dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
```
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index f876374..0000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-# -- Path setup --------------------------------------------------------------
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
-
-
-# -- Project information -----------------------------------------------------
-
-project = "jsonrpcserver"
-copyright = "2021, Beau Barker"
-author = "Beau Barker"
-
-# The full version, including alpha/beta/rc tags
-release = "5.0.0"
-
-
-# -- General configuration ---------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = ["sphinx_rtd_theme", "myst_parser"]
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = []
-
-
-# -- Options for HTML output -------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-#
-html_theme = "sphinx_rtd_theme"
-html_theme_options = {
- "analytics_id": "G-G05775CD6C", # UA-81795603-3
- "display_version": True,
-}
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ["_static"]
-
-source_suffix = [".rst", ".md"]
-html_show_sourcelink = False
diff --git a/docs/dispatch.md b/docs/dispatch.md
index c53e342..719ea9c 100644
--- a/docs/dispatch.md
+++ b/docs/dispatch.md
@@ -1,6 +1,6 @@
# Dispatch
-The `dispatch` function takes a JSON-RPC request, calls the appropriate method
+The `dispatch` function processes a JSON-RPC request, attempting to call the method(s)
and gives a JSON-RPC response.
```python
@@ -8,19 +8,25 @@ and gives a JSON-RPC response.
'{"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 a group of methods to dispatch to. It's an alternative to
-using the `@method` decorator. The value should be a dict mapping function
-names to functions.
+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 Success("pong")
+ return Ok("pong")
dispatch(request, methods={"ping": ping})
```
@@ -35,20 +41,40 @@ If specified, this will be the first argument to all methods.
```python
@method
def greet(context, name):
- return Success(context + " " + name)
+ return Ok(f"Hello {context}")
->>> dispatch('{"jsonrpc": "2.0", "method": "greet", "params": ["Beau"], "id": 1}', context="Hello")
+>>> 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 request string. Default is `json.loads`.
+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`.
@@ -56,10 +82,3 @@ A function that serializes the response string. Default is `json.dumps`.
```python
dispatch(request, serializer=ujson.dumps)
```
-
-### validator
-
-A function that validates the request once the json has been parsed. The
-function should raise an exception (any exception) if the request doesn't match
-the JSON-RPC spec. Default is `default_validator` which validates the request
-against a schema.
diff --git a/docs/examples.md b/docs/examples.md
deleted file mode 100644
index cfa8c22..0000000
--- a/docs/examples.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Examples
-
-```{contents}
-```
-
-## aiohttp
-
-```{literalinclude} ../examples/aiohttp_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/aiohttp).
-
-## Django
-
-Create a `views.py`:
-
-```{literalinclude} ../examples/django_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/django).
-
-## FastAPI
-
-```{literalinclude} ../examples/fastapi_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/fastapi).
-
-## Flask
-
-```{literalinclude} ../examples/flask_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/flask).
-
-## http.server
-
-Using Python's built-in
-[http.server](https://docs.python.org/3/library/http.server.html) module.
-
-```{literalinclude} ../examples/http_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/httpserver).
-
-## jsonrpcserver
-
-Using jsonrpcserver's built-in `serve` method.
-
-```{literalinclude} ../examples/jsonrpcserver_server.py
-```
-
-## Sanic
-
-```{literalinclude} ../examples/sanic_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/sanic).
-
-## Socket.IO
-
-```{literalinclude} ../examples/socketio_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/flask-socketio).
-
-## Tornado
-
-```{literalinclude} ../examples/tornado_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/tornado).
-
-## Websockets
-
-```{literalinclude} ../examples/websockets_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/websockets).
-
-## Werkzeug
-
-```{literalinclude} ../examples/werkzeug_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/werkzeug).
-
-## ZeroMQ
-
-```{literalinclude} ../examples/zeromq_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/zeromq).
-
-## ZeroMQ (asynchronous)
-
-```{literalinclude} ../examples/aiozmq_server.py
-```
-
-See [blog post](https://composed.blog/jsonrpc/zeromq-async).
diff --git a/docs/faq.md b/docs/faq.md
index f928e24..98bf4e7 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -1,5 +1,3 @@
-# FAQ
-
## How to disable schema validation?
Validating requests is costly - roughly 40% of dispatching time is spent on schema validation.
diff --git a/docs/index.md b/docs/index.md
index 12a7251..3612046 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,23 +1,37 @@
-```{warning}
-This is the documentation for version 5, released August 16, 2021. [Read about
-the changes in version 5](https://composed.blog/jsonrpcserver-5-changes).
+# Jsonrpcserver
+
+Jsonrpcserver processes JSON-RPC requests.
+
+## Quickstart
+
+Install jsonrpcserver:
+```python
+pip install jsonrpcserver
```
-# Process incoming JSON-RPC requests in Python | jsonrpcserver Documentation
+Create a `server.py`:
+
+```python
+from jsonrpcserver import method, serve, Ok
-
+@method
+def ping():
+ return Ok("pong")
-Process incoming JSON-RPC requests in Python.
+if __name__ == "__main__":
+ serve()
+```
+
+Start the server:
+```sh
+$ python server.py
+```
-```{toctree}
----
-maxdepth: 3
-caption: Contents
----
-installation
-methods
-dispatch
-async
-faq
-examples
+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.
diff --git a/docs/installation.md b/docs/installation.md
deleted file mode 100644
index 6c40ec8..0000000
--- a/docs/installation.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Quickstart
-
-Create a `server.py`:
-
-```python
-from jsonrpcserver import Success, method, serve
-
-@method
-def ping():
- return Success("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}
-```
diff --git a/docs/methods.md b/docs/methods.md
index dd5be3b..28457f8 100644
--- a/docs/methods.md
+++ b/docs/methods.md
@@ -1,23 +1,27 @@
# Methods
-Methods are functions that can be called by a JSON-RPC request. To write one,
-decorate a function with `@method`:
+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, Result, Success, Error
+from jsonrpcserver import method, Error, Ok, Result
@method
def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
```
-If you don't need to respond with any value simply `return Success()`.
+If you don't need to respond with any value simply `return Ok()`.
## Responses
-Methods return either `Success` or `Error`. These are the [JSON-RPC response
+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'.
+`jsonrpc` and `id` parts). `Error` takes a code, message, and optionally
+'data'.
```python
@method
@@ -25,9 +29,7 @@ def test() -> Result:
return Error(1, "There was a problem")
```
-```{note}
Alternatively, raise a `JsonRpcError`, which takes the same arguments as `Error`.
-```
## Parameters
@@ -36,7 +38,7 @@ Methods can accept arguments.
```python
@method
def hello(name: str) -> Result:
- return Success("Hello " + name)
+ return Ok("Hello " + name)
```
Testing it:
@@ -53,13 +55,13 @@ 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 method, Result, InvalidParams, Success, dispatch
+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 Success()
+ return Ok()
```
This is the same as saying
diff --git a/examples/aiohttp_server.py b/examples/aiohttp_server.py
index 41982c4..6626a32 100644
--- a/examples/aiohttp_server.py
+++ b/examples/aiohttp_server.py
@@ -1,12 +1,14 @@
"""AioHTTP server"""
+
from aiohttp import web
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
-@method
+@async_method
async def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
async def handle(request: web.Request) -> web.Response:
diff --git a/examples/aiozmq_server.py b/examples/aiozmq_server.py
index 60ef29d..545bceb 100644
--- a/examples/aiozmq_server.py
+++ b/examples/aiozmq_server.py
@@ -1,15 +1,17 @@
"""AioZMQ server"""
+
import asyncio
import aiozmq # type: ignore
import zmq
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
-@method
+@async_method
async def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
async def main() -> None:
diff --git a/examples/asyncio_server.py b/examples/asyncio_server.py
index d668a76..4352403 100644
--- a/examples/asyncio_server.py
+++ b/examples/asyncio_server.py
@@ -1,15 +1,16 @@
"""Demonstrates processing a batch of 100 requests asynchronously with asyncio."""
+
import asyncio
import json
-from jsonrpcserver import method, Result, Success, async_dispatch
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
-@method
+@async_method
async def sleep_() -> Result:
"""JSON-RPC method"""
await asyncio.sleep(1)
- return Success()
+ return Ok()
async def handle(req: str) -> None:
diff --git a/examples/django_server.py b/examples/django_server.py
index 52228b9..86f77fd 100644
--- a/examples/django_server.py
+++ b/examples/django_server.py
@@ -1,13 +1,15 @@
"""Django server"""
+
from django.http import HttpRequest, HttpResponse # type: ignore
from django.views.decorators.csrf import csrf_exempt # type: ignore
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@csrf_exempt # type: ignore
diff --git a/examples/fastapi_server.py b/examples/fastapi_server.py
index 812a636..858ba07 100644
--- a/examples/fastapi_server.py
+++ b/examples/fastapi_server.py
@@ -1,7 +1,9 @@
"""FastAPI server"""
+
+import uvicorn
from fastapi import FastAPI, Request, Response
-import uvicorn # type: ignore
-from jsonrpcserver import Result, Success, dispatch, method
+
+from jsonrpcserver import Ok, Result, dispatch, method
app = FastAPI()
@@ -9,7 +11,7 @@
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@app.post("/")
diff --git a/examples/flask_server.py b/examples/flask_server.py
index 24581d9..071b110 100644
--- a/examples/flask_server.py
+++ b/examples/flask_server.py
@@ -1,6 +1,8 @@
"""Flask server"""
+
from flask import Flask, Response, request
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
app = Flask(__name__)
@@ -8,7 +10,7 @@
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@app.route("/", methods=["POST"])
diff --git a/examples/http_server.py b/examples/http_server.py
index a240ea8..59c7bd6 100644
--- a/examples/http_server.py
+++ b/examples/http_server.py
@@ -2,21 +2,22 @@
Demonstrates using Python's builtin http.server module to serve JSON-RPC.
"""
+
from http.server import BaseHTTPRequestHandler, HTTPServer
-from jsonrpcserver import method, Result, Success, dispatch
+from jsonrpcserver import Ok, Result, dispatch, method
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
class TestHttpServer(BaseHTTPRequestHandler):
"""HTTPServer request handler"""
- def do_POST(self) -> None: # pylint: disable=invalid-name
+ def do_POST(self) -> None:
"""POST handler"""
# Process request
request = self.rfile.read(int(self.headers["Content-Length"])).decode()
diff --git a/examples/jsonrpcserver_server.py b/examples/jsonrpcserver_server.py
index 5d87590..55f7db6 100644
--- a/examples/jsonrpcserver_server.py
+++ b/examples/jsonrpcserver_server.py
@@ -2,13 +2,14 @@
Uses jsonrpcserver's built-in "serve" function.
"""
-from jsonrpcserver import method, Result, Success, serve
+
+from jsonrpcserver import Ok, Result, method, serve
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
if __name__ == "__main__":
diff --git a/examples/sanic_server.py b/examples/sanic_server.py
index 0b4b68a..be80c26 100644
--- a/examples/sanic_server.py
+++ b/examples/sanic_server.py
@@ -1,8 +1,10 @@
"""Sanic server"""
+
from sanic import Sanic
from sanic.request import Request
from sanic.response import HTTPResponse, json
-from jsonrpcserver import Result, Success, dispatch_to_serializable, method
+
+from jsonrpcserver import Ok, Result, dispatch_to_serializable, method
app = Sanic("JSON-RPC app")
@@ -10,7 +12,7 @@
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@app.route("/", methods=["POST"])
diff --git a/examples/socketio_server.py b/examples/socketio_server.py
index 2911f11..7dd4b94 100644
--- a/examples/socketio_server.py
+++ b/examples/socketio_server.py
@@ -1,7 +1,9 @@
"""SocketIO server"""
+
from flask import Flask, Request
from flask_socketio import SocketIO, send # type: ignore
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
app = Flask(__name__)
socketio = SocketIO(app)
@@ -10,7 +12,7 @@
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@socketio.on("message") # type: ignore
diff --git a/examples/tornado_server.py b/examples/tornado_server.py
index 6a86b35..3a027a4 100644
--- a/examples/tornado_server.py
+++ b/examples/tornado_server.py
@@ -1,14 +1,16 @@
"""Tornado server"""
+
from typing import Awaitable, Optional
from tornado import ioloop, web
-from jsonrpcserver import method, Result, Success, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
-@method
+@async_method
async def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
class MainHandler(web.RequestHandler):
diff --git a/examples/websockets_server.py b/examples/websockets_server.py
index 7367a65..ed8812e 100644
--- a/examples/websockets_server.py
+++ b/examples/websockets_server.py
@@ -1,14 +1,16 @@
"""Websockets server"""
+
import asyncio
from websockets.server import WebSocketServerProtocol, serve
-from jsonrpcserver import method, Success, Result, async_dispatch
+
+from jsonrpcserver import Ok, Result, async_dispatch, async_method
-@method
+@async_method
async def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
async def main(websocket: WebSocketServerProtocol, _: str) -> None:
diff --git a/examples/werkzeug_server.py b/examples/werkzeug_server.py
index e6a1e8f..dcc8590 100644
--- a/examples/werkzeug_server.py
+++ b/examples/werkzeug_server.py
@@ -1,13 +1,15 @@
"""Werkzeug server"""
+
from werkzeug.serving import run_simple
from werkzeug.wrappers import Request, Response
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
@Request.application
diff --git a/examples/zeromq_server.py b/examples/zeromq_server.py
index 6d0c032..d88368c 100644
--- a/examples/zeromq_server.py
+++ b/examples/zeromq_server.py
@@ -1,6 +1,8 @@
"""ZeroMQ server"""
+
import zmq
-from jsonrpcserver import method, Result, Success, dispatch
+
+from jsonrpcserver import Ok, Result, dispatch, method
socket = zmq.Context().socket(zmq.REP)
@@ -8,7 +10,7 @@
@method
def ping() -> Result:
"""JSON-RPC method"""
- return Success("pong")
+ return Ok("pong")
if __name__ == "__main__":
diff --git a/jsonrpcserver/__init__.py b/jsonrpcserver/__init__.py
index 9f6c5a6..8136ba0 100644
--- a/jsonrpcserver/__init__.py
+++ b/jsonrpcserver/__init__.py
@@ -1,28 +1,41 @@
-"""Use __all__ so mypy considers these re-exported."""
+"""Jsonrpcserver"""
+
+from returns.result import Result as R
+
+from .async_main import (
+ dispatch as async_dispatch,
+)
+from .async_main import (
+ dispatch_to_response as async_dispatch_to_response,
+)
+from .async_main import (
+ dispatch_to_serializable as async_dispatch_to_serializable,
+)
+from .async_methods import method as async_method
+from .exceptions import JsonRpcError
+from .main import dispatch, dispatch_to_response, dispatch_to_serializable
+from .methods import method
+from .result import Error, ErrorResult, InvalidParams, Ok, SuccessResult
+from .server import serve
+
+Success = Ok # For backward compatibility - version 5 used Success instead of Ok
+Result = R[SuccessResult, ErrorResult]
+
+
__all__ = [
"Error",
"InvalidParams",
"JsonRpcError",
+ "Ok",
"Result",
"Success",
"async_dispatch",
"async_dispatch_to_response",
"async_dispatch_to_serializable",
+ "async_method",
"dispatch",
"dispatch_to_response",
"dispatch_to_serializable",
"method",
"serve",
]
-
-
-from .async_main import (
- dispatch as async_dispatch,
- dispatch_to_response as async_dispatch_to_response,
- dispatch_to_serializable as async_dispatch_to_serializable,
-)
-from .exceptions import JsonRpcError
-from .main import dispatch, dispatch_to_response, dispatch_to_serializable
-from .methods import method
-from .result import Error, InvalidParams, Result, Success
-from .server import serve
diff --git a/jsonrpcserver/async_dispatcher.py b/jsonrpcserver/async_dispatcher.py
index 6ce66ae..bfd47a0 100644
--- a/jsonrpcserver/async_dispatcher.py
+++ b/jsonrpcserver/async_dispatcher.py
@@ -1,12 +1,15 @@
"""Async version of dispatcher.py"""
+
+import asyncio
+import logging
from functools import partial
+from inspect import signature
from itertools import starmap
from typing import Any, Callable, Iterable, Tuple, Union
-import asyncio
-import logging
-from oslash.either import Left # type: ignore
+from returns.result import Failure, Result, Success
+from .async_methods import Method, Methods
from .dispatcher import (
Deserialized,
create_request,
@@ -14,60 +17,86 @@
extract_args,
extract_kwargs,
extract_list,
- get_method,
not_notification,
to_response,
- validate_args,
validate_request,
validate_result,
)
from .exceptions import JsonRpcError
-from .methods import Method, Methods
from .request import Request
-from .result import Result, InternalErrorResult, ErrorResult
from .response import Response, ServerErrorResponse
+from .result import (
+ ErrorResult,
+ InternalErrorResult,
+ InvalidParamsResult,
+ MethodNotFoundResult,
+ SuccessResult,
+)
from .utils import make_list
logger = logging.getLogger(__name__)
-# pylint: disable=missing-function-docstring,duplicate-code
-
-async def call(request: Request, context: Any, method: Method) -> Result:
+async def call(
+ request: Request, context: Any, method: Method
+) -> Result[SuccessResult, ErrorResult]:
try:
result = await method(
*extract_args(request, context), **extract_kwargs(request)
)
validate_result(result)
except JsonRpcError as exc:
- return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
- except Exception as exc: # pylint: disable=broad-except
+ return Failure(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
+ except Exception as exc:
# Other error inside method - Internal error
logger.exception(exc)
- return Left(InternalErrorResult(str(exc)))
+ return Failure(InternalErrorResult(str(exc)))
return result
+def validate_args(
+ request: Request, context: Any, func: Method
+) -> Result[Method, ErrorResult]:
+ """Ensure the method can be called with the arguments given.
+
+ Returns: Either the function to be called, or an Invalid Params error result.
+ """
+ try:
+ signature(func).bind(*extract_args(request, context), **extract_kwargs(request))
+ except TypeError as exc:
+ return Failure(InvalidParamsResult(str(exc)))
+ return Success(func)
+
+
+def get_method(methods: Methods, method_name: str) -> Result[Method, ErrorResult]:
+ """Get the requested method from the methods dict.
+
+ Returns: Either the function to be called, or a Method Not Found result.
+ """
+ try:
+ return Success(methods[method_name])
+ except KeyError:
+ return Failure(MethodNotFoundResult(method_name))
+
+
async def dispatch_request(
methods: Methods, context: Any, request: Request
-) -> Tuple[Request, Result]:
+) -> Tuple[Request, Result[SuccessResult, ErrorResult]]:
method = get_method(methods, request.method).bind(
partial(validate_args, request, context)
)
return (
request,
method
- if isinstance(method, Left)
- else await call(
- request, context, method._value # pylint: disable=protected-access
- ),
+ if isinstance(method, Failure)
+ else await call(request, context, method.unwrap()),
)
async def dispatch_deserialized(
methods: Methods,
context: Any,
- post_process: Callable[[Response], Iterable[Any]],
+ post_process: Callable[[Response], Response],
deserialized: Deserialized,
) -> Union[Response, Iterable[Response], None]:
results = await asyncio.gather(
@@ -91,7 +120,7 @@ async def dispatch_to_response_pure(
validator: Callable[[Deserialized], Deserialized],
methods: Methods,
context: Any,
- post_process: Callable[[Response], Iterable[Any]],
+ post_process: Callable[[Response], Response],
request: str,
) -> Union[Response, Iterable[Response], None]:
try:
@@ -100,14 +129,14 @@ async def dispatch_to_response_pure(
)
return (
post_process(result)
- if isinstance(result, Left)
+ if isinstance(result, Failure)
else await dispatch_deserialized(
methods,
context,
post_process,
- result._value, # pylint: disable=protected-access
+ result.unwrap(),
)
)
- except Exception as exc: # pylint: disable=broad-except
+ except Exception as exc:
logger.exception(exc)
- return post_process(Left(ServerErrorResponse(str(exc), None)))
+ return post_process(Failure(ServerErrorResponse(str(exc), None)))
diff --git a/jsonrpcserver/async_main.py b/jsonrpcserver/async_main.py
index 35656eb..4ff3773 100644
--- a/jsonrpcserver/async_main.py
+++ b/jsonrpcserver/async_main.py
@@ -1,26 +1,24 @@
"""Async version of main.py. The public async functions."""
+
import json
from typing import Any, Callable, Dict, Iterable, List, Optional, Union, cast
from .async_dispatcher import dispatch_to_response_pure
+from .async_methods import Methods, global_methods
from .dispatcher import Deserialized
-from .main import default_validator, default_deserializer
-from .methods import Methods, global_methods
+from .main import default_deserializer, default_jsonrpc_validator
from .response import Response, to_serializable
from .sentinels import NOCONTEXT
from .utils import identity
-# pylint: disable=missing-function-docstring,duplicate-code
-
-
async def dispatch_to_response(
request: str,
methods: Optional[Methods] = None,
*,
context: Any = NOCONTEXT,
deserializer: Callable[[str], Deserialized] = default_deserializer,
- validator: Callable[[Deserialized], Deserialized] = default_validator,
+ validator: Callable[[Deserialized], Deserialized] = default_jsonrpc_validator,
post_process: Callable[[Response], Any] = identity,
) -> Union[Response, Iterable[Response], None]:
return await dispatch_to_response_pure(
diff --git a/jsonrpcserver/async_methods.py b/jsonrpcserver/async_methods.py
new file mode 100644
index 0000000..63edfc1
--- /dev/null
+++ b/jsonrpcserver/async_methods.py
@@ -0,0 +1,33 @@
+"""Async methods"""
+
+from typing import Any, Awaitable, Callable, Dict, Optional, cast
+
+from returns.result import Result
+
+from .result import ErrorResult, SuccessResult
+
+Method = Callable[..., Awaitable[Result[SuccessResult, ErrorResult]]]
+Methods = Dict[str, Method]
+global_methods: Methods = {}
+
+
+def method(
+ func: Optional[Method] = None, name: Optional[str] = None
+) -> Callable[..., Awaitable[Any]]:
+ """A decorator to add a function into jsonrpcserver's internal global_methods dict.
+ The global_methods dict will be used by default unless a methods argument is passed
+ to `dispatch`.
+
+ Functions can be renamed by passing a name argument:
+
+ @method(name=bar)
+ def foo():
+ ...
+ """
+
+ def decorator(func_: Method) -> Method:
+ nonlocal name
+ global_methods[name or func_.__name__] = func_
+ return func_
+
+ return decorator(func) if callable(func) else cast(Method, decorator)
diff --git a/jsonrpcserver/dispatcher.py b/jsonrpcserver/dispatcher.py
index c6b6229..467869a 100644
--- a/jsonrpcserver/dispatcher.py
+++ b/jsonrpcserver/dispatcher.py
@@ -1,14 +1,14 @@
"""Dispatcher - does the hard work of this library: parses, validates and dispatches
requests, providing responses.
"""
-# pylint: disable=protected-access
+
+import logging
from functools import partial
from inspect import signature
from itertools import starmap
from typing import Any, Callable, Dict, Iterable, List, Tuple, Union
-import logging
-from oslash.either import Either, Left, Right # type: ignore
+from returns.result import Failure, Result, Success
from .exceptions import JsonRpcError
from .methods import Method, Methods
@@ -26,12 +26,12 @@
InternalErrorResult,
InvalidParamsResult,
MethodNotFoundResult,
- Result,
SuccessResult,
)
from .sentinels import NOCONTEXT, NOID
from .utils import compose, make_list
+ArgsValidator = Callable[[Any, Request, Method], Result[Method, ErrorResult]]
Deserialized = Union[Dict[str, Any], List[Dict[str, Any]]]
logger = logging.getLogger(__name__)
@@ -66,7 +66,9 @@ def extract_list(
return response_list[0]
-def to_response(request: Request, result: Result) -> Response:
+def to_response(
+ request: Request, result: Result[SuccessResult, ErrorResult]
+) -> Response:
"""Maps a Request plus a Result to a Response. A Response is just a Result plus the
id from the original Request.
@@ -79,9 +81,9 @@ def to_response(request: Request, result: Result) -> Response:
"""
assert request.id is not NOID
return (
- Left(ErrorResponse(**result._error._asdict(), id=request.id))
- if isinstance(result, Left)
- else Right(SuccessResponse(**result._value._asdict(), id=request.id))
+ Failure(ErrorResponse(**result.failure()._asdict(), id=request.id))
+ if isinstance(result, Failure)
+ else Success(SuccessResponse(**result.unwrap()._asdict(), id=request.id))
)
@@ -104,19 +106,25 @@ def extract_kwargs(request: Request) -> Dict[str, Any]:
return request.params if isinstance(request.params, dict) else {}
-def validate_result(result: Result) -> None:
+def validate_result(result: Result[SuccessResult, ErrorResult]) -> None:
"""Validate the return value from a method.
Raises an AssertionError if the result returned from a method is invalid.
Returns: None
"""
- assert (isinstance(result, Left) and isinstance(result._error, ErrorResult)) or (
- isinstance(result, Right) and isinstance(result._value, SuccessResult)
+ assert (
+ isinstance(result, Failure) and isinstance(result.failure(), ErrorResult)
+ ) or (
+ isinstance(result, Success) and isinstance(result.unwrap(), SuccessResult)
), f"The method did not return a valid Result (returned {result!r})"
-def call(request: Request, context: Any, method: Method) -> Result:
+def call(
+ request: Request,
+ context: Any,
+ method: Method,
+) -> Result[SuccessResult, ErrorResult]:
"""Call the method.
Handles any exceptions raised in the method, being sure to return an Error response.
@@ -132,17 +140,19 @@ def call(request: Request, context: Any, method: Method) -> Result:
# Raising JsonRpcError inside the method is an alternative way of returning an error
# response.
except JsonRpcError as exc:
- return Left(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
+ return Failure(ErrorResult(code=exc.code, message=exc.message, data=exc.data))
# Any other uncaught exception inside method - internal error.
- except Exception as exc: # pylint: disable=broad-except
+ except Exception as exc:
logger.exception(exc)
- return Left(InternalErrorResult(str(exc)))
+ return Failure(InternalErrorResult(str(exc)))
return result
def validate_args(
- request: Request, context: Any, func: Method
-) -> Either[ErrorResult, Method]:
+ request: Request,
+ context: Any,
+ func: Method,
+) -> Result[Method, ErrorResult]:
"""Ensure the method can be called with the arguments given.
Returns: Either the function to be called, or an Invalid Params error result.
@@ -150,34 +160,37 @@ def validate_args(
try:
signature(func).bind(*extract_args(request, context), **extract_kwargs(request))
except TypeError as exc:
- return Left(InvalidParamsResult(str(exc)))
- return Right(func)
+ return Failure(InvalidParamsResult(str(exc)))
+ return Success(func)
-def get_method(methods: Methods, method_name: str) -> Either[ErrorResult, Method]:
+def get_method(methods: Methods, method_name: str) -> Result[Method, ErrorResult]:
"""Get the requested method from the methods dict.
Returns: Either the function to be called, or a Method Not Found result.
"""
try:
- return Right(methods[method_name])
+ return Success(methods[method_name])
except KeyError:
- return Left(MethodNotFoundResult(method_name))
+ return Failure(MethodNotFoundResult(method_name))
def dispatch_request(
- methods: Methods, context: Any, request: Request
-) -> Tuple[Request, Result]:
+ args_validator: ArgsValidator,
+ methods: Methods,
+ context: Any,
+ request: Request,
+) -> Tuple[Request, Result[SuccessResult, ErrorResult]]:
"""Get the method, validates the arguments and calls the method.
- Returns: A tuple containing the Result of the method, along with the original
- Request. We need the ids from the original request to remove notifications
- before responding, and create a Response.
+ Returns: A tuple containing the original Request, and the Result of the method call.
+ We need the ids from the original request to remove notifications before
+ responding, and create a Response.
"""
return (
request,
get_method(methods, request.method)
- .bind(partial(validate_args, request, context))
+ .bind(partial(args_validator, request, context))
.bind(partial(call, request, context)),
)
@@ -198,20 +211,23 @@ def not_notification(request_result: Any) -> bool:
def dispatch_deserialized(
+ args_validator: ArgsValidator,
+ post_process: Callable[[Response], Response],
methods: Methods,
context: Any,
- post_process: Callable[[Response], Iterable[Any]],
deserialized: Deserialized,
) -> Union[Response, List[Response], None]:
"""This is simply continuing the pipeline from dispatch_to_response_pure. It exists
only to be an abstraction, otherwise that function is doing too much. It continues
on from the request string having been parsed and validated.
- Returns: A Response, a list of Responses, or None. If post_process is passed, it's
+ Returns: A Result, a list of Results, or None. If post_process is passed, it's
applied to the Response(s).
"""
results = map(
- compose(partial(dispatch_request, methods, context), create_request),
+ compose(
+ partial(dispatch_request, args_validator, methods, context), create_request
+ ),
make_list(deserialized),
)
responses = starmap(to_response, filter(not_notification, results))
@@ -219,8 +235,8 @@ def dispatch_deserialized(
def validate_request(
- validator: Callable[[Deserialized], Deserialized], request: Deserialized
-) -> Either[ErrorResponse, Deserialized]:
+ jsonrpc_validator: Callable[[Deserialized], Deserialized], request: Deserialized
+) -> Result[Deserialized, ErrorResponse]:
"""Validate the request against a JSON-RPC schema.
Ensures the parsed request is valid JSON-RPC.
@@ -228,57 +244,59 @@ def validate_request(
Returns: Either the same request passed in or an Invalid request response.
"""
try:
- validator(request)
+ jsonrpc_validator(request)
# Since the validator is unknown, the specific exception that will be raised is also
- # unknown. Any exception raised we assume the request is invalid and return an
+ # unknown. Any exception raised we assume the request is invalid and return an
# "invalid request" response.
- except Exception: # pylint: disable=broad-except
- return Left(InvalidRequestResponse("The request failed schema validation"))
- return Right(request)
+ except Exception:
+ return Failure(InvalidRequestResponse("The request failed schema validation"))
+ return Success(request)
def deserialize_request(
deserializer: Callable[[str], Deserialized], request: str
-) -> Either[ErrorResponse, Deserialized]:
+) -> Result[Deserialized, ErrorResponse]:
"""Parse the JSON request string.
Returns: Either the deserialized request or a "Parse Error" response.
"""
try:
- return Right(deserializer(request))
+ return Success(deserializer(request))
# Since the deserializer is unknown, the specific exception that will be raised is
# also unknown. Any exception raised we assume the request is invalid, return a
# parse error response.
- except Exception as exc: # pylint: disable=broad-except
- return Left(ParseErrorResponse(str(exc)))
+ except Exception as exc:
+ return Failure(ParseErrorResponse(str(exc)))
def dispatch_to_response_pure(
- *,
+ args_validator: ArgsValidator,
deserializer: Callable[[str], Deserialized],
- validator: Callable[[Deserialized], Deserialized],
+ jsonrpc_validator: Callable[[Deserialized], Deserialized],
+ post_process: Callable[[Response], Response],
methods: Methods,
context: Any,
- post_process: Callable[[Response], Iterable[Any]],
request: str,
) -> Union[Response, List[Response], None]:
"""A function from JSON-RPC request string to Response namedtuple(s), (yet to be
serialized to json).
Returns: A single Response, a list of Responses, or None. None is given for
- notifications or batches of notifications, to indicate that we should not
- respond.
+ notifications or batches of notifications, to indicate that we should
+ not respond.
"""
try:
result = deserialize_request(deserializer, request).bind(
- partial(validate_request, validator)
+ partial(validate_request, jsonrpc_validator)
)
return (
post_process(result)
- if isinstance(result, Left)
- else dispatch_deserialized(methods, context, post_process, result._value)
+ if isinstance(result, Failure)
+ else dispatch_deserialized(
+ args_validator, post_process, methods, context, result.unwrap()
+ )
)
- except Exception as exc: # pylint: disable=broad-except
+ except Exception as exc:
# There was an error with the jsonrpcserver library.
- logger.exception(exc)
- return post_process(Left(ServerErrorResponse(str(exc), None)))
+ logging.exception(exc)
+ return post_process(Failure(ServerErrorResponse(str(exc), None)))
diff --git a/jsonrpcserver/exceptions.py b/jsonrpcserver/exceptions.py
index b2e2afb..0314e44 100644
--- a/jsonrpcserver/exceptions.py
+++ b/jsonrpcserver/exceptions.py
@@ -1,5 +1,7 @@
"""Exceptions"""
+
from typing import Any
+
from .sentinels import NODATA
diff --git a/jsonrpcserver/main.py b/jsonrpcserver/main.py
index bbf824b..70fdd18 100644
--- a/jsonrpcserver/main.py
+++ b/jsonrpcserver/main.py
@@ -9,36 +9,43 @@
- dispatch_to_json/dispatch: Returns a JSON-RPC response string (or an empty string for
notifications).
"""
-from importlib.resources import read_text
-from typing import Any, Callable, Dict, List, Optional, Union, cast
+
import json
+from typing import Any, Callable, Dict, List, Union, cast
from jsonschema.validators import validator_for # type: ignore
-from .dispatcher import dispatch_to_response_pure, Deserialized
+from .dispatcher import (
+ ArgsValidator,
+ Deserialized,
+ dispatch_to_response_pure,
+ validate_args,
+)
from .methods import Methods, global_methods
+from .request_schema import REQUEST_SCHEMA
from .response import Response, to_dict
from .sentinels import NOCONTEXT
from .utils import identity
-
+default_args_validator = validate_args
default_deserializer = json.loads
-# Prepare the jsonschema validator. This is global so it loads only once, not every
-# time dispatch is called.
-schema = json.loads(read_text(__package__, "request-schema.json"))
-klass = validator_for(schema)
-klass.check_schema(schema)
-default_validator = klass(schema).validate
+# Prepare the jsonschema validator. This is global so it loads only once, not every time
+# dispatch is called.
+klass = validator_for(REQUEST_SCHEMA)
+klass.check_schema(REQUEST_SCHEMA)
+default_jsonrpc_validator = klass(REQUEST_SCHEMA).validate
def dispatch_to_response(
request: str,
- methods: Optional[Methods] = None,
- *,
+ methods: Methods = global_methods,
context: Any = NOCONTEXT,
+ args_validator: ArgsValidator = default_args_validator,
deserializer: Callable[[str], Deserialized] = json.loads,
- validator: Callable[[Deserialized], Deserialized] = default_validator,
+ jsonrpc_validator: Callable[
+ [Deserialized], Deserialized
+ ] = default_jsonrpc_validator,
post_process: Callable[[Response], Any] = identity,
) -> Union[Response, List[Response], None]:
"""Takes a JSON-RPC request string and dispatches it to method(s), giving Response
@@ -54,9 +61,11 @@ def dispatch_to_response(
populated with the @method decorator.
context: If given, will be passed as the first argument to methods.
deserializer: Function that deserializes the request string.
- validator: Function that validates the JSON-RPC request. The function should
- raise an exception if the request is invalid. To disable validation, pass
- lambda _: None.
+ args_validator: Function that validates that the parameters in the request match
+ the Python function being called.
+ jsonrpc_validator: Function that validates the JSON-RPC request. The function
+ should raise an exception if the request is invalid. To disable validation,
+ pass lambda _: None.
post_process: Function that will be applied to Responses.
Returns:
@@ -67,12 +76,13 @@ def dispatch_to_response(
'{"jsonrpc": "2.0", "result": "pong", "id": 1}'
"""
return dispatch_to_response_pure(
- deserializer=deserializer,
- validator=validator,
- post_process=post_process,
- context=context,
- methods=global_methods if methods is None else methods,
- request=request,
+ args_validator,
+ deserializer,
+ jsonrpc_validator,
+ post_process,
+ methods,
+ context,
+ request,
)
@@ -82,9 +92,10 @@ def dispatch_to_serializable(
"""Takes a JSON-RPC request string and dispatches it to method(s), giving responses
as dicts (or None).
"""
+ kwargs.setdefault("post_process", to_dict)
return cast(
Union[Dict[str, Any], List[Dict[str, Any]], None],
- dispatch_to_response(*args, post_process=to_dict, **kwargs),
+ dispatch_to_response(*args, **kwargs),
)
@@ -106,7 +117,7 @@ def dispatch_to_json(
The rest: Passed through to dispatch_to_serializable.
"""
response = dispatch_to_serializable(*args, **kwargs)
- # Better to respond with the empty string instead of json "null", because "null" is
+ # Better to respond with an empty string instead of json "null", because "null" is
# an invalid JSON-RPC response.
return "" if response is None else serializer(response)
diff --git a/jsonrpcserver/methods.py b/jsonrpcserver/methods.py
index 38d999a..6a24930 100644
--- a/jsonrpcserver/methods.py
+++ b/jsonrpcserver/methods.py
@@ -11,18 +11,21 @@
Methods can take either positional or named arguments, but not both. This is a
limitation of JSON-RPC.
"""
+
from typing import Any, Callable, Dict, Optional, cast
-from .result import Result
+from returns.result import Result
+
+from .result import ErrorResult, SuccessResult
-Method = Callable[..., Result]
+Method = Callable[..., Result[SuccessResult, ErrorResult]]
Methods = Dict[str, Method]
-global_methods = {}
+global_methods: Methods = {}
def method(
- f: Optional[Method] = None, # pylint: disable=invalid-name
+ f: Optional[Method] = None,
name: Optional[str] = None,
) -> Callable[..., Any]:
"""A decorator to add a function into jsonrpcserver's internal global_methods dict.
diff --git a/jsonrpcserver/request.py b/jsonrpcserver/request.py
index 37f88c1..d82043e 100644
--- a/jsonrpcserver/request.py
+++ b/jsonrpcserver/request.py
@@ -3,6 +3,7 @@
After parsing a request string, we put the (dict) requests into these Request
namedtuples, simply because they're nicer to work with.
"""
+
from typing import Any, Dict, List, NamedTuple, Union
diff --git a/jsonrpcserver/request-schema.json b/jsonrpcserver/request_schema.py
similarity index 51%
rename from jsonrpcserver/request-schema.json
rename to jsonrpcserver/request_schema.py
index 52bb147..fc163e4 100644
--- a/jsonrpcserver/request-schema.json
+++ b/jsonrpcserver/request_schema.py
@@ -1,39 +1,32 @@
-{
+REQUEST_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "A JSON RPC 2.0 request",
"oneOf": [
- {
- "description": "An individual request",
- "$ref": "#/definitions/request"
- },
+ {"description": "An individual request", "$ref": "#/definitions/request"},
{
"description": "An array of requests",
"type": "array",
- "items": { "$ref": "#/definitions/request" },
- "minItems": 1
- }
+ "items": {"$ref": "#/definitions/request"},
+ "minItems": 1,
+ },
],
"definitions": {
"request": {
"type": "object",
- "required": [ "jsonrpc", "method" ],
+ "required": ["jsonrpc", "method"],
"properties": {
- "jsonrpc": { "enum": [ "2.0" ] },
- "method": {
- "type": "string"
- },
+ "jsonrpc": {"enum": ["2.0"]},
+ "method": {"type": "string"},
"id": {
- "type": [ "string", "number", "null" ],
+ "type": ["string", "number", "null"],
"note": [
"While allowed, null should be avoided: http://www.jsonrpc.org/specification#id1",
- "While allowed, a number with a fractional part should be avoided: http://www.jsonrpc.org/specification#id2"
- ]
+ "While allowed, a number with a fractional part should be avoided: http://www.jsonrpc.org/specification#id2",
+ ],
},
- "params": {
- "type": [ "array", "object" ]
- }
+ "params": {"type": ["array", "object"]},
},
- "additionalProperties": false
+ "additionalProperties": False,
}
- }
+ },
}
diff --git a/jsonrpcserver/response.py b/jsonrpcserver/response.py
index 80f388e..2702b0d 100644
--- a/jsonrpcserver/response.py
+++ b/jsonrpcserver/response.py
@@ -2,9 +2,10 @@
https://www.jsonrpc.org/specification#response_object
"""
-from typing import Any, Dict, List, Type, NamedTuple, Union
-from oslash.either import Either, Left # type: ignore
+from typing import Any, Dict, List, NamedTuple, Union
+
+from returns.result import Failure, Result
from .codes import (
ERROR_INVALID_REQUEST,
@@ -37,11 +38,10 @@ class ErrorResponse(NamedTuple):
id: Any
-Response = Either[ErrorResponse, SuccessResponse]
-ResponseType = Type[Either[ErrorResponse, SuccessResponse]]
+Response = Result[SuccessResponse, ErrorResponse]
-def ParseErrorResponse(data: Any) -> ErrorResponse: # pylint: disable=invalid-name
+def ParseErrorResponse(data: Any) -> ErrorResponse:
"""An ErrorResponse with most attributes already populated.
From the spec: "This (id) member is REQUIRED. It MUST be the same as the value of
@@ -51,7 +51,7 @@ def ParseErrorResponse(data: Any) -> ErrorResponse: # pylint: disable=invalid-n
return ErrorResponse(ERROR_PARSE_ERROR, "Parse error", data, None)
-def InvalidRequestResponse(data: Any) -> ErrorResponse: # pylint: disable=invalid-name
+def InvalidRequestResponse(data: Any) -> ErrorResponse:
"""An ErrorResponse with most attributes already populated.
From the spec: "This (id) member is REQUIRED. It MUST be the same as the value of
@@ -63,13 +63,11 @@ def InvalidRequestResponse(data: Any) -> ErrorResponse: # pylint: disable=inval
def MethodNotFoundResponse(data: Any, id: Any) -> ErrorResponse:
"""An ErrorResponse with some attributes already populated."""
- # pylint: disable=invalid-name,redefined-builtin
return ErrorResponse(ERROR_METHOD_NOT_FOUND, "Method not found", data, id)
def ServerErrorResponse(data: Any, id: Any) -> ErrorResponse:
"""An ErrorResponse with some attributes already populated."""
- # pylint: disable=invalid-name,redefined-builtin
return ErrorResponse(ERROR_SERVER_ERROR, "Server error", data, id)
@@ -92,18 +90,17 @@ def to_success_dict(response: SuccessResponse) -> Dict[str, Any]:
return {"jsonrpc": "2.0", "result": response.result, "id": response.id}
-def to_dict(response: ResponseType) -> Dict[str, Any]:
+def to_dict(response: Response) -> Dict[str, Any]:
"""Serialize either an error or success response object to dict"""
- # pylint: disable=protected-access
return (
- to_error_dict(response._error)
- if isinstance(response, Left)
- else to_success_dict(response._value)
+ to_error_dict(response.failure())
+ if isinstance(response, Failure)
+ else to_success_dict(response.unwrap())
)
def to_serializable(
- response: Union[ResponseType, List[ResponseType], None]
+ response: Union[Response, List[Response], None],
) -> Union[Deserialized, None]:
"""Serialize a response object (or list of them), to a dict, or list of them."""
if response is None:
diff --git a/jsonrpcserver/result.py b/jsonrpcserver/result.py
index 9dd78f9..37740c2 100644
--- a/jsonrpcserver/result.py
+++ b/jsonrpcserver/result.py
@@ -6,15 +6,15 @@
The public functions are Success, Error and InvalidParams.
"""
+
from typing import Any, NamedTuple
-from oslash.either import Either, Left, Right # type: ignore
+from returns.result import Failure, Success
+from returns.result import Result as R
-from .codes import ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND, ERROR_INTERNAL_ERROR
+from .codes import ERROR_INTERNAL_ERROR, ERROR_INVALID_PARAMS, ERROR_METHOD_NOT_FOUND
from .sentinels import NODATA
-# pylint: disable=missing-class-docstring,missing-function-docstring,invalid-name
-
class SuccessResult(NamedTuple):
result: Any = None
@@ -29,11 +29,13 @@ class ErrorResult(NamedTuple):
data: Any = NODATA # The spec says this value may be omitted
def __repr__(self) -> str:
- return f"ErrorResult(code={self.code!r}, message={self.message!r}, data={self.data!r})"
+ return (
+ f"ErrorResult(code={self.code!r}, message={self.message!r}, "
+ f"data={self.data!r})"
+ )
-# Union of the two valid result types
-Result = Either[ErrorResult, SuccessResult]
+Result = R[SuccessResult, ErrorResult]
# Helpers
@@ -54,16 +56,16 @@ def InvalidParamsResult(data: Any = NODATA) -> ErrorResult:
# Helpers (the public functions)
-def Success(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
- return Right(SuccessResult(*args, **kwargs))
+def Ok(*args: Any, **kwargs: Any) -> Success[SuccessResult]:
+ return Success(SuccessResult(*args, **kwargs))
-def Error(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
- return Left(ErrorResult(*args, **kwargs))
+def Error(*args: Any, **kwargs: Any) -> Failure[ErrorResult]:
+ return Failure(ErrorResult(*args, **kwargs))
-def InvalidParams(*args: Any, **kwargs: Any) -> Either[ErrorResult, SuccessResult]:
+def InvalidParams(*args: Any, **kwargs: Any) -> Failure[ErrorResult]:
"""InvalidParams is a shortcut to save you from having to pass the Invalid Params
JSON-RPC code to Error.
"""
- return Left(InvalidParamsResult(*args, **kwargs))
+ return Failure(InvalidParamsResult(*args, **kwargs))
diff --git a/jsonrpcserver/sentinels.py b/jsonrpcserver/sentinels.py
index 0ed0cca..46ca287 100644
--- a/jsonrpcserver/sentinels.py
+++ b/jsonrpcserver/sentinels.py
@@ -12,7 +12,6 @@ class Sentinel:
Has a nicer repr than `object()`.
"""
- # pylint: disable=too-few-public-methods
def __init__(self, name: str):
self.name = name
diff --git a/jsonrpcserver/server.py b/jsonrpcserver/server.py
index 643b6a7..c0977c2 100644
--- a/jsonrpcserver/server.py
+++ b/jsonrpcserver/server.py
@@ -1,7 +1,7 @@
"""A simple development server for serving JSON-RPC requests using Python's builtin
http.server module.
"""
-import logging
+
from http.server import BaseHTTPRequestHandler, HTTPServer
from .main import dispatch
@@ -10,19 +10,22 @@
class RequestHandler(BaseHTTPRequestHandler):
"""Handle HTTP requests"""
- def do_POST(self) -> None: # pylint: disable=invalid-name
+ def do_POST(self) -> None:
"""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)
- HTTPServer((name, port), RequestHandler).serve_forever()
+ httpd = HTTPServer((name, port), RequestHandler)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ pass
+ finally:
+ httpd.shutdown()
diff --git a/jsonrpcserver/utils.py b/jsonrpcserver/utils.py
index 4ee5a2c..ec47b32 100644
--- a/jsonrpcserver/utils.py
+++ b/jsonrpcserver/utils.py
@@ -1,9 +1,8 @@
"""Utility functions"""
+
from functools import reduce
from typing import Any, Callable, List
-# pylint: disable=invalid-name
-
def identity(x: Any) -> Any:
"""Returns the argument."""
diff --git a/docs/logo.png b/logo.png
similarity index 100%
rename from docs/logo.png
rename to logo.png
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..57eb2b9
--- /dev/null
+++ b/mkdocs.yml
@@ -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
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..9bb71fa
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,3 @@
+[mypy]
+plugins =
+ returns.contrib.mypy.returns_plugin
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..cd8e31b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,58 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+authors = [
+ {name = "Beau Barker", email = "beau@explodinglabs.com"}
+]
+classifiers = [
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11"
+]
+description = "Process JSON-RPC requests"
+dependencies = ["returns", "jsonschema"]
+license = {file = "LICENSE"}
+name = "jsonrpcserver"
+readme = {file = "README.md", content-type = "text/markdown"}
+requires-python = ">=3.8"
+version = "6.0.0"
+
+[project.urls]
+homepage = "https://www.jsonrpcserver.com"
+repository = "https://github.com/explodinglabs/jsonrpcserver"
+
+[project.optional-dependencies]
+qa = [
+ "pytest",
+ "pytest-asyncio",
+ "pytest-cov",
+ "tox",
+]
+examples = [
+ "aiohttp",
+ "aiozmq",
+ "flask",
+ "flask-socketio",
+ "gmqtt",
+ "pyzmq",
+ "sanic",
+ "tornado",
+ "websockets",
+ "werkzeug",
+]
+
+[tool.setuptools]
+include-package-data = true
+packages = [
+ "jsonrpcserver"
+]
+zip-safe = false
+
+[tool.pytest.ini_options]
+addopts = '--doctest-glob="*.md"'
+testpaths = [
+ "tests",
+]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index ebce509..0000000
--- a/setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-"""setup.py"""
-from setuptools import setup
-
-with open("README.md", encoding="utf-8") as f:
- README = f.read()
-
-setup(
- author="Beau Barker",
- author_email="beau@explodinglabs.com",
- classifiers=[
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- ],
- description="Process JSON-RPC requests",
- extras_require={
- "examples": [
- "aiohttp",
- "aiozmq",
- "flask",
- "flask-socketio",
- "gmqtt",
- "pyzmq",
- "tornado",
- "websockets",
- "werkzeug",
- ],
- "test": [
- "pytest",
- "pytest-cov",
- "tox",
- ],
- },
- include_package_data=True,
- install_requires=["jsonschema<5", "oslash<1"],
- license="MIT",
- long_description=README,
- long_description_content_type="text/markdown",
- name="jsonrpcserver",
- packages=["jsonrpcserver"],
- url="https://github.com/explodinglabs/jsonrpcserver",
- version="5.0.9",
- # Be PEP 561 compliant
- # https://mypy.readthedocs.io/en/stable/installed_packages.html#making-pep-561-compatible-packages
- zip_safe=False,
-)
diff --git a/tests/test_async_dispatcher.py b/tests/test_async_dispatcher.py
index 050e96d..78253c2 100644
--- a/tests/test_async_dispatcher.py
+++ b/tests/test_async_dispatcher.py
@@ -1,8 +1,9 @@
"""Test async_dispatcher.py"""
+
from unittest.mock import Mock, patch
-import pytest
-from oslash.either import Left, Right # type: ignore
+import pytest
+from returns.result import Failure, Success
from jsonrpcserver.async_dispatcher import (
call,
@@ -10,45 +11,43 @@
dispatch_request,
dispatch_to_response_pure,
)
-from jsonrpcserver.main import default_deserializer, default_validator
from jsonrpcserver.codes import ERROR_INTERNAL_ERROR, ERROR_SERVER_ERROR
from jsonrpcserver.exceptions import JsonRpcError
+from jsonrpcserver.main import default_deserializer, default_jsonrpc_validator
from jsonrpcserver.request import Request
from jsonrpcserver.response import ErrorResponse, SuccessResponse
-from jsonrpcserver.result import ErrorResult, Result, Success, SuccessResult
+from jsonrpcserver.result import ErrorResult, Ok, Result, SuccessResult
from jsonrpcserver.sentinels import NOCONTEXT, NODATA
from jsonrpcserver.utils import identity
-# pylint: disable=missing-function-docstring,duplicate-code
-
async def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
@pytest.mark.asyncio
async def test_call() -> None:
- assert await call(Request("ping", [], 1), NOCONTEXT, ping) == Right(
+ assert await call(Request("ping", [], 1), NOCONTEXT, ping) == Success(
SuccessResult("pong")
)
@pytest.mark.asyncio
async def test_call_raising_jsonrpcerror() -> None:
- def method() -> None:
+ async def method_() -> Result:
raise JsonRpcError(code=1, message="foo", data=NODATA)
- assert await call(Request("ping", [], 1), NOCONTEXT, method) == Left(
+ assert await call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
ErrorResult(1, "foo")
)
@pytest.mark.asyncio
async def test_call_raising_exception() -> None:
- def method() -> None:
+ async def method_() -> Result:
raise ValueError("foo")
- assert await call(Request("ping", [], 1), NOCONTEXT, method) == Left(
+ assert await call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
ErrorResult(ERROR_INTERNAL_ERROR, "Internal error", "foo")
)
@@ -58,7 +57,7 @@ async def test_dispatch_request() -> None:
request = Request("ping", [], 1)
assert await dispatch_request({"ping": ping}, NOCONTEXT, request) == (
request,
- Right(SuccessResult("pong")),
+ Success(SuccessResult("pong")),
)
@@ -69,32 +68,32 @@ async def test_dispatch_deserialized() -> None:
NOCONTEXT,
identity,
{"jsonrpc": "2.0", "method": "ping", "id": 1},
- ) == Right(SuccessResponse("pong", 1))
+ ) == Success(SuccessResponse("pong", 1))
@pytest.mark.asyncio
async def test_dispatch_to_response_pure_success() -> None:
assert await dispatch_to_response_pure(
deserializer=default_deserializer,
- validator=default_validator,
+ validator=default_jsonrpc_validator,
post_process=identity,
context=NOCONTEXT,
methods={"ping": ping},
request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
- ) == Right(SuccessResponse("pong", 1))
+ ) == Success(SuccessResponse("pong", 1))
@patch("jsonrpcserver.async_dispatcher.dispatch_request", side_effect=ValueError("foo"))
@pytest.mark.asyncio
-async def test_dispatch_to_response_pure_server_error(*_: Mock) -> None:
- async def hello() -> Result:
- return Success()
+async def test_dispatch_to_response_pure_server_error(_: Mock) -> None:
+ async def ping() -> Result:
+ return Ok()
assert await dispatch_to_response_pure(
deserializer=default_deserializer,
- validator=default_validator,
+ validator=default_jsonrpc_validator,
post_process=identity,
context=NOCONTEXT,
- methods={"hello": hello},
- request='{"jsonrpc": "2.0", "method": "hello", "id": 1}',
- ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "hello", None))
+ methods={"ping": ping},
+ request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
+ ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
diff --git a/tests/test_async_main.py b/tests/test_async_main.py
index d820182..f70e83f 100644
--- a/tests/test_async_main.py
+++ b/tests/test_async_main.py
@@ -1,28 +1,26 @@
"""Test async_main.py"""
-import pytest
-from oslash.either import Right # type: ignore
+import pytest
+from returns.result import Success
from jsonrpcserver.async_main import (
+ dispatch_to_json,
dispatch_to_response,
dispatch_to_serializable,
- dispatch_to_json,
)
from jsonrpcserver.response import SuccessResponse
-from jsonrpcserver.result import Result, Success
-
-# pylint: disable=missing-function-docstring
+from jsonrpcserver.result import Ok, Result
async def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
@pytest.mark.asyncio
async def test_dispatch_to_response() -> None:
assert await dispatch_to_response(
'{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
- ) == Right(SuccessResponse("pong", 1))
+ ) == Success(SuccessResponse("pong", 1))
@pytest.mark.asyncio
diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py
index 727bf70..a58885a 100644
--- a/tests/test_dispatcher.py
+++ b/tests/test_dispatcher.py
@@ -2,12 +2,13 @@
TODO: Add tests for dispatch_requests (non-pure version)
"""
-from typing import Any, Callable, Dict
-from unittest.mock import Mock, patch, sentinel
+
import json
-import pytest
+from typing import Any, Dict
+from unittest.mock import Mock, patch, sentinel
-from oslash.either import Left, Right # type: ignore
+import pytest
+from returns.result import Failure, Success
from jsonrpcserver.codes import (
ERROR_INTERNAL_ERROR,
@@ -23,9 +24,9 @@
dispatch_deserialized,
dispatch_request,
dispatch_to_response_pure,
- extract_list,
extract_args,
extract_kwargs,
+ extract_list,
get_method,
not_notification,
to_response,
@@ -33,46 +34,41 @@
validate_request,
)
from jsonrpcserver.exceptions import JsonRpcError
-from jsonrpcserver.main import (
- default_deserializer,
- default_validator,
- dispatch_to_response,
- dispatch,
-)
-from jsonrpcserver.methods import method
+from jsonrpcserver.main import default_jsonrpc_validator
+from jsonrpcserver.methods import Method
from jsonrpcserver.request import Request
from jsonrpcserver.response import ErrorResponse, SuccessResponse
from jsonrpcserver.result import (
ErrorResult,
InvalidParams,
+ Ok,
Result,
- Success,
SuccessResult,
)
from jsonrpcserver.sentinels import NOCONTEXT, NODATA, NOID
from jsonrpcserver.utils import identity
-# pylint: disable=missing-function-docstring,missing-class-docstring,too-few-public-methods,unnecessary-lambda-assignment,invalid-name,disallowed-name
-
def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
# extract_list
def test_extract_list() -> None:
- assert extract_list(False, [SuccessResponse("foo", 1)]) == SuccessResponse("foo", 1)
+ assert extract_list(False, [Success(SuccessResponse("foo", 1))]) == Success(
+ SuccessResponse("foo", 1)
+ )
def test_extract_list_notification() -> None:
- assert extract_list(False, [None]) is None
+ assert extract_list(False, []) is None
def test_extract_list_batch() -> None:
- assert extract_list(True, [SuccessResponse("foo", 1)]) == [
- SuccessResponse("foo", 1)
+ assert extract_list(True, [Success(SuccessResponse("foo", 1))]) == [
+ Success(SuccessResponse("foo", 1))
]
@@ -85,21 +81,21 @@ def test_extract_list_batch_all_notifications() -> None:
def test_to_response_SuccessResult() -> None:
assert to_response(
- Request("ping", [], sentinel.id), Right(SuccessResult(sentinel.result))
- ) == Right(SuccessResponse(sentinel.result, sentinel.id))
+ Request("ping", [], sentinel.id), Success(SuccessResult(sentinel.result))
+ ) == Success(SuccessResponse(sentinel.result, sentinel.id))
def test_to_response_ErrorResult() -> None:
assert (
to_response(
Request("ping", [], sentinel.id),
- Left(
+ Failure(
ErrorResult(
code=sentinel.code, message=sentinel.message, data=sentinel.data
)
),
)
- ) == Left(
+ ) == Failure(
ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id)
)
@@ -107,18 +103,20 @@ def test_to_response_ErrorResult() -> None:
def test_to_response_InvalidParams() -> None:
assert to_response(
Request("ping", [], sentinel.id), InvalidParams(sentinel.data)
- ) == Left(ErrorResponse(-32602, "Invalid params", sentinel.data, sentinel.id))
+ ) == Failure(ErrorResponse(-32602, "Invalid params", sentinel.data, sentinel.id))
def test_to_response_InvalidParams_no_data() -> None:
- assert to_response(Request("ping", [], sentinel.id), InvalidParams()) == Left(
+ assert to_response(Request("ping", [], sentinel.id), InvalidParams()) == Failure(
ErrorResponse(-32602, "Invalid params", NODATA, sentinel.id)
)
def test_to_response_notification() -> None:
with pytest.raises(AssertionError):
- to_response(Request("ping", [], NOID), SuccessResult(result=sentinel.result))
+ to_response(
+ Request("ping", [], NOID), Success(SuccessResult(result=sentinel.result))
+ )
# extract_args
@@ -139,16 +137,21 @@ def test_extract_kwargs() -> None:
assert extract_kwargs(Request("ping", {"foo": "bar"}, NOID)) == {"foo": "bar"}
-# validate_result
+# validate_args
+
+
+def test_validate_args_result_no_arguments() -> None:
+ def f() -> Result:
+ return Ok()
+ assert validate_args(Request("f", [], NOID), NOCONTEXT, f) == Success(f)
-def test_validate_result_no_arguments() -> None:
- f = lambda: None
- assert validate_args(Request("f", [], NOID), NOCONTEXT, f) == Right(f)
+def test_validate_args_result_no_arguments_too_many_positionals() -> None:
+ def f() -> Result:
+ return Ok()
-def test_validate_result_no_arguments_too_many_positionals() -> None:
- assert validate_args(Request("f", ["foo"], NOID), NOCONTEXT, lambda: None) == Left(
+ assert validate_args(Request("f", ["foo"], NOID), NOCONTEXT, f) == Failure(
ErrorResult(
code=ERROR_INVALID_PARAMS,
message="Invalid params",
@@ -157,56 +160,69 @@ def test_validate_result_no_arguments_too_many_positionals() -> None:
)
-def test_validate_result_positionals() -> None:
- f = lambda x: None
- assert validate_args(Request("f", [1], NOID), NOCONTEXT, f) == Right(f)
+def test_validate_args_positionals() -> None:
+ def ping_(_: int) -> Result:
+ return Ok()
+ assert validate_args(Request("ping_", [1], NOID), NOCONTEXT, ping_) == Success(
+ ping_
+ )
+
+
+def test_validate_args_positionals_not_passed() -> None:
+ def ping_(name: str) -> Result:
+ return Ok()
-def test_validate_result_positionals_not_passed() -> None:
assert validate_args(
- Request("f", {"foo": "bar"}, NOID), NOCONTEXT, lambda x: None
- ) == Left(
+ Request("ping_", {"foo": "bar"}, NOID), NOCONTEXT, ping_
+ ) == Failure(
ErrorResult(
- ERROR_INVALID_PARAMS, "Invalid params", "missing a required argument: 'x'"
+ ERROR_INVALID_PARAMS,
+ "Invalid params",
+ "missing a required argument: 'name'",
)
)
-def test_validate_result_keywords() -> None:
- f = lambda **kwargs: None
- assert validate_args(Request("f", {"foo": "bar"}, NOID), NOCONTEXT, f) == Right(f)
+def test_validate_args_keywords() -> None:
+ def f(**_: str) -> Result:
+ return Ok()
+ assert validate_args(Request("f", {"foo": "bar"}, NOID), NOCONTEXT, f) == Success(f)
-def test_validate_result_object_method() -> None:
+
+def test_validate_args_object_method() -> None:
class FooClass:
- def f(self, *_: str) -> str:
- return ""
+ def f(self, *_: str) -> Result:
+ return Ok()
g = FooClass().f
- assert validate_args(Request("g", ["one", "two"], NOID), NOCONTEXT, g) == Right(g)
+ assert validate_args(Request("g", ["one", "two"], NOID), NOCONTEXT, g) == Success(g)
# call
def test_call() -> None:
- assert call(Request("ping", [], 1), NOCONTEXT, ping) == Right(SuccessResult("pong"))
+ assert call(Request("ping", [], 1), NOCONTEXT, ping) == Success(
+ SuccessResult("pong")
+ )
def test_call_raising_jsonrpcerror() -> None:
- def method_() -> None:
+ def method_() -> Result:
raise JsonRpcError(code=1, message="foo", data=NODATA)
- assert call(Request("ping", [], 1), NOCONTEXT, method_) == Left(
+ assert call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
ErrorResult(1, "foo")
)
def test_call_raising_exception() -> None:
- def method_() -> None:
+ def method_() -> Result:
raise ValueError("foo")
- assert call(Request("ping", [], 1), NOCONTEXT, method_) == Left(
+ assert call(Request("ping", [], 1), NOCONTEXT, method_) == Failure(
ErrorResult(ERROR_INTERNAL_ERROR, "Internal error", "foo")
)
@@ -218,12 +234,12 @@ def method_() -> None:
"argument,value",
[
(
- validate_args(Request("ping", [], 1), NOCONTEXT, ping),
- Right(ping),
+ Request("ping", [], 1),
+ Success(ping),
),
(
- validate_args(Request("ping", ["foo"], 1), NOCONTEXT, ping),
- Left(
+ Request("ping", ["foo"], 1),
+ Failure(
ErrorResult(
ERROR_INVALID_PARAMS,
"Invalid params",
@@ -233,8 +249,8 @@ def method_() -> None:
),
],
)
-def test_validate_args(argument: Result, value: Result) -> None:
- assert argument == value
+def test_validate_args(argument: Request, value: Result) -> None:
+ assert validate_args(argument, NOCONTEXT, ping) == value
# get_method
@@ -245,11 +261,11 @@ def test_validate_args(argument: Result, value: Result) -> None:
[
(
get_method({"ping": ping}, "ping"),
- Right(ping),
+ Success(ping),
),
(
get_method({"ping": ping}, "non-existant"),
- Left(
+ Failure(
ErrorResult(ERROR_METHOD_NOT_FOUND, "Method not found", "non-existant")
),
),
@@ -264,18 +280,19 @@ def test_get_method(argument: Result, value: Result) -> None:
def test_dispatch_request() -> None:
request = Request("ping", [], 1)
- assert dispatch_request({"ping": ping}, NOCONTEXT, request) == (
+ assert dispatch_request(validate_args, {"ping": ping}, NOCONTEXT, request) == (
request,
- Right(SuccessResult("pong")),
+ Success(SuccessResult("pong")),
)
def test_dispatch_request_with_context() -> None:
def ping_with_context(context: Any) -> Result:
assert context is sentinel.context
- return Success()
+ return Ok()
dispatch_request(
+ validate_args,
{"ping_with_context": ping_with_context},
sentinel.context,
Request("ping_with_context", [], 1),
@@ -307,11 +324,12 @@ def test_not_notification_false() -> None:
def test_dispatch_deserialized() -> None:
assert dispatch_deserialized(
- methods={"ping": ping},
- context=NOCONTEXT,
- post_process=identity,
- deserialized={"jsonrpc": "2.0", "method": "ping", "id": 1},
- ) == Right(SuccessResponse("pong", 1))
+ validate_args,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ {"jsonrpc": "2.0", "method": "ping", "id": 1},
+ ) == Success(SuccessResponse("pong", 1))
# validate_request
@@ -319,11 +337,11 @@ def test_dispatch_deserialized() -> None:
def test_validate_request() -> None:
request = {"jsonrpc": "2.0", "method": "ping"}
- assert validate_request(default_validator, request) == Right(request)
+ assert validate_request(default_jsonrpc_validator, request) == Success(request)
def test_validate_request_invalid() -> None:
- assert validate_request(default_validator, {"jsonrpc": "2.0"}) == Left(
+ assert validate_request(default_jsonrpc_validator, {"jsonrpc": "2.0"}) == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -338,29 +356,34 @@ def test_validate_request_invalid() -> None:
def test_dispatch_to_response_pure() -> None:
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request='{"jsonrpc": "2.0", "method": "ping", "id": 1}',
- ) == Right(SuccessResponse("pong", 1))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "ping", "id": 1}',
+ ) == Success(SuccessResponse("pong", 1))
def test_dispatch_to_response_pure_parse_error() -> None:
"""Unable to parse, must return an error"""
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="{",
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "{",
+ ) == Failure(
ErrorResponse(
ERROR_PARSE_ERROR,
"Parse error",
- "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+ (
+ "Expecting property name enclosed in double quotes: "
+ "line 1 column 2 (char 1)"
+ ),
None,
)
)
@@ -371,13 +394,14 @@ def test_dispatch_to_response_pure_invalid_request() -> None:
notification).
"""
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="{}",
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "{}",
+ ) == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -389,29 +413,31 @@ def test_dispatch_to_response_pure_invalid_request() -> None:
def test_dispatch_to_response_pure_method_not_found() -> None:
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={},
- request='{"jsonrpc": "2.0", "method": "non_existant", "id": 1}',
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "non_existant", "id": 1}',
+ ) == Failure(
ErrorResponse(ERROR_METHOD_NOT_FOUND, "Method not found", "non_existant", 1)
)
def test_dispatch_to_response_pure_invalid_params_auto() -> None:
- def f(colour: str, size: str) -> Result: # pylint: disable=unused-argument
- return Success()
+ def f(colour: str, size: str) -> Result:
+ return Ok()
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"f": f},
- request='{"jsonrpc": "2.0", "method": "f", "params": {"colour":"blue"}, "id": 1}',
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"f": f},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "f", "params": {"colour":"blue"}, "id": 1}',
+ ) == Failure(
ErrorResponse(
ERROR_INVALID_PARAMS,
"Invalid params",
@@ -425,16 +451,17 @@ def test_dispatch_to_response_pure_invalid_params_explicitly_returned() -> None:
def foo(colour: str) -> Result:
if colour not in ("orange", "red", "yellow"):
return InvalidParams()
- return Success()
+ return Ok()
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo", "params": ["blue"], "id": 1}',
- ) == Left(ErrorResponse(ERROR_INVALID_PARAMS, "Invalid params", NODATA, 1))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo", "params": ["blue"], "id": 1}',
+ ) == Failure(ErrorResponse(ERROR_INVALID_PARAMS, "Invalid params", NODATA, 1))
def test_dispatch_to_response_pure_internal_error() -> None:
@@ -442,44 +469,47 @@ def foo() -> Result:
raise ValueError("foo")
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo", "id": 1}',
- ) == Left(ErrorResponse(ERROR_INTERNAL_ERROR, "Internal error", "foo", 1))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo", "id": 1}',
+ ) == Failure(ErrorResponse(ERROR_INTERNAL_ERROR, "Internal error", "foo", 1))
@patch("jsonrpcserver.dispatcher.dispatch_request", side_effect=ValueError("foo"))
def test_dispatch_to_response_pure_server_error(*_: Mock) -> None:
def foo() -> Result:
- return Success()
+ return Ok()
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo", "id": 1}',
- ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo", "id": 1}',
+ ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
def test_dispatch_to_response_pure_invalid_result() -> None:
"""Methods should return a Result, otherwise we get an Internal Error response."""
- def not_a_result() -> None:
- return None
+ def not_a_result() -> Result:
+ return None # type: ignore
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"not_a_result": not_a_result},
- request='{"jsonrpc": "2.0", "method": "not_a_result", "id": 1}',
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"not_a_result": not_a_result},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "not_a_result", "id": 1}',
+ ) == Failure(
ErrorResponse(
ERROR_INTERNAL_ERROR,
"Internal error",
@@ -492,17 +522,18 @@ def not_a_result() -> None:
def test_dispatch_to_response_pure_raising_exception() -> None:
"""Allow raising an exception to return an error."""
- def raise_exception() -> None:
+ def raise_exception() -> Result:
raise JsonRpcError(code=0, message="foo", data="bar")
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"raise_exception": raise_exception},
- request='{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
- ) == Left(ErrorResponse(0, "foo", "bar", 1))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"raise_exception": raise_exception},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "raise_exception", "id": 1}',
+ ) == Failure(ErrorResponse(0, "foo", "bar", 1))
# dispatch_to_response_pure -- Notifications
@@ -511,12 +542,13 @@ def raise_exception() -> None:
def test_dispatch_to_response_pure_notification() -> None:
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request='{"jsonrpc": "2.0", "method": "ping"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "ping"}',
)
is None
)
@@ -525,32 +557,39 @@ def test_dispatch_to_response_pure_notification() -> None:
def test_dispatch_to_response_pure_notification_parse_error() -> None:
"""Unable to parse, must return an error"""
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="{",
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "{",
+ ) == Failure(
ErrorResponse(
ERROR_PARSE_ERROR,
"Parse error",
- "Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
+ (
+ "Expecting property name enclosed in double quotes: "
+ "line 1 column 2 (char 1)"
+ ),
None,
)
)
def test_dispatch_to_response_pure_notification_invalid_request() -> None:
- """Invalid JSON-RPC, must return an error. (impossible to determine if notification)"""
+ """Invalid JSON-RPC, must return an error. (impossible to determine if
+ notification)
+ """
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="{}",
- ) == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "{}",
+ ) == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -563,48 +602,51 @@ def test_dispatch_to_response_pure_notification_invalid_request() -> None:
def test_dispatch_to_response_pure_notification_method_not_found() -> None:
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={},
- request='{"jsonrpc": "2.0", "method": "non_existant"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "non_existant"}',
)
is None
)
def test_dispatch_to_response_pure_notification_invalid_params_auto() -> None:
- def foo(colour: str, size: str) -> Result: # pylint: disable=unused-argument
- return Success()
+ def foo(colour: str, size: str) -> Result:
+ return Ok()
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo", "params": {"colour":"blue"}}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo", "params": {"colour":"blue"}}',
)
is None
)
-def test_dispatch_to_response_pure_invalid_params_notification_explicitly_returned() -> None:
+def test_dispatch_to_response_pure_invalid_params_notification_returned() -> None:
def foo(colour: str) -> Result:
if colour not in ("orange", "red", "yellow"):
return InvalidParams()
- return Success()
+ return Ok()
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo", "params": ["blue"]}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo", "params": ["blue"]}',
)
is None
)
@@ -616,12 +658,13 @@ def foo(bar: str) -> Result:
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo"}',
)
is None
)
@@ -630,32 +673,34 @@ def foo(bar: str) -> Result:
@patch("jsonrpcserver.dispatcher.dispatch_request", side_effect=ValueError("foo"))
def test_dispatch_to_response_pure_notification_server_error(*_: Mock) -> None:
def foo() -> Result:
- return Success()
+ return Ok()
assert dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"foo": foo},
- request='{"jsonrpc": "2.0", "method": "foo"}',
- ) == Left(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"foo": foo},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foo"}',
+ ) == Failure(ErrorResponse(ERROR_SERVER_ERROR, "Server error", "foo", None))
def test_dispatch_to_response_pure_notification_invalid_result() -> None:
"""Methods should return a Result, otherwise we get an Internal Error response."""
- def not_a_result() -> None:
- return None
+ def not_a_result() -> Result:
+ return None # type: ignore
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"not_a_result": not_a_result},
- request='{"jsonrpc": "2.0", "method": "not_a_result"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"not_a_result": not_a_result},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "not_a_result"}',
)
is None
)
@@ -664,138 +709,130 @@ def not_a_result() -> None:
def test_dispatch_to_response_pure_notification_raising_exception() -> None:
"""Allow raising an exception to return an error."""
- def raise_exception() -> None:
+ def raise_exception() -> Result:
raise JsonRpcError(code=0, message="foo", data="bar")
assert (
dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"raise_exception": raise_exception},
- request='{"jsonrpc": "2.0", "method": "raise_exception"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"raise_exception": raise_exception},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "raise_exception"}',
)
is None
)
-# dispatch_to_response
-
-
-def test_dispatch_to_response() -> None:
- response = dispatch_to_response(
- '{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
- )
- assert response == Right(SuccessResponse("pong", 1))
-
-
-def test_dispatch_to_response_with_global_methods() -> None:
- @method
- def ping() -> Result: # pylint: disable=redefined-outer-name
- return Success("ping")
-
- response = dispatch_to_response('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
- assert response == Right(SuccessResponse("pong", 1))
-
-
# The remaining tests are direct from the examples in the specification
def test_examples_positionals() -> None:
def subtract(minuend: int, subtrahend: int) -> Result:
- return Success(minuend - subtrahend)
+ return Ok(minuend - subtrahend)
response = dispatch_to_response_pure(
- methods={"subtract": subtract},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request='{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"subtract": subtract},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}',
)
- assert response == Right(SuccessResponse(19, 1))
+ assert response == Success(SuccessResponse(19, 1))
# Second example
response = dispatch_to_response_pure(
- methods={"subtract": subtract},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request='{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"subtract": subtract},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}',
)
- assert response == Right(SuccessResponse(-19, 2))
+ assert response == Success(SuccessResponse(-19, 2))
def test_examples_nameds() -> None:
def subtract(**kwargs: int) -> Result:
- return Success(kwargs["minuend"] - kwargs["subtrahend"])
+ return Ok(kwargs["minuend"] - kwargs["subtrahend"])
response = dispatch_to_response_pure(
- methods={"subtract": subtract},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request=(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"subtract": subtract},
+ NOCONTEXT,
+ (
'{"jsonrpc": "2.0", "method": "subtract", '
'"params": {"subtrahend": 23, "minuend": 42}, "id": 3}'
),
)
- assert response == Right(SuccessResponse(19, 3))
+ assert response == Success(SuccessResponse(19, 3))
# Second example
response = dispatch_to_response_pure(
- methods={"subtract": subtract},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request=(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"subtract": subtract},
+ NOCONTEXT,
+ (
'{"jsonrpc": "2.0", "method": "subtract", '
'"params": {"minuend": 42, "subtrahend": 23}, "id": 4}'
),
)
- assert response == Right(SuccessResponse(19, 4))
+ assert response == Success(SuccessResponse(19, 4))
def test_examples_notification() -> None:
+ def f() -> Result:
+ return Ok()
+
response = dispatch_to_response_pure(
- methods={"update": lambda: None, "foobar": lambda: None},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request='{"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"update": f, "foobar": f},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "update", "params": [1, 2, 3, 4, 5]}',
)
assert response is None
# Second example
response = dispatch_to_response_pure(
- methods={"update": lambda: None, "foobar": lambda: None},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request='{"jsonrpc": "2.0", "method": "foobar"}',
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"update": f, "foobar": f},
+ NOCONTEXT,
+ '{"jsonrpc": "2.0", "method": "foobar"}',
)
assert response is None
def test_examples_invalid_json() -> None:
response = dispatch_to_response_pure(
- methods={"ping": ping},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- request=(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ (
'[{"jsonrpc": "2.0", "method": "sum", '
'"params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]'
),
)
- assert response == Left(
+ assert response == Failure(
ErrorResponse(
ERROR_PARSE_ERROR,
"Parse error",
@@ -808,14 +845,15 @@ def test_examples_invalid_json() -> None:
def test_examples_empty_array() -> None:
# This is an invalid JSON-RPC request, should return an error.
response = dispatch_to_response_pure(
- request="[]",
- methods={"ping": ping},
- context=NOCONTEXT,
- validator=default_validator,
- post_process=identity,
- deserializer=default_deserializer,
- )
- assert response == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "[]",
+ )
+ assert response == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -831,14 +869,15 @@ def test_examples_invalid_jsonrpc_batch() -> None:
The examples are expecting a batch response full of error responses.
"""
response = dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="[1]",
- )
- assert response == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "[1]",
+ )
+ assert response == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -854,14 +893,15 @@ def test_examples_multiple_invalid_jsonrpc() -> None:
The examples are expecting a batch response full of error responses.
"""
response = dispatch_to_response_pure(
- deserializer=default_deserializer,
- validator=default_validator,
- post_process=identity,
- context=NOCONTEXT,
- methods={"ping": ping},
- request="[1, 2, 3]",
- )
- assert response == Left(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ {"ping": ping},
+ NOCONTEXT,
+ "[1, 2, 3]",
+ )
+ assert response == Failure(
ErrorResponse(
ERROR_INVALID_REQUEST,
"Invalid request",
@@ -872,32 +912,42 @@ def test_examples_multiple_invalid_jsonrpc() -> None:
def test_examples_mixed_requests_and_notifications() -> None:
- methods: Dict[str, Callable[..., Any]] = {
- "sum": lambda *args: Success(sum(args)),
- "notify_hello": lambda *args: Success(19),
- "subtract": lambda *args: Success(args[0] - sum(args[1:])),
- "get_data": lambda: Success(["hello", 5]),
+ """We break the spec here. The examples put an invalid jsonrpc request in the mix
+ here, but it's removed to test the rest, because we're not validating each request
+ individually. Any invalid jsonrpc will respond with a single error message.
+
+ The spec example includes this which invalidates the entire request:
+ {"foo": "boo"},
+ """
+ methods: Dict[str, Method] = {
+ "sum": lambda *args: Ok(sum(args)),
+ "notify_hello": lambda *args: Ok(19),
+ "subtract": lambda *args: Ok(args[0] - sum(args[1:])),
+ "get_data": lambda: Ok(["hello", 5]),
}
- response = dispatch(
- deserializer=default_deserializer,
- validator=default_validator,
- context=NOCONTEXT,
- methods=methods,
- request="""[
+ response = dispatch_to_response_pure(
+ validate_args,
+ json.loads,
+ default_jsonrpc_validator,
+ identity,
+ methods,
+ NOCONTEXT,
+ """[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
- {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
+ {
+ "jsonrpc": "2.0",
+ "method": "foo.get",
+ "params": {"name": "myself"},
+ "id": "5"
+ },
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]""",
)
- assert json.loads(response) == [
- {"jsonrpc": "2.0", "result": 7, "id": "1"},
- {"jsonrpc": "2.0", "result": 19, "id": "2"},
- {
- "jsonrpc": "2.0",
- "error": {"code": -32601, "message": "Method not found", "data": "foo.get"},
- "id": "5",
- },
- {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"},
+ assert response == [
+ Success(SuccessResponse(7, id="1")),
+ Success(SuccessResponse(19, id="2")),
+ Failure(ErrorResponse(-32601, "Method not found", "foo.get", id="5")),
+ Success(SuccessResponse(["hello", 5], id="9")),
]
diff --git a/tests/test_main.py b/tests/test_main.py
index 8b83069..52c936b 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,25 +1,34 @@
"""Test main.py"""
-from oslash.either import Right # type: ignore
+
+from returns.result import Success
from jsonrpcserver.main import (
+ dispatch_to_json,
dispatch_to_response,
dispatch_to_serializable,
- dispatch_to_json,
)
+from jsonrpcserver.methods import method
from jsonrpcserver.response import SuccessResponse
-from jsonrpcserver.result import Result, Success
-
-# pylint: disable=missing-function-docstring
+from jsonrpcserver.result import Ok, Result
def ping() -> Result:
- return Success("pong")
+ return Ok("pong")
def test_dispatch_to_response() -> None:
assert dispatch_to_response(
'{"jsonrpc": "2.0", "method": "ping", "id": 1}', {"ping": ping}
- ) == Right(SuccessResponse("pong", 1))
+ ) == Success(SuccessResponse("pong", 1))
+
+
+def test_dispatch_to_response_with_global_methods() -> None:
+ @method
+ def ping() -> Result:
+ return Ok("pong")
+
+ response = dispatch_to_response('{"jsonrpc": "2.0", "method": "ping", "id": 1}')
+ assert response == Success(SuccessResponse("pong", 1))
def test_dispatch_to_serializable() -> None:
diff --git a/tests/test_methods.py b/tests/test_methods.py
index 5649155..901b781 100644
--- a/tests/test_methods.py
+++ b/tests/test_methods.py
@@ -1,13 +1,13 @@
"""Test methods.py"""
-from jsonrpcserver.methods import global_methods, method
-# pylint: disable=missing-function-docstring
+from jsonrpcserver.methods import global_methods, method
+from jsonrpcserver.result import Ok, Result
def test_decorator() -> None:
@method
- def func() -> None:
- pass
+ def func() -> Result:
+ return Ok()
assert callable(global_methods["func"])
diff --git a/tests/test_request.py b/tests/test_request.py
index 114a34d..728cc9a 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -1,7 +1,6 @@
"""Test request.py"""
-from jsonrpcserver.request import Request
-# pylint: disable=missing-function-docstring
+from jsonrpcserver.request import Request
def test_request() -> None:
diff --git a/tests/test_response.py b/tests/test_response.py
index 29c2385..a0da382 100644
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -1,7 +1,8 @@
"""Test response.py"""
+
from unittest.mock import sentinel
-from oslash.either import Left, Right # type: ignore
+from returns.result import Failure, Success
from jsonrpcserver.response import (
ErrorResponse,
@@ -13,8 +14,6 @@
to_serializable,
)
-# pylint: disable=missing-function-docstring,invalid-name,duplicate-code
-
def test_SuccessResponse() -> None:
response = SuccessResponse(sentinel.result, sentinel.id)
@@ -65,7 +64,7 @@ def test_ServerErrorResponse() -> None:
def test_to_serializable() -> None:
- assert to_serializable(Right(SuccessResponse(sentinel.result, sentinel.id))) == {
+ assert to_serializable(Success(SuccessResponse(sentinel.result, sentinel.id))) == {
"jsonrpc": "2.0",
"result": sentinel.result,
"id": sentinel.id,
@@ -77,7 +76,7 @@ def test_to_serializable_None() -> None:
def test_to_serializable_SuccessResponse() -> None:
- assert to_serializable(Right(SuccessResponse(sentinel.result, sentinel.id))) == {
+ assert to_serializable(Success(SuccessResponse(sentinel.result, sentinel.id))) == {
"jsonrpc": "2.0",
"result": sentinel.result,
"id": sentinel.id,
@@ -86,7 +85,9 @@ def test_to_serializable_SuccessResponse() -> None:
def test_to_serializable_ErrorResponse() -> None:
assert to_serializable(
- Left(ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id))
+ Failure(
+ ErrorResponse(sentinel.code, sentinel.message, sentinel.data, sentinel.id)
+ )
) == {
"jsonrpc": "2.0",
"error": {
@@ -99,7 +100,9 @@ def test_to_serializable_ErrorResponse() -> None:
def test_to_serializable_list() -> None:
- assert to_serializable([Right(SuccessResponse(sentinel.result, sentinel.id))]) == [
+ assert to_serializable(
+ [Success(SuccessResponse(sentinel.result, sentinel.id))]
+ ) == [
{
"jsonrpc": "2.0",
"result": sentinel.result,
diff --git a/tests/test_result.py b/tests/test_result.py
index 793dbed..3cb2f9b 100644
--- a/tests/test_result.py
+++ b/tests/test_result.py
@@ -1,19 +1,18 @@
"""Test result.py"""
+
from unittest.mock import sentinel
-from oslash.either import Left, Right # type: ignore
+from returns.result import Failure, Success
from jsonrpcserver.result import (
Error,
ErrorResult,
InvalidParamsResult,
- Success,
+ Ok,
SuccessResult,
)
from jsonrpcserver.sentinels import NODATA
-# pylint: disable=missing-function-docstring,invalid-name
-
def test_SuccessResult() -> None:
assert SuccessResult(None).result is None
@@ -58,9 +57,9 @@ def test_InvalidParamsResult_with_data() -> None:
assert result.data == sentinel.data
-def test_Success() -> None:
- assert Success() == Right(SuccessResult(None))
+def test_Ok() -> None:
+ assert Ok() == Success(SuccessResult(None))
def test_Error() -> None:
- assert Error(1, "foo", None) == Left(ErrorResult(1, "foo", None))
+ assert Error(1, "foo", None) == Failure(ErrorResult(1, "foo", None))
diff --git a/tests/test_sentinels.py b/tests/test_sentinels.py
index c5c8f02..555460a 100644
--- a/tests/test_sentinels.py
+++ b/tests/test_sentinels.py
@@ -1,7 +1,6 @@
"""Test sentinels.py"""
-from jsonrpcserver.sentinels import Sentinel
-# pylint: disable=missing-function-docstring
+from jsonrpcserver.sentinels import Sentinel
def test_sentinel() -> None:
diff --git a/tests/test_server.py b/tests/test_server.py
index ad37266..94753cf 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -1,10 +1,9 @@
"""Test server.py"""
+
from unittest.mock import Mock, patch
from jsonrpcserver.server import serve
-# pylint: disable=missing-function-docstring
-
@patch("jsonrpcserver.server.HTTPServer")
def test_serve(*_: Mock) -> None:
diff --git a/tox.ini b/tox.ini
index d8673ad..bd427c4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,5 +11,5 @@ setenv = PYTHONDONTWRITEBYTECODE=1
deps =
pytest
pytest-asyncio
-commands = pytest tests
+commands = pytest tests --asyncio-mode=strict
install_command=pip install --trusted-host=pypi.org --trusted-host=files.pythonhosted.org {opts} {packages}