From 7b190a52b899ff2d1702966fb3fa135a35971e69 Mon Sep 17 00:00:00 2001 From: Beau Barker Date: Wed, 31 Jul 2024 09:57:47 +1000 Subject: [PATCH] Move documentation to Github wiki --- CHANGELOG.md | 19 +++-- README.md | 6 -- docs/Makefile | 177 ------------------------------------------- docs/async.md | 43 ----------- docs/conf.py | 62 --------------- docs/dispatch.md | 81 -------------------- docs/examples.md | 100 ------------------------ docs/faq.md | 41 ---------- docs/index.md | 23 ------ docs/installation.md | 32 -------- docs/logo.png | Bin 17627 -> 0 bytes docs/methods.md | 69 ----------------- pyproject.toml | 6 ++ 13 files changed, 15 insertions(+), 644 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/async.md delete mode 100644 docs/conf.py delete mode 100644 docs/dispatch.md delete mode 100644 docs/examples.md delete mode 100644 docs/faq.md delete mode 100644 docs/index.md delete mode 100644 docs/installation.md delete mode 100644 docs/logo.png delete mode 100644 docs/methods.md 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 d7024dca11a882b994b4ca9aee8549294e39dbe4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17627 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 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", +]