# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['trio_jsonrpc', 'trio_jsonrpc.transport']

package_data = \
{'': ['*']}

install_requires = \
['sansio-jsonrpc==0.1.0', 'trio-websocket>=0.8.0,<0.9.0']

setup_kwargs = {
    'name': 'trio-jsonrpc',
    'version': '0.2.0',
    'description': 'JSON-RPC v2.0 for Trio',
    'long_description': '# JSON-RPC v2.0 for Trio\n\n[![PyPI](https://img.shields.io/pypi/v/trio-jsonrpc.svg?style=flat-square)](https://pypi.org/project/trio-jsonrpc/)\n![Python Versions](https://img.shields.io/pypi/pyversions/trio-jsonrpc.svg?style=flat-square)\n![MIT License](https://img.shields.io/github/license/HyperionGray/trio-jsonrpc.svg?style=flat-square)\n[![Build Status](https://img.shields.io/travis/com/HyperionGray/trio-jsonrpc.svg?style=flat-square&branch=master)](https://travis-ci.com/HyperionGray/trio-jsonrpc)\n[![codecov](https://codecov.io/gh/HyperionGray/trio-jsonrpc/branch/master/graph/badge.svg)](https://codecov.io/gh/HyperionGray/trio-jsonrpc)\n\nThis project provides an implementation of [JSON-RPC v\n2.0](https://www.jsonrpc.org/specification) based on\n[sansio-jsonrpc](https://github.com/hyperiongray/sansio-jsonrpc) with all of the I/O\nimplemented using the [Trio asynchronous framework](https://trio.readthedocs.io).\n\n## Client Example\n\nThe following example shows a basic JSON-RPC client.\n\n```python\nfrom trio_jsonrpc import open_jsonrpc_ws, JsonRpcException\n\nasync def main():\n    async with open_jsonrpc_ws(\'ws://example.com/\') as client:\n        try:\n            result = await client.request(\n                method=\'open_vault_door\',\n                {\'employee\': \'Mark\', \'pin\': 1234}\n            )\n            print(\'vault open:\', result[\'vault_open\'])\n\n            await client.notify(method=\'hello_world\')\n        except JsonRpcException as jre:\n            print(\'RPC failed:\', jre)\n\ntrio.run(main)\n```\n\nThe example begins by opening a JSON-RPC connection using a WebSocket transport. The\nimplementation is designed to support multiple types of transport, but currently\nWebSocket transport is the only one that has been implemented.\n\n> Note that JSON-RPC does not contain any framing logic, i.e. a specification for how to\n> identify message boundaries within a stream. Therefore, if you want to use JSON-RPC\n> with raw TCP sockets, you either need to add your own framing logic or else use a\n> streaming JSON parser. For this reason, we have chosen to focus on WebSocket transport\n> initially, because WebSocket does include framing.\n\nThe connection is opened inside a context manager that guarantees the connection is\nready when entering the block and automatically closes the connection when leaving the\nblock.\n\nWithin the block, we call the client\'s `request(...)` method to send a JSON-RPC request.\nThis method sends the request to the server, waits for a response, and returns a result.\nIf the server indicates that an error occurred, a `JsonRpcException` will be raised\ninstead. The client multiplexes requests so that it can be use concurrently from\nmultiple tasks; responses are routed back to the appropriate task that called\n`request(...)`.\n\nThe client also has a `notify(...)` method which sends a request to the server but does\nnot expect or wait for a response.\n\n## Server Example\n\nThe following example shows a basic JSON-RPC server. The server is more DIY (do it\nyourself) than the client because a server has to incorporate several disparate\nfunctionalities:\n\n1. Setting up the transport, especially if the transport requires a handshake as\n   WebSocket does.\n2. Handling new connections to the server.\n3. Multiplexing requests on a single connection.\n4. Dispatching a request to an appropriate handler.\n5. Managing connection state over the course of multiple requests. (I.e. allowing one\n   handler to indicate that the connection is authorized, so other handlers can use that\n   authorization information to make access control decisions.)\n6. Applying pre-handler or post-handler logic to each request, for example logging\n   each request before it is dispatched.\n\nThis library cannot feasibly implement a default solution that handles the\naforementioned items in a way that satsifies every downstream project. Instead, the\nlibrary gives you the pieces you need to build a server. We will go through each piece\none at a time.\n\n```python\nfrom dataclasses import dataclass\nimport trio\nfrom trio_jsonrpc import Dispatch, JsonRpcApplicationError\nimport trio_websocket\n\n@dataclass\nclass ConnectionContext:\n    """ A sample implementation for request context. """\n    db: typing.Any = None\n    authorized_employee: str = None\n\ndispatch = Dispatch()\n```\n\nIn this first piece, we import a few things we need. We also define a\n`ConnectionContext` class. The purpose of this class is to share mutable connection\nstate between different handlers on the same connection. For example, we can have one\nhandler that authenticates a user and then sets authorization data in the connection\ncontext. Later, another handler can check that authorization data to make access control\ndecisions.\n\nYou are free to pass any object as a connection context, as long as it can be copied\nwith `copy.copy()`. A dataclass is often convenient for this purpose.\n\n```python\n@dispatch.handler\nasync def open_vault_door(employee, pin):\n    access = await dispatch.ctx.db.check_pin(employee, pin)\n    if access:\n        dispatch.ctx.authorized_employee = employee\n        return {"vault_open": True}\n    else:\n        dispatch.ctx.authorized_employee = None\n        raise JsonRpcApplicationError(code=-1, message="Not authorized.")\n\n@dispatch.handler\nasync def close_vault_door():\n    dispatch.ctx.authorized_employee = None\n    return {"vault_open": False}\n```\n\nIn this section, we define two JSON-RPC methods. Each one is annotated with\n`@dispatch.handler`, which means when we dispatch an incoming request, it will look up\nthe Python function that matches the JSON-RPC method name. The JSON-RPC parameters are\npassed as arguments to the handler function.\n\nEach handler can access the connection context as `dispatch.ctx`.\n\nAlso note that if a handler needs to signal an error, it can raise\n`JsonRpcApplicationError` (or any subclass of it). The dispatcher will automatically\nconvert the exception into a JSON-RPC error to send back to the client. If a handler\nraises any exception that is not a subclass of `JsonRpcException`—i.e. if your handler\nis buggy and raises something like `KeyError`—then a generic `JsonRpcInternalError` is\nsent back to the client, and the entire exception is logged.\n\n```\nasync def main():\n    db = ...\n    base_context = ConnectionContext(db=db)\n\n    async def responder(conn, recv_channel):\n        async for result in recv_channel:\n            if isinstance(result, JsonRpcException):\n                await conn.respond_with_error(result.get_error())\n            else:\n                await conn.respond_with_result(result)\n\n    async def connection_handler(ws_request):\n        ws = await ws_request.accept()\n        transport = WebSocketTransport(ws)\n        rpc_conn = JsonRpcConnection(transport, JsonRpcConnectionType.SERVER)\n        conn_context = copy(base_context)\n        result_send, result_recv = trio.open_memory_channel(10)\n        async with trio.open_nursery() as nursery:\n            nursery.start_soon(responder, result_recv)\n            nursery.start_soon(rpc_conn._background_task)\n            async with dispatch.connection_context(conn_context):\n                async for request in rpc_conn.iter_requests():\n                    nursery.start_soon(dispatch.handle_request, request, result_send)\n            nursery.cancel_scope.cancel()\n\n    await trio_websocket.serve_websocket(connection_handler, \'localhost\', 8000, None)\n\ntrio.run(main)\n```\n\nThe final section has a lot going on. First of all, we set up a base connection context.\nThis base object is used as a blueprint: for each new connection, the context is copied\nand then set as the context for that connection. As long as that connection stays alive,\nall handlers will share that same context object.\n\nAt the end of `main()`, the server is started by calling\n`trio_websocket.serve_websocket()`. For each new connection, the\n`connection_handler(...)` is called. This function finishes the WebSocket handshake and\nthen wraps the WebSocket connection into a JSON-RPC connection. Then it iterates over\nthe incoming requests and uses the dispatcher to handle each one.\n\nSince each JSON-RPC request is dispatched in a new task, it isn\'t possible to directly\n`await` the result of each task. Instead, we create a Trio channel and pass it into the\ndispatcher. When the handler finishes, its result will be written to this channel. We\nuse a background task called `responder(...)` to read from this channel and actually\nsend the response to the client.\n',
    'author': 'Mark E. Haase',
    'author_email': 'mehaase@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/hyperiongray/trio-jsonrpc',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
