diff --git a/CHANGELOG.md b/CHANGELOG.md index 737b24a..036af4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,19 +7,18 @@ change for async use. Breaking changes: -- Decorate async JSON-RPC methods with `@async_method` instead of `@method`. - The reason for this change is due to the typing of the decorator, async - functions return a different type (`Awaitable`) to other functions. +- 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: -- Internally, replaced the Oslash dependency with - [Returns](https://github.com/dry-python/returns). Because Oslash is not meant - for production use. -- Use `Ok` instead of `Success` when returning a response. This is to avoid - confusion with the Returns library now used internally which has it's own - `Success` class. It also matches Jsonrpcclient's `Ok` type. This is not a - breaking change, `Success` will still work for now. But use `Ok` instead. +- 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) diff --git a/README.md b/README.md index d556c04..86f5770 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,6 @@ from jsonrpcserver import method, serve, Ok, Result 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}' ``` 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 deleted file mode 100644 index e52fde8..0000000 --- a/docs/async.md +++ /dev/null @@ -1,43 +0,0 @@ -# Async - -Async dispatch is supported. - -```python -from jsonrpcserver import async_dispatch, async_method, Ok, Result - -@async_method -async def ping() -> Result: - return Ok("pong") - -await async_dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}') -``` - -Some reasons to use this: - -- Use it with an asynchronous protocol like sockets or message queues. -- `await` long-running functions from your method. -- Batch requests are dispatched concurrently. - -## Notifications - -Notifications are requests without an `id`. We should not respond to -notifications, so jsonrpcserver gives an empty string to signify there is *no -response*. - -```python ->>> await async_dispatch('{"jsonrpc": "2.0", "method": "ping"}') -'' -``` - -If the response is an empty string, don't send it. - -```python -if response := dispatch(request): - send(response) -``` - -```{note} -A synchronous protocol like HTTP requires a response no matter what, so we can -send back the empty string. However with async protocols, we have the choice of -responding or not. -``` diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 218ef00..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,62 +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 -from typing import List - -# -- 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: List[str] = [] - - -# -- 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 deleted file mode 100644 index 2278b47..0000000 --- a/docs/dispatch.md +++ /dev/null @@ -1,81 +0,0 @@ -# Dispatch - -The `dispatch` function takes a JSON-RPC request, calls the appropriate method -and gives a JSON-RPC response. - -```python ->>> dispatch('{"jsonrpc": "2.0", "method": "ping", "id": 1}') -'{"jsonrpc": "2.0", "result": "pong", "id": 1}' -``` - -[See how dispatch is used in different frameworks.](examples) - -## Optional parameters - -The `dispatch` function has some optional parameters that allow you to -customise how it works. - -### methods - -This lets you specify the methods to dispatch to. It's an alternative to using -the `@method` decorator. The value should be a dict mapping function names to -functions. - -```python -def ping(): - return Ok("pong") - -dispatch(request, methods={"ping": ping}) -``` - -Default is `global_methods`, which is an internal dict populated by the -`@method` decorator. - -### context - -If specified, this will be the first argument to all methods. - -```python -@method -def greet(context, name): - return Ok(context + " " + name) - ->>> dispatch('{"jsonrpc": "2.0", "method": "greet", "params": ["Beau"], "id": 1}', context="Hello") -'{"jsonrpc": "2.0", "result": "Hello Beau", "id": 1}' -``` - -### deserializer - -A function that parses the JSON request string. Default is `json.loads`. - -```python -dispatch(request, deserializer=ujson.loads) -``` - -### jsonrpc_validator - -A function that validates the request once the JSON string has been parsed. The -function should raise an exception (any exception) if the request doesn't match -the JSON-RPC spec (https://www.jsonrpc.org/specification). Default is -`default_jsonrpc_validator` which uses Jsonschema to validate requests against -a schema. - -To disable JSON-RPC validation, pass `jsonrpc_validator=lambda _: None`, which -will improve performance because this validation takes around half the dispatch -time. - -### args_validator - -A function that validates a request's parameters against the signature of the -Python function that will be called for it. Note this should not validate the -_values_ of the parameters, it should simply ensure the parameters match the -Python function's signature. For reference, see the `validate_args` function in -`dispatcher.py`, which is the default `args_validator`. - -### serializer - -A function that serializes the response string. Default is `json.dumps`. - -```python -dispatch(request, serializer=ujson.dumps) -``` 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 deleted file mode 100644 index f928e24..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,41 +0,0 @@ -# FAQ - -## How to disable schema validation? - -Validating requests is costly - roughly 40% of dispatching time is spent on schema validation. -If you know the incoming requests are valid, you can disable the validation for better -performance. - -```python -dispatch(request, validator=lambda _: None) -``` - -## Which HTTP status code to respond with? - -I suggest: - -```python -200 if response else 204 -``` - -If the request was a notification, `dispatch` will give you an empty string. So -since there's no http body, use status code 204 - no content. - -## How to rename a method - -Use `@method(name="new_name")`. - -Or use the dispatch function's [methods -parameter](https://www.jsonrpcserver.com/en/latest/dispatch.html#methods). - -## How to get the response in other forms? - -Instead of `dispatch`, use: - -- `dispatch_to_serializable` to get the response as a dict. -- `dispatch_to_response` to get the response as a namedtuple (either a - `SuccessResponse` or `ErrorResponse`, these are defined in - [response.py](https://github.com/explodinglabs/jsonrpcserver/blob/main/jsonrpcserver/response.py)). - -For these functions, if the request was a batch, you'll get a list of -responses. If the request was a notification, you'll get `None`. diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 12a7251..0000000 --- a/docs/index.md +++ /dev/null @@ -1,23 +0,0 @@ -```{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). -``` - -# Process incoming JSON-RPC requests in Python | jsonrpcserver Documentation - -![jsonrpcserver](/logo.png) - -Process incoming JSON-RPC requests in Python. - -```{toctree} ---- -maxdepth: 3 -caption: Contents ---- -installation -methods -dispatch -async -faq -examples -``` diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index d2f3680..0000000 --- a/docs/installation.md +++ /dev/null @@ -1,32 +0,0 @@ -# Quickstart - -Create a `server.py`: - -```python -from jsonrpcserver import method, serve, Ok - -@method -def ping(): - return Ok("pong") - -if __name__ == "__main__": - serve() -``` - -Start the server: - -```sh -$ pip install jsonrpcserver -$ python server.py - * Listening on port 5000 -``` - -Test the server: - -```sh -$ curl -X POST http://localhost:5000 -d '{"jsonrpc": "2.0", "method": "ping", "id": 1}' -{"jsonrpc": "2.0", "result": "pong", "id": 1} -``` - -`serve` is good for serving methods in development, but for production use -`dispatch` instead. diff --git a/docs/logo.png b/docs/logo.png deleted file mode 100644 index d7024dc..0000000 Binary files a/docs/logo.png and /dev/null differ diff --git a/docs/methods.md b/docs/methods.md deleted file mode 100644 index cba2406..0000000 --- a/docs/methods.md +++ /dev/null @@ -1,69 +0,0 @@ -# Methods - -Methods are functions that can be called by a JSON-RPC request. To write one, -decorate a function with `@method`: - -```python -from jsonrpcserver import method, Error, Ok, Result - -@method -def ping() -> Result: - return Ok("pong") -``` - -If you don't need to respond with any value simply `return Ok()`. - -## Responses - -Methods return either `Ok` or `Error`. These are the [JSON-RPC response -objects](https://www.jsonrpc.org/specification#response_object) (excluding the -`jsonrpc` and `id` parts). `Error` takes a code, message, and optionally -'data'. - -```python -@method -def test() -> Result: - return Error(1, "There was a problem") -``` - -```{note} -Alternatively, raise a `JsonRpcError`, which takes the same arguments as `Error`. -``` - -## Parameters - -Methods can accept arguments. - -```python -@method -def hello(name: str) -> Result: - return Ok("Hello " + name) -``` - -Testing it: - -```sh -$ curl -X POST http://localhost:5000 -d '{"jsonrpc": "2.0", "method": "hello", "params": ["Beau"], "id": 1}' -{"jsonrpc": "2.0", "result": "Hello Beau", "id": 1} -``` - -## Invalid params - -A common error response is *invalid params*. -The JSON-RPC error code for this is **-32602**. A shortcut, *InvalidParams*, is -included so you don't need to remember that. - -```python -from jsonrpcserver import dispatch, method, InvalidParams, Ok, Result - -@method -def within_range(num: int) -> Result: - if num not in range(1, 5): - return InvalidParams("Value must be 1-5") - return Ok() -``` - -This is the same as saying -```python -return Error(-32602, "Invalid params", "Value must be 1-5") -``` diff --git a/pyproject.toml b/pyproject.toml index 5e4171f..ffb3378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,3 +49,9 @@ packages = [ "jsonrpcserver" ] zip-safe = false + +[tool.pytest.ini_options] +addopts = '--doctest-glob="*.md"' +testpaths = [ + "tests", +]