[
  {
    "path": ".github/workflows/python-tox.yml",
    "content": "# This workflow will install tox and run tox for each version of Python defined in the matrix\n\n\nname: Unit tests\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.7\", \"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v2\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install tox\n      run: |\n        python -m pip install --upgrade pip\n        python -m pip install tox\n    - name: Run tox\n      run: tox -e py\n"
  },
  {
    "path": ".gitignore",
    "content": ".cache\n.tox\n.idea\n__pycache__\n*.pyc\n*egg-info\ndocs/_build\n.coverage\n.pytest_cache\n_env\nbuild/\ndist/\nhtmlcov/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\n# python:\n#   install:\n#   - requirements: docs/requirements.txt"
  },
  {
    "path": ".style.yapf",
    "content": "[style]\n# Align closing bracket with visual indentation.\nalign_closing_bracket_with_visual_indent=True\n\n# Allow dictionary keys to exist on multiple lines. For example:\n#\n#   x = {\n#       ('this is the first element of a tuple',\n#        'this is the second element of a tuple'):\n#            value,\n#   }\nallow_multiline_dictionary_keys=False\n\n# Allow lambdas to be formatted on more than one line.\nallow_multiline_lambdas=True\n\n# Allow splits before the dictionary value.\nallow_split_before_dict_value=True\n\n# Number of blank lines surrounding top-level function and class\n# definitions.\nblank_lines_around_top_level_definition=2\n\n# Insert a blank line before a class-level docstring.\nblank_line_before_class_docstring=False\n\n# Insert a blank line before a 'def' or 'class' immediately nested\n# within another 'def' or 'class'. For example:\n#\n#   class Foo:\n#                      # <------ this blank line\n#     def method():\n#       ...\nblank_line_before_nested_class_or_def=False\n\n# Do not split consecutive brackets. Only relevant when\n# dedent_closing_brackets is set. For example:\n#\n#    call_func_that_takes_a_dict(\n#        {\n#            'key1': 'value1',\n#            'key2': 'value2',\n#        }\n#    )\n#\n# would reformat to:\n#\n#    call_func_that_takes_a_dict({\n#        'key1': 'value1',\n#        'key2': 'value2',\n#    })\ncoalesce_brackets=True\n\n# The column limit.\ncolumn_limit=79\n\n# The style for continuation alignment. Possible values are:\n#\n# - SPACE: Use spaces for continuation alignment. This is default behavior.\n# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns\n#   (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation\n#   alignment.\n# - LESS: Slightly left if cannot vertically align continuation lines with\n#   indent characters.\n# - VALIGN-RIGHT: Vertically align continuation lines with indent\n#   characters. Slightly right (one more indent character) if cannot\n#   vertically align continuation lines with indent characters.\n#\n# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is\n# enabled.\ncontinuation_align_style=SPACE\n\n# Indent width used for line continuations.\ncontinuation_indent_width=4\n\n# Put closing brackets on a separate line, dedented, if the bracketed\n# expression can't fit in a single line. Applies to all kinds of brackets,\n# including function definitions and calls. For example:\n#\n#   config = {\n#       'key1': 'value1',\n#       'key2': 'value2',\n#   }        # <--- this bracket is dedented and on a separate line\n#\n#   time_series = self.remote_client.query_entity_counters(\n#       entity='dev3246.region1',\n#       key='dns.query_latency_tcp',\n#       transform=Transformation.AVERAGE(window=timedelta(seconds=60)),\n#       start_ts=now()-timedelta(days=3),\n#       end_ts=now(),\n#   )        # <--- this bracket is dedented and on a separate line\ndedent_closing_brackets=True\n\n# Place each dictionary entry onto its own line.\neach_dict_entry_on_separate_line=True\n\n# The regex for an i18n comment. The presence of this comment stops\n# reformatting of that line, because the comments are required to be\n# next to the string they translate.\ni18n_comment=\n\n# The i18n function call names. The presence of this function stops\n# reformattting on that line, because the string it has cannot be moved\n# away from the i18n comment.\ni18n_function_call=\n\n# Indent the dictionary value if it cannot fit on the same line as the\n# dictionary key. For example:\n#\n#   config = {\n#       'key1':\n#           'value1',\n#       'key2': value1 +\n#               value2,\n#   }\nindent_dictionary_value=False\n\n# The number of columns to use for indentation.\nindent_width=4\n\n# Join short lines into one line. E.g., single line 'if' statements.\njoin_multiple_lines=True\n\n# Do not include spaces around selected binary operators. For example:\n#\n#   1 + 2 * 3 - 4 / 5\n#\n# will be formatted as follows when configured with a value \"*,/\":\n#\n#   1 + 2*3 - 4/5\n#\nno_spaces_around_selected_binary_operators=set()\n\n# Use spaces around default or named assigns.\nspaces_around_default_or_named_assign=False\n\n# Use spaces around the power operator.\nspaces_around_power_operator=False\n\n# The number of spaces required before a trailing comment.\nspaces_before_comment=2\n\n# Insert a space between the ending comma and closing bracket of a list,\n# etc.\nspace_between_ending_comma_and_closing_bracket=True\n\n# Split before arguments if the argument list is terminated by a\n# comma.\nsplit_arguments_when_comma_terminated=False\n\n# Set to True to prefer splitting before '&', '|' or '^' rather than\n# after.\nsplit_before_bitwise_operator=True\n\n# Split before the closing bracket if a list or dict literal doesn't fit on\n# a single line.\nsplit_before_closing_bracket=True\n\n# Split before a dictionary or set generator (comp_for). For example, note\n# the split before the 'for':\n#\n#   foo = {\n#       variable: 'Hello world, have a nice day!'\n#       for variable in bar if variable != 42\n#   }\nsplit_before_dict_set_generator=True\n\n# Split after the opening paren which surrounds an expression if it doesn't\n# fit on a single line.\nsplit_before_expression_after_opening_paren=False\n\n# If an argument / parameter list is going to be split, then split before\n# the first argument.\nsplit_before_first_argument=True\n\n# Set to True to prefer splitting before 'and' or 'or' rather than\n# after.\nsplit_before_logical_operator=True\n\n# Split named assignments onto individual lines.\nsplit_before_named_assigns=True\n\n# Set to True to split list comprehensions and generators that have\n# non-trivial expressions and multiple clauses before each of these\n# clauses. For example:\n#\n#   result = [\n#       a_long_var + 100 for a_long_var in xrange(1000)\n#       if a_long_var % 10]\n#\n# would reformat to something like:\n#\n#   result = [\n#       a_long_var + 100\n#       for a_long_var in xrange(1000)\n#       if a_long_var % 10]\nsplit_complex_comprehension=True\n\n# The penalty for splitting right after the opening bracket.\nsplit_penalty_after_opening_bracket=30\n\n# The penalty for splitting the line after a unary operator.\nsplit_penalty_after_unary_operator=10000\n\n# The penalty for splitting right before an if expression.\nsplit_penalty_before_if_expr=0\n\n# The penalty of splitting the line around the '&', '|', and '^'\n# operators.\nsplit_penalty_bitwise_operator=300\n\n# The penalty for splitting a list comprehension or generator\n# expression.\nsplit_penalty_comprehension=80\n\n# The penalty for characters over the column limit.\nsplit_penalty_excess_character=4500\n\n# The penalty incurred by adding a line split to the unwrapped line. The\n# more line splits added the higher the penalty.\nsplit_penalty_for_added_line_split=30\n\n# The penalty of splitting a list of \"import as\" names. For example:\n#\n#   from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,\n#                                                             long_argument_2,\n#                                                             long_argument_3)\n#\n# would reformat to something like:\n#\n#   from a_very_long_or_indented_module_name_yada_yad import (\n#       long_argument_1, long_argument_2, long_argument_3)\nsplit_penalty_import_names=0\n\n# The penalty of splitting the line around the 'and' and 'or'\n# operators.\nsplit_penalty_logical_operator=300\n\n# Use the Tab character for indentation.\nuse_tabs=False\n\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"restructuredtext.confPath\": \"${workspaceFolder}/docs\"\n    \"files.watcherExclude\": {\n        \"**/.git/objects/**\": true,\n        \"**/.git/subtree-cache/**\": true,\n        \"**/node_modules/*/**\": true,\n        \".tox/**\": true\n    }\n}"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2013 Marc Brinkmann\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude LICENSE\n"
  },
  {
    "path": "README.rst",
    "content": "tinyrpc: A small and modular way of handling web-related RPC\n============================================================\n\n.. image:: https://readthedocs.org/projects/tinyrpc/badge/?version=latest\n    :target: https://tinyrpc.readthedocs.io/en/latest\n.. image:: https://github.com/mbr/tinyrpc/actions/workflows/python-tox.yml/badge.svg\n    :target: https://github.com/mbr/tinyrpc/actions/workflows/python-tox.yml\n.. image:: https://badge.fury.io/py/tinyrpc.svg\n    :target: https://pypi.org/project/tinyrpc/\n\nNote\n----\n\nTinyrpc has been revised.\n\nThe current version will support Python3 only.\nHave a look at the 0.9.x version if you need Python2 support.\nPython2 support will be dropped completely when Python2 retires,\nsomewhere in 2020.\n\nMotivation\n----------\n\nAs of this writing (in Jan 2013) there are a few jsonrpc_ libraries already out\nthere on PyPI_, most of them handling one specific use case (e.g. json via\nWSGI, using Twisted, or TCP-sockets).\n\nNone of the libraries, however, makes it easy to reuse the jsonrpc_-parsing bits\nand substitute a different transport (i.e. going from json_ via TCP_ to an\nimplementation using WebSockets_ or 0mq_).\n\nIn the end, all these libraries have their own dispatching interfaces and a\ncustom implementation of handling jsonrpc_.  Today (march 2019) that hasn't changed.\n\n``tinyrpc`` aims to do better by dividing the problem into cleanly\ninterchangeable parts that allow easy addition of new transport methods, RPC\nprotocols or dispatchers.\n\nExample:\n\nTo create a server process receiving and handling JSONRPC requests do:\n\n.. code-block:: python\n\n    import gevent\n    import gevent.pywsgi\n    import gevent.queue\n    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n    from tinyrpc.transports.wsgi import WsgiServerTransport\n    from tinyrpc.server.gevent import RPCServerGreenlets\n    from tinyrpc.dispatch import RPCDispatcher\n\n    dispatcher = RPCDispatcher()\n    transport = WsgiServerTransport(queue_class=gevent.queue.Queue)\n\n    # start wsgi server as a background-greenlet\n    wsgi_server = gevent.pywsgi.WSGIServer(('127.0.0.1', 5000), transport.handle)\n    gevent.spawn(wsgi_server.serve_forever)\n\n    rpc_server = RPCServerGreenlets(transport, JSONRPCProtocol(), dispatcher)\n\n    @dispatcher.public\n    def reverse_string(s):\n        return s[::-1]\n\n    # in the main greenlet, run our rpc_server\n    rpc_server.serve_forever()\n\nThe corresponding client code looks like:\n\n.. code-block:: python\n\n    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n    from tinyrpc.transports.http import HttpPostClientTransport\n    from tinyrpc import RPCClient\n\n    rpc_client = RPCClient(\n        JSONRPCProtocol(),\n        HttpPostClientTransport('http://127.0.0.1:5000/'))\n\n    remote_server = rpc_client.get_proxy()\n\n    # call a method called 'reverse_string' with a single string argument\n    result = remote_server.reverse_string('Hello, World!')\n\n    print(\"Server answered:\", result)\n\nDocumentation\n-------------\n\nYou'll quickly find that ``tinyrpc`` has more documentation and tests than core\ncode, hence the name. See the documentation at\n<https://tinyrpc.readthedocs.org> for more details, especially the\nStructure-section to get a birds-eye view.\n\nInstallation\n------------\n\n.. code-block:: sh\n\n   pip install tinyrpc\n\nwill install ``tinyrpc`` with its default dependencies.\n\nOptional dependencies\n---------------------\n\nDepending on the protocols and transports you want to use additional dependencies\nare required. You can instruct pip to install these dependencies by specifying\nextras to the basic install command.\n\n.. code-block:: sh\n\n   pip install tinyrpc[httpclient, wsgi]\n\nwill install ``tinyrpc`` with dependencies for the httpclient and wsgi transports.\n\nAvailable extras are:\n\n+------------+-------------------------------------------------------+\n| Option     |  Needed to use objects of class                       |\n+============+=======================================================+\n| gevent     | optional in RPCClient, required by RPCServerGreenlets |\n+------------+-------------------------------------------------------+\n| httpclient | HttpPostClientTransport, HttpWebSocketClientTransport |\n+------------+-------------------------------------------------------+\n| msgpack    | implements MSGPACKRPCProtocol                         |\n+------------+-------------------------------------------------------+\n| jsonext    | optional in JSONRPCProtocol                           |\n+------------+-------------------------------------------------------+\n| rabbitmq   | RabbitMQServerTransport, RabbitMQClientTransport      |\n+------------+-------------------------------------------------------+\n| websocket  | WSServerTransport                                     |\n+------------+-------------------------------------------------------+\n| wsgi       | WsgiServerTransport                                   |\n+------------+-------------------------------------------------------+\n| zmq        | ZmqServerTransport, ZmqClientTransport                |\n+------------+-------------------------------------------------------+\n\nNew in version 1.1.0\n--------------------\n\nTinyrpc supports RabbitMQ has transport medium.\n\nNew in version 1.0.4\n--------------------\n\nTinyrpc now supports the MSGPACK RPC protocol in addition to JSON-RPC.\n\n\n.. _jsonrpc: http://www.jsonrpc.org/\n.. _PyPI: http://pypi.python.org\n.. _json: http://www.json.org/\n.. _TCP: http://en.wikipedia.org/wiki/Transmission_Control_Protocol\n.. _WebSockets: http://en.wikipedia.org/wiki/WebSocket\n.. _0mq: http://www.zeromq.org/\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\t-rm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/tinyrpc.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/tinyrpc.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/tinyrpc\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tinyrpc\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n"
  },
  {
    "path": "docs/_static/uml.xmi",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XMI verified=\"false\" xmi.version=\"1.2\" timestamp=\"2019-01-19T17:13:06\" xmlns:UML=\"http://schema.omg.org/spec/UML/1.3\">\n <XMI.header>\n  <XMI.documentation>\n   <XMI.exporter>umbrello uml modeller http://umbrello.kde.org</XMI.exporter>\n   <XMI.exporterVersion>1.6.9</XMI.exporterVersion>\n   <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>\n  </XMI.documentation>\n  <XMI.metamodel xmi.version=\"1.3\" href=\"UML.xml\" xmi.name=\"UML\"/>\n </XMI.header>\n <XMI.content>\n  <UML:Model isSpecification=\"false\" isAbstract=\"false\" isLeaf=\"false\" xmi.id=\"m1\" isRoot=\"false\" name=\"UML Model\">\n   <UML:Namespace.ownedElement>\n    <UML:Stereotype visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"folder\" name=\"folder\"/>\n    <UML:Stereotype visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"datatype\" name=\"datatype\"/>\n    <UML:Model stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Logical View\" name=\"Logical View\">\n     <UML:Namespace.ownedElement>\n      <UML:Package stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Datatypes\" name=\"Datatypes\">\n       <UML:Namespace.ownedElement>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"OjpkERcLwQ38\" name=\"array\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"vcBNGYol65kB\" name=\"bool\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"msV7kE98ywqA\" name=\"tuple\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"NQVJqXRl9b6O\" name=\"float\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"9v9FyrOkiuqS\" name=\"int\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"6za4uOEHoiZd\" name=\"long\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"F0MMZTkiMrvK\" name=\"dict\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"hK2PCsrIqPLk\" name=\"object\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"lZ1XFYAUXXp6\" name=\"set\"/>\n        <UML:DataType stereotype=\"datatype\" visibility=\"public\" isSpecification=\"false\" namespace=\"Datatypes\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"yXpKPic62eTE\" name=\"string\"/>\n       </UML:Namespace.ownedElement>\n      </UML:Package>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"d85AfuEbS1vp\" name=\"RPCClient\">\n       <UML:Classifier.feature>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"EPqcqu9AWUkQ\" type=\"95NxQdnjLPTQ\" name=\"protocol\"/>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"suWOOGpkneAV\" type=\"FM5KzcTsgUmV\" name=\"transport\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Qyx1eBYVYGsw\" name=\"call\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"laB8nntFEPTJ\" name=\"call_all\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"2ClpW2nwykwc\" name=\"get_proxy\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"vU6TiwNSWZaH\" name=\"batch_all\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"gRGItEb9GKnL\" name=\"RPCDispatcher\">\n       <UML:Classifier.feature>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"2ILlIYmTV1NY\" type=\"ZRYc8HBwIjgt\" name=\"validator\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"yYQFVDA8Z305\" name=\"public\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"XJE2KtsHI6VS\" name=\"add_subdispatch\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"m5IKJCjFi7SD\" name=\"add_method\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"KcJrfJwBc8Dw\" name=\"get_method\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"13BKAgkuj2E2\" name=\"register_instance\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"CxFu2h5MIODv\" name=\"dispatch\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"f86FGHCGJLYR\" name=\"validate_parameters\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Jkej48LjYa1P\" name=\"ServerTransport\">\n       <UML:Classifier.feature>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"v43upCaD8gak\" name=\"receive_message\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"9n2pFEijk20K\" name=\"send_reply\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"95NxQdnjLPTQ\" name=\"RPCProtocol\">\n       <UML:Classifier.feature>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"EsgVEEwEWGzn\" type=\"vcBNGYol65kB\" name=\"supports_out_of_order\"/>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"ojGHjbnQ2gNq\" type=\"vcBNGYol65kB\" name=\"raises_errors\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"kFHuCzMRs3w4\" name=\"create_request\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"FrIk7HXtznAi\" name=\"parse_request\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Oy8PcTDKiUEv\" name=\"parse_reply\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"TYKJZENn5fXA\" name=\"raise_error\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Dependency visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" supplier=\"I2lf5iGgqMoT\" xmi.id=\"MZXhsJS1Onwp\" client=\"gRGItEb9GKnL\" name=\"\"/>\n      <UML:Association visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" xmi.id=\"RcpqOlfnT9XZ\" name=\"\">\n       <UML:Association.connection>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"9eDtrtxB3CDC\" type=\"Jkej48LjYa1P\" name=\"\" aggregation=\"composite\"/>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"cpgXxqNPTADr\" type=\"I2lf5iGgqMoT\" name=\"\" aggregation=\"none\"/>\n       </UML:Association.connection>\n      </UML:Association>\n      <UML:Association visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" xmi.id=\"vqcMz7w6l7zd\" name=\"\">\n       <UML:Association.connection>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"false\" isSpecification=\"false\" xmi.id=\"0vo27zEinuDk\" type=\"Jkej48LjYa1P\" name=\"\" aggregation=\"none\"/>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"la7CkK1eAdy8\" type=\"d85AfuEbS1vp\" name=\"\" aggregation=\"none\"/>\n       </UML:Association.connection>\n      </UML:Association>\n      <UML:Association visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" xmi.id=\"3db2Fj9wB5IA\" name=\"\">\n       <UML:Association.connection>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"false\" isSpecification=\"false\" xmi.id=\"y1trlVLTC706\" type=\"gRGItEb9GKnL\" name=\"\" aggregation=\"none\"/>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"dOS1cL4dwdf9\" type=\"I2lf5iGgqMoT\" name=\"\" aggregation=\"none\"/>\n       </UML:Association.connection>\n      </UML:Association>\n      <UML:Package visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"MjZAD10HkUF5\" name=\"X\">\n       <UML:Namespace.ownedElement/>\n      </UML:Package>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"I2lf5iGgqMoT\" name=\"RPCServer\">\n       <UML:Classifier.feature>\n        <UML:Attribute visibility=\"public\" isSpecification=\"false\" xmi.id=\"q3XlaI1x1Y5D\" type=\"ZRYc8HBwIjgt\" name=\"trace\"/>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"KSVZrysrAdfe\" type=\"Jkej48LjYa1P\" name=\"transport\"/>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"dSYZAUA3wGWf\" type=\"95NxQdnjLPTQ\" name=\"protocol\"/>\n        <UML:Attribute visibility=\"private\" isSpecification=\"false\" xmi.id=\"A326JWjTOo8n\" type=\"gRGItEb9GKnL\" name=\"dispatcher\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"UpEqKrwKoUUp\" name=\"serve_forever\"/>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"3iU0NBVNudIG\" name=\"receive_one_message\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"a3SIWhN9Qofn\" name=\"trace\"/>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"ZRYc8HBwIjgt\" name=\"callable\"/>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"BbKoeiZzFEaT\" name=\"str\"/>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"hRszn4uTCziS\" name=\"function\"/>\n      <UML:Class visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"FM5KzcTsgUmV\" name=\"ClientTransport\">\n       <UML:Classifier.feature>\n        <UML:Operation visibility=\"public\" isSpecification=\"false\" isQuery=\"false\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"kfzVgxnvJsDJ\" name=\"send_message\"/>\n       </UML:Classifier.feature>\n      </UML:Class>\n      <UML:Package visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"LeMauwpQ19ve\" name=\"Transports\">\n       <UML:Namespace.ownedElement/>\n      </UML:Package>\n      <UML:Association visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" xmi.id=\"z1nOqt9ItgrK\" name=\"\">\n       <UML:Association.connection>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"UL8SZP9EnEiq\" type=\"Jkej48LjYa1P\" name=\"\" aggregation=\"composite\"/>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"DB4FeMYeNJ0D\" type=\"I2lf5iGgqMoT\" name=\"\" aggregation=\"none\"/>\n       </UML:Association.connection>\n      </UML:Association>\n      <UML:Association visibility=\"public\" isSpecification=\"false\" namespace=\"Logical View\" xmi.id=\"A9T3qJNIcTkC\" name=\"\">\n       <UML:Association.connection>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"ssOd67UHGWWt\" type=\"I2lf5iGgqMoT\" name=\"\" aggregation=\"composite\"/>\n        <UML:AssociationEnd changeability=\"changeable\" visibility=\"public\" isNavigable=\"true\" isSpecification=\"false\" xmi.id=\"s3XFMVRXeuaS\" type=\"Jkej48LjYa1P\" name=\"\" aggregation=\"none\"/>\n       </UML:Association.connection>\n      </UML:Association>\n     </UML:Namespace.ownedElement>\n     <XMI.extension xmi.extender=\"umbrello\">\n      <diagrams>\n       <diagram showopsig=\"1\" linecolor=\"#ff0000\" snapx=\"25\" showattribassocs=\"1\" snapy=\"25\" linewidth=\"0\" showattsig=\"1\" textcolor=\"#000000\" isopen=\"1\" showpackage=\"1\" showpubliconly=\"1\" showstereotype=\"1\" name=\"class diagram\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" canvasheight=\"1074.705882352941\" canvaswidth=\"2441.470588235296\" localid=\"-1\" snapcsgrid=\"0\" showgrid=\"0\" showops=\"1\" griddotcolor=\"#d3d3d3\" backgroundcolor=\"#ffffff\" usefillcolor=\"1\" fillcolor=\"#ffff00\" zoom=\"85\" xmi.id=\"WYGz5jAMLtj4\" documentation=\"\" showscope=\"1\" snapgrid=\"0\" showatts=\"1\" type=\"1\">\n        <widgets>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-1171.470588235297\" showattsigs=\"600\" y=\"-442.882352941176\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"kXtk9MxeeSzJ\" width=\"86\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"d85AfuEbS1vp\" showscope=\"1\" height=\"91\" showopsigs=\"600\"/>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-733.4117647058823\" showattsigs=\"600\" y=\"-441.9411764705877\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"E24g4xTmSKWc\" width=\"158\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"I2lf5iGgqMoT\" showscope=\"1\" height=\"91\" showopsigs=\"600\"/>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-970.2352941176472\" showattsigs=\"600\" y=\"-293.6470588235293\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"33zrPnJcqNGs\" width=\"148\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"gRGItEb9GKnL\" showscope=\"1\" height=\"117\" showopsigs=\"600\"/>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-709.0588235294114\" showattsigs=\"601\" showstereotype=\"1\" y=\"-242.2941176470588\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"obZ2vKMwNLol\" width=\"131\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"Jkej48LjYa1P\" showscope=\"1\" height=\"45\" showopsigs=\"601\"/>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-975.7647058823527\" showattsigs=\"600\" y=\"-441.9411764705877\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"Xm75M6TAh2Db\" width=\"148\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"95NxQdnjLPTQ\" showscope=\"1\" height=\"91\" showopsigs=\"600\"/>\n         <classwidget linecolor=\"none\" usesdiagramfillcolor=\"0\" linewidth=\"0\" showoperations=\"1\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" showpubliconly=\"0\" showpackage=\"0\" x=\"-1183.529411764708\" showattsigs=\"600\" y=\"-230.5882352941177\" showattributes=\"1\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" localid=\"gKgo0FW72WxI\" width=\"118\" isinstance=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" xmi.id=\"FM5KzcTsgUmV\" showscope=\"1\" height=\"32\" showopsigs=\"600\"/>\n        </widgets>\n        <messages/>\n        <associations>\n         <assocwidget linecolor=\"#ff0000\" indexa=\"1\" usesdiagramfillcolor=\"0\" widgetbid=\"95NxQdnjLPTQ\" indexb=\"1\" linewidth=\"0\" seqnum=\"\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" totalcounta=\"2\" totalcountb=\"2\" widgetaid=\"d85AfuEbS1vp\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" visibilityA=\"1\" visibilityB=\"1\" usefillcolor=\"1\" fillcolor=\"#ffff00\" changeabilityA=\"900\" xmi.id=\"EPqcqu9AWUkQ\" changeabilityB=\"900\" type=\"510\">\n          <linepath layout=\"Polyline\">\n           <startpoint startx=\"-1085.470588235297\" starty=\"-393.7058823529407\"/>\n           <endpoint endx=\"-975.7647058823527\" endy=\"-393.7058823529407\"/>\n          </linepath>\n         </assocwidget>\n         <assocwidget linecolor=\"#ff0000\" indexa=\"1\" usesdiagramfillcolor=\"0\" widgetbid=\"FM5KzcTsgUmV\" indexb=\"1\" linewidth=\"0\" seqnum=\"\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" totalcounta=\"2\" totalcountb=\"2\" widgetaid=\"d85AfuEbS1vp\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" visibilityA=\"1\" visibilityB=\"1\" usefillcolor=\"1\" fillcolor=\"#ffff00\" changeabilityA=\"900\" xmi.id=\"suWOOGpkneAV\" changeabilityB=\"900\" type=\"510\">\n          <linepath layout=\"Polyline\">\n           <startpoint startx=\"-1129.411764705885\" starty=\"-351.882352941176\"/>\n           <endpoint endx=\"-1129.411764705885\" endy=\"-230.5882352941177\"/>\n          </linepath>\n         </assocwidget>\n         <assocwidget linecolor=\"#ff0000\" indexa=\"1\" usesdiagramfillcolor=\"0\" widgetbid=\"95NxQdnjLPTQ\" indexb=\"1\" linewidth=\"0\" seqnum=\"\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" totalcounta=\"2\" totalcountb=\"2\" widgetaid=\"I2lf5iGgqMoT\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" visibilityA=\"1\" visibilityB=\"1\" usefillcolor=\"1\" fillcolor=\"#ffff00\" changeabilityA=\"900\" xmi.id=\"dSYZAUA3wGWf\" changeabilityB=\"900\" type=\"510\">\n          <linepath layout=\"Polyline\">\n           <startpoint startx=\"-733.4117647058823\" starty=\"-393.7058823529407\"/>\n           <endpoint endx=\"-827.7647058823527\" endy=\"-393.7058823529407\"/>\n          </linepath>\n         </assocwidget>\n         <assocwidget linecolor=\"#ff0000\" indexa=\"1\" usesdiagramfillcolor=\"0\" widgetbid=\"gRGItEb9GKnL\" indexb=\"1\" linewidth=\"0\" seqnum=\"\" textcolor=\"#000000\" usesdiagramusefillcolor=\"0\" totalcounta=\"2\" totalcountb=\"2\" widgetaid=\"I2lf5iGgqMoT\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" visibilityA=\"1\" visibilityB=\"1\" usefillcolor=\"1\" fillcolor=\"#ffff00\" changeabilityA=\"900\" xmi.id=\"A326JWjTOo8n\" changeabilityB=\"900\" type=\"510\">\n          <linepath layout=\"Polyline\">\n           <startpoint startx=\"-733.4117647058823\" starty=\"-350.9411764705877\"/>\n           <endpoint endx=\"-822.2352941176472\" endy=\"-293.6470588235293\"/>\n          </linepath>\n         </assocwidget>\n         <assocwidget indexa=\"1\" indexb=\"1\" usesdiagramusefillcolor=\"0\" widgetaid=\"I2lf5iGgqMoT\" usesdiagramfillcolor=\"0\" fillcolor=\"#ffff00\" linecolor=\"none\" seqnum=\"\" totalcounta=\"2\" xmi.id=\"A9T3qJNIcTkC\" widgetbid=\"Jkej48LjYa1P\" totalcountb=\"2\" type=\"510\" textcolor=\"none\" usefillcolor=\"1\" linewidth=\"none\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\">\n          <linepath layout=\"Polyline\">\n           <startpoint startx=\"-652.5882352941173\" starty=\"-350.9411764705877\"/>\n           <endpoint endx=\"-652.5882352941173\" endy=\"-242.2941176470588\"/>\n          </linepath>\n         </assocwidget>\n        </associations>\n       </diagram>\n       <diagram showopsig=\"1\" linecolor=\"#ff0000\" snapx=\"25\" showattribassocs=\"1\" snapy=\"25\" linewidth=\"0\" showattsig=\"1\" textcolor=\"#000000\" isopen=\"1\" showpackage=\"1\" showpubliconly=\"1\" showstereotype=\"1\" name=\"collaboration diagram\" font=\"DejaVu Sans,9,-1,5,50,0,0,0,0,0\" canvasheight=\"0\" canvaswidth=\"0\" localid=\"-1\" snapcsgrid=\"0\" showgrid=\"0\" showops=\"1\" griddotcolor=\"#d3d3d3\" backgroundcolor=\"#ffffff\" autoincrementsequence=\"0\" usefillcolor=\"1\" fillcolor=\"#ffff00\" zoom=\"113\" xmi.id=\"XKVLhq5SN4zd\" documentation=\"\" showscope=\"1\" snapgrid=\"0\" showatts=\"1\" type=\"4\">\n        <widgets/>\n        <messages/>\n        <associations/>\n       </diagram>\n      </diagrams>\n     </XMI.extension>\n    </UML:Model>\n    <UML:Model stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Use Case View\" name=\"Use Case View\">\n     <UML:Namespace.ownedElement/>\n    </UML:Model>\n    <UML:Model stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Component View\" name=\"Component View\">\n     <UML:Namespace.ownedElement/>\n    </UML:Model>\n    <UML:Model stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Deployment View\" name=\"Deployment View\">\n     <UML:Namespace.ownedElement/>\n    </UML:Model>\n    <UML:Model stereotype=\"folder\" visibility=\"public\" isSpecification=\"false\" namespace=\"m1\" isAbstract=\"false\" isLeaf=\"false\" isRoot=\"false\" xmi.id=\"Entity Relationship Model\" name=\"Entity Relationship Model\">\n     <UML:Namespace.ownedElement/>\n    </UML:Model>\n   </UML:Namespace.ownedElement>\n  </UML:Model>\n </XMI.content>\n <XMI.extensions xmi.extender=\"umbrello\">\n  <docsettings viewid=\"WYGz5jAMLtj4\" uniqueid=\"s3XFMVRXeuaS\" documentation=\"\"/>\n  <listview>\n   <listitem open=\"1\" type=\"800\" id=\"Views\">\n    <listitem open=\"1\" type=\"821\" id=\"Component View\"/>\n    <listitem open=\"1\" type=\"827\" id=\"Deployment View\"/>\n    <listitem open=\"1\" type=\"836\" id=\"Entity Relationship Model\"/>\n    <listitem open=\"1\" type=\"801\" id=\"Logical View\">\n     <listitem open=\"1\" type=\"813\" id=\"ZRYc8HBwIjgt\"/>\n     <listitem open=\"0\" type=\"807\" id=\"WYGz5jAMLtj4\" label=\"class diagram\"/>\n     <listitem open=\"1\" type=\"813\" id=\"FM5KzcTsgUmV\">\n      <listitem open=\"0\" type=\"815\" id=\"kfzVgxnvJsDJ\"/>\n     </listitem>\n     <listitem open=\"0\" type=\"806\" id=\"XKVLhq5SN4zd\" label=\"collaboration diagram\"/>\n     <listitem open=\"0\" type=\"830\" id=\"Datatypes\">\n      <listitem open=\"1\" type=\"829\" id=\"OjpkERcLwQ38\"/>\n      <listitem open=\"1\" type=\"829\" id=\"vcBNGYol65kB\"/>\n      <listitem open=\"1\" type=\"829\" id=\"F0MMZTkiMrvK\"/>\n      <listitem open=\"1\" type=\"829\" id=\"NQVJqXRl9b6O\"/>\n      <listitem open=\"1\" type=\"829\" id=\"9v9FyrOkiuqS\"/>\n      <listitem open=\"1\" type=\"829\" id=\"6za4uOEHoiZd\"/>\n      <listitem open=\"1\" type=\"829\" id=\"hK2PCsrIqPLk\"/>\n      <listitem open=\"1\" type=\"829\" id=\"lZ1XFYAUXXp6\"/>\n      <listitem open=\"1\" type=\"829\" id=\"yXpKPic62eTE\"/>\n      <listitem open=\"1\" type=\"829\" id=\"msV7kE98ywqA\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"813\" id=\"hRszn4uTCziS\"/>\n     <listitem open=\"1\" type=\"817\" id=\"d85AfuEbS1vp\">\n      <listitem open=\"0\" type=\"815\" id=\"vU6TiwNSWZaH\"/>\n      <listitem open=\"0\" type=\"815\" id=\"laB8nntFEPTJ\"/>\n      <listitem open=\"0\" type=\"815\" id=\"Qyx1eBYVYGsw\"/>\n      <listitem open=\"0\" type=\"815\" id=\"2ClpW2nwykwc\"/>\n      <listitem open=\"0\" type=\"814\" id=\"EPqcqu9AWUkQ\"/>\n      <listitem open=\"0\" type=\"814\" id=\"suWOOGpkneAV\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"817\" id=\"gRGItEb9GKnL\">\n      <listitem open=\"0\" type=\"815\" id=\"m5IKJCjFi7SD\"/>\n      <listitem open=\"0\" type=\"815\" id=\"XJE2KtsHI6VS\"/>\n      <listitem open=\"0\" type=\"815\" id=\"CxFu2h5MIODv\"/>\n      <listitem open=\"0\" type=\"815\" id=\"KcJrfJwBc8Dw\"/>\n      <listitem open=\"0\" type=\"815\" id=\"yYQFVDA8Z305\"/>\n      <listitem open=\"0\" type=\"815\" id=\"13BKAgkuj2E2\"/>\n      <listitem open=\"0\" type=\"815\" id=\"f86FGHCGJLYR\"/>\n      <listitem open=\"0\" type=\"814\" id=\"2ILlIYmTV1NY\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"817\" id=\"95NxQdnjLPTQ\">\n      <listitem open=\"0\" type=\"815\" id=\"kFHuCzMRs3w4\"/>\n      <listitem open=\"0\" type=\"815\" id=\"Oy8PcTDKiUEv\"/>\n      <listitem open=\"0\" type=\"815\" id=\"FrIk7HXtznAi\"/>\n      <listitem open=\"0\" type=\"815\" id=\"TYKJZENn5fXA\"/>\n      <listitem open=\"0\" type=\"814\" id=\"ojGHjbnQ2gNq\"/>\n      <listitem open=\"0\" type=\"814\" id=\"EsgVEEwEWGzn\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"817\" id=\"I2lf5iGgqMoT\">\n      <listitem open=\"0\" type=\"814\" id=\"A326JWjTOo8n\"/>\n      <listitem open=\"0\" type=\"814\" id=\"dSYZAUA3wGWf\"/>\n      <listitem open=\"0\" type=\"815\" id=\"3iU0NBVNudIG\"/>\n      <listitem open=\"0\" type=\"815\" id=\"UpEqKrwKoUUp\"/>\n      <listitem open=\"0\" type=\"814\" id=\"q3XlaI1x1Y5D\"/>\n      <listitem open=\"0\" type=\"814\" id=\"KSVZrysrAdfe\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"817\" id=\"Jkej48LjYa1P\">\n      <listitem open=\"0\" type=\"815\" id=\"v43upCaD8gak\"/>\n      <listitem open=\"0\" type=\"815\" id=\"9n2pFEijk20K\"/>\n     </listitem>\n     <listitem open=\"1\" type=\"813\" id=\"BbKoeiZzFEaT\"/>\n     <listitem open=\"1\" type=\"813\" id=\"a3SIWhN9Qofn\"/>\n     <listitem open=\"1\" type=\"818\" id=\"LeMauwpQ19ve\"/>\n     <listitem open=\"1\" type=\"818\" id=\"MjZAD10HkUF5\"/>\n    </listitem>\n    <listitem open=\"1\" type=\"802\" id=\"Use Case View\"/>\n   </listitem>\n  </listview>\n  <codegeneration>\n   <codegenerator language=\"Python\"/>\n  </codegeneration>\n </XMI.extensions>\n</XMI>\n"
  },
  {
    "path": "docs/client.rst",
    "content": "RPC Client\n==========\n\n:py:class:`~tinyrpc.client.RPCClient` instances are high-level handlers for\nmaking remote procedure calls to servers. Other than\n:py:class:`~tinyrpc.client.RPCProxy` objects, they are what most user\napplications interact with.\n\nClients needs to be instantiated with a protocol and a transport to function.\nProxies are syntactic sugar for using clients.\n\n.. autoclass:: tinyrpc.client.RPCClient\n    :members:\n    :show-inheritance:\n    :noindex:\n\n.. autoclass:: tinyrpc.client.RPCProxy\n    :members:\n    :show-inheritance:\n    :noindex:\n\n.. automodule:: tinyrpc.client\n    :members: RPCCall, RPCCallTo\n    :show-inheritance:\n    :noindex:\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# tinyrpc documentation build configuration file, created by\n# sphinx-quickstart on Wed Jan 23 19:15:13 2013.\n#\n# This file is execfile()d with the current directory set to its containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys, os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\nsys.path.insert(0, os.path.abspath('..'))\n\n# -- General configuration -----------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be extensions\n# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'tinyrpc'\ncopyright = u'2013 - 2023, Marc Brinkmann, Leo Noordergraaf'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '1.1'\n# The full version, including alpha/beta/rc tags.\nrelease = '1.1.7'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n\n# -- Options for HTML output ---------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#html_theme = 'default'\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\nhtml_theme_options = {\n    'prev_next_buttons_location': 'both',\n    'collapse_navigation': True,\n    'sticky_navigation': True,\n    'navigation_depth': 4\n}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'tinyrpcdoc'\n\n\n# -- Options for LaTeX output --------------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title, author, documentclass [howto/manual]).\nlatex_documents = [\n  ('index', 'tinyrpc.tex', u'tinyrpc Documentation',\n   u'Marc Brinkmann, Leo Noordergraaf', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output --------------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'tinyrpc', u'tinyrpc Documentation',\n     [u'Marc Brinkmann, Leo Noordergraaf'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output ------------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'tinyrpc', u'tinyrpc Documentation',\n   u'Marc Brinkmann, Leo Noordergraaf', 'tinyrpc', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\n    'https://docs.python.org/3/': None,\n    'https://pyzmq.readthedocs.io/en/latest/': None,\n    'http://docs.python-requests.org/en/latest/': None,\n    'http://werkzeug.pocoo.org/docs/': None,\n    'http://www.gevent.org/': None,\n}\n\nautoclass_content = \"both\"\n\nautodoc_mock_imports = [\"msgpack\", \"zmq\", \"werkzeug\", \"pika\", \"geventwebsocket\"]\n"
  },
  {
    "path": "docs/dispatch.rst",
    "content": "Dispatching\n===========\n\nDispatching in ``tinyrpc`` is very similiar to url-routing in web frameworks.\nFunctions are registered with a specific name and made public, i.e. callable,\nto remote clients.\n\nExamples\n--------\n\nExposing a few functions:\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n   from tinyrpc.dispatch import RPCDispatcher\n\n   dispatch = RPCDispatcher()\n\n   @dispatch.public\n   def foo():\n       # ...\n\n   @dispatch.public\n   def bar(arg):\n       # ...\n\n   # later on, assuming we know we want to call foo(*args, **kwargs):\n\n   f = dispatch.get_method('foo')\n   f(*args, **kwargs)\n\nUsing prefixes and instance registration:\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n   from tinyrpc.dispatch import public\n\n   class SomeWebsite(object):\n       def __init__(self, ...):\n           # note: this method will not be exposed\n\n       def secret(self):\n           # another unexposed method\n\n       @public\n       def get_user_info(self, user):\n           # ...\n\n       # using a different name\n       @public('get_user_comment')\n       def get_comment(self, comment_id):\n           # ...\n\nThe code above declares an RPC interface for ``SomeWebsite`` objects,\nconsisting of two visible methods: ``get_user_info(user)`` and\n``get_user_comment(comment_id)``.\n\nThese can be used with a dispatcher now:\n\n.. code-block:: python\n\n   def hello():\n       # ...\n\n   website1 = SomeWebsite(...)\n   website2 = SomeWebsite(...)\n\n   from tinyrpc.dispatch import RPCDispatcher\n\n   dispatcher = RPCDispatcher()\n\n   # directly register version method\n   @dispatcher.public\n   def version():\n       # ...\n\n   # add earlier defined method\n   dispatcher.add_method(hello)\n\n   # register the two website instances\n   dispatcher.register_instance(website1, 'sitea.')\n   dispatcher.register_instance(website2, 'siteb.')\n\nIn the example above, the :py:class:`~tinyrpc.dispatch.RPCDispatcher` now knows\na total of six registered methods: ``version``, ``hello``,\n``sitea.get_user_info``, ``sitea.get_user_comment``, ``siteb.get_user_info``,\n``siteb.get_user_comment``.\n\nAutomatic dispatching\n~~~~~~~~~~~~~~~~~~~~~\n\nWhen writing a server application, a higher level dispatching method is\navailable with :py:func:`~tinyrpc.dispatch.RPCDispatcher.dispatch`:\n\n.. code-block:: python\n\n   from tinyrpc.dispatch import RPCDispatcher\n\n   dispatcher = RPCDispatcher()\n\n   # register methods like in the examples above\n   # ...\n   # now assumes that a valid RPCRequest has been obtained, as `request`\n\n   response = dispatcher.dispatch(request)\n\n   # response can be directly processed back to the client, all Exceptions have\n   # been handled already\n\nClass, static and unbound method dispatching\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAlthough you will only rarely use these method types they *do* work and here we show you how.\n\nClass methods do not have `self` as the initial parameter but rather a reference to their class.\nYou may want to use such methods to instantiate class instances.\n\n.. code-block:: python\n\n    class ShowClassMethod:\n        @classmethod\n        @public\n        def func(cls, a, b):\n            return a-b\n\nNote the ordering of the decorators.\nOrdering them differently will not work.\nYou call dispatch to the `func` method just as you would dispatch to any other method.\n\nStatic methods have neither a class nor instance reference as first parameter:\n\n.. code-block:: python\n\n    class ShowStaticMethod:\n        @staticmethod\n        @public\n        def func(a, b):\n            return a-b\n\nAgain the ordering of the decorators is critical and you dispatch them as any other method.\n\nFinally it is possible to dispatch to unbound methods but I strongly advise against it.\nIf you really want to do that see the tests to learn how. Everyone else should use static methods instead.\n\nAPI reference\n-------------\n\n.. autoclass:: tinyrpc.dispatch.RPCDispatcher\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nClasses can be made to support an RPC interface without coupling it to a\ndispatcher using a decorator:\n\n.. autofunction:: tinyrpc.dispatch.public\n"
  },
  {
    "path": "docs/examples.rst",
    "content": "Quickstart examples\n===================\n\nThe source contains all of these examples in a working fashion in the examples\nsubfolder.\n\nHTTP based\n----------\n\nA client making JSONRPC calls via HTTP (this requires :py:mod:`requests` to be\ninstalled):\n\n.. code-block:: python\n\n   from tinyrpc import RPCClient\n   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n   from tinyrpc.transports.http import HttpPostClientTransport\n\n   rpc_client = RPCClient(\n       JSONRPCProtocol(),\n       HttpPostClientTransport('http://localhost')\n   )\n\n   str_server = rpc_client.get_proxy()\n\n   # ...\n\n   # call a method called 'reverse_string' with a single string argument\n   result = str_server.reverse_string('Simple is better.')\n\n   print(\"Server answered:\", result)\n\nThis call can be answered by a server implemented as follows:\n\n.. code-block:: python\n\n   import gevent\n   import gevent.pywsgi\n   import gevent.queue\n\n   from tinyrpc.server.gevent import RPCServerGreenlets\n   from tinyrpc.dispatch import RPCDispatcher\n   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n   from tinyrpc.transports.wsgi import WsgiServerTransport\n\n   dispatcher = RPCDispatcher()\n   transport = WsgiServerTransport(queue_class=gevent.queue.Queue)\n\n   # start wsgi server as a background-greenlet\n   wsgi_server = gevent.pywsgi.WSGIServer(('127.0.0.1', 80), transport.handle)\n   gevent.spawn(wsgi_server.serve_forever)\n\n   rpc_server = RPCServerGreenlets(\n       transport,\n       JSONRPCProtocol(),\n       dispatcher\n   )\n\n   @dispatcher.public\n   def reverse_string(s):\n       return s[::-1]\n\n   # in the main greenlet, run our rpc_server\n   rpc_server.serve_forever()\n\n\n0mq\n---\n\nAn example using :py:mod:`zmq` is very similiar, differing only in the\ninstantiation of the transport:\n\n.. code-block:: python\n\n  import zmq\n\n  from tinyrpc import RPCClient\n  from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n  from tinyrpc.transports.zmq import ZmqClientTransport\n\n  ctx = zmq.Context()\n\n  rpc_client = RPCClient(\n      JSONRPCProtocol(),\n      ZmqClientTransport.create(ctx, 'tcp://127.0.0.1:5001')\n  )\n\n  str_server = rpc_client.get_proxy()\n\n  # call a method called 'reverse_string' with a single string argument\n  result = str_server.reverse_string('Hello, World!')\n\n  print(\"Server answered:\", result)\n\n\nMatching server:\n\n.. code-block:: python\n\n   import zmq\n\n   from tinyrpc.server import RPCServer\n   from tinyrpc.dispatch import RPCDispatcher\n   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n   from tinyrpc.transports.zmq import ZmqServerTransport\n\n   ctx = zmq.Context()\n   dispatcher = RPCDispatcher()\n   transport = ZmqServerTransport.create(ctx, 'tcp://127.0.0.1:5001')\n\n   rpc_server = RPCServer(\n       transport,\n       JSONRPCProtocol(),\n       dispatcher\n   )\n\n   @dispatcher.public\n   def reverse_string(s):\n       return s[::-1]\n\n   rpc_server.serve_forever()\n\n\n\nFurther examples\n----------------\n\nIn :doc:`protocols`, you can find client and server examples on how\nto use just the protocol parsing parts of ``tinyrpc``.\n\nThe :py:class:`~tinyrpc.dispatch.RPCDispatcher` should be useful on its own (or\nat least easily replaced with one of your choosing), see :doc:`dispatch` for\ndetails.\n\n\n\n"
  },
  {
    "path": "docs/exceptions.rst",
    "content": "The Exceptions hierarchy\n========================\n\nAll exceptions are rooted in the :py:class:`Exception` class.\nThe :py:class:`~tinyrpc.exc.RPCError` class derives from it and forms the basis of all tinyrpc exceptions.\n\nAbstract exceptions\n-------------------\nThese exceptions, most of them will be overridden, define errors concerning the transport and structure\nof messages.\n\n.. autoclass:: tinyrpc.exc.RPCError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.BadRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.exc.BadReplyError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.InvalidRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.InvalidReplyError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.MethodNotFoundError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.InvalidParamsError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.ServerError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nProtocol exceptions\n-------------------\nEach protocol provides its own concrete implementations of these exceptions.\n\nJSON-RPC\n^^^^^^^^\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCParseError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInternalError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCServerError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\nThis last exception is a client side exception designed to represent the server side error in the client.\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n\nMSGPACK-RPC\n^^^^^^^^^^^\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCParseError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCMethodNotFoundError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidParamsError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInternalError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCServerError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n\nThis last exception is a client side exception designed to represent the server side error in the client.\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n    :noindex:\n"
  },
  {
    "path": "docs/index.rst",
    "content": "tinyrpc: A modular RPC library\n==============================\n\n``tinyrpc`` is a framework for constructing remote procedure call (RPC) services in Python.\n\nIn ``tinyrpc`` all components (transport, protocol and dispatcher) that together make an\nRPC service are independently replacable.\n\nAlthough its initial scope is handling jsonrpc_ it is easy to add further protocols or\nadd additional transports (one such example is msgpackrpc_, which is now fully supported).\nIf so desired it is even possible to replace the default method dispatcher.\n\n\nTable of contents\n-----------------\n\n.. toctree::\n    :maxdepth: 2\n\n    examples\n    structure\n    dispatch\n    protocols\n    jsonrpc\n    msgpackrpc\n    transports\n    client\n    server\n    exceptions\n\nInstallation\n------------\n\n.. code-block:: sh\n\n   pip install tinyrpc\n\nwill install ``tinyrpc`` with its default dependencies.\n\nOptional dependencies\n+++++++++++++++++++++\n\nDepending on the protocols and transports you want to use additional dependencies\nare required. You can instruct pip to install these dependencies by specifying\nextras to the basic install command.\n\n.. code-block:: sh\n\n   pip install tinyrpc[httpclient, wsgi]\n\nwill install ``tinyrpc`` with dependencies for the httpclient and wsgi transports.\n\nAvailable extras are:\n\n+------------+-------------------------------------------------------+\n| Option     |  Needed to use objects of class                       |\n+============+=======================================================+\n| gevent     | optional in RPCClient, required by RPCServerGreenlets |\n+------------+-------------------------------------------------------+\n| httpclient | HttpPostClientTransport, HttpWebSocketClientTransport |\n+------------+-------------------------------------------------------+\n| jsonext    | optional in JSONRPCProtocol                           |\n+------------+-------------------------------------------------------+\n| msgpack    | required by MSGPACKRPCProtocol                        |\n+------------+-------------------------------------------------------+\n| rabbitmq   | RabbitMQServerTransport, RabbitMQClientTransport      |\n+------------+-------------------------------------------------------+\n| websocket  | WSServerTransport, HttpWebSocketClientTransport       |\n+------------+-------------------------------------------------------+\n| wsgi       | WsgiServerTransport                                   |\n+------------+-------------------------------------------------------+\n| zmq        | ZmqServerTransport, ZmqClientTransport                |\n+------------+-------------------------------------------------------+\n\nPeople\n------\n\nCreator\n+++++++\n\n- Marc Brinkmann: https://github.com/mbr\n\n    As of this writing (in Jan 2013) there are a few jsonrpc_ libraries already out\n    there on PyPI_, most of them handling one specific use case (e.g. json via\n    WSGI, using Twisted, or TCP-sockets).\n\n    None of the libraries, however, made it easy to reuse the jsonrpc_-parsing bits\n    and substitute a different transport (i.e. going from json_ via TCP_ to an\n    implementation using WebSockets_ or 0mq_).\n\n    In the end, all these libraries have their own dispatching interfaces and a\n    custom implementation of handling jsonrpc_.\n\n    ``tinyrpc`` aims to do better by dividing the problem into cleanly\n    interchangeable parts that allow easy addition of new transport methods, RPC\n    protocols or dispatchers.\n\nMaintainer\n++++++++++\n\n- Leo Noordergraaf: https://github.com/lnoor\n\n    Looking for a Python jsonrpc_ library I found ``tinyrpc``.\n    I was immediately taken by its modular concept and construction.\n\n    After creating a couple transports and trying to get them integrated in tinyrpc,\n    I learned that Marc got involved with other projects and that maintaining\n    ``tinyrpc`` became too much a burden.\n    I then volunteered to become its maintainer.\n\n.. _jsonrpc: http://jsonrpc.org\n.. _msgpackrpc: https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md\n.. _PyPI: http://pypi.python.org\n.. _json: http://www.json.org/\n.. _TCP: http://en.wikipedia.org/wiki/Transmission_Control_Protocol\n.. _WebSockets: http://en.wikipedia.org/wiki/WebSocket\n.. _0mq: http://www.zeromq.org/\n"
  },
  {
    "path": "docs/jsonrpc.rst",
    "content": "The JSON-RPC protocol\n=====================\n\nExample\n-------\n\nThe following example shows how to use the\n:py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol` class in a custom\napplication, without using any other components:\n\nServer\n++++++\n\n.. code-block:: python\n\n   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n   from tinyrpc import BadRequestError, RPCBatchRequest\n\n   rpc = JSONRPCProtocol()\n\n   # the code below is valid for all protocols, not just JSONRPC:\n\n   def handle_incoming_message(self, data):\n       try:\n           request = rpc.parse_request(data)\n       except BadRequestError as e:\n           # request was invalid, directly create response\n           response = e.error_respond(e)\n       else:\n           # we got a valid request\n           # the handle_request function is user-defined\n           # and returns some form of response\n           if hasattr(request, create_batch_response):\n               response = request.create_batch_response(\n                   handle_request(req) for req in request\n               )\n           else:\n               response = handle_request(request)\n\n       # now send the response to the client\n       if response != None:\n            send_to_client(response.serialize())\n\n\n   def handle_request(request):\n       try:\n           # do magic with method, args, kwargs...\n           return request.respond(result)\n       except Exception as e:\n           # for example, a method wasn't found\n           return request.error_respond(e)\n\nClient\n++++++\n\n.. code-block:: python\n\n   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n\n   rpc = JSONRPCProtocol()\n\n   # again, code below is protocol-independent\n\n   # assuming you want to call method(*args, **kwargs)\n\n   request = rpc.create_request(method, args, kwargs)\n   reply = send_to_server_and_get_reply(request)\n\n   response = rpc.parse_reply(reply)\n\n   if hasattr(response, 'error'):\n       # error handling...\n   else:\n       # the return value is found in response.result\n       do_something_with(response.result)\n\n\nAnother example, this time using batch requests:\n\n.. code-block:: python\n\n   # or using batch requests:\n\n   requests = rpc.create_batch_request([\n       rpc.create_request(method_1, args_1, kwargs_1)\n       rpc.create_request(method_2, args_2, kwargs_2)\n       # ...\n   ])\n\n   reply = send_to_server_and_get_reply(request)\n\n   responses = rpc.parse_reply(reply)\n\n   for responses in response:\n       if hasattr(reponse, 'error'):\n           # ...\n\n\nFinally, one-way requests are requests where the client does not expect an\nanswer:\n\n.. code-block:: python\n\n   request = rpc.create_request(method, args, kwargs, one_way=True)\n   send_to_server(request)\n\n   # done\n\n\nProtocol implementation\n-----------------------\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCProtocol\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCRequest\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nBatch protocol\n--------------\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n\nErrors and error handling\n-------------------------\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCParseError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInternalError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCServerError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nAdding custom exceptions\n------------------------\n\n.. note:: As per the specification_ you should use error codes -32000 to\n    -32099 when adding server specific error messages.\n    Error codes outside the range -32768 to -32000 are available for\n    application specific error codes.\n\nTo add custom errors you need to combine an :py:class:`Exception` subclass\nwith the :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin` class to\ncreate your exception object which you can raise.\n\nSo a version of the reverse string example that dislikes palindromes could\nlook like:\n\n\n.. code-block:: python\n\n    from tinyrpc.protocols.jsonrpc import FixedErrorMessageMixin, JSONRPCProtocol\n    from tinyrpc.dispatch import RPCDispatcher\n\n    dispatcher = RPCDispatcher()\n\n    class PalindromeError(FixedErrorMessageMixin, Exception):\n        jsonrpc_error_code = 99\n        message = \"Ah, that's cheating!\"\n\n\n    @dispatcher.public\n    def reverse_string(s):\n        r = s[::-1]\n        if r == s:\n            raise PalindromeError()\n        return r\n\nError with data\n---------------\n\nThe specification_ states that the ``error`` element of a reply may contain\nan optional ``data`` property. This property is now available for your use.\n\nThere are two ways that you can use to pass additional data with an :py:class:`Exception`.\nIt depends whether your application generates regular exceptions or exceptions derived\nfrom :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin`.\n\nWhen using ordinary exceptions you normally pass a single parameter (an error message)\nto the :py:class:`Exception` constructor.\nBy passing two parameters, the second parameter is assumed to be the data element.\n\n.. code-block:: python\n\n    @public\n    def fn():\n        raise Exception('error message', {'msg': 'structured data', 'lst': [1, 2, 3]})\n\nThis will produce the reply message::\n\n    {   \"jsonrpc\": \"2.0\",\n        \"id\": <some id>,\n        \"error\": {\n            \"code\": -32000,\n            \"message\": \"error message\",\n            \"data\": {\"msg\": \"structured data\", \"lst\": [1, 2, 3]}\n        }\n    }\n\nWhen using :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin` based exceptions the data is passed using\na keyword parameter.\n\n.. code-block:: python\n\n    class MyException(FixedErrorMessageMixin, Exception):\n        jsonrcp_error_code = 99\n        message = 'standard message'\n\n    @public\n    def fn():\n        raise MyException(data={'msg': 'structured data', 'lst': [1, 2, 3]})\n\nThis will produce the reply message::\n\n    {   \"jsonrpc\": \"2.0\",\n        \"id\": <some id>,\n        \"error\": {\n            \"code\": 99,\n            \"message\": \"standard message\",\n            \"data\": {\"msg\": \"structured data\", \"lst\": [1, 2, 3]}\n        }\n    }\n\n\n.. _specification: http://www.jsonrpc.org/specification#error_object\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\tinyrpc.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\tinyrpc.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/msgpackrpc.rst",
    "content": "The MSGPACK-RPC protocol\n========================\n\nExample\n-------\n\nThe following example shows how to use the\n:py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol` class in a custom\napplication, without using any other components:\n\nServer\n++++++\n\n.. code-block:: python\n\n   from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol\n   from tinyrpc import BadRequestError, RPCRequest\n\n   rpc = MSGPACKRPCProtocol()\n\n   # the code below is valid for all protocols, not just MSGPACKRPCProtocol,\n   # as long as you don't need to handle batch RPC requests:\n\n   def handle_incoming_message(self, data):\n       try:\n           request = rpc.parse_request(data)\n       except BadRequestError as e:\n           # request was invalid, directly create response\n           response = e.error_respond(e)\n       else:\n           # we got a valid request\n           # the handle_request function is user-defined\n           # and returns some form of response\n           response = handle_request(request)\n\n       # now send the response to the client\n       if response != None:\n            send_to_client(response.serialize())\n\n\n   def handle_request(request):\n       try:\n           # do magic with method, args, kwargs...\n           return request.respond(result)\n       except Exception as e:\n           # for example, a method wasn't found\n           return request.error_respond(e)\n\nClient\n++++++\n\n.. code-block:: python\n\n   from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol\n\n   rpc = MSGPACKRPCProtocol()\n\n   # again, code below is protocol-independent\n\n   # assuming you want to call method(*args, **kwargs)\n\n   request = rpc.create_request(method, args, kwargs)\n   reply = send_to_server_and_get_reply(request)\n\n   response = rpc.parse_reply(reply)\n\n   if hasattr(response, 'error'):\n       # error handling...\n   else:\n       # the return value is found in response.result\n       do_something_with(response.result)\n\n\nFinally, one-way requests are requests where the client does not expect an\nanswer:\n\n.. code-block:: python\n\n   request = rpc.create_request(method, args, kwargs, one_way=True)\n   send_to_server(request)\n\n   # done\n\n\nProtocol implementation\n-----------------------\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCRequest\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCSuccessResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCErrorResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nErrors and error handling\n-------------------------\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.FixedErrorMessageMixin\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCParseError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCMethodNotFoundError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidParamsError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInternalError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCServerError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nAdding custom exceptions\n------------------------\n\n.. note:: Unlike JSON-RPC, the MSGPACK-RPC specification does not specify how\n    the error messages should look like; the protocol allows any arbitrary\n    MSGPACK object as an error object. For sake of compatibility with JSON-RPC,\n    this implementation uses MSGPACK lists of length 2 (consisting of a numeric\n    error code and an error description) to represent errors in the serialized\n    representation. These are transparently decoded into\n    :py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCError` instances as\n    needed. The error codes for parsing errors, invalid requests, unknown RPC\n    methods and so on match those from the `JSON-RPC specification`_.\n\nTo add custom errors you need to combine an :py:class:`Exception` subclass\nwith the :py:class:`~tinyrpc.protocols.msgpackrpc.FixedErrorMessageMixin` class\nto create your exception object which you can raise.\n\nSo a version of the reverse string example that dislikes palindromes could\nlook like:\n\n\n.. code-block:: python\n\n    from tinyrpc.protocols.msgpackrpc import FixedErrorMessageMixin, MSGPACKRPCProtocol\n    from tinyrpc.dispatch import RPCDispatcher\n\n    dispatcher = RPCDispatcher()\n\n    class PalindromeError(FixedErrorMessageMixin, Exception):\n        msgpackrpc_error_code = 99\n        message = \"Ah, that's cheating!\"\n\n\n    @dispatcher.public\n    def reverse_string(s):\n        r = s[::-1]\n        if r == s:\n            raise PalindromeError()\n        return r\n\n.. _specification: https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md\n.. _JSON-RPC specification: http://www.jsonrpc.org/specification#error_object\n"
  },
  {
    "path": "docs/protocols.rst",
    "content": "The protocol layer\n==================\n\nInterface definition\n--------------------\n\nAll protocols are implemented by deriving from :py:class:`~tinyrpc.protocols.RPCProtocol`\nand implementing all of its members.\n\nEvery protocol deals with multiple kinds of structures: ``data`` arguments are\nalways byte strings, either messages or replies, that are sent via or received\nfrom a transport.\n\nProtocol-specific subclasses of :py:class:`~tinyrpc.protocols.RPCRequest` and\n:py:class:`~tinyrpc.protocols.RPCResponse` represent well-formed requests and responses.\n\nProtocol specific subclasses of :py:class:`~tinyrpc.protocols.RPCErrorResponse` represent\nerrors and error responses.\n\nFinally, if an error occurs during parsing of a request, a\n:py:class:`~tinyrpc.exc.BadRequestError` instance must be thrown. These need to be\nsubclassed for each protocol as well, since they generate error replies.\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.RPCProtocol\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.RPCRequest\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.RPCResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.RPCErrorResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.exc.BadRequestError\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n\nBatch protocols\n---------------\n\nSome protocols may support batch requests. In this case, they need to derive\nfrom :py:class:`~tinyrpc.protocols.RPCBatchProtocol`.\n\nBatch protocols differ in that their\n:py:func:`~tinyrpc.protocols.RPCProtocol.parse_request` method may return an instance of\n:py:class:`~tinyrpc.protocols.RPCBatchRequest`. They also possess an addional method in\n:py:func:`~tinyrpc.protocols.RPCBatchProtocol.create_batch_request`.\n\nHandling a batch request is slightly different, while it supports\n:py:func:`~tinyrpc.protocols.RPCBatchRequest.error_respond`, to make actual responses,\n:py:func:`~tinyrpc.protocols.RPCBatchRequest.create_batch_response` needs to be used.\n\nNo assumptions are made whether or not it is okay for batch requests to be\nhandled in parallel. This is up to the server/dispatch implementation, which\nmust be chosen appropriately.\n\nAPI Reference\n+++++++++++++\n\n.. autoclass:: tinyrpc.protocols.RPCBatchProtocol\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.RPCBatchRequest\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.protocols.RPCBatchResponse\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\n\nID Generators\n-------------\n\nBy default, the :py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol` \nand :py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol` classes\ngenerates ids as sequential integers starting at 1.\nIf alternative id generation is needed, you may supply your own\ngenerator.\n\nExample\n-------\n\nThe following example shows how to use alternative id generators in a protocol\nthat supports them.\n\n.. code-block:: python\n\n    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n    \n    def collatz_generator():\n        \"\"\"A sample generator for demonstration purposes ONLY.\"\"\"\n        n = 27\n        while True:\n            if n % 2 != 0:\n                n = 3*n + 1\n            else:\n                n = n / 2\n            yield n\n\n    rpc = JSONRPCProtocol(id_generator=collatz_generator())\n\n\nSupported protocols\n-------------------\n\nAny supported protocol is used by instantiating its class and calling the\ninterface of :py:class:`~tinyrpc.protocols.RPCProtocol`. Note that constructors\nare not part of the interface, any protocol may have specific arguments for its\ninstances.\n\nProtocols usually live in their own module because they may need to import\noptional modules that needn't be a dependency for all of ``tinyrpc``.\n\n.. _jsonrpc: http://jsonrpc.org\n"
  },
  {
    "path": "docs/server.rst",
    "content": "Server implementations\n======================\n\nLike :doc:`client`, servers are top-level instances that most user code should\ninteract with. They provide runnable functions that are combined with\ntransports, protocols and dispatchers to form a complete RPC system.\n\n.. automodule:: tinyrpc.server\n   :members:\n   :noindex:\n\n.. py:class:: tinyrpc.server.gevent.RPCServerGreenlets\n\n   Asynchronous RPCServer.\n\n   This implementation of :py:class:`~tinyrpc.server.RPCServer` uses\n   :py:func:`gevent.spawn` to spawn new client handlers, result in asynchronous\n   handling of clients using greenlets.\n"
  },
  {
    "path": "docs/structure.rst",
    "content": "Structure of tinyrpc\n====================\n\nArchitecture\n------------\n\n``tinyrpc`` is constructed around the :py:class:`~tinyrpc.server.RPCServer` and\n:py:class:`~tinyrpc.client.RPCClient` classes.\n\nThey in turn depend on the :py:class:`~tinyrpc.dispatch.RPCDispatcher`,\n:py:class:`~tinyrpc.protocols.RPCProtocol`, :py:class:`~tinyrpc.transports.ServerTransport`\nand :py:class:`~tinyrpc.transports.ClientTransport` classes as visualized in the image below.\n\n.. image:: _static/uml.png\n\nOf these :py:class:`~tinyrpc.protocols.RPCProtocol`,\n:py:class:`~tinyrpc.transports.ServerTransport` and\n:py:class:`~tinyrpc.transports.ClientTransport` are abstract base classes.\n\nEach layer is useful \"on its own\" and can be used separately.\nIf you just need to decode a jsonrpc_ message, without passing it on or sending it through\na transport, the :py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol`-class is completely usable\non its own.\n\nLikewise the :py:class:`~tinyrpc.dispatch.RPCDispatcher` could be used to dispatch calls in a\ncommandline REPL like application.\n\nTransport\n---------\n\nThe transport classes are responsible for receiving and sending messages.\nNo assumptions are made about messages, except that they are of a fixed size.\nMessages are received and possibly passed on as Python :py:class:`bytes` objects.\n\nIn an RPC context, messages coming in (containing requests) are simply called\nmessages, a message sent in reply is called a reply. Replies are always\nserialized responses.\n\nProtocol\n--------\n\nThe protocol class(es) are responsible for two tasks:\n\n* they implement the protocol, defining how method names, method parameters and errors are represented in requests and responses.\n* they serialize the requests and responses into messages and deserialize messages back into requests and responses.\n\n\nDispatcher\n----------\n\n:doc:`dispatch` performs the actual method calling determining with method to call and how to\npass it the parameters.\nThe result of the method call, or the exception if the call failed is assembled and made available\nto the protocol for serialization.\n\nClient and Server\n-----------------\n\nThe client and server classes tie all components together to provide the application interface.\n\n.. _jsonrpc: http://jsonrpc.org\n"
  },
  {
    "path": "docs/transports.rst",
    "content": "Transports\n==========\n\nTransports are somewhat low level interface concerned with transporting\nmessages across through different means. \"Messages\" in this case are simple\nstrings. All transports need to support two different interfaces:\n\n.. autoclass:: tinyrpc.transports.ServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.transports.ClientTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nNote that these transports are of relevance when using ``tinyrpc``-built in\nfacilities. They can be coopted for any other purpose, if you simply need\nreliable server-client message passing as well.\n\nAlso note that the client transport interface is not designed for asynchronous\nuse. For simple use cases (sending multiple concurrent requests) monkey patching\nwith gevent may get the job done.\n\n\nTransport implementations\n-------------------------\n\nA few transport implementations are included with ``tinyrpc``:\n\n0mq\n~~~\n\nBased on :py:mod:`zmq`, supports 0mq based sockets. Highly recommended:\n\n.. autoclass:: tinyrpc.transports.zmq.ZmqServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.transports.zmq.ZmqClientTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nHTTP\n~~~~\n\nThere is only an HTTP client, no server (use WSGI instead).\n\n.. autoclass:: tinyrpc.transports.http.HttpPostClientTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. note:: To set a timeout on your client transport provide a ``timeout``\n    keyword parameter like::\n\n        transport = HttpPostClientTransport(endpoint, timeout=0.1)\n\n    It will result in a ``requests.exceptions.Timeout`` exception when a\n    timeout occurs.\n\nWSGI\n~~~~\n\n.. autoclass:: tinyrpc.transports.wsgi.WsgiServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nCGI\n~~~\n\n.. autoclass:: tinyrpc.transports.cgi.CGIServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nCallback\n~~~~~~~~\n\n.. autoclass:: tinyrpc.transports.callback.CallbackServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nRabbitMQ\n~~~~~~~~\n\n.. autoclass:: tinyrpc.transports.rabbitmq.RabbitMQServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.transports.rabbitmq.RabbitMQClientTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\nWebSocket\n~~~~~~~~~\n\n.. autoclass:: tinyrpc.transports.websocket.WSServerTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.transports.websocket.WSApplication\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n.. autoclass:: tinyrpc.transports.websocketclient.HttpWebSocketClientTransport\n    :members:\n    :noindex:\n    :show-inheritance:\n    :member-order: bysource\n\n"
  },
  {
    "path": "examples/http_client_example.py",
    "content": "#!/usr/bin/env python3\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.http import HttpPostClientTransport\nfrom tinyrpc import RPCClient\n\nrpc_client = RPCClient(\n    JSONRPCProtocol(),\n    HttpPostClientTransport('http://127.0.0.1:5000/')\n)\n\nremote_server = rpc_client.get_proxy()\n\n# call a method called 'reverse_string' with a single string argument\nresult = remote_server.reverse_string('Hello, World!')\n\nprint(\"Server answered:\", result)\n"
  },
  {
    "path": "examples/http_server_example.py",
    "content": "#!/usr/bin/env python3\n\nimport gevent\nimport gevent.wsgi\nimport gevent.queue\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.wsgi import WsgiServerTransport\nfrom tinyrpc.server.gevent import RPCServerGreenlets\nfrom tinyrpc.dispatch import RPCDispatcher\n\ndispatcher = RPCDispatcher()\ntransport = WsgiServerTransport(queue_class=gevent.queue.Queue)\n\n# start wsgi server as a background-greenlet\nwsgi_server = gevent.wsgi.WSGIServer(('127.0.0.1', 5000), transport.handle)\ngevent.spawn(wsgi_server.serve_forever)\n\nrpc_server = RPCServerGreenlets(\n    transport,\n    JSONRPCProtocol(),\n    dispatcher\n)\n\n@dispatcher.public\ndef reverse_string(s):\n    return s[::-1]\n\n# in the main greenlet, run our rpc_server\nrpc_server.serve_forever()\n"
  },
  {
    "path": "examples/zmq_client_example.py",
    "content": "#!/usr/bin/env python3\n\nimport zmq\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.zmq import ZmqClientTransport\nfrom tinyrpc import RPCClient\n\nctx = zmq.Context()\n\nrpc_client = RPCClient(\n    JSONRPCProtocol(),\n    ZmqClientTransport.create(ctx, 'tcp://127.0.0.1:5001')\n)\n\nremote_server = rpc_client.get_proxy()\n\n# call a method called 'reverse_string' with a single string argument\nresult = remote_server.reverse_string('Hello, World!')\n\nprint(\"Server answered:\", result)\n"
  },
  {
    "path": "examples/zmq_server_example.py",
    "content": "#!/usr/bin/env python3\n\nimport zmq\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.zmq import ZmqServerTransport\nfrom tinyrpc.server import RPCServer\nfrom tinyrpc.dispatch import RPCDispatcher\n\nctx = zmq.Context()\ndispatcher = RPCDispatcher()\ntransport = ZmqServerTransport.create(ctx, 'tcp://127.0.0.1:5001')\n\nrpc_server = RPCServer(\n    transport,\n    JSONRPCProtocol(),\n    dispatcher\n)\n\n@dispatcher.public\ndef reverse_string(s):\n    return s[::-1]\n\nrpc_server.serve_forever()\n"
  },
  {
    "path": "optional_features.pip",
    "content": "requests\nwerkzeug\ngevent\npyzmq\nwebsocket-client\ngevent-websocket\npyzmq\njsonext\nmsgpack\npika\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n"
  },
  {
    "path": "requirements.txt",
    "content": "gevent==22.10.2\ngevent-websocket==0.10.1\nmsgpack==1.0.2\npika==1.2.0\npytest==6.2.4\npytest-cov==2.11.1\npyzmq==23.2.1\nrequests==2.31.0\nWerkzeug==2.2.3\n"
  },
  {
    "path": "setup.py",
    "content": "import os\n\nfrom setuptools import setup, find_packages\n\n\ndef read(fname):\n    return open(os.path.join(os.path.dirname(__file__), fname)).read()\n\n\nsetup(\n    name='tinyrpc',\n    version='1.1.7',\n    description='A small, modular, transport and protocol neutral RPC '\n                'library that, among other things, supports JSON-RPC and zmq.',\n    long_description=read('README.rst'),\n    long_description_content_type=\"text/x-rst\",\n    packages=find_packages(exclude=['examples']),\n    keywords='json rpc json-rpc jsonrpc 0mq zmq zeromq',\n    author='Marc Brinkmann',\n    author_email='git@marcbrinkmann.de',\n    maintainer='Leo Noordergraaf',\n    maintainer_email='leo@noordergraaf.net',\n    url='http://github.com/mbr/tinyrpc',\n    license='MIT',\n    extras_require={\n        'gevent': ['gevent'],\n        'httpclient': ['requests', 'websocket-client', 'gevent-websocket'],\n        'msgpack': ['msgpack'],\n        'websocket': ['gevent-websocket'],\n        'wsgi': ['werkzeug'],\n        'zmq': ['pyzmq'],\n        'jsonext': ['jsonext'],\n        'rabbitmq': ['pika']\n    }\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_client.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import Mock\n\nfrom tinyrpc.exc import RPCError\nfrom tinyrpc.client import RPCClient, RPCProxy\nfrom tinyrpc.protocols import RPCProtocol, RPCResponse, RPCErrorResponse, RPCRequest\nfrom tinyrpc.transports import ClientTransport\n\n\n@pytest.fixture(params=['test_method1', 'method2', 'CamelCasedMethod'])\ndef method_name(request):\n    return request.param\n\n\n@pytest.fixture(params=[(), ('foo', None, 42), (1, )])\ndef method_args(request):\n    return request.param\n\n\n@pytest.fixture(\n    params=[(), (('foo', 'bar'), ('x', None), ('y', 42)), (('q', 1), )]\n)\ndef method_kwargs(request):\n    return dict(request.param or {})\n\n\n@pytest.fixture(params=['', 'NoDot', 'dot.'])\ndef prefix(request):\n    return request.param\n\n\n@pytest.fixture(params=[True, False])\ndef one_way_setting(request):\n    return request.param\n\n\n@pytest.fixture\ndef mock_client():\n    return Mock(RPCClient)\n\n\n@pytest.fixture\ndef mock_protocol():\n    mproto = Mock(RPCProtocol)\n\n    foo = Mock(RPCResponse)\n    foo.result = None\n\n    mproto.parse_reply = Mock(return_value=foo)\n\n    return mproto\n\n\n@pytest.fixture\ndef mock_transport():\n    return Mock(ClientTransport)\n\n\n@pytest.fixture()\ndef client(mock_protocol, mock_transport):\n    return RPCClient(mock_protocol, mock_transport)\n\n\n@pytest.fixture\ndef m_proxy(mock_client, prefix, one_way_setting):\n    return RPCProxy(mock_client, prefix, one_way_setting)\n\n\ndef test_proxy_calls_correct_method(\n        m_proxy, mock_client, prefix, method_kwargs, method_args, method_name,\n        one_way_setting\n):\n\n    getattr(m_proxy, method_name)(*method_args, **method_kwargs)\n\n    mock_client.call.assert_called_with(\n        prefix + method_name,\n        method_args,\n        method_kwargs,\n        one_way=one_way_setting\n    )\n\n\ndef test_client_uses_correct_protocol(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting\n):\n    client.call(method_name, method_args, method_kwargs, one_way_setting)\n\n    assert mock_protocol.create_request.called\n\n\ndef test_client_uses_correct_transport(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting, mock_transport\n):\n    client.call(method_name, method_args, method_kwargs, one_way_setting)\n    assert mock_transport.send_message.called\n\n\ndef test_client_passes_correct_reply(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting, mock_transport\n):\n    transport_return = '023hoisdfh'\n    mock_transport.send_message = Mock(return_value=transport_return)\n    client.call(method_name, method_args, method_kwargs, one_way_setting)\n    if one_way_setting:\n        mock_protocol.parse_reply.assert_not_called()\n    else:\n        mock_protocol.parse_reply.assert_called_with(transport_return)\n\n\ndef test_client_raises_error_replies(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting\n):\n    error_response = RPCErrorResponse()\n    error_response.error = 'foo'\n    mock_protocol.parse_reply = Mock(return_value=error_response)\n\n    if not one_way_setting:\n        client.call(method_name, method_args, method_kwargs, one_way_setting)\n        assert mock_protocol.raise_error.call_args is not None\n        args, kwargs = mock_protocol.raise_error.call_args\n        assert isinstance(args[0], RPCErrorResponse)\n        assert args[0].error == 'foo'\n        print(mock_protocol.mock_calls)\n        mock_protocol.raise_error.assert_called_with(error_response)\n\n\ndef test_client_raises_indirect_error_replies(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting\n):\n    class MockException(Exception):\n        pass\n\n    def raise_error(error):\n        raise MockException(error)\n\n    error_response = RPCErrorResponse()\n    error_response.error = 'foo'\n    mock_protocol.parse_reply = Mock(return_value=error_response)\n    mock_protocol.raise_error = raise_error\n\n    if not one_way_setting:\n        with pytest.raises(MockException):\n            client.call(\n                method_name, method_args, method_kwargs, one_way_setting\n            )\n\n\ndef test_client_produces_good_proxy(client, prefix, one_way_setting):\n    proxy = client.get_proxy(prefix, one_way_setting)\n    assert proxy.client == client\n    assert proxy.prefix == prefix\n    assert proxy.one_way == one_way_setting\n    assert callable(proxy.foobar)\n\n\n@pytest.mark.skip(\n    'no longer performs automatic conversion, serialize() always returns bytes'\n)\ndef test_client_send_binary_message(\n        client, mock_protocol, method_name, method_args, method_kwargs,\n        one_way_setting, mock_transport\n):\n    req = Mock(RPCRequest)\n    req.serialize.return_value = u'unicode not acceptable'\n    mock_protocol.create_request.return_value = req\n    client.call(method_name, method_args, method_kwargs, one_way_setting)\n    assert mock_transport.send_message.called\n    assert isinstance(mock_transport.send_message.call_args[0][0], bytes)\n"
  },
  {
    "path": "tests/test_dispatch.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom unittest.mock import Mock\nimport pytest\nimport inspect\n\nfrom tinyrpc.dispatch import RPCDispatcher, public\nfrom tinyrpc import RPCRequest, RPCBatchRequest, RPCBatchResponse\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol, JSONRPCInvalidParamsError\nfrom tinyrpc.exc import *\n\n@pytest.fixture\ndef dispatch():\n    return RPCDispatcher()\n\n\n@pytest.fixture()\ndef subdispatch():\n    return RPCDispatcher()\n\n\n\ndef mock_request(method='subtract', args=None, kwargs=None):\n    mock_request = Mock(RPCRequest)\n    mock_request.method = method\n    mock_request.args = args or [4, 6]\n    mock_request.kwargs = kwargs or {}\n\n    return mock_request\n\n@pytest.fixture(name=\"mock_request\")\ndef mock_request_fixture():\n    return mock_request()\n\ndef test_function_decorating_without_paramters(dispatch):\n    @dispatch.public\n    def foo(bar):\n        pass\n\n    assert dispatch.get_method('foo') == foo\n\n\ndef test_function_decorating_with_empty_paramters(dispatch):\n    @dispatch.public()\n    def foo(bar):\n        pass\n\n    assert dispatch.get_method('foo') == foo\n\n\ndef test_function_decorating_with_paramters(dispatch):\n    @dispatch.public(name='baz')\n    def foo(bar):\n        pass\n\n    with pytest.raises(MethodNotFoundError):\n        dispatch.get_method('foo')\n\n\n    assert dispatch.get_method('baz') == foo\n\n\ndef test_subdispatchers(dispatch, subdispatch):\n    @dispatch.public()\n    def foo(bar):\n        pass\n\n    @subdispatch.public(name='foo')\n    def subfoo(bar):\n        pass\n\n    dispatch.add_subdispatch(subdispatch, 'sub.')\n\n    assert dispatch.get_method('foo') == foo\n    assert dispatch.get_method('sub.foo') == subfoo\n\n\ndef test_object_method_marking():\n    class Foo(object):\n        def foo1(self):\n            pass\n\n        @public\n        def foo2(self):\n            pass\n\n        @public(name='baz')\n        def foo3(self):\n            pass\n\n    f = Foo()\n\n    assert not hasattr(f.foo1, '_rpc_public_name')\n    assert f.foo2._rpc_public_name == 'foo2'\n    assert f.foo3._rpc_public_name == 'baz'\n\n\ndef test_object_method_register(dispatch):\n    class Foo(object):\n        def foo1(self):\n            pass\n\n        @public\n        def foo2(self):\n            pass\n\n        @public(name='baz')\n        def foo3(self):\n            pass\n\n    f = Foo()\n    dispatch.register_instance(f)\n\n    with pytest.raises(MethodNotFoundError):\n        assert dispatch.get_method('foo1')\n\n    assert dispatch.get_method('foo2') == f.foo2\n    assert dispatch.get_method('baz') == f.foo3\n\n\ndef test_object_method_register_with_prefix(dispatch):\n    class Foo(object):\n        def foo1(self):\n            pass\n\n        @public\n        def foo2(self):\n            pass\n\n        @public(name='baz')\n        def foo3(self):\n            pass\n\n    f = Foo()\n    dispatch.register_instance(f, 'myprefix')\n\n    with pytest.raises(MethodNotFoundError):\n        assert dispatch.get_method('foo1')\n\n    with pytest.raises(MethodNotFoundError):\n        assert dispatch.get_method('myprefixfoo1')\n\n    with pytest.raises(MethodNotFoundError):\n        assert dispatch.get_method('foo2')\n\n    with pytest.raises(MethodNotFoundError):\n        assert dispatch.get_method('foo3')\n\n    assert dispatch.get_method('myprefixfoo2') == f.foo2\n    assert dispatch.get_method('myprefixbaz') == f.foo3\n\n\ndef test_dispatch_calls_method_and_responds(dispatch, mock_request):\n    m = Mock()\n    m.subtract = Mock(return_value=-2)\n\n    dispatch.add_method(m.subtract, 'subtract')\n    response = dispatch.dispatch(mock_request)\n\n    assert m.subtract.called\n\n    mock_request.respond.assert_called_with(-2)\n\n\ndef test_dispatch_handles_in_function_exceptions(dispatch, mock_request):\n    m = Mock()\n    m.subtract = Mock(return_value=-2)\n\n    class MockError(Exception):\n        pass\n\n    m.subtract.side_effect = MockError('mock error')\n\n    dispatch.add_method(m.subtract, 'subtract')\n    response = dispatch.dispatch(mock_request)\n\n    assert m.subtract.called\n\n    mock_request.error_respond.assert_called_with(m.subtract.side_effect)\n\n\ndef test_batch_dispatch(dispatch):\n    method1 = Mock(return_value='rv1')\n    method2 = Mock(return_value=None)\n\n    dispatch.add_method(method1, 'method1')\n    dispatch.add_method(method2, 'method2')\n\n    batch_request = RPCBatchRequest()\n    batch_request.error_respond = Mock(return_value='ERROR')\n    batch_request.append(mock_request('method1', args=[1,2]))\n    batch_request.append(mock_request('non_existant_method', args=[5,6]))\n    batch_request.append(mock_request('method2', args=[3,4]))\n\n    batch_request.create_batch_response = lambda: RPCBatchResponse()\n\n    assert batch_request.error_respond.call_count == 0\n\n    response = dispatch.dispatch(batch_request)\n\n    # assert all methods are called\n    method1.assert_called_with(1, 2)\n    method2.assert_called_with(3, 4)\n\n    # FIXME: could use better checking?\n\n\ndef test_dispatch_raises_key_error(dispatch):\n    with pytest.raises(MethodNotFoundError):\n        dispatch.get_method('foo')\n\n@pytest.fixture(params=[\n    ('fn_a', [4, 6], {}, -2),\n    ('fn_a', [4], {}, InvalidParamsError),\n    # InvalidParamsError instead of JSONRPCInvalidParamsError due to mocking\n    ('fn_a', [], {'a':4, 'b':6}, -2),\n    ('fn_a', [4], {'b':6}, -2),\n    ('fn_b', [4, 6], {}, -2),\n    ('fn_b', [], {'a':4, 'b':6}, InvalidParamsError),\n    ('fn_b', [4], {}, IndexError),\n    # a[1] doesn't exist, can't be detected beforehand\n    ('fn_c', [4, 6], {}, InvalidParamsError),\n    ('fn_c', [], {'a':4, 'b':6}, -2),\n    ('fn_c', [], {'a':4}, KeyError)\n    # a['b'] doesn't exist, can't be detected beforehand\n])\ndef invoke_with(request):\n    return request.param\n\ndef test_argument_error(dispatch, invoke_with):\n    method, args, kwargs, result = invoke_with\n\n    protocol = JSONRPCProtocol()\n\n    @dispatch.public\n    def fn_a(a, b):\n        return a-b\n\n    @dispatch.public\n    def fn_b(*a):\n        return a[0]-a[1]\n\n    @dispatch.public\n    def fn_c(**a):\n        return a['a']-a['b']\n\n    mock_request = Mock(RPCRequest)\n    mock_request.args = args\n    mock_request.kwargs = kwargs\n    mock_request.method = method\n    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))\n    if inspect.isclass(result) and issubclass(result, Exception):\n        assert type(mock_request.error_respond.call_args[0][0]) == result\n    else:\n        mock_request.respond.assert_called_with(result)\n\ndef test_call_argument_validation(dispatch):\n    def f(a,b):\n        return a+b\n\n    dispatch.validate_parameters(f, [1, 2], {})\n    with pytest.raises(InvalidParamsError):\n        dispatch.validate_parameters(f, [1], {})\n    dispatch.validate_parameters(dir, [], {})\n    # should skip validation, will produce error otherwise\n\ndef test_bound_method_argument_error(dispatch, invoke_with):\n    method, args, kwargs, result = invoke_with\n\n    protocol = JSONRPCProtocol()\n\n    class Test:\n        c = 0\n        @public\n        def fn_a(self, a, b):\n            return a-b+self.c\n\n        @public\n        def fn_b(self, *a):\n            return a[0]-a[1]+self.c\n\n        @public\n        def fn_c(self, **a):\n            return a['a']-a['b']+self.c\n\n    test=Test()\n    dispatch.register_instance(test)\n    mock_request = Mock(RPCRequest)\n    mock_request.args = args\n    mock_request.kwargs = kwargs\n    mock_request.method = method\n    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))\n    if inspect.isclass(result) and issubclass(result, Exception):\n        assert type(mock_request.error_respond.call_args[0][0]) == result\n    else:\n        mock_request.respond.assert_called_with(result)\n\ndef test_bound_method_validation(dispatch):\n    class Test:\n        def f(self, a, b):\n            return a+b\n    inst = Test()\n\n    dispatch.validate_parameters(inst.f, [1, 2], {})\n    with pytest.raises(InvalidParamsError):\n        dispatch.validate_parameters(inst.f, [1], {})\n\ndef test_unbound_method_argument_error(dispatch, invoke_with):\n    method, args, kwargs, result = invoke_with\n\n    protocol = JSONRPCProtocol()\n\n    class Test:\n        c = 0\n        @public\n        def fn_a(a, b):\n            return a-b\n\n        @public\n        def fn_b(*a):\n            return a[0]-a[1]\n\n        @public\n        def fn_c(**a):\n            return a['a']-a['b']\n\n    dispatch.register_instance(Test)\n    mock_request = Mock(RPCRequest)\n    mock_request.args = args\n    mock_request.kwargs = kwargs\n    mock_request.method = method\n    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))\n    if inspect.isclass(result) and issubclass(result, Exception):\n        assert type(mock_request.error_respond.call_args[0][0]) == result\n    else:\n        mock_request.respond.assert_called_with(result)\n\ndef test_unbound_method_validation(dispatch):\n    class Test:\n        def f(a, b):\n            return a+b\n\n    dispatch.validate_parameters(Test.f, [1, 2], {})\n    with pytest.raises(InvalidParamsError):\n        dispatch.validate_parameters(Test.f, [1], {})\n\ndef test_static_method_argument_error(dispatch, invoke_with):\n    method, args, kwargs, result = invoke_with\n\n    protocol = JSONRPCProtocol()\n\n    class Test:\n        c = 0\n        @staticmethod\n        @public\n        def fn_a(a, b):\n            return a-b\n\n        @staticmethod\n        @public\n        def fn_b(*a):\n            return a[0]-a[1]\n\n        @staticmethod\n        @public\n        def fn_c(**a):\n            return a['a']-a['b']\n\n    test=Test()\n    dispatch.register_instance(test)\n    mock_request = Mock(RPCRequest)\n    mock_request.args = args\n    mock_request.kwargs = kwargs\n    mock_request.method = method\n    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))\n    if inspect.isclass(result) and issubclass(result, Exception):\n        assert type(mock_request.error_respond.call_args[0][0]) == result\n    else:\n        mock_request.respond.assert_called_with(result)\n\ndef test_static_method_validation(dispatch):\n    class Test:\n        @staticmethod\n        def f(a, b):\n            return a+b\n    inst = Test()\n\n    dispatch.validate_parameters(inst.f, [1, 2], {})\n    with pytest.raises(InvalidParamsError):\n        dispatch.validate_parameters(inst.f, [1], {})\n\ndef test_class_method_argument_error(dispatch, invoke_with):\n    method, args, kwargs, result = invoke_with\n\n    protocol = JSONRPCProtocol()\n\n    class Test:\n        c = 0\n        @classmethod\n        @public\n        def fn_a(cls, a, b):\n            return a-b-cls.c\n\n        @classmethod\n        @public\n        def fn_b(cls, *a):\n            return a[0]-a[1]-cls.c\n\n        @classmethod\n        @public\n        def fn_c(cls, **a):\n            return a['a']-a['b']-cls.c\n\n    test=Test()\n    dispatch.register_instance(test)\n    mock_request = Mock(RPCRequest)\n    mock_request.args = args\n    mock_request.kwargs = kwargs\n    mock_request.method = method\n    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))\n    if inspect.isclass(result) and issubclass(result, Exception):\n        assert type(mock_request.error_respond.call_args[0][0]) == result\n    else:\n        mock_request.respond.assert_called_with(result)\n\ndef test_class_method_validation(dispatch):\n    class Test:\n        @classmethod\n        def f(cls, a, b):\n            return a+b\n    inst = Test()\n\n    dispatch.validate_parameters(inst.f, [1, 2], {})\n    with pytest.raises(InvalidParamsError):\n        dispatch.validate_parameters(inst.f, [1], {})\n\n"
  },
  {
    "path": "tests/test_jsonrpc.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport json\n\nimport pytest\n\nfrom tinyrpc import MethodNotFoundError, InvalidRequestError, ServerError, \\\n                    RPCError, RPCResponse, InvalidReplyError\nfrom tinyrpc.protocols.jsonrpc import JSONRPCParseError, \\\n                                      JSONRPCInvalidRequestError, \\\n                                      JSONRPCMethodNotFoundError, \\\n                                      JSONRPCInvalidParamsError, \\\n                                      JSONRPCInternalError,\\\n                                      JSONRPCErrorResponse\n\n\ndef _json_equal(a, b):\n    da = json.loads(a.decode() if isinstance(a, bytes) else a)\n    db = json.loads(b.decode() if isinstance(b, bytes) else b)\n\n    return da == db\n\n\n@pytest.fixture\ndef prot():\n    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol\n\n    return JSONRPCProtocol()\n\n\n@pytest.mark.parametrize(('data', 'attrs'), [\n    # examples from the spec, parsing only\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\",\n       \"params\": [42, 23], \"id\": 1}\"\"\",\n     {'method': 'subtract', 'args': [42, 23], 'unique_id': 1}\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\":\n        [23, 42], \"id\": 2}\"\"\",\n     {'method': 'subtract', 'args': [23, 42], 'unique_id': 2}\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\":\n        {\"subtrahend\": 23, \"minuend\": 42}, \"id\": 3}\"\"\",\n     {'method': 'subtract', 'kwargs': {'subtrahend': 23, 'minuend': 42},\n      'unique_id': 3}\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": {\"minuend\": 42,\n        \"subtrahend\": 23}, \"id\": 4}\"\"\",\n     {'method': 'subtract', 'kwargs': {'minuend': 42, 'subtrahend': 23},\n      'unique_id': 4},\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}\"\"\",\n     {'method': 'update', 'args': [1, 2, 3, 4, 5]}\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"foobar\"}\"\"\",\n     {'method': 'foobar'}\n    ),\n])\ndef test_parsing_good_request_samples(prot, data, attrs):\n    req = prot.parse_request(data)\n\n    for k, v in attrs.items():\n        assert getattr(req, k) == v\n\n\n@pytest.mark.parametrize('invalid_json', [\n    '{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\": \"bar\", \"baz]',\n    'garbage',\n])\ndef test_parsing_invalid_json(prot, invalid_json):\n    with pytest.raises(JSONRPCParseError):\n        prot.parse_request(invalid_json)\n\n\ndef test_parsing_invalid_arguments(prot):\n    with pytest.raises(JSONRPCInvalidParamsError):\n        prot.parse_request(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": 9}\"\"\"\n        )\n\n\n@pytest.mark.parametrize(('data', 'id', 'result'), [\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 1}\"\"\",\n     1,\n     19,\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"result\": -19, \"id\": 2}\"\"\",\n     2,\n     -19,\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 3}\"\"\",\n     3,\n     19,\n    ),\n\n    (\"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 4}\"\"\",\n     4,\n     19,\n    ),\n])\ndef test_good_reply_samples(prot, data, id, result):\n    # assume the protocol is awaiting a response for\n    # a request with `id`\n    prot._pending_replies = [id]\n    \n    reply = prot.parse_reply(data)\n\n    assert reply.unique_id == id\n    assert reply.result == result\n\n\n@pytest.mark.parametrize(('data'), [\n    \"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 9001}\"\"\"\n])\ndef test_unsolicited_reply_raises_error(prot, data):\n    prot._pending_replies = [4]\n    with pytest.raises(InvalidReplyError):\n        reply = prot.parse_reply(data)\n\n\n@pytest.mark.parametrize(('exc', 'code', 'message'), [\n    (JSONRPCParseError, -32700, 'Parse error'),\n    (JSONRPCInvalidRequestError, -32600, 'Invalid Request'),\n    (JSONRPCMethodNotFoundError, -32601, 'Method not found'),\n    (JSONRPCInvalidParamsError, -32602, 'Invalid params'),\n    (JSONRPCInternalError, -32603, 'Internal error'),\n\n    # generic errors\n    #(InvalidRequestError, -32600, 'Invalid Request'),\n    #(MethodNotFoundError, -32601, 'Method not found'),\n    #(ServerError, -32603, 'Internal error'),\n])\ndef test_proper_construction_of_error_codes(prot, exc, code, message):\n    request = prot.parse_request(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4],\n           \"id\": \"1\"}\"\"\"\n    )\n    reply = exc().error_respond().serialize()\n    assert isinstance(reply, bytes)\n    reply = reply.decode()\n\n    err = json.loads(reply)\n\n    assert err['error']['code'] == code\n    assert err['error']['message'] == message\n\n\ndef test_notification_yields_None_response(prot):\n    data = \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}\"\"\"\n\n    req = prot.parse_request(data)\n\n    assert req.one_way == True\n\n    # updates should never cause retries\n    assert req.respond(True) == None\n\n\ndef test_batch_empty_array(prot):\n    with pytest.raises(JSONRPCInvalidRequestError):\n        prot.parse_request(\"\"\"[]\"\"\")\n\n\ndef test_batch_invalid_array(prot):\n    assert isinstance(prot.parse_request(\"\"\"[1]\"\"\")[0],\n                      JSONRPCInvalidRequestError)\n\n\ndef test_batch_invalid_batch(prot):\n    for r in prot.parse_request(\"\"\"[1, 2, 3]\"\"\"):\n        assert isinstance(r, JSONRPCInvalidRequestError)\n\n\ndef test_batch_good_examples(prot):\n    data = \"\"\"\n    [\n        {\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]},\n        {\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"},\n        {\"foo\": \"boo\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"foo.get\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}\n    ]\n    \"\"\"\n\n    results = prot.parse_request(data)\n\n    assert isinstance(results, list)\n    assert results[0].method == 'sum'\n    assert results[0].args == [1, 2, 4]\n    assert results[0].unique_id == \"1\"\n\n    assert results[1].method == 'notify_hello'\n    assert results[1].args == [7]\n    assert results[1].unique_id == None\n\n    assert results[2].method == 'subtract'\n    assert results[2].args == [42, 23]\n    assert results[2].unique_id == \"2\"\n\n    assert isinstance(results[3], JSONRPCInvalidRequestError)\n\n    assert results[4].method == 'foo.get'\n    assert results[4].kwargs == {'name': 'myself'}\n    assert results[4].unique_id == \"5\"\n\n    assert results[5].method == 'get_data'\n    assert results[5].args == []\n    assert results[5].kwargs == {}\n    assert results[5].unique_id == \"9\"\n\n\ndef test_unique_ids(prot):\n    req1 = prot.create_request('foo', [1, 2])\n    req2 = prot.create_request('foo', [1, 2])\n\n    assert req1.unique_id != req2.unique_id\n\n\ndef test_out_of_order(prot):\n    req = prot.create_request('foo', ['a', 'b'], None)\n    rep = req.respond(1)\n\n    assert req.unique_id == rep.unique_id\n\n\ndef test_request_generation(prot):\n    jdata = json.loads(prot.create_request('subtract', [42, 23]).serialize().decode())\n\n    assert jdata['method'] == 'subtract'\n    assert jdata['params'] == [42, 23]\n    assert jdata['id'] != None\n    assert jdata['jsonrpc'] == '2.0'\n\n\ndef test_jsonrpc_spec_v2_example1(prot):\n    # reset id counter\n    from tinyrpc.protocols import default_id_generator\n    prot._id_generator = default_id_generator(1)\n\n    request = prot.create_request('subtract', [42, 23])\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42, 23], \"id\":\n        1}\"\"\",\n        request.serialize()\n    )\n\n    reply = request.respond(19)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 1}\"\"\",\n        reply.serialize()\n    )\n\n    request = prot.create_request('subtract', [23, 42])\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [23, 42], \"id\": 2}\"\"\",\n        request.serialize()\n    )\n\n    reply = request.respond(-19)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"result\": -19, \"id\": 2}\"\"\",\n        reply.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example2(prot):\n    # reset id counter\n    from tinyrpc.protocols import default_id_generator\n    prot._id_generator = default_id_generator(3)\n\n    request = prot.create_request('subtract',\n                                  kwargs={'subtrahend': 23, 'minuend': 42})\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\":\n           {\"subtrahend\": 23, \"minuend\": 42}, \"id\": 3}\"\"\",\n        request.serialize()\n    )\n\n    reply = request.respond(19)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": 3}\"\"\",\n        reply.serialize()\n    )\n\n    request = prot.create_request('subtract',\n                                  kwargs={'subtrahend': 23, 'minuend': 42})\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": {\"minuend\":\n           42, \"subtrahend\": 23}, \"id\": 4}\"\"\",\n        request.serialize()\n    )\n\n    reply = request.respond(-19)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"result\": -19, \"id\": 4}\"\"\",\n        reply.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example3(prot):\n    request = prot.create_request('update', [1, 2, 3, 4, 5], one_way=True)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"update\", \"params\": [1,2,3,4,5]}\"\"\",\n        request.serialize()\n    )\n\n    request = prot.create_request('foobar', one_way=True)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"foobar\"}\"\"\",\n        request.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example4(prot):\n    request = prot.create_request('foobar')\n    request.unique_id = str(1)\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"foobar\", \"id\": \"1\"}\"\"\",\n        request.serialize()\n    )\n\n    response = request.error_respond(MethodNotFoundError('foobar'))\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32601, \"message\":\n           \"Method not found\"}, \"id\": \"1\"}\"\"\",\n           response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example5(prot):\n    try:\n        prot.parse_request(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"method\": \"foobar, \"params\":\n            \"bar\", \"baz]\"\"\")\n        assert False  # parsing must fail\n    except JSONRPCParseError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _json_equal(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32700, \"message\":\n            \"Parse error\"}, \"id\": null}\"\"\",\n            response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example6(prot):\n    try:\n        prot.parse_request(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"method\": 1, \"params\": \"bar\"}\"\"\")\n        assert False  # parsing must fail\n    except JSONRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _json_equal(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\":\n            \"Invalid Request\"}, \"id\": null}\"\"\",\n            response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example6_with_request_id(prot):\n    try:\n        prot.parse_request(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"id\": 42, \"method\": 1, \"params\": \"bar\"}\"\"\")\n        assert False  # parsing must fail\n    except JSONRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _json_equal(\n            \"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\":\n            \"Invalid Request\"}, \"id\": 42}\"\"\",\n            response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example7(prot):\n    try:\n        prot.parse_request(\"\"\"[\n            {\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"},\n            {\"jsonrpc\": \"2.0\", \"method\" ]\"\"\")\n        assert False\n    except JSONRPCParseError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _json_equal(\n        \"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32700, \"message\":\n           \"Parse error\"}, \"id\": null}\"\"\",\n           response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example8(prot):\n    try:\n        prot.parse_request(\"\"\"[]\"\"\")\n        assert False\n    except JSONRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _json_equal(\"\"\"{\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600,\n    \"message\": \"Invalid Request\"}, \"id\": null}\"\"\",\n           response.serialize())\n\n\ndef test_jsonrpc_spec_v2_example9(prot):\n    requests = prot.parse_request(\"\"\"[1]\"\"\")\n\n    assert isinstance(requests[0], JSONRPCInvalidRequestError)\n\n    responses = requests.create_batch_response()\n    responses.append(requests[0].error_respond())\n\n    assert _json_equal(\"\"\"[ {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600,\n                       \"message\": \"Invalid Request\"}, \"id\": null} ]\"\"\",\n           responses.serialize())\n\n\ndef test_jsonrpc_spec_v2_example10(prot):\n    requests = prot.parse_request(\"\"\"[1, 2, 3]\"\"\")\n\n    assert isinstance(requests[0], JSONRPCInvalidRequestError)\n    assert isinstance(requests[1], JSONRPCInvalidRequestError)\n    assert isinstance(requests[2], JSONRPCInvalidRequestError)\n\n    responses = requests.create_batch_response()\n    responses.append(requests[0].error_respond())\n    responses.append(requests[1].error_respond())\n    responses.append(requests[2].error_respond())\n\n    assert _json_equal(\"\"\"[\n  {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": null},\n  {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": null},\n  {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": null}\n]\"\"\",\n           responses.serialize())\n\n\ndef test_jsonrpc_spec_v2_example11(prot):\n    requests = prot.parse_request(\"\"\"[\n        {\"jsonrpc\": \"2.0\", \"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"notify_hello\", \"params\": [7]},\n        {\"jsonrpc\": \"2.0\", \"method\": \"subtract\", \"params\": [42,23], \"id\": \"2\"},\n        {\"foo\": \"boo\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"foo.get\", \"params\": {\"name\": \"myself\"}, \"id\": \"5\"},\n        {\"jsonrpc\": \"2.0\", \"method\": \"get_data\", \"id\": \"9\"}\n    ]\"\"\")\n\n    assert isinstance(requests[3], JSONRPCInvalidRequestError)\n\n    responses = requests.create_batch_response()\n    responses.append(requests[0].respond(7))\n    responses.append(requests[2].respond(19))\n    responses.append(requests[3].error_respond())\n    responses.append(requests[4].error_respond(MethodNotFoundError('foo.get')))\n    responses.append(requests[5].respond(['hello', 5]))\n\n    assert _json_equal(\"\"\"[\n        {\"jsonrpc\": \"2.0\", \"result\": 7, \"id\": \"1\"},\n        {\"jsonrpc\": \"2.0\", \"result\": 19, \"id\": \"2\"},\n        {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": null},\n        {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32601, \"message\": \"Method not found\"}, \"id\": \"5\"},\n        {\"jsonrpc\": \"2.0\", \"result\": [\"hello\", 5], \"id\": \"9\"}\n    ]\"\"\",\n        responses.serialize())\n\n\ndef test_jsonrpc_spec_v2_example12(prot):\n    reqs = []\n    reqs.append(prot.create_request('notify_sum', [1, 2, 4], one_way=True))\n    reqs.append(prot.create_request('notify_hello', [7], one_way=True))\n\n    request = prot.create_batch_request(reqs)\n\n    assert request.create_batch_response() == None\n\n\ndef test_can_get_custom_error_messages_out(prot):\n    request = prot.create_request('foo')\n\n    custom_msg = 'join the army, they said. see the world, they said.'\n\n    e = Exception(custom_msg)\n\n    response = request.error_respond(e)\n\n    jstr = response.serialize()\n    assert isinstance(jstr, bytes)\n    jstr = jstr.decode()\n\n    data = json.loads(jstr)\n\n    assert data['error']['message'] == custom_msg\n\n\ndef test_accepts_empty_but_not_none_args_kwargs(prot):\n    request = prot.create_request('foo', args=[], kwargs={})\n\n\ndef test_missing_jsonrpc_version_on_request(prot):\n    with pytest.raises(JSONRPCInvalidRequestError):\n        prot.parse_request('{\"method\": \"sum\", \"params\": [1,2,4], \"id\": \"1\"}')\n\ndef test_missing_jsonrpc_version_on_reply(prot):\n    with pytest.raises(InvalidReplyError):\n        prot.parse_reply('{\"result\": 7, \"id\": \"1\"}')\n\ndef test_pass_error_data_with_standard_exception(prot):\n    request = prot.create_request('foo')\n\n    custom_msg = 'join the army, they said. see the world, they said.'\n    data = {'pi': 3.14, 'lst': ['a', 'b', 'c']}\n\n    e = Exception(custom_msg, data)\n\n    response = request.error_respond(e)\n    jmsg = response.serialize()\n    assert isinstance(jmsg, bytes)\n    jmsg = jmsg.decode()\n\n    decoded = json.loads(jmsg)\n    print(\"decoded=\", decoded)\n    assert decoded['error']['code'] == -32000\n    assert decoded['error']['message'] == custom_msg\n    assert decoded['error']['data'] == data\n\n    # on the client side, when reply is parsed\n    parsed_reply = prot.parse_reply(jmsg)\n    serialized_reply = parsed_reply.serialize().decode(\"utf-8\")\n    decoded_reply = json.loads(serialized_reply)\n    print(\"decoded_reply=\", decoded_reply)\n    assert isinstance(parsed_reply, JSONRPCErrorResponse)\n    assert hasattr(parsed_reply, \"data\")\n    assert serialized_reply == jmsg\n    assert decoded_reply == decoded\n\ndef test_pass_error_data_with_custom_exception(prot):\n    # type: (JSONRPCProtocol) -> None\n    request = prot.create_request('foo')\n\n    data = {'pi': 3.14, 'lst': ['a', 'b', 'c']}\n\n    e = JSONRPCParseError(data=data)\n\n    response = request.error_respond(e)\n    jmsg = response.serialize()\n    assert isinstance(jmsg, bytes)\n    jmsg = jmsg.decode()\n\n    decoded = json.loads(jmsg)\n    print(\"decoded=\", decoded)\n    assert decoded['error']['code'] == -32700\n    assert decoded['error']['message'] == JSONRPCParseError.message\n    assert decoded['error']['data'] == data\n\n    # on the client side, when reply is parsed\n    parsed_reply = prot.parse_reply(jmsg)\n    serialized_reply = parsed_reply.serialize().decode(\"utf-8\")\n    decoded_reply = json.loads(serialized_reply)\n    print(\"decoded_reply=\", decoded_reply)\n    assert isinstance(parsed_reply, JSONRPCErrorResponse)\n    assert hasattr(parsed_reply, \"data\")\n    assert serialized_reply == jmsg\n    assert decoded_reply == decoded\n"
  },
  {
    "path": "tests/test_msgpackrpc.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport msgpack\nimport pytest\n\nfrom tinyrpc import InvalidReplyError, MethodNotFoundError\nfrom tinyrpc.protocols.msgpackrpc import (\n    MSGPACKRPCParseError,\n    MSGPACKRPCInvalidRequestError,\n    MSGPACKRPCMethodNotFoundError,\n    MSGPACKRPCInvalidParamsError,\n    MSGPACKRPCInternalError,\n)\n\n\ndef _msgpack_equal(a, b):\n    return msgpack.unpackb(a) == msgpack.unpackb(b)\n\n\n@pytest.fixture\ndef prot():\n    from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol\n\n    return MSGPACKRPCProtocol()\n\n\n@pytest.mark.parametrize(\n    (\"data\", \"attrs\"),\n    [\n        # examples from the JSON-RPC spec, translated to MSGPACK, parsing only\n        (\n            b\"\\x94\\x00\\x01\\xa8subtract\\x92*\\x17\",\n            {\"method\": \"subtract\", \"args\": [42, 23], \"unique_id\": 1},\n        ),\n        (\n            b\"\\x94\\x00\\x02\\xa8subtract\\x92\\x17*\",\n            {\"method\": \"subtract\", \"args\": [23, 42], \"unique_id\": 2},\n        ),\n        (\n            b\"\\x93\\x02\\xa6update\\x95\\x01\\x02\\x03\\x04\\x05\",\n            {\"method\": \"update\", \"args\": [1, 2, 3, 4, 5]},\n        ),\n        (b\"\\x93\\x02\\xa6foobar\\x90\", {\"method\": \"foobar\", \"args\": []}),\n    ],\n)\ndef test_parsing_good_request_samples(prot, data, attrs):\n    req = prot.parse_request(data)\n\n    for k, v in attrs.items():\n        assert getattr(req, k) == v\n\n\n@pytest.mark.parametrize(\n    \"invalid_msgpack\",\n    [\n        b\"\\x81\\xa3\\x66\\x6f\\x6f\\xa4\\x62\\x61\\x72\",\n        b\"\\x94\\x00\\x01\\x81\\xa3aaa\\xa3bb\",\n        b\"garbage\",\n    ],\n)\ndef test_parsing_invalid_msgpack(prot, invalid_msgpack):\n    with pytest.raises(MSGPACKRPCParseError):\n        prot.parse_request(invalid_msgpack)\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    [\n        b\"\\xc0\",  # None\n        b\"\\x94\\x00\\xc0\\xa3aaa\\x90\",  # [0, None, \"aaa\", []] - request ID not int\n        b\"\\x95\\x00\\x02\\xa3aaa\\x90\\xc0\",  # [0, 2, \"aaa\", [], None] - too long\n        b\"\\x94\\x02\\xa3aaa\\x90\\xc0\",  # [2, \"aaa\", [], None] - too long\n        b\"\\x93\\x02\\x01\\x90\",  # [2, 1, []] - method name not string\n    ],\n)\ndef test_parsing_valid_msgpack_but_invalid_rpc_message(prot, data):\n    with pytest.raises(MSGPACKRPCInvalidRequestError):\n        prot.parse_request(data)\n\n\n@pytest.mark.parametrize(\n    \"invalid_args\",\n    [\n        b\"\\x94\\x00\\x03\\xa6update\\t\",  # [0, 3, \"update\", 9]\n        b\"\\x94\\x00\\x03\\xa6foobar\\xc0\",  # [0, 3, \"foobar\", None]\n        b\"\\x93\\x02\\xa3aaa\\xc0\",  # [2, \"aaa\", None]\n    ],\n)\ndef test_parsing_invalid_arguments(prot, invalid_args):\n    with pytest.raises(MSGPACKRPCInvalidParamsError):\n        prot.parse_request(invalid_args)\n\n\n@pytest.mark.parametrize(\n    (\"data\", \"id\", \"result\"),\n    [\n        (b\"\\x94\\x01\\x01\\xc0\\x13\", 1, 19),  # [1, 1, None, 19]\n        (b\"\\x94\\x01\\x02\\xc0\\xed\", 2, -19),  # [1, 2, None, -19]\n        (b\"\\x94\\x01\\x03\\xc0\\x13\", 3, 19),  # [1, 3, None, 19]\n        (b\"\\x94\\x01\\x04\\xc0\\x13\", 4, 19),  # [1, 4, None, 19]\n    ],\n)\ndef test_good_reply_samples(prot, data, id, result):\n    reply = prot.parse_reply(data)\n\n    assert reply.unique_id == id\n    assert reply.result == result\n\n\n@pytest.mark.parametrize(\n    (\"data\", \"id\", \"code\", \"message\"),\n    [\n        # Neovim-style\n        (b\"\\x94\\x01\\x05\\x92\\xcd\\x04\\xd2\\xa5Error\\xc0\", 5, 1234, \"Error\"),\n        # Ordinary error string\n        (b\"\\x94\\x01\\x05\\xa5Error\\xc0\", 5, None, \"Error\"),\n        # Two-item list but the types don't match Neovim's style\n        (b\"\\x94\\x01\\x05\\x92\\xa41234\\xa5Error\\xc0\", 5, None, [\"1234\", \"Error\"]),\n    ],\n)\ndef test_good_error_reply_samples(prot, data, id, code, message):\n    reply = prot.parse_reply(data)\n\n    assert reply.unique_id == id\n    assert reply._msgpackrpc_error_code == code\n    assert reply.error == message\n\n\n@pytest.mark.parametrize(\n    (\"exc\", \"code\", \"message\"),\n    [\n        (MSGPACKRPCParseError, -32700, \"Parse error\"),\n        (MSGPACKRPCInvalidRequestError, -32600, \"Invalid request\"),\n        (MSGPACKRPCMethodNotFoundError, -32601, \"Method not found\"),\n        (MSGPACKRPCInvalidParamsError, -32602, \"Invalid params\"),\n        (MSGPACKRPCInternalError, -32603, \"Internal error\"),\n    ],\n)\ndef test_proper_construction_of_error_codes(prot, exc, code, message):\n    reply = exc().error_respond().serialize()\n    assert isinstance(reply, bytes)\n\n    err = msgpack.unpackb(reply, raw=False)\n\n    assert err[0] == 1\n    assert err[2] == [code, message]\n\n\ndef test_notification_yields_None_response(prot):\n    # [2, \"update\", [1,2,3,4,5]]\n    data = b\"\\x93\\x02\\xa6update\\x95\\x01\\x02\\x03\\x04\\x05\"\n\n    req = prot.parse_request(data)\n\n    assert req.one_way is True\n\n    # updates should never cause retries\n    assert req.respond(True) is None\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    [\n        b\"\\x90\",  # \\x90 = []\n        b\"\\x91\\x01\",  # \\x91\\x01 = [1]\n        b\"\\x93\\x01\\x02\\x03\",  # \\x93\\x01\\x02\\x03 = [1, 2, 3]\n        (\n            b\"\\x95\\x94\\x00\\x01\\xa3sum\\x93\\x01\\x02\\x04\"\n            b\"\\x93\\x02\\xacnotify_hello\\x91\\x07\"\n            b\"\\x94\\x00\\x02\\xa8subtract\\x92*\\x17\"\n            b\"\\x94\\x00\\x05\\xa7foo.get\\x81\\xa4name\\xa6myself\"\n            b\"\\x94\\x00\\t\\xa8get_data\\xc0\"\n        ),\n    ],\n)\ndef test_batch_examples(prot, data):\n    with pytest.raises(MSGPACKRPCInvalidRequestError):\n        prot.parse_request(data)\n\n\ndef test_unique_ids(prot):\n    req1 = prot.create_request(\"foo\", [1, 2])\n    req2 = prot.create_request(\"foo\", [1, 2])\n\n    assert req1.unique_id != req2.unique_id\n\n\ndef test_out_of_order(prot):\n    req = prot.create_request(\"foo\", [\"a\", \"b\"], None)\n    rep = req.respond(1)\n\n    assert req.unique_id == rep.unique_id\n\n\ndef test_request_generation(prot):\n    data = msgpack.unpackb(\n        prot.create_request(\"subtract\", [42, 23]).serialize(), raw=False\n    )\n\n    assert data[0] == 0\n    assert isinstance(data[1], int)\n    assert data[2] == \"subtract\"\n    assert data[3] == [42, 23]\n\n\n# The tests below are adapted from the JSON-RPC specification, hence their names\n\n\ndef test_jsonrpc_spec_v2_example1(prot):\n    # reset id counter\n    from tinyrpc.protocols import default_id_generator\n    prot._id_generator = default_id_generator(1)\n\n    request = prot.create_request(\"subtract\", [42, 23])\n\n    assert request.serialize() == b\"\\x94\\x00\\x01\\xa8subtract\\x92*\\x17\"\n\n    reply = request.respond(19)\n\n    assert reply.serialize() == b\"\\x94\\x01\\x01\\xc0\\x13\"\n\n    request = prot.create_request(\"subtract\", [23, 42])\n\n    assert request.serialize() == b\"\\x94\\x00\\x02\\xa8subtract\\x92\\x17*\"\n\n    reply = request.respond(-19)\n\n    assert reply.serialize() == b\"\\x94\\x01\\x02\\xc0\\xed\"\n\n\ndef test_jsonrpc_spec_v2_example3(prot):\n    request = prot.create_request(\"update\", [1, 2, 3, 4, 5], one_way=True)\n\n    assert request.serialize() == b\"\\x93\\x02\\xa6update\\x95\\x01\\x02\\x03\\x04\\x05\"\n\n    request = prot.create_request(\"foobar\", one_way=True)\n\n    assert request.serialize() == b\"\\x93\\x02\\xa6foobar\\x90\"\n\n\ndef test_jsonrpc_spec_v2_example4(prot):\n    request = prot.create_request(\"foobar\")\n    request.unique_id = 1\n\n    assert request.serialize() == b\"\\x94\\x00\\x01\\xa6foobar\\x90\"\n\n    response = request.error_respond(MethodNotFoundError(\"foobar\"))\n\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\x01\\x92\\xd1\\x80\\xa7\\xb0Method not found\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example5(prot):\n    try:\n        prot.parse_request(b\"\\x94\\x00\\x01\\x81\\xa3aaa\\xa3bb\")\n        assert False  # parsing must fail\n    except MSGPACKRPCParseError as error:\n        e = error\n\n    response = e.error_respond()\n\n    # TODO(ntamas): here we are sending None as the request ID because\n    # obviously we could not parse it from a malformed request. We need to\n    # decide whether this is valid MSGPACK or not.\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\xc0\\x92\\xd1\\x80D\\xabParse error\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example6(prot):\n    try:\n        prot.parse_request(b\"\\x94\\x00\\x01\\x01\\xa3bar\")\n        assert False  # parsing must fail\n    except MSGPACKRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\x01\\x92\\xd1\\x80\\xa8\\xafInvalid request\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example8(prot):\n    try:\n        prot.parse_request(b\"\\x90\")\n        assert False\n    except MSGPACKRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\xc0\\x92\\xd1\\x80\\xa8\\xafInvalid request\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example9(prot):\n    try:\n        prot.parse_request(b\"\\x91\\x01\")\n        assert False\n    except MSGPACKRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\xc0\\x92\\xd1\\x80\\xa8\\xafInvalid request\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example10(prot):\n    try:\n        prot.parse_request(b\"\\x93\\x01\\x02\\x03\")\n        assert False\n    except MSGPACKRPCInvalidRequestError as error:\n        e = error\n\n    response = e.error_respond()\n\n    assert _msgpack_equal(\n        b\"\\x94\\x01\\xc0\\x92\\xd1\\x80\\xa8\\xafInvalid request\\xc0\", response.serialize()\n    )\n\n\ndef test_jsonrpc_spec_v2_example11(prot):\n    # Since MSGPACK does not support batched request, we test the requests\n    # one by one\n    requests = []\n\n    for data in [\n        b\"\\x94\\x00\\x01\\xa3sum\\x93\\x01\\x02\\x04\",  # [0, 1, \"sum\", [1,2,4]]\n        b\"\\x93\\x02\\xacnotify_hello\\x91\\x07\",  # [2, \"notify_hello\", [7]\n        b\"\\x94\\x00\\x02\\xa8subtract\\x92*\\x17\",  # [0, 2, \"subtract\", [42,23]]\n        b\"\\x92\\xa3foo\\xa3boo\",  # [\"foo\", \"boo\"]\n        b\"\\x94\\x00\\x05\\xa7foo.get\\x92\\xa4name\\xa6myself\",  # [0, 5, \"foo.get\", [\"name\", \"myself\"]]\n        b\"\\x94\\x00\\t\\xa8get_data\\x90\",  # [0, 9, \"get_data\", []]\n    ]:\n        try:\n            requests.append(prot.parse_request(data))\n        except Exception as ex:\n            requests.append(ex)\n\n    assert isinstance(requests[3], MSGPACKRPCInvalidRequestError)\n\n    responses = []\n    responses.append(requests[0].respond(7))\n    responses.append(requests[1].error_respond(MethodNotFoundError(\"notify_hello\")))\n    responses.append(requests[2].respond(19))\n    responses.append(requests[3].error_respond())\n    responses.append(requests[4].error_respond(MethodNotFoundError(\"foo.get\")))\n    responses.append(requests[5].respond([\"hello\", 5]))\n\n    responses = [\n        response.serialize() if response else response for response in responses\n    ]\n\n    assert responses[0] == b\"\\x94\\x01\\x01\\xc0\\x07\"\n    assert responses[1] is None\n    assert responses[2] == b\"\\x94\\x01\\x02\\xc0\\x13\"\n    assert responses[3] == b\"\\x94\\x01\\xc0\\x92\\xd1\\x80\\xa8\\xafInvalid request\\xc0\"\n    assert responses[4] == b\"\\x94\\x01\\x05\\x92\\xd1\\x80\\xa7\\xb0Method not found\\xc0\"\n    assert responses[5] == b\"\\x94\\x01\\t\\xc0\\x92\\xa5hello\\x05\"\n\n\ndef test_can_get_custom_error_messages_out(prot):\n    request = prot.create_request(\"foo\")\n\n    custom_msg = \"join the army, they said. see the world, they said.\"\n\n    e = Exception(custom_msg)\n\n    response = request.error_respond(e)\n\n    data = response.serialize()\n    assert isinstance(data, bytes)\n\n    decoded = msgpack.unpackb(data, raw=False)\n\n    assert decoded[0] == 1\n    assert decoded[1] == request.unique_id\n    assert isinstance(decoded[2], list)\n    assert decoded[2][1] == custom_msg\n\n\ndef test_accepts_empty_but_not_none_args(prot):\n    prot.create_request(\"foo\", args=[])\n\n\ndef test_rejects_nonempty_kwargs(prot):\n    with pytest.raises(MSGPACKRPCInvalidRequestError):\n        prot.create_request(\"foo\", kwargs={\"foo\": \"bar\"})\n\n\ndef test_accepts_empty_kwargs(prot):\n    prot.create_request(\"foo\", kwargs={})\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    [\n        b\"\\x97\\x01\",  # complete garbage\n        b\"\\x93\\x01\\xc0\\xa5hello\",  # too short\n        b\"\\x94\\x00\\x01\\xc0\\xa5hello\",  # not a reply (message type is request)\n        b\"\\x94\\x01\\xc0\\xc0\\xa5hello\",  # missing message ID in response\n        b\"\\x94\\x01\\x01\\xa5hello\\xa5hello\",  # contains error _and_ result\n    ],\n)\ndef test_invalid_replies(prot, data):\n    with pytest.raises(InvalidReplyError):\n        prot.parse_reply(data)\n"
  },
  {
    "path": "tests/test_protocols.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc import RPCErrorResponse\n\n\n@pytest.fixture(params=['jsonrpc'])\ndef protocol(request):\n    if 'jsonrpc':\n        return JSONRPCProtocol()\n\n    raise RuntimeError('Bad protocol name in test case')\n\n\ndef test_protocol_returns_bytes(protocol):\n    req = protocol.create_request('foo', ['bar'])\n\n    assert isinstance(req.serialize(), bytes)\n\ndef test_procotol_responds_bytes(protocol):\n    req = protocol.create_request('foo', ['bar'])\n    rep = req.respond(42)\n    err_rep = req.error_respond(Exception('foo'))\n\n    assert isinstance(rep.serialize(), bytes)\n    assert isinstance(err_rep.serialize(), bytes)\n\n\ndef test_one_way(protocol):\n    req = protocol.create_request('foo', None, {'a': 'b'}, True)\n\n    assert req.respond(None) == None\n\n\ndef test_raises_on_args_and_kwargs(protocol):\n    with pytest.raises(Exception):\n        protocol.create_request('foo', ['arg1', 'arg2'], {'kw_key': 'kw_value'})\n\n\ndef test_supports_no_args(protocol):\n        protocol.create_request('foo')\n\n\ndef test_creates_error_response(protocol):\n    req = protocol.create_request('foo', ['bar'])\n    err_rep = req.error_respond(Exception('foo'))\n\n    assert hasattr(err_rep, 'error')\n\n\ndef test_parses_error_response(protocol):\n    req = protocol.create_request('foo', ['bar'])\n    err_rep = req.error_respond(Exception('foo'))\n\n    parsed = protocol.parse_reply(err_rep.serialize())\n\n    assert hasattr(parsed, 'error')\n\ndef test_default_id_generator():\n    from tinyrpc.protocols import default_id_generator\n    g = default_id_generator(1)\n    assert next(g) == 1\n    assert next(g) == 2\n    assert next(g) == 3\n"
  },
  {
    "path": "tests/test_rabbitmq_transport.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import patch\n\nfrom tinyrpc.transports.rabbitmq import RabbitMQServerTransport, RabbitMQClientTransport\n\nFAKE_REQUEST_MSG = b'a fake request message'\nFAKE_RESPONSE_MSG = b'a fake response message'\nFAKE_MESSAGE_DATA = b'some fake message data'\nTEST_QUEUE = 'test_queue'\nTEST_ROUTE = 'test_route'\n\nclass DummyBlockingConnection:\n    class DummyChannel:\n        class GenericObject(object):\n            pass\n\n        def __init__(self):\n            self.properties = self.GenericObject()\n            self.properties.reply_to = \"reply_to\"\n            self.properties.correlation_id = \"correlation_id\"\n\n        def queue_declare(self, *args, **kwargs):\n            result = self.GenericObject()\n            result.method = self.GenericObject()\n            result.method.queue = \"queue_id\"\n            return result\n\n        def basic_consume(self, on_message_callback, *args, **kwargs):\n            self.on_message_callback = on_message_callback\n\n        def basic_publish(self, properties, *args, **kwargs):\n            self.properties = properties\n\n        def basic_ack(self, *args, **kwargs):\n            pass\n\n    def __init__(self, *args, **kwargs):\n        pass\n\n    def channel(self):\n        self.channel = self.DummyChannel()\n        return self.channel\n\n    def process_data_events(self):\n        fake_response = FAKE_MESSAGE_DATA\n        method = self.DummyChannel.GenericObject()\n        method.delivery_tag = \"delivery_tag\"\n        self.channel.on_message_callback(self.channel, method, self.channel.properties, fake_response)\n\n@pytest.fixture\ndef dummy_blockingconnection():\n    return DummyBlockingConnection()\n\n@pytest.fixture\ndef rabbitmq_server(dummy_blockingconnection):\n    return RabbitMQServerTransport(dummy_blockingconnection, TEST_QUEUE)\n\n@pytest.fixture\ndef rabbitmq_client(dummy_blockingconnection):\n    return RabbitMQClientTransport(dummy_blockingconnection, TEST_ROUTE)\n\n@patch('pika.BlockingConnection', DummyBlockingConnection)\ndef test_can_create_rabbitmq_server():\n    RabbitMQServerTransport.create(\"localhost\", TEST_QUEUE)\n\n@patch('pika.BlockingConnection', DummyBlockingConnection)\ndef test_can_create_rabbitmq_client():\n    RabbitMQClientTransport.create(\"localhost\", TEST_ROUTE)\n\ndef test_server_can_receive_message(rabbitmq_server):\n    context, message = rabbitmq_server.receive_message()\n    assert context\n    assert message == FAKE_MESSAGE_DATA\n\ndef test_server_can_send_reply(rabbitmq_server):\n    context, message = rabbitmq_server.receive_message()\n    assert context\n    assert message == FAKE_MESSAGE_DATA\n    rabbitmq_server.send_reply(context, FAKE_RESPONSE_MSG)\n\ndef test_client_can_send_message(rabbitmq_client):\n    response = rabbitmq_client.send_message(FAKE_REQUEST_MSG, expect_reply=False)\n    assert response is None\n\ndef test_client_can_send_message_and_get_reply(rabbitmq_client):\n    response = rabbitmq_client.send_message(FAKE_REQUEST_MSG, expect_reply=True)\n    assert response == FAKE_MESSAGE_DATA\n"
  },
  {
    "path": "tests/test_server.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import Mock, call\n\nfrom tinyrpc.server import RPCServer\nfrom tinyrpc.transports import ServerTransport\nfrom tinyrpc.protocols import RPCProtocol, RPCResponse\nfrom tinyrpc.dispatch import RPCDispatcher\n\n\nCONTEXT='sapperdeflap'\nRECMSG='out of receive_message'\nPARMSG='out of parse_request'\nSERMSG='out of serialize'\n\n@pytest.fixture\ndef transport():\n    transport = Mock(ServerTransport)\n    transport.receive_message = Mock(return_value=(CONTEXT, RECMSG))\n    return transport\n\n@pytest.fixture\ndef protocol():\n    protocol = Mock(RPCProtocol)\n    protocol.parse_request = Mock(return_value=PARMSG)\n    return protocol\n\n@pytest.fixture()\ndef response():\n    response = Mock(RPCResponse)\n    response.serialize = Mock(return_value=SERMSG)\n    return response\n\n@pytest.fixture\ndef dispatcher(response):\n    dispatcher = Mock(RPCDispatcher)\n    dispatcher.dispatch = Mock(return_value=response)\n    return dispatcher\n\ndef test_handle_message(transport, protocol, dispatcher):\n    server = RPCServer(transport, protocol, dispatcher)\n    server.receive_one_message()\n\n    transport.receive_message.assert_called()\n    protocol.parse_request.assert_called_with(RECMSG)\n    dispatcher.dispatch.assert_called_with(PARMSG, None)\n    dispatcher.dispatch().serialize.assert_called()\n    transport.send_reply.assert_called_with(CONTEXT, SERMSG)\n\ndef test_handle_message_callback(transport, protocol, dispatcher):\n    server = RPCServer(transport, protocol, dispatcher)\n    server.trace = Mock(return_value=None)\n    server.receive_one_message()\n\n    assert server.trace.call_args_list == [call('-->', CONTEXT, RECMSG), call('<--', CONTEXT, SERMSG)]\n    server.trace.assert_called()\n"
  },
  {
    "path": "tests/test_transport.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\nimport zmq\nimport zmq.green\n\nfrom tinyrpc.transports import ServerTransport, ClientTransport\nfrom tinyrpc.transports.zmq import ZmqServerTransport, ZmqClientTransport\n\n\nclass DummyServerTransport(ServerTransport):\n    def __init__(self):\n        self.messages = []\n        self.clients = {}\n\n    def receive_message(self):\n        return self.messages.pop()\n\n    def send_reply(self, context, message):\n        if not isinstance(message, str):\n            raise TypeError('Message must be str().')\n        self.clients[context].messages.append(message)\n\n\nclass DummyClientTransport(ClientTransport):\n    def __init__(self, server):\n        self.server = server\n        self.id = id(self)\n        self.server.clients[self.id] = self\n        self.messages = []\n\n    def send_message(self, message):\n        if not isinstance(message, str):\n            raise TypeError('Message must be str().')\n        self.server.messages.append((self.id, message))\n\n    def receive_reply(self):\n        return self.messages.pop()\n\n\nZMQ_ENDPOINT = 'inproc://example2'\n\n\n@pytest.fixture(scope='session')\ndef zmq_context(request):\n    ctx = zmq.Context()\n    def fin():\n        request.addfinalizer(ctx.destroy())\n    return ctx\n\n\n@pytest.fixture(scope='session')\ndef zmq_green_context(request):\n    ctx = zmq.Context()\n    def fin():\n        request.addfinalizer(ctx.destroy())\n    return ctx\n\n\n# zmq and zmq.green fail on python3\nSERVERS=['dummy']\n\n@pytest.fixture(params=SERVERS)\ndef transport(request, zmq_context, zmq_green_context):\n    if request.param == 'dummy':\n        server = DummyServerTransport()\n        client = DummyClientTransport(server)\n    elif request.param in ('zmq', 'zmq.green'):\n        ctx = zmq_context if request.param == 'zmq' else zmq_green_context\n\n        server = ZmqServerTransport.create(ctx, ZMQ_ENDPOINT)\n        client = ZmqClientTransport.create(ctx, ZMQ_ENDPOINT)\n\n        def fin():\n            server.socket.close()\n            client.socket.close()\n\n        request.addfinalizer(fin)\n    else:\n        raise ValueError('Invalid transport.')\n    return (client, server)\n\nSAMPLE_MESSAGES = ['asdf', 'loremipsum' * 1500, '', '\\x00', 'b\\x00a', '\\r\\n',\n                   '\\n', '\\u1234'.encode('utf8')]\nBAD_MESSAGES = [b'asdf', b'', 1234, 1.2, None, True, False, ('foo',)]\n\n\n@pytest.fixture(scope='session',\n                params=SAMPLE_MESSAGES)\ndef sample_msg(request):\n    return request.param\n\n\n@pytest.fixture(scope='session',\n                params=SAMPLE_MESSAGES)\ndef sample_msg2(request):\n    return request.param\n\n\n@pytest.fixture(scope='session',\n                params=BAD_MESSAGES)\ndef bad_msg(request):\n    return request.param\n\ndef test_transport_rejects_bad_values(transport, bad_msg):\n    client, server = transport\n   \n    with pytest.raises(TypeError):\n        client.send_message(bad_msg)\n\n\n# FIXME: these tests need to be rethought, as they no longer work properly with\n# the change to the interface of ClientTransport\n\n# FIXME: the actual client needs tests as well\n"
  },
  {
    "path": "tests/test_wsgi_transport.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\n\nimport gevent\nimport gevent.queue\nimport gevent.monkey\nfrom gevent.pywsgi import WSGIServer\nimport requests\n\nfrom importlib import reload\n\nfrom tinyrpc.transports.wsgi import WsgiServerTransport\nfrom tinyrpc.transports.http import HttpPostClientTransport\n\nTEST_SERVER_ADDR = ('127.0.0.1', 49294)\n\n\n@pytest.fixture(scope='module', autouse=True)\ndef monkey_patches(request):\n    # ugh? ugh. ugh. ugh!\n    import socket\n    gevent.monkey.patch_all(\n        socket=True,\n        dns=False,\n        time=False,\n        select=False,\n        thread=False,\n        os=True,\n        httplib=False,\n        ssl=False,\n        aggressive=False)\n\n    def fin():\n        reload(socket)\n\n    request.addfinalizer(fin)\n\n\n@pytest.fixture()\ndef wsgi_server(request):\n    app = WsgiServerTransport(queue_class=gevent.queue.Queue)\n\n    server = WSGIServer(TEST_SERVER_ADDR, app.handle)\n\n    def fin():\n        server.stop()\n        server_greenlet.join()\n\n    request.addfinalizer(fin)\n    server_greenlet = gevent.spawn(server.serve_forever)\n    gevent.sleep(0)  # wait for server to come up\n\n    return (app, 'http://%s:%d' % TEST_SERVER_ADDR)\n\n\ndef test_server_supports_post_only(wsgi_server):\n    transport, addr = wsgi_server\n\n    r = requests.get(addr)\n\n    # we expect a \"not supported\" response\n    assert r.status_code == 405\n\n    r = requests.head(addr)\n\n    # we expect a \"not supported\" response\n    assert r.status_code == 405\n\n\n@pytest.mark.parametrize(('msg',),\n    [(b'foo',), (b'',), (b'bar',), (b'1234',), (b'{}',), (b'{',), (b'\\x00\\r\\n',)])\ndef test_server_receives_messages(wsgi_server, msg):\n    transport, addr = wsgi_server\n\n    def consumer():\n        context, received_msg = transport.receive_message()\n        assert received_msg == msg\n        reply = b'reply:' + msg\n        transport.send_reply(context, reply)\n\n    gevent.spawn(consumer)\n\n    r = requests.post(addr, data=msg)\n\n    assert r.content == b'reply:' + msg\n\n\n@pytest.fixture\ndef sessioned_client():\n    session = requests.Session()\n    adapter = requests.adapters.HTTPAdapter(pool_maxsize=100)\n    session.mount('http://', adapter)\n    client = HttpPostClientTransport(\n        'http://%s:%d' % TEST_SERVER_ADDR,\n        post_method=session.post\n    )\n    return client\n\n\n@pytest.fixture\ndef non_sessioned_client():\n    client = HttpPostClientTransport('http://%s:%d' % TEST_SERVER_ADDR)\n    return client\n\n\n@pytest.mark.parametrize(('msg',),\n    [(b'foo',), (b'',), (b'bar',), (b'1234',), (b'{}',), (b'{',), (b'\\x00\\r\\n',)])\ndef test_sessioned_http_sessioned_client(wsgi_server, sessioned_client, msg):\n    transport, addr = wsgi_server\n\n    def consumer():\n        context, received_msg = transport.receive_message()\n        assert received_msg == msg\n        reply = b'reply:' + msg\n        transport.send_reply(context, reply)\n\n    gevent.spawn(consumer)\n\n    result = sessioned_client.send_message(msg)\n    assert result == b'reply:' + msg\n\n\n@pytest.mark.skip('somehow fails on travis')\ndef test_exhaust_ports(wsgi_server, non_sessioned_client):\n    \"\"\"\n    This raises a\n    > ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=49294):\n    >    Max retries exceeded with url: / (Caused by\n    >    NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection\n    >    object at 0x7f6f86246210>: Failed to establish a new connection:\n    >    [Errno 99] Cannot assign requested address',))\n    \"\"\"\n\n    transport, addr = wsgi_server\n\n    def consumer():\n        context, received_msg = transport.receive_message()\n        reply = b'reply:' + received_msg\n        transport.send_reply(context, reply)\n\n    def send_and_receive(i):\n        try:\n            gevent.spawn(consumer)\n            msg = b'msg_%s' % i\n            result = non_sessioned_client.send_message(msg)\n            return result == b'reply:' + msg\n        except Exception as e:\n            return e\n\n    pool = gevent.pool.Pool(500)\n\n    with pytest.raises(requests.ConnectionError):\n        for result in pool.imap_unordered(send_and_receive, range(55000)):\n            assert result\n            if isinstance(result, Exception):\n                raise result\n"
  },
  {
    "path": "tinyrpc/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom .protocols import *\nfrom .exc import *\nfrom .client import *\n"
  },
  {
    "path": "tinyrpc/client.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport sys\nfrom collections import namedtuple\nfrom typing import List, Any, Dict, Callable, Optional\n\nfrom .transports import ClientTransport\nfrom .exc import RPCError\nfrom .protocols import RPCErrorResponse, RPCProtocol, RPCRequest, RPCResponse, RPCBatchResponse\n\nRPCCall = namedtuple('RPCCall', 'method args kwargs')\n\"\"\"Defines the elements of an RPC call.\n\nRPCCall is used with :py:meth:`~tinyrpc.client.RPCClient.call_all`\nto provide the list of requests to be processed. Each request contains the\nelements defined in this tuple.\n\"\"\"\n\nRPCCallTo = namedtuple('RPCCallTo', 'transport method args kwargs')\n\"\"\"Defines the elements of a RPC call directed to multiple transports.\n\nRPCCallTo is used with :py:meth:`~tinyrpc.client.RPCClient.call_all`\nto provide the list of requests to be processed.\n\"\"\"\n\n\nclass RPCClient(object):\n    \"\"\"Client for making RPC calls to connected servers.\n\n    :param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.\n    :type protocol: RPCProtocol\n    :param transport: The data transport mechanism\n    :type transport: ClientTransport\n    \"\"\"\n    def __init__(\n            self, protocol: RPCProtocol, transport: ClientTransport\n    ) -> None:\n        self.protocol = protocol\n        self.transport = transport\n\n    def _send_and_handle_reply(\n            self,\n            req: RPCRequest,\n            one_way: bool = False,\n            transport: ClientTransport = None,\n            no_exception: bool = False\n    ) -> Optional[RPCResponse]:\n        tport = self.transport if transport is None else transport\n\n        # sends ...\n        reply = tport.send_message(req.serialize(), expect_reply=(not one_way))\n\n        if one_way:\n            # ... and be done\n            return\n\n        # ... or process the reply\n        response = self.protocol.parse_reply(reply)\n\n        if not no_exception and isinstance(response, RPCErrorResponse):\n            if hasattr(self.protocol, 'raise_error') and callable(\n                    self.protocol.raise_error):\n                response = self.protocol.raise_error(response)\n            else:\n                raise RPCError(\n                    'Error calling remote procedure: %s' % response.error\n                )\n\n        return response\n\n    def call(\n            self, method: str, args: List, kwargs: Dict, one_way: bool = False\n    ) -> Any:\n        \"\"\"Calls the requested method and returns the result.\n\n        If an error occurred, an :py:class:`~tinyrpc.exc.RPCError` instance\n        is raised.\n\n        :param str method: Name of the method to call.\n        :param list args: Arguments to pass to the method.\n        :param dict kwargs: Keyword arguments to pass to the method.\n        :param bool one_way: Whether or not a reply is desired.\n        :return: The result of the call\n        :rtype: any\n        \"\"\"\n        req = self.protocol.create_request(method, args, kwargs, one_way)\n\n        rep = self._send_and_handle_reply(req, one_way)\n\n        if one_way:\n            return\n\n        return rep.result\n\n    def call_all(self, requests: List[RPCCall]) -> List[Any]:\n        \"\"\"Calls the methods in the request in parallel.\n\n        When the :py:mod:`gevent` module is already loaded it is assumed to be\n        correctly initialized, including monkey patching if necessary.\n        In that case the RPC calls defined by ``requests`` are performed in\n        parallel otherwise the methods are called sequentially.\n\n        :param requests: A list of either :py:class:`~tinyrpc.client.RPCCall` or :py:class:`~tinyrpc.client.RPCCallTo`\n                         elements.\n                         When RPCCallTo is used each element defines a transport.\n                         Otherwise the default transport set when RPCClient is\n                         created is used.\n        :return: A list with replies matching the order of the requests.\n        \"\"\"\n        threads = []\n\n        if 'gevent' in sys.modules:\n            # assume that gevent is available and functional, make calls in parallel\n            import gevent\n            for r in requests:\n                req = self.protocol.create_request(r.method, r.args, r.kwargs)\n                tr = r.transport.transport if len(r) == 4 else None\n                threads.append(\n                    gevent.spawn(\n                        self._send_and_handle_reply, req, False, tr, True\n                    )\n                )\n            gevent.joinall(threads)\n            return [t.value for t in threads]\n        else:\n            # call serially\n            for r in requests:\n                req = self.protocol.create_request(r.method, r.args, r.kwargs)\n                tr = r.transport.transport if len(r) == 4 else None\n                threads.append(\n                    self._send_and_handle_reply(req, False, tr, True)\n                )\n            return threads\n\n    def get_proxy(self, prefix: str = '', one_way: bool = False) -> 'RPCProxy':\n        \"\"\"Convenience method for creating a proxy.\n\n        :param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.\n        :param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.\n        :return: :py:class:`~tinyrpc.client.RPCProxy` instance.\n        \"\"\"\n        return RPCProxy(self, prefix, one_way)\n\n    def batch_call(self, calls: List[RPCCallTo]) -> RPCBatchResponse:\n        \"\"\"Experimental, use at your own peril.\"\"\"\n        req = self.protocol.create_batch_request()\n\n        for call_args in calls:\n            req.append(self.protocol.create_request(*call_args))\n\n        return self._send_and_handle_reply(req)\n\n\nclass RPCProxy(object):\n    \"\"\"Create a new remote proxy object.\n\n    Proxies allow calling of methods through a simpler interface. See the\n    documentation for an example.\n\n    :param client: An :py:class:`~tinyrpc.client.RPCClient` instance.\n    :param prefix: Prefix to prepend to every method name.\n    :param one_way: Passed to every call of\n                    :py:func:`~tinyrpc.client.call`.\n    \"\"\"\n    def __init__(\n            self, client: RPCClient, prefix: str = '', one_way: bool = False\n    ) -> None:\n        self.client = client\n        self.prefix = prefix\n        self.one_way = one_way\n\n    def __getattr__(self, name: str) -> Callable:\n        \"\"\"Returns a proxy function that, when called, will call a function\n        name ``name`` on the client associated with the proxy.\n        \"\"\"\n        proxy_func = lambda *args, **kwargs: self.client.call(\n            self.prefix + name, args, kwargs, one_way=self.one_way\n        )\n        return proxy_func\n"
  },
  {
    "path": "tinyrpc/dispatch/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nDispatcher\n==========\n\nGiven an RPC request the dispatcher will try to locate a server function that\nimplements the request and will call that function returning its return value\nto the caller.\n\"\"\"\n\nimport inspect\nfrom typing import Callable, Any, Dict, List, Optional, TypeVar, Union, overload\n\nfrom tinyrpc import RPCRequest, RPCResponse, RPCBatchRequest, RPCBatchResponse\nfrom .. import exc\n\n\nT = TypeVar(\"T\")\n\n\n@overload\ndef public(name: Callable[..., T]) -> Callable[..., T]:\n    ...\n\n@overload\ndef public(name: Optional[str] = None) -> Callable[[Callable[..., T]], Callable[..., T]]:\n    ...\n\ndef public(name = None):\n    # noinspection SpellCheckingInspection\n    \"\"\"Decorator. Mark a method as eligible for registration by a dispatcher.\n\n        The dispatchers :py:func:`~tinyrpc.dispatch.RPCDispatcher.register_instance` function\n        will do the actual registration of the marked method.\n\n        The difference with :py:func:`~tinyrpc.dispatch.RPCDispatcher.public` is that this decorator does\n        not register with a dispatcher, therefore binding the marked methods with a dispatcher is delayed\n        until runtime.\n        It also becomes possible to bind with multiple dispatchers.\n\n        :param name: The name to register the function with.\n\n        Example:\n\n        .. code-block:: python\n\n            def class Baz(object):\n                def not_exposed(self);\n                    # ...\n\n                @public('do_something')\n                def visible_method(self, arg1):\n                    # ...\n\n            baz = Baz()\n            dispatch = RPCDispatcher()\n            dispatch.register_instance(baz, 'bazzies`)\n            # Baz.visible_method is now callable via RPC as bazzies.do_something('hello')\n\n        ``@public`` is a shortcut for ``@public()``.\n        \"\"\"\n    if callable(name):\n        f = name\n        f._rpc_public_name = f.__name__\n        return f\n\n    def _(f):\n        f._rpc_public_name = name or f.__name__\n        return f\n\n    return _\n\n\nclass RPCDispatcher(object):\n    \"\"\"Stores name-to-method mappings.\"\"\"\n    def __init__(self) -> None:\n        self.method_map = {}\n        self.subdispatchers = {}\n\n    @overload\n    def public(self, name: Callable[..., T]) -> Callable[..., T]:\n        ...\n\n    @overload\n    def public(self, name: Optional[str] = None) -> Callable[[Callable[..., T]], Callable[..., T]]:\n        ...\n\n    def public(self, name = None):\n        \"\"\"Convenient decorator.\n\n        Allows easy registering of functions to this dispatcher. Example:\n\n        .. code-block:: python\n\n            dispatch = RPCDispatcher()\n\n            @dispatch.public\n            def foo(bar):\n                # ...\n\n            class Baz(object):\n                def not_exposed(self):\n                    # ...\n\n                @dispatch.public(name='do_something')\n                def visible_method(arg1)\n                    # ...\n\n        :param str name: Name to register callable with.\n        \"\"\"\n        if callable(name):\n            self.add_method(name)\n            return name\n\n        def _(f):\n            self.add_method(f, name=name)\n            return f\n\n        return _\n\n    def add_subdispatch(self, dispatcher: 'RPCDispatcher', prefix: str = ''):\n        \"\"\"Adds a subdispatcher, possibly in its own namespace.\n\n        :param dispatcher: The dispatcher to add as a subdispatcher.\n        :type dispatcher: RPCDispatcher\n        :param str prefix: A prefix. All of the new subdispatchers methods will be\n                       available as prefix + their original name.\n        \"\"\"\n        self.subdispatchers.setdefault(prefix, []).append(dispatcher)\n\n    def add_method(self, f: Callable, name: str = None) -> None:\n        \"\"\"Add a method to the dispatcher.\n\n        :param f: Callable to be added.\n        :type f: callable\n        :param str name: Name to register it with. If ``None``, ``f.__name__`` will\n                     be used.\n        :raises ~tinyrpc.exc.RPCError: When the `name` is already registered.\n        \"\"\"\n        assert callable(f), \"method argument must be callable\"\n        # catches a few programming errors that are\n        # commonly silently swallowed otherwise\n        if not name:\n            name = f.__name__\n\n        if name in self.method_map:\n            raise exc.RPCError('Name \\'{}\\' already registered'.format(name))\n\n        self.method_map[name] = f\n\n    def get_method(self, name: str) -> Callable:\n        \"\"\"Retrieve a previously registered method.\n\n        Checks if a method matching ``name`` has been registered.\n\n        If :py:func:`get_method` cannot find a method, every subdispatcher\n        with a prefix matching the method name is checked as well.\n\n        :param str name: Function to find.\n        :returns: The callable implementing the function.\n        :rtype: callable\n        :raises: :py:exc:`~tinyrpc.exc.MethodNotFoundError`\n        \"\"\"\n        if name in self.method_map:\n            return self.method_map[name]\n\n        for prefix, subdispatchers in self.subdispatchers.items():\n            if name.startswith(prefix):\n                for sd in subdispatchers:\n                    try:\n                        return sd.get_method(name[len(prefix):])\n                    except exc.MethodNotFoundError:\n                        pass\n\n        raise exc.MethodNotFoundError(name)\n\n    def register_instance(self, obj: object, prefix: str = '') -> None:\n        \"\"\"Create new subdispatcher and register all public object methods on\n        it.\n\n        To be used in conjunction with the :py:func:`public`\n        decorator (*not* :py:func:`RPCDispatcher.public`).\n\n        :param obj: The object whose public methods should be made available.\n        :type obj: object\n        :param str prefix: A prefix for the new subdispatcher.\n        \"\"\"\n        dispatch = self.__class__()  # type: 'RPCDispatcher'\n        for name, f in inspect.getmembers(\n                obj, lambda f: callable(f) and hasattr(f, '_rpc_public_name')):\n            dispatch.add_method(f, f._rpc_public_name)\n\n        # add to dispatchers\n        self.add_subdispatch(dispatch, prefix)\n\n    def dispatch(\n            self,\n            request: Union[RPCRequest, RPCBatchRequest],\n            caller: Callable = None\n    ) -> Union[RPCResponse, RPCBatchResponse]:\n        \"\"\"Fully handle request.\n\n        The dispatch method determines which method to call, calls it and\n        returns a response containing a result.\n\n        No exceptions will be thrown, rather, every exception will be turned\n        into a response using :py:func:`~tinyrpc.RPCRequest.error_respond`.\n\n        If a method isn't found, a :py:exc:`~tinyrpc.exc.MethodNotFoundError`\n        response will be returned. If any error occurs outside of the requested\n        method, a :py:exc:`~tinyrpc.exc.ServerError` without any error\n        information will be returned.\n\n        If the method is found and called but throws an exception, the\n        exception thrown is used as a response instead. This is the only case\n        in which information from the exception is possibly propagated back to\n        the client, as the exception is part of the requested method.\n\n        :py:class:`~tinyrpc.RPCBatchRequest` instances are handled by handling\n        all its children in order and collecting the results, then returning an\n        :py:class:`~tinyrpc.RPCBatchResponse` with the results.\n\n        :param request: The request containing the function to be called and its parameters.\n        :type request: ~tinyrpc.protocols.RPCRequest or ~tinyrpc.protocols.RPCBatchRequest\n        :param caller: An optional callable used to invoke the method.\n        :type caller: callable\n        :return: The result produced by calling the requested function.\n        :rtype: ~tinyrpc.protocols.RPCResponse or ~tinyrpc.protocols.RPCBatchResponse\n        :raises ~exc.MethodNotFoundError: If the requested function is not published.\n        :raises ~exc.ServerError: If some other error occurred.\n\n        .. Note::\n\n            The :py:exc:`~tinyrpc.exc.ServerError` is raised for any kind of exception not\n            raised by the called function itself or :py:exc:`~tinyrpc.exc.MethodNotFoundError`.\n        \"\"\"\n        if hasattr(request, 'create_batch_response'):\n            results = [self._dispatch(req, caller) for req in request]\n\n            response = request.create_batch_response()\n            if response is not None:\n                response.extend(results)\n\n            return response\n        else:\n            return self._dispatch(request, caller)\n\n    def _dispatch(self, request, caller):\n        try:\n            method = self.get_method(request.method)\n        except exc.MethodNotFoundError as e:\n            return request.error_respond(e)\n        except Exception:\n            # unexpected error, do not let client know what happened\n            return request.error_respond(exc.ServerError())\n\n        # we found the method\n        try:\n            if self.validator is not None:\n                self.validator(method, request.args, request.kwargs)\n            if caller is not None:\n                result = caller(method, request.args, request.kwargs)\n            else:\n                result = method(*request.args, **request.kwargs)\n        except Exception as e:\n            # an error occurred within the method, return it\n            return request.error_respond(e)\n\n        # respond with result\n        return request.respond(result)\n\n    @staticmethod\n    def validate_parameters(\n            method: Callable, args: List[Any], kwargs: Dict[str, Any]\n    ) -> None:\n        \"\"\"Verify that `*args` and `**kwargs` are appropriate parameters for `method`.\n\n        .. Warning::\n\n            This function has changed to a static function.\n            This will make it easier to replace it with a regular function instead of having to\n            subclass only to replace it.\n\n        :param method: A callable.\n        :param args: List of positional arguments for `method`\n        :param kwargs: Keyword arguments for `method`\n        :raises ~tinyrpc.exc.InvalidParamsError:\n            Raised when the provided arguments are not acceptable for `method`.\n        \"\"\"\n        if hasattr(method, '__code__'):\n            try:\n                inspect.getcallargs(method, *args, **kwargs)\n            except TypeError:\n                raise exc.InvalidParamsError()\n\n    validator = validate_parameters\n    \"\"\"Dispatched function parameter validation.\n\n    :type: callable\n    \n    By default this attribute is set to :py:func:`validate_parameters`.\n    The value can be set to any callable implementing the same interface\n    as :py:func:`validate_parameters` or to `None` to disable validation\n    entirely.\n    \"\"\"\n"
  },
  {
    "path": "tinyrpc/exc.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom abc import ABC\n\n\nclass RPCError(Exception, ABC):\n    \"\"\"Base class for all exceptions thrown by :py:mod:`tinyrpc`.\"\"\"\n    def error_respond(self):\n        \"\"\"Converts the error to an error response object.\n\n        :returns: An error response instance or ``None`` if the protocol decides to drop the error silently.\n        :rtype: :py:class:`~tinyrpc.protocols.RPCErrorResponse`\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass BadRequestError(RPCError, ABC):\n    \"\"\"Base class for all errors that caused the processing of a request to\n    abort before a request object could be instantiated.\"\"\"\n\n\nclass BadReplyError(RPCError, ABC):\n    \"\"\"Base class for all errors that caused processing of a reply to abort\n    before it could be turned in a response object.\"\"\"\n\n\nclass InvalidRequestError(BadRequestError, ABC):\n    \"\"\"A request made was malformed (i.e. violated the specification) and could\n    not be parsed.\"\"\"\n\n\nclass InvalidReplyError(BadReplyError, ABC):\n    \"\"\"A reply received was malformed (i.e. violated the specification) and\n    could not be parsed into a response.\"\"\"\n\n\nclass UnexpectedIDError (InvalidReplyError, ABC):\n    \"\"\"A reply received contained an invalid unique identifier.\"\"\"\n\n\nclass MethodNotFoundError(RPCError, ABC):\n    \"\"\"The desired method was not found.\"\"\"\n\n\nclass InvalidParamsError(RPCError, ABC):\n    \"\"\"The provided parameters do not match those of the desired method.\"\"\"\n\n\nclass ServerError(RPCError, ABC):\n    \"\"\"An internal error in the RPC system occurred.\"\"\"\n\nclass TimeoutError(Exception):\n    \"\"\"No reply received within the timeout period.\"\"\"\n"
  },
  {
    "path": "tinyrpc/protocols/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Protocol definition.\n\nDefines the abstract base classes from which a protocol definition must be constructed.\n\"\"\"\nfrom abc import ABC\nfrom typing import Any, Generator, List, Dict, Union, Optional\nimport itertools\n\nfrom tinyrpc import exc\n\n\nclass RPCRequest(object):\n    \"\"\"Defines a generic RPC request.\"\"\"\n    def __init__(self) -> None:\n        self.unique_id = None\n        \"\"\"Correlation ID used to match request and response.\n\n        :type: int or str or None\n\n        Protocol specific, may or may not be set.\n        This value should only be set by :py:func:`~tinyrpc.protocols.RPCProtocol.create_request`.\n\n        When the protocol permits it this ID allows servers to respond to requests out\n        of order and allows clients to relate a response to the corresponding request.\n\n        Only supported if the protocol has its\n        :py:attr:`~tinyrpc.protocols.RPCProtocol.supports_out_of_order` set to ``True``.\n\n        Generated by the client, the server copies it from request to corresponding response.\n        \"\"\"\n\n        self.method = None\n        \"\"\"The name of the RPC function to be called.\n\n        :type: str\n\n        The :py:attr:`method` attribute uses the name of the function as it is known by the public.\n        The :py:class:`~tinyrpc.dispatch.RPCDispatcher` allows the use of public aliases in the\n        ``@public`` decorators.\n        These are the names used in the :py:attr:`method` attribute.\n        \"\"\"\n\n        self.args = []\n        \"\"\"The positional arguments of the method call.\n\n        :type: list\n\n        The contents of this list are the positional parameters for the :py:attr:`method` called.\n        It is eventually called as ``method(*args)``.\n        \"\"\"\n\n        self.kwargs = {}\n        \"\"\"The keyword arguments of the method call.\n\n        :type: dict\n\n        The contents of this dict are the keyword parameters for the :py:attr:`method` called.\n        It is eventually called as ``method(**kwargs)``.\n        \"\"\"\n    def error_respond(self, error: Union[Exception, str]\n                      ) -> Optional['RPCErrorResponse']:\n        \"\"\"Creates an error response.\n\n        Create a response indicating that the request was parsed correctly,\n        but an error has occurred trying to fulfill it.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :param error: An exception or a string describing the error.\n        :type error: Exception or str\n        :return: A response or ``None`` to indicate that no error should be sent out.\n        :rtype: :py:class:`RPCErrorResponse`\n        \"\"\"\n        raise NotImplementedError()\n\n    def respond(self, result: Any) -> Optional['RPCResponse']:\n        \"\"\"Create a response.\n\n        Call this to return the result of a successful method invocation.\n\n        This creates and returns an instance of a protocol-specific subclass of\n        :py:class:`~tinyrpc.RPCResponse`.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :param result: Passed on to new response instance.\n        :type result: Any type that can be serialized by the protocol.\n\n        :return: A response or ``None`` to indicate this request does not expect a response.\n        :rtype: :py:class:`RPCResponse`\n        \"\"\"\n        raise NotImplementedError()\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the request.\n\n        Converts the request into a bytes object that can be passed to and by the transport layer.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :return: A bytes object to be passed on to a transport.\n        :rtype: bytes\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass RPCBatchRequest(list):\n    \"\"\"Multiple requests batched together.\n\n    Protocols that support multiple requests in a single message use this to group them together.\n    Note that not all protocols may support batch requests.\n\n    Handling a batch requests is done in any order, responses must be gathered\n    in a batch response and be in the same order as their respective requests.\n\n    Any item of a batch request is either an :py:class:`RPCRequest` or an\n    :py:class:`~tinyrpc.exc.BadRequestError`, which indicates that there has been\n    an error in parsing the request.\n    \"\"\"\n    def create_batch_response(self) -> Optional['RPCBatchResponse']:\n        \"\"\"Creates a response suitable for responding to this request.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :return: An :py:class:`RPCBatchResponse` or None if no response is expected.\n        :rtype: :py:class:`RPCBatchResponse`\n        \"\"\"\n        raise NotImplementedError()\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the request.\n\n        Converts the request into a bytes object that can be passed to and by the transport layer.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :return: A bytes object to be passed on to a transport.\n        :rtype: bytes\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass RPCResponse(ABC):\n    \"\"\"Defines a generic RPC response.\n\n    Base class for all responses.\n\n    .. py:attribute:: id\n\n        Correlation ID to match request and response\n\n        :type: str or int\n\n    .. py:attribute:: result\n\n        When present this attribute contains the result of the RPC call.\n        Otherwise the :py:attr:`error` attribute must be defined.\n\n        :type: Any type that can be serialized by the protocol.\n\n    .. py:attribute:: error\n\n        When present the :py:attr:`result` attribute must be absent.\n        Presence of this attribute indicates an error condition.\n\n        :type: :py:class:`~tinyrpc.exc.RPCError`\n    \"\"\"\n    def __init__(self) -> None:\n        self.unique_id = None\n        \"\"\"Correlation ID used to match request and response.\n\n        :type: int or str or None\n        \"\"\"\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the response.\n\n        Converts the response into a bytes object that can be passed to and by the transport layer.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :return: The serialized encoded response object.\n        :rtype: bytes\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass RPCErrorResponse(RPCResponse, ABC):\n    \"\"\"RPC error response class.\n\n    Base class for all deriving responses.\n\n    .. py:attribute:: error\n\n        This attribute contains the fields ``message`` (str) and\n        ``code`` (int) where at least ``message`` is required to contain a value.\n\n        :type: dict\n    \"\"\"\n    error = None\n\n\nclass RPCBatchResponse(list):\n    \"\"\"Multiple response from a batch request. See\n    :py:class:`RPCBatchRequest` on how to handle.\n\n    Items in a batch response need to be\n    :py:class:`RPCResponse` instances or None, meaning no reply should\n    generated for the request.\n    \"\"\"\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the batch response.\n\n        Converts the response into a bytes object that can be passed to and by the transport layer.\n\n        This is an abstract method that must be overridden in a derived class.\n\n        :return: A bytes object to be passed on to a transport.\n        :rtype: bytes\n        \"\"\"\n        raise NotImplementedError()\n\n\nclass RPCProtocol(ABC):\n    \"\"\"Abstract base class for all protocol implementations.\"\"\"\n\n    supports_out_of_order = False\n    \"\"\"If true, this protocol can receive responses out of order correctly.\n\n    Note that this usually depends on the generation of unique_ids, the\n    generation of these may or may not be thread safe, depending on the\n    protocol. Ideally, only one instance of RPCProtocol should be used per\n    client.\n\n    :type: bool\n    \"\"\"\n\n    raises_errors = True\n    \"\"\"If True, this protocol instance will raise an RPCError exception.\n\n    On receipt of an RPCErrorResponse instance an RPCError exception is raised.\n    When this flag is False the RPCErrorResponse object is returned to the caller\n    which is then responsible for handling the error.\n\n    :type: bool\n    \"\"\"\n    def create_request(\n            self,\n            method: str,\n            args: List[Any] = None,\n            kwargs: Dict[str, Any] = None,\n            one_way: bool = False\n    ) -> 'RPCRequest':\n        \"\"\"Creates a new :py:class:`RPCRequest` object.\n\n        Called by the client when constructing a request.\n        It is up to the implementing protocol whether or not ``args``,\n        ``kwargs``, one of these, both at once or none of them are supported.\n\n        :param str method: The method name to invoke.\n        :param list args: The positional arguments to call the method with.\n        :param dict kwargs: The keyword arguments to call the method with.\n        :param bool one_way: The request is an update, i.e. it does not expect a reply.\n        :return: A new request instance\n        :rtype: :py:class:`RPCRequest`\n        \"\"\"\n        raise NotImplementedError()\n\n    def parse_request(self, data: bytes) -> 'RPCRequest':\n        \"\"\"De-serializes and validates a request.\n\n        Called by the server to reconstruct the serialized :py:class:`RPCRequest`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized request.\n        :return: A reconstructed request.\n        :rtype: :py:class:`RPCRequest`\n        \"\"\"\n        raise NotImplementedError()\n\n    def parse_reply(self, data: bytes) -> Union['RPCResponse', 'RPCBatchResponse']:\n        \"\"\"De-serializes and validates a response.\n\n        Called by the client to reconstruct the serialized :py:class:`RPCResponse`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized response.\n        :return: A reconstructed response.\n        :rtype: :py:class:`RPCResponse`\n        \"\"\"\n        raise NotImplementedError()\n\n    def raise_error(self, error: 'RPCErrorResponse') -> exc.RPCError:\n        \"\"\"Raises the exception in the client.\n\n        Called by the client to convert the :py:class:`RPCErrorResponse` into an Exception\n        and raise or return it depending on the :py:attr:`raises_errors` attribute.\n\n        :param error: The error response received from the server.\n        :type error: :py:class:`RPCResponse`\n        :rtype: :py:exc:`~tinyrpc.exc.RPCError` when :py:attr:`raises_errors` is False.\n        :raises: :py:exc:`~tinyrpc.exc.RPCError` when :py:attr:`raises_errors` is True.\n        \"\"\"\n        ex = exc.RPCError(\n            'Error calling remote procedure: %s' % error.error['message']\n        )\n        if self.raises_errors:\n            raise ex\n        return ex\n\n\nclass RPCBatchProtocol(RPCProtocol, ABC):\n    \"\"\"Abstract base class for all batch protocol implementations.\"\"\"\n    def create_batch_request(\n            self, requests: List['RPCRequest'] = None\n    ) -> 'RPCBatchRequest':\n        \"\"\"Create a new :py:class:`RPCBatchRequest` object.\n\n        Called by the client when constructing a request.\n\n        :param requests: A list of requests.\n        :type requests: :py:class:`list` or :py:class:`RPCRequest`\n        :return: A new request instance.\n        :rtype: :py:class:`RPCBatchRequest`\n        \"\"\"\n        raise NotImplementedError()\n\n\ndef default_id_generator(start: int = 1) -> Generator[int, None, None]:\n    \"\"\"Generates sequential integers from `start`.\n\n    e.g. 1, 2, 3, .. 9, 10, 11, ...\n\n    :param start: The first value to start with.`\n    :type start: int\n    :return: A generator that yields a sequence of integers.\n    :rtype: :py:class:`Generator[int, None, None]`\n    \"\"\"\n    return itertools.count(start)\n"
  },
  {
    "path": "tinyrpc/protocols/jsonrpc.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"JSON RPC 2.0 Protocol implementation.\n\nThis module can use the jsonext_ package to make it easier to convert to JSON.\nIn order to use jsonext import it before importing tinyrpc.\nTinyrpc will detect the presence of jsonext and use it automatically.\n\n.. _jsonext: https://pypi.org/project/jsonext\n\n\"\"\"\n\nimport json\nimport sys\nfrom tinyrpc.exc import UnexpectedIDError\nfrom typing import Dict, Any, Union, Optional, List, Tuple, Callable, Generator\n\nfrom . import default_id_generator\nfrom .. import (\n    RPCBatchProtocol, RPCRequest, RPCResponse, RPCErrorResponse,\n    InvalidRequestError, MethodNotFoundError, InvalidReplyError, RPCError,\n    RPCBatchRequest, RPCBatchResponse, InvalidParamsError,\n)\n\nif 'jsonext' in sys.modules:\n    # jsonext was imported before this file, assume the intent is that\n    # it is used in place of the regular json encoder.\n    import jsonext\n\n    json_dumps = jsonext.dumps\nelse:\n    json_dumps = json.dumps\n\n\nclass FixedErrorMessageMixin(object):\n    \"\"\"Combines JSON RPC exceptions with the generic RPC exceptions.\n\n    Constructs the exception using the provided parameters as well as\n    properties of the JSON RPC Exception.\n\n    JSON RPC exceptions declare two attributes:\n\n    .. py:attribute:: jsonrpc_error_code\n\n        This is an error code conforming to the JSON RPC `error codes`_ convention.\n\n        :type: :py:class:`int`\n\n    .. py:attribute:: message\n\n        This is a textual representation of the error code.\n\n        :type: :py:class:`str`\n\n    :param list args: Positional arguments for the constructor.\n        When present it overrules the :py:attr:`message` attribute.\n    :param dict kwargs: Keyword arguments for the constructor.\n        If the ``data`` parameter is found in ``kwargs`` its contents are\n        used as the *data* property of the JSON RPC Error object.\n\n    :py:class:`FixedErrorMessageMixin` is the basis for adding your own\n    exceptions to the predefined ones.\n    Here is a version of the reverse string example that dislikes palindromes:\n\n    .. code-block:: python\n\n        class PalindromeError(FixedErrorMessageMixin, Exception)\n            jsonrpc_error_code = 99\n            message = \"Ah, that's cheating\"\n\n        @public\n        def reverse_string(s):\n            r = s[::-1]\n            if r == s:\n                raise PalindromeError(data=s)\n            return r\n\n    >>> client.reverse('rotator')\n\n    Will return an error object to the client looking like:\n\n    .. code-block:: json\n\n        {\n            \"jsonrpc\": \"2.0\",\n            \"id\": 1,\n            \"error\": {\n                \"code\": 99,\n                \"message\": \"Ah, that's cheating\",\n                \"data\": \"rotator\"\n            }\n        }\n\n    .. _error codes: https://www.jsonrpc.org/specification#error_object\n    \"\"\"\n    def __init__(self, *args, **kwargs) -> None:\n        if not args:\n            args = [self.message]\n        self.request_id = kwargs.pop('request_id', None)\n        if 'data' in kwargs:\n            self.data = kwargs.pop('data')\n        super(FixedErrorMessageMixin, self).__init__(*args, **kwargs)\n\n    def error_respond(self) -> 'JSONRPCErrorResponse':\n        \"\"\"Converts the error to an error response object.\n\n        :return: An error response object ready to be serialized and sent to the client.\n        :rtype: :py:class:`JSONRPCErrorResponse`\n        \"\"\"\n        response = JSONRPCErrorResponse()\n\n        response.error = self.message\n        response.unique_id = self.request_id\n        response._jsonrpc_error_code = self.jsonrpc_error_code\n        if hasattr(self, 'data'):\n            response.data = self.data\n        return response\n\n\nclass JSONRPCParseError(FixedErrorMessageMixin, InvalidRequestError):\n    \"\"\"The request cannot be decoded or is malformed.\"\"\"\n    jsonrpc_error_code = -32700\n    message = 'Parse error'\n\n\nclass JSONRPCInvalidRequestError(FixedErrorMessageMixin, InvalidRequestError):\n    \"\"\"The request contents are not valid for JSON RPC 2.0\"\"\"\n    jsonrpc_error_code = -32600\n    message = 'Invalid Request'\n\n\nclass JSONRPCMethodNotFoundError(FixedErrorMessageMixin, MethodNotFoundError):\n    \"\"\"The requested method name is not found in the registry.\"\"\"\n    jsonrpc_error_code = -32601\n    message = 'Method not found'\n\n\nclass JSONRPCInvalidParamsError(FixedErrorMessageMixin, InvalidRequestError):\n    \"\"\"The provided parameters are not appropriate for the function called.\"\"\"\n    jsonrpc_error_code = -32602\n    message = 'Invalid params'\n\n\nclass JSONRPCInternalError(FixedErrorMessageMixin, InvalidRequestError):\n    \"\"\"Unspecified error, not in the called function.\"\"\"\n    jsonrpc_error_code = -32603\n    message = 'Internal error'\n\n\nclass JSONRPCServerError(FixedErrorMessageMixin, InvalidRequestError):\n    \"\"\"Unspecified error, this message originates from the called function.\"\"\"\n    jsonrpc_error_code = -32000\n    message = ''\n\n\nclass JSONRPCError(FixedErrorMessageMixin, RPCError):\n    \"\"\"Reconstructs (to some extend) the server-side exception.\n\n    The client creates this exception by providing it with the ``error``\n    attribute of the JSON error response object returned by the server.\n\n    :param dict error: This dict contains the error specification:\n\n        * code (int): the numeric error code.\n        * message (str): the error description.\n        * data (any): if present, the data attribute of the error\n    \"\"\"\n    def __init__(\n            self, error: Union['JSONRPCErrorResponse', Dict[str, Any]]\n    ) -> None:\n        if isinstance(error, JSONRPCErrorResponse):\n            super(JSONRPCError, self).__init__(error.error)\n            self.message = error.error\n            self._jsonrpc_error_code = error._jsonrpc_error_code\n            if hasattr(error, 'data'):\n                self.data = error.data\n        else:\n            super(JSONRPCError, self).__init__(error.message)\n            self.message = error['message']\n            self._jsonrpc_error_code = error['code']\n            if 'data' in error:\n                self.data = error['data']\n\n\nclass JSONRPCSuccessResponse(RPCResponse):\n    \"\"\"Collects the attributes of a successful response message.\n\n    Contains the fields of a normal (i.e. a non-error) response message.\n\n    .. py:attribute:: unique_id\n\n        Correlation ID to match request and response.\n        A JSON RPC response *must* have a defined matching id attribute.\n        ``None`` is not a valid value for a successful response.\n\n        :type: str or int\n\n    .. py:attribute:: result\n\n        Contains the result of the RPC call.\n\n        :type: Any type that can be serialized by the protocol.\n    \"\"\"\n    def _to_dict(self):\n        return {\n            'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,\n            'id': self.unique_id,\n            'result': self.result\n        }\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the response.\n\n        Converts the response into a bytes object that can be passed to and by the transport layer.\n\n        :return: The serialized encoded response object.\n        :rtype: bytes\n        \"\"\"\n        return json_dumps(self._to_dict()).encode()\n\n\nclass JSONRPCErrorResponse(RPCErrorResponse):\n    \"\"\"Collects the attributes of an error response message.\n\n    Contains the fields of an error response message.\n\n    .. py:attribute:: unique_id\n\n        Correlation ID to match request and response.\n        ``None`` is a valid ID when the error cannot be matched to a particular request.\n\n        :type: str or int or None\n\n    .. py:attribute:: error\n\n        The error message. A string describing the error condition.\n\n        :type: str\n\n    .. py:attribute:: data\n\n        This field may contain any JSON encodable datum that the server may want\n        to return the client.\n\n        It may contain additional information about the error condition, a partial result\n        or whatever. Its presence and value are entirely optional.\n\n        :type: Any type that can be serialized by the protocol.\n\n    .. py:attribute:: _jsonrpc_error_code\n\n        The numeric error code.\n\n        The value is usually predefined by one of the JSON protocol exceptions.\n        It can be set by the developer when defining application specific exceptions.\n        See :py:class:`FixedErrorMessageMixin` for an example on how to do this.\n\n        Note that the value of this field *must* comply with the defined values in\n        the standard_.\n\n    .. _standard: https://www.jsonrpc.org/specification#error_object\n    \"\"\"\n    def _to_dict(self):\n        msg = {\n            'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,\n            'id': self.unique_id,\n            'error': {\n                'message': str(self.error),\n                'code': self._jsonrpc_error_code\n            }\n        }\n        if hasattr(self, 'data'):\n            msg['error']['data'] = self.data\n        return msg\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the error.\n\n        Converts the response into a bytes object that can be passed to and by the transport layer.\n\n        :return: The serialized encoded error object.\n        :rtype: bytes\n        \"\"\"\n        return json_dumps(self._to_dict()).encode()\n\n\ndef _get_code_message_and_data(error: Union[Exception, str]\n                               ) -> Tuple[int, str, Any]:\n    assert isinstance(error, (Exception, str))\n    data = None\n    if isinstance(error, Exception):\n        if hasattr(error, 'jsonrpc_error_code'):\n            code = error.jsonrpc_error_code\n            msg = str(error)\n            try:\n                data = error.data\n            except AttributeError:\n                pass\n        elif isinstance(error, InvalidRequestError):\n            code = JSONRPCInvalidRequestError.jsonrpc_error_code\n            msg = JSONRPCInvalidRequestError.message\n        elif isinstance(error, MethodNotFoundError):\n            code = JSONRPCMethodNotFoundError.jsonrpc_error_code\n            msg = JSONRPCMethodNotFoundError.message\n        elif isinstance(error, InvalidParamsError):\n            code = JSONRPCInvalidParamsError.jsonrpc_error_code\n            msg = JSONRPCInvalidParamsError.message\n        else:\n            # allow exception message to propagate\n            code = JSONRPCServerError.jsonrpc_error_code\n            if len(error.args) == 2:\n                msg = str(error.args[0])\n                data = error.args[1]\n            else:\n                msg = str(error)\n    else:\n        code = -32000\n        msg = error\n\n    return code, msg, data\n\n\nclass JSONRPCRequest(RPCRequest):\n    \"\"\"Defines a JSON RPC request.\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.one_way = False\n        \"\"\"Request or Notification.\n\n        :type: bool\n\n        This flag indicates if the client expects to receive a reply (request: ``one_way = False``)\n        or not (notification: ``one_way = True``).\n\n        Note that according to the specification it is possible for the server to return an error response.\n        For example if the request becomes unreadable and the server is not able to determine that it is\n        in fact a notification an error should be returned. However, once the server had verified that the\n        request is a notification no reply (not even an error) should be returned.\n        \"\"\"\n\n        self.unique_id = None\n        \"\"\"Correlation ID used to match request and response.\n\n        :type: int or str\n\n        Generated by the client, the server copies it from request to corresponding response.\n        \"\"\"\n\n        self.method = None\n        \"\"\"The name of the RPC function to be called.\n\n        :type: str\n\n        The :py:attr:`method` attribute uses the name of the function as it is known by the public.\n        The :py:class:`~tinyrpc.dispatch.RPCDispatcher` allows the use of public aliases in the\n        ``@public`` decorators.\n        These are the names used in the :py:attr:`method` attribute.\n        \"\"\"\n\n        self.args = []\n        \"\"\"The positional arguments of the method call.\n\n        :type: list\n\n        The contents of this list are the positional parameters for the :py:attr:`method` called.\n        It is eventually called as ``method(*args)``.\n        \"\"\"\n\n        self.kwargs = {}\n        \"\"\"The keyword arguments of the method call.\n\n        :type: dict\n\n        The contents of this dict are the keyword parameters for the :py:attr:`method` called.\n        It is eventually called as ``method(**kwargs)``.\n        \"\"\"\n    def error_respond(self, error: Union[Exception, str]\n                      ) -> Optional['JSONRPCErrorResponse']:\n        \"\"\"Create an error response to this request.\n\n        When processing the request produces an error condition this method can be used to\n        create the error response object.\n\n        :param error: Specifies what error occurred.\n        :type error: Exception or str\n        :returns: An error response object that can be serialized and sent to the client.\n        :rtype: ;py:class:`JSONRPCErrorResponse`\n        \"\"\"\n        if self.unique_id is None:\n            return None\n\n        response = JSONRPCErrorResponse()\n        response.unique_id = None if self.one_way else self.unique_id\n\n        code, msg, data = _get_code_message_and_data(error)\n\n        response.error = msg\n        response._jsonrpc_error_code = code\n        if data:\n            response.data = data\n        return response\n\n    def respond(self, result: Any) -> Optional['JSONRPCSuccessResponse']:\n        \"\"\"Create a response to this request.\n\n        When processing the request completed successfully this method can be used to\n        create a response object.\n\n        :param result: The result of the invoked method.\n        :type result: Anything that can be encoded by JSON.\n        :returns: A response object that can be serialized and sent to the client.\n        :rtype: :py:class:`JSONRPCSuccessResponse`\n        \"\"\"\n        if self.one_way or self.unique_id is None:\n            return None\n\n        response = JSONRPCSuccessResponse()\n\n        response.result = result\n        response.unique_id = self.unique_id\n\n        return response\n\n    def _to_dict(self):\n        jdata = {\n            'jsonrpc': JSONRPCProtocol.JSON_RPC_VERSION,\n            'method': self.method,\n        }\n        if self.args:\n            jdata['params'] = self.args\n        if self.kwargs:\n            jdata['params'] = self.kwargs\n        if not self.one_way and hasattr(\n                self, 'unique_id') and self.unique_id is not None:\n            jdata['id'] = self.unique_id\n        return jdata\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the request.\n\n        Converts the request into a bytes object that can be sent to the server.\n\n        :return: The serialized encoded request object.\n        :rtype: bytes\n        \"\"\"\n        return json_dumps(self._to_dict()).encode()\n\n\nclass JSONRPCBatchRequest(RPCBatchRequest):\n    \"\"\"Defines a JSON RPC batch request.\"\"\"\n    def create_batch_response(self) -> Optional['JSONRPCBatchResponse']:\n        \"\"\"Produces a batch response object if a response is expected.\n\n        :return: A batch response if needed\n        :rtype: :py:class:`JSONRPCBatchResponse`\n        \"\"\"\n        if self._expects_response():\n            return JSONRPCBatchResponse()\n\n    def _expects_response(self):\n        for request in self:\n            if isinstance(request, Exception):\n                return True\n            if not request.one_way and request.unique_id is not None:\n                return True\n\n        return False\n\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the request.\n\n        Converts the request into a bytes object that can be passed to and by the transport layer.\n\n        :return: A bytes object to be passed on to a transport.\n        :rtype: bytes\n        \"\"\"\n        return json_dumps([req._to_dict() for req in self]).encode()\n\n\nclass JSONRPCBatchResponse(RPCBatchResponse):\n    \"\"\"Multiple responses from a batch request. See\n    :py:class:`JSONRPCBatchRequest` on how to handle.\n\n    Items in a batch response need to be\n    :py:class:`JSONRPCResponse` instances or None, meaning no reply should be\n    generated for the request.\n    \"\"\"\n    def serialize(self) -> bytes:\n        \"\"\"Returns a serialization of the batch response.\n\n        Converts the response into a bytes object that can be passed to and by the transport layer.\n\n        :return: A bytes object to be passed on to a transport.\n        :rtype: bytes\n        \"\"\"\n        return json_dumps([\n            resp._to_dict() for resp in self if resp is not None\n        ]).encode()\n\n\nclass JSONRPCProtocol(RPCBatchProtocol):\n    \"\"\"JSONRPC protocol implementation.\"\"\"\n\n    JSON_RPC_VERSION = \"2.0\"\n    \"\"\"Currently, only version 2.0 is supported.\"\"\"\n\n    _ALLOWED_REPLY_KEYS = sorted(['id', 'jsonrpc', 'error', 'result'])\n    _ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params'])\n\n    def __init__(\n            self,\n            id_generator: Optional[Generator[object, None, None]] = None,\n            *args,\n            **kwargs\n    ) -> None:\n        super(JSONRPCProtocol, self).__init__(*args, **kwargs)\n        self._id_generator = id_generator or default_id_generator()\n        self._pending_replies = []\n\n    def _get_unique_id(self) -> object:\n        return next(self._id_generator)\n\n    def request_factory(self) -> 'JSONRPCRequest':\n        \"\"\"Factory for request objects.\n\n        Allows derived classes to use requests derived from :py:class:`JSONRPCRequest`.\n\n        :rtype: :py:class:`JSONRPCRequest`\n        \"\"\"\n        return JSONRPCRequest()\n\n    def create_batch_request(\n            self,\n            requests: Union['JSONRPCRequest', List['JSONRPCRequest']] = None\n    ) -> 'JSONRPCBatchRequest':\n        \"\"\"Create a new :py:class:`JSONRPCBatchRequest` object.\n\n        Called by the client when constructing a request.\n\n        :param requests: A list of requests.\n        :type requests: :py:class:`list` or :py:class:`JSONRPCRequest`\n        :return: A new request instance.\n        :rtype: :py:class:`JSONRPCBatchRequest`\n        \"\"\"\n        return JSONRPCBatchRequest(requests or [])\n\n    def create_request(\n            self,\n            method: str,\n            args: List[Any] = None,\n            kwargs: Dict[str, Any] = None,\n            one_way: bool = False\n    ) -> 'JSONRPCRequest':\n        \"\"\"Creates a new :py:class:`JSONRPCRequest` object.\n\n        Called by the client when constructing a request.\n        JSON RPC allows either the ``args`` or ``kwargs`` argument to be set.\n\n        :param str method: The method name to invoke.\n        :param list args: The positional arguments to call the method with.\n        :param dict kwargs: The keyword arguments to call the method with.\n        :param bool one_way: The request is an update, i.e. it does not expect a reply.\n        :return: A new request instance\n        :rtype: :py:class:`JSONRPCRequest`\n        :raises InvalidRequestError: when ``args`` and ``kwargs`` are both defined.\n        \"\"\"\n        if args and kwargs:\n            raise InvalidRequestError(\n                'Does not support args and kwargs at '\n                'the same time'\n            )\n\n        request = self.request_factory()\n        request.one_way = one_way\n\n        if not one_way:\n            request.unique_id = self._get_unique_id()\n            self._pending_replies.append(request.unique_id)\n\n        request.method = method\n        if args is not None:\n            request.args = args\n        if kwargs is not None:\n            request.kwargs = kwargs\n\n        return request\n\n    def parse_reply(\n            self, data: bytes\n    ) -> Union['JSONRPCSuccessResponse', 'JSONRPCErrorResponse', 'JSONRPCBatchResponse']:\n        \"\"\"De-serializes and validates a response.\n\n        Called by the client to reconstruct the serialized :py:class:`JSONRPCResponse`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized request.\n        :return: A reconstructed response.\n        :rtype: :py:class:`JSONRPCSuccessResponse` or :py:class:`JSONRPCErrorResponse`\n        :raises InvalidReplyError: if the response is not valid JSON or does not conform\n            to the standard.\n        \"\"\"\n        if isinstance(data, bytes):\n            data = data.decode()\n\n        try:\n            rep = json.loads(data)\n        except Exception as e:\n            raise InvalidReplyError(e)\n\n        if isinstance(rep, list):\n            # batch request\n            replies = JSONRPCBatchResponse()\n            for subrep in rep:\n                try:\n                    replies.append(self._parse_subreply(subrep))\n                except RPCError as e:\n                    replies.append(e)\n                except Exception as e:\n                    replies.append(InvalidReplyError(e))\n\n            if not replies:\n                raise InvalidReplyError(\"Empty batch response received.\")\n            return replies\n        else:\n            return self._parse_subreply(rep)\n\n    def _parse_subreply(self, rep):\n        for k in rep.keys():\n            if k not in self._ALLOWED_REPLY_KEYS:\n                raise InvalidReplyError('Key not allowed: %s' % k)\n\n        if 'jsonrpc' not in rep:\n            raise InvalidReplyError('Missing jsonrpc (version) in response.')\n\n        if rep['jsonrpc'] != self.JSON_RPC_VERSION:\n            raise InvalidReplyError('Wrong JSONRPC version')\n\n        if 'id' not in rep:\n            raise InvalidReplyError('Missing id in response')\n\n        if ('error' in rep) and ('result' in rep):\n            raise InvalidReplyError(\n                'Reply must contain exactly one of result and error.'\n            )\n\n        if 'error' in rep:\n            response = JSONRPCErrorResponse()\n            error = rep['error']\n            response.error = error[\"message\"]\n            response._jsonrpc_error_code = error[\"code\"]\n            if \"data\" in error:\n                response.data = error[\"data\"]\n        else:\n            response = JSONRPCSuccessResponse()\n            response.result = rep.get('result', None)\n\n        response.unique_id = rep['id']\n        if response.unique_id not in self._pending_replies:\n            raise UnexpectedIDError(\n                'Reply id does not correspond to any sent requests.'\n            )\n        else:\n            self._pending_replies.remove(response.unique_id)\n\n        return response\n\n    def parse_request(self, data: bytes\n                      ) -> Union['JSONRPCRequest', 'JSONRPCBatchRequest']:\n        \"\"\"De-serializes and validates a request.\n\n        Called by the server to reconstruct the serialized :py:class:`JSONRPCRequest`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized request.\n        :return: A reconstructed request.\n        :rtype: :py:class:`JSONRPCRequest`\n        :raises JSONRPCParseError: if the ``data`` cannot be parsed as valid JSON.\n        :raises JSONRPCInvalidRequestError: if the request does not comply with the standard.\n        \"\"\"\n        if isinstance(data, bytes):\n            data = data.decode()\n\n        try:\n            req = json.loads(data)\n        except Exception as e:\n            raise JSONRPCParseError()\n\n        if isinstance(req, list):\n            # batch request\n            requests = JSONRPCBatchRequest()\n            for subreq in req:\n                try:\n                    requests.append(self._parse_subrequest(subreq))\n                except RPCError as e:\n                    requests.append(e)\n                except Exception as e:\n                    requests.append(JSONRPCInvalidRequestError(request_id=subreq.get(\"id\")))\n\n            if not requests:\n                raise JSONRPCInvalidRequestError()\n            return requests\n        else:\n            return self._parse_subrequest(req)\n\n    def _parse_subrequest(self, req):\n        if not isinstance(req, dict):\n            raise JSONRPCInvalidRequestError()\n\n        for k in req.keys():\n            if k not in self._ALLOWED_REQUEST_KEYS:\n                raise JSONRPCInvalidRequestError(request_id=req.get(\"id\"))\n\n        if req.get('jsonrpc', None) != self.JSON_RPC_VERSION:\n            raise JSONRPCInvalidRequestError(request_id=req.get(\"id\"))\n\n        if not isinstance(req['method'], str):\n            raise JSONRPCInvalidRequestError(request_id=req.get(\"id\"))\n\n        request = self.request_factory()\n\n        request.method = req['method']\n        request.one_way = 'id' not in req\n        if not request.one_way:\n            request.unique_id = req['id']\n\n        params = req.get('params', None)\n        if params is not None:\n            if isinstance(params, list):\n                request.args = req['params']\n            elif isinstance(params, dict):\n                request.kwargs = req['params']\n            else:\n                raise JSONRPCInvalidParamsError(request_id=req.get(\"id\"))\n\n        return request\n\n    def raise_error(\n            self, error: Union['JSONRPCErrorResponse', Dict[str, Any]]\n    ) -> 'JSONRPCError':\n        \"\"\"Recreates the exception.\n\n        Creates a :py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCError` instance\n        and raises it.\n        This allows the error, message and data attributes of the original\n        exception to propagate into the client code.\n\n        The :py:attr:`~tinyrpc.protocols.RPCProtocol.raises_error` flag controls if the exception object is\n        raised or returned.\n\n        :returns: the exception object if it is not allowed to raise it.\n        :raises JSONRPCError: when the exception can be raised.\n            The exception object will contain ``message``, ``code`` and optionally a\n            ``data`` property.\n        \"\"\"\n        exc = JSONRPCError(error)\n        if self.raises_errors:\n            raise exc\n        return exc\n\n    def _caller(\n            self, method: Callable, args: List[Any], kwargs: Dict[str, Any]\n    ) -> Any:\n        # Custom dispatcher called by RPCDispatcher._dispatch().\n        # Override this when you need to call the method with additional parameters for example.\n        return method(*args, **kwargs)\n"
  },
  {
    "path": "tinyrpc/protocols/msgpackrpc.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom . import default_id_generator\nfrom .. import (\n    RPCError,\n    RPCErrorResponse,\n    RPCProtocol,\n    RPCRequest,\n    RPCResponse,\n    InvalidRequestError,\n    MethodNotFoundError,\n    InvalidReplyError,\n)\n\nimport msgpack\n\nfrom typing import Any, Dict, List, Optional, Tuple, Union, Generator\n\n\nclass FixedErrorMessageMixin(object):\n    def __init__(self, *args, **kwargs):\n        if not args:\n            args = [self.message]\n\n        self.request_id = kwargs.pop(\"request_id\", None)\n        super(FixedErrorMessageMixin, self).__init__(*args, **kwargs)\n\n    def error_respond(self):\n        response = MSGPACKRPCErrorResponse()\n\n        response.error = self.message\n        response.unique_id = self.request_id\n        response._msgpackrpc_error_code = self.msgpackrpc_error_code\n        return response\n\n\nclass MSGPACKRPCParseError(FixedErrorMessageMixin, InvalidRequestError):\n    msgpackrpc_error_code = -32700\n    message = \"Parse error\"\n\n\nclass MSGPACKRPCInvalidRequestError(FixedErrorMessageMixin, InvalidRequestError):\n    msgpackrpc_error_code = -32600\n    message = \"Invalid request\"\n\n\nclass MSGPACKRPCMethodNotFoundError(FixedErrorMessageMixin, MethodNotFoundError):\n    msgpackrpc_error_code = -32601\n    message = \"Method not found\"\n\n\nclass MSGPACKRPCInvalidParamsError(FixedErrorMessageMixin, InvalidRequestError):\n    msgpackrpc_error_code = -32602\n    message = \"Invalid params\"\n\n\nclass MSGPACKRPCInternalError(FixedErrorMessageMixin, InvalidRequestError):\n    msgpackrpc_error_code = -32603\n    message = \"Internal error\"\n\n\nclass MSGPACKRPCServerError(FixedErrorMessageMixin, InvalidRequestError):\n    msgpackrpc_error_code = -32000\n    message = \"\"\n\n\nclass MSGPACKRPCError(FixedErrorMessageMixin, RPCError):\n    \"\"\"Reconstructs (to some extend) the server-side exception.\n\n    The client creates this exception by providing it with the ``error``\n    attribute of the MSGPACK error response object returned by the server.\n\n    :param error: This tuple contains the error specification: the numeric error\n        code and the error description.\n    \"\"\"\n\n    def __init__(\n        self, error: Union[\"MSGPACKRPCErrorResponse\", Tuple[int, str]]\n    ) -> None:\n        if isinstance(error, MSGPACKRPCErrorResponse):\n            super().__init__(error.error)\n            self._msgpackrpc_error_code = error._msgpackrpc_error_code\n        else:\n            super().__init__()\n            self._msgpackrpc_error_code, self.message = error\n\n\nclass MSGPACKRPCSuccessResponse(RPCResponse):\n    def _to_list(self):\n        return [1, self.unique_id, None, self.result]\n\n    def serialize(self):\n        return msgpack.packb(self._to_list(), use_bin_type=True)\n\n\nclass MSGPACKRPCErrorResponse(RPCErrorResponse):\n    def _to_list(self):\n        return [1, self.unique_id, [self._msgpackrpc_error_code, str(self.error)], None]\n\n    def serialize(self):\n        return msgpack.packb(self._to_list(), use_bin_type=True)\n\n\ndef _get_code_and_message(error):\n    assert isinstance(error, (Exception, str))\n    if isinstance(error, Exception):\n        if hasattr(error, \"msgpackrpc_error_code\"):\n            code = error.msgpackrpc_error_code\n            msg = str(error)\n        elif isinstance(error, InvalidRequestError):\n            code = MSGPACKRPCInvalidRequestError.msgpackrpc_error_code\n            msg = MSGPACKRPCInvalidRequestError.message\n        elif isinstance(error, MethodNotFoundError):\n            code = MSGPACKRPCMethodNotFoundError.msgpackrpc_error_code\n            msg = MSGPACKRPCMethodNotFoundError.message\n        else:\n            # allow exception message to propagate\n            code = MSGPACKRPCServerError.msgpackrpc_error_code\n            msg = str(error)\n    else:\n        code = -32000\n        msg = error\n\n    return code, msg\n\n\nclass MSGPACKRPCRequest(RPCRequest):\n    \"\"\"Defines a MSGPACK-RPC request.\"\"\"\n\n    def __init__(self):\n        super().__init__()\n        self.one_way = False\n        \"\"\"Request or Notification.\n\n        :type: bool\n\n        This flag indicates if the client expects to receive a reply (request: ``one_way = False``)\n        or not (notification: ``one_way = True``).\n\n        Note that it is possible for the server to return an error response.\n        For example if the request becomes unreadable and the server is not able to determine that it is\n        in fact a notification an error should be returned. However, once the server had verified that the\n        request is a notification no reply (not even an error) should be returned.\n        \"\"\"\n\n        self.unique_id = None\n        \"\"\"Correlation ID used to match request and response.\n\n        :type: int\n\n        Generated by the client, the server copies it from request to corresponding response.\n        \"\"\"\n\n        self.method = None\n        \"\"\"The name of the RPC function to be called.\n\n        :type: str\n\n        The :py:attr:`method` attribute uses the name of the function as it is known by the public.\n        The :py:class:`~tinyrpc.dispatch.RPCDispatcher` allows the use of public aliases in the\n        ``@public`` decorators.\n        These are the names used in the :py:attr:`method` attribute.\n        \"\"\"\n\n        self.args = []\n        \"\"\"The positional arguments of the method call.\n\n        :type: list\n\n        The contents of this list are the positional parameters for the :py:attr:`method` called.\n        It is eventually called as ``method(*args)``.\n        \"\"\"\n\n    def error_respond(\n        self, error: Union[Exception, str]\n    ) -> Optional[\"MSGPACKRPCErrorResponse\"]:\n        \"\"\"Create an error response to this request.\n\n        When processing the request produces an error condition this method can be used to\n        create the error response object.\n\n        :param error: Specifies what error occurred.\n        :type error: Exception or str\n        :returns: An error response object that can be serialized and sent to the client.\n        :rtype: ;py:class:`MSGPACKRPCErrorResponse`\n        \"\"\"\n        if not self.unique_id:\n            return None\n\n        response = MSGPACKRPCErrorResponse()\n        response.unique_id = None if self.one_way else self.unique_id\n\n        code, msg = _get_code_and_message(error)\n\n        response.error = msg\n        response._msgpackrpc_error_code = code\n        return response\n\n    def respond(self, result: Any) -> Optional[\"MSGPACKRPCSuccessResponse\"]:\n        \"\"\"Create a response to this request.\n\n        When processing the request completed successfully this method can be used to\n        create a response object.\n\n        :param result: The result of the invoked method.\n        :type result: Anything that can be encoded by MSGPACK.\n        :returns: A response object that can be serialized and sent to the client.\n        :rtype: :py:class:`MSGPACKRPCSuccessResponse`\n        \"\"\"\n        if self.one_way or self.unique_id is None:\n            return None\n\n        response = MSGPACKRPCSuccessResponse()\n\n        response.result = result\n        response.unique_id = self.unique_id\n\n        return response\n\n    def _to_list(self):\n        if self.one_way or self.unique_id is None:\n            return [2, self.method, self.args if self.args is not None else []]\n        else:\n            return [\n                0,\n                self.unique_id,\n                self.method,\n                self.args if self.args is not None else [],\n            ]\n\n    def serialize(self) -> bytes:\n        return msgpack.packb(self._to_list(), use_bin_type=True)\n\n\nclass MSGPACKRPCProtocol(RPCProtocol):\n    \"\"\"MSGPACKRPC protocol implementation.\"\"\"\n\n    def __init__(\n            self,\n            id_generator: Optional[Generator[object, None, None]] = None,\n            *args,\n            **kwargs\n    ) -> None:\n        super(MSGPACKRPCProtocol, self).__init__(*args, **kwargs)\n        self._id_generator = id_generator or default_id_generator()\n\n    def _get_unique_id(self):\n        return next(self._id_generator)\n\n    def request_factory(self) -> \"MSGPACKRPCRequest\":\n        \"\"\"Factory for request objects.\n\n        Allows derived classes to use requests derived from :py:class:`MSGPACKRPCRequest`.\n\n        :rtype: :py:class:`MSGPACKRPCRequest`\n        \"\"\"\n        return MSGPACKRPCRequest()\n\n    def create_request(\n        self,\n        method: str,\n        args: List[Any] = None,\n        kwargs: Dict[str, Any] = None,\n        one_way: bool = False,\n    ) -> \"MSGPACKRPCRequest\":\n        \"\"\"Creates a new :py:class:`MSGPACKRPCRequest` object.\n\n        Called by the client when constructing a request.\n        MSGPACK-RPC allows only the ``args`` argument to be set; keyword\n        arguments are not supported.\n\n        :param str method: The method name to invoke.\n        :param list args: The positional arguments to call the method with.\n        :param dict kwargs: The keyword arguments to call the method with; must\n            be ``None`` as the protocol does not support keyword arguments.\n        :param bool one_way: The request is an update, i.e. it does not expect a reply.\n        :return: A new request instance\n        :rtype: :py:class:`MSGPACKRPCRequest`\n        :raises InvalidRequestError: when ``kwargs`` is defined.\n        \"\"\"\n        if kwargs:\n            raise MSGPACKRPCInvalidRequestError(\"Does not support kwargs\")\n\n        request = self.request_factory()\n        request.one_way = one_way\n\n        if not one_way:\n            request.unique_id = self._get_unique_id()\n\n        request.method = method\n        request.args = list(args) if args is not None else []\n        request.kwargs = None\n\n        return request\n\n    def parse_reply(\n        self, data: bytes\n    ) -> Union[\"MSGPACKRPCSuccessResponse\", \"MSGPACKRPCErrorResponse\"]:\n        \"\"\"De-serializes and validates a response.\n\n        Called by the client to reconstruct the serialized :py:class:`MSGPACKRPCResponse`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized response.\n        :return: A reconstructed response.\n        :rtype: :py:class:`MSGPACKRPCSuccessResponse` or :py:class:`MSGPACKRPCErrorResponse`\n        :raises InvalidReplyError: if the response is not valid MSGPACK or does not conform\n            to the standard.\n        \"\"\"\n        try:\n            rep = msgpack.unpackb(data, raw=False)\n        except Exception as e:\n            raise InvalidReplyError(e)\n\n        if len(rep) != 4:\n            raise InvalidReplyError(\"MSGPACKRPC spec requires reply of length 4\")\n\n        if rep[0] != 1:\n            raise InvalidReplyError(\"Invalid MSGPACK message type\")\n\n        if not isinstance(rep[1], int):\n            raise InvalidReplyError(\"Invalid or missing message ID in response\")\n\n        if rep[2] is not None and rep[3] is not None:\n            raise InvalidReplyError(\"Reply must contain only one of result and error.\")\n\n        if rep[2] is not None:\n            response = MSGPACKRPCErrorResponse()\n            if isinstance(rep[2], list) and len(rep[2]) == 2:\n                code, message = rep[2]\n                if isinstance(code, int) and isinstance(message, str):\n                    response.error = str(message)\n                    response._msgpackrpc_error_code = int(code)\n                else:\n                    response.error = rep[2]\n                    response._msgpackrpc_error_code = None\n            else:\n                response.error = rep[2]\n                response._msgpackrpc_error_code = None\n        else:\n            response = MSGPACKRPCSuccessResponse()\n            response.result = rep[3]\n\n        response.unique_id = rep[1]\n\n        return response\n\n    def parse_request(self, data: bytes) -> \"MSGPACKRPCRequest\":\n        \"\"\"De-serializes and validates a request.\n\n        Called by the server to reconstruct the serialized :py:class:`MSGPACKRPCRequest`.\n\n        :param bytes data: The data stream received by the transport layer containing the\n            serialized request.\n        :return: A reconstructed request.\n        :rtype: :py:class:`MSGPACKRPCRequest`\n        :raises MSGPACKRPCParseError: if the ``data`` cannot be parsed as valid MSGPACK.\n        :raises MSGPACKRPCInvalidRequestError: if the request does not comply with the standard.\n        \"\"\"\n        try:\n            req = msgpack.unpackb(data, raw=False)\n        except Exception:\n            raise MSGPACKRPCParseError()\n\n        if not isinstance(req, list):\n            raise MSGPACKRPCInvalidRequestError()\n\n        if len(req) < 2:\n            raise MSGPACKRPCInvalidRequestError()\n\n        if req[0] == 0:\n            # MSGPACK request\n            request_id = req[1]\n            if not isinstance(request_id, int):\n                raise MSGPACKRPCInvalidRequestError()\n\n            if len(req) == 4:\n                return self._parse_request(req)\n            else:\n                raise MSGPACKRPCInvalidRequestError(request_id=request_id)\n        elif req[0] == 2:\n            # MSGPACK notification\n            if len(req) == 3:\n                return self._parse_notification(req)\n            else:\n                raise MSGPACKRPCInvalidRequestError()\n        else:\n            raise MSGPACKRPCInvalidRequestError()\n\n    def _parse_notification(self, req):\n        if not isinstance(req[1], str):\n            raise MSGPACKRPCInvalidRequestError()\n\n        request = MSGPACKRPCRequest()\n        request.one_way = True\n        request.method = req[1]\n\n        params = req[2]\n        # params should not be None according to the spec; if there are\n        # no params, an empty array must be used\n        if isinstance(params, list):\n            request.args = params\n        else:\n            raise MSGPACKRPCInvalidParamsError(request_id=req[1])\n\n        return request\n\n    def _parse_request(self, req):\n        if not isinstance(req[2], str):\n            raise MSGPACKRPCInvalidRequestError(request_id=req[1])\n\n        request = MSGPACKRPCRequest()\n        request.one_way = False\n        request.method = req[2]\n        request.unique_id = req[1]\n\n        params = req[3]\n        # params should not be None according to the spec; if there are\n        # no params, an empty array must be used\n        if isinstance(params, list):\n            request.args = params\n        else:\n            raise MSGPACKRPCInvalidParamsError(request_id=req[1])\n\n        return request\n\n    def raise_error(\n        self, error: Union[\"MSGPACKRPCErrorResponse\", Dict[str, Any]]\n    ) -> \"MSGPACKRPCError\":\n        \"\"\"Recreates the exception.\n\n        Creates a :py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCError`\n        instance and raises it.\n\n        This allows the error code and the message of the original exception to\n        propagate into the client code.\n\n        The :py:attr:`~tinyrpc.protocols.MSGPACKProtocol.raises_error` flag\n        controls if the exception object is raised or returned.\n\n        :returns: the exception object if it is not allowed to raise it.\n        :raises MSGPACKRPCError: when the exception can be raised.\n            The exception object will contain ``message`` and ``code``.\n        \"\"\"\n        exc = MSGPACKRPCError(error)\n        if self.raises_errors:\n            raise exc\n        return exc\n"
  },
  {
    "path": "tinyrpc/server/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Server definition.\n\nDefines and implements a single-threaded, single-process, synchronous server.\n\"\"\"\n\n# FIXME: needs (more) unittests\n# FIXME: needs checks for out-of-order, concurrency, etc as attributes\nfrom typing import Any, Callable\n\nimport tinyrpc.exc\nfrom tinyrpc import RPCProtocol\nfrom tinyrpc.dispatch import RPCDispatcher\nfrom tinyrpc.transports import ServerTransport\n\n\nclass RPCServer(object):\n    \"\"\"High level RPC server.\n\n    The server is completely generic only assuming some form of RPC communication is intended.\n    Protocol, data transport and method dispatching are injected into the server object.\n\n    :param transport: The data transport mechanism to use.\n    :param protocol: The RPC protocol to use.\n    :param dispatcher: The dispatching mechanism to use.\n    :type transport: :py:class:`~tinyrpc.transports.ServerTransport`\n    :type protocol: :py:class:`~tinyrpc.protocols.RPCProtocol`\n    :type dispatcher: :py:class:`~tinyrpc.dispatch.RPCDispatcher`\n    \"\"\"\n\n    trace = None\n    \"\"\"Trace incoming and outgoing messages.\n\n    When this attribute is set to a callable this callable will be called directly\n    after a message has been received and immediately after a reply is sent.\n    The callable should accept three positional parameters:\n    \n    :param str direction: Either '-->' for incoming or '<--' for outgoing data.\n    :param any context: The context returned by :py:meth:`~tinyrpc.transports.ServerTransport.receive_message`.\n    :param bytes message: The message itself.\n\n    Example:\n    \n    .. code-block:: python\n\n        def my_trace(direction, context, message):\n            logger.debug('%s%s', direction, message)\n\n        server = RPCServer(transport, protocol, dispatcher)\n        server.trace = my_trace\n        server.serve_forever()\n\n    will log all incoming and outgoing traffic of the RPC service.\n    \n    Note that the ``message`` will be the data stream that is transported,\n    not the interpreted meaning of that data.\n    It is therefore possible that the binary stream is unreadable without further translation.\n    \"\"\"\n    def __init__(\n            self, transport: ServerTransport, protocol: RPCProtocol,\n            dispatcher: RPCDispatcher\n    ):\n        self.transport = transport\n        self.protocol = protocol\n        self.dispatcher = dispatcher\n        self.trace = None\n\n    def serve_forever(self) -> None:\n        \"\"\"Handle requests forever.\n\n        Starts the server loop; continuously calling :py:meth:`receive_one_message`\n        to process the next incoming request.\n        \"\"\"\n        while True:\n            self.receive_one_message()\n\n    def receive_one_message(self) -> None:\n        \"\"\"Handle a single request.\n\n        Polls the transport for a new message.\n\n        After a new message has arrived :py:meth:`_spawn` is called with a handler\n        function and arguments to handle the request.\n\n        The handler function will try to decode the message using the supplied\n        protocol, if that fails, an error response will be sent. After decoding\n        the message, the dispatcher will be asked to handle the resulting\n        request and the return value (either an error or a result) will be sent\n        back to the client using the transport.\n        \"\"\"\n        context, message = self.transport.receive_message()\n        if callable(self.trace):\n            self.trace('-->', context, message)\n\n        # assuming protocol is thread-safe and dispatcher is thread-safe, as\n        # long as its immutable\n\n        def handle_message(context: Any, message: bytes) -> None:\n            \"\"\"Parse, process and reply a single request.\"\"\"\n            try:\n                request = self.protocol.parse_request(message)\n            except tinyrpc.exc.RPCError as e:\n                response = e.error_respond()\n            else:\n                response = self.dispatcher.dispatch(\n                    request, getattr(self.protocol, '_caller', None)\n                )\n\n            # send reply\n            if response is not None:\n                result = response.serialize()\n                if callable(self.trace):\n                    self.trace('<--', context, result)\n                self.transport.send_reply(context, result)\n\n        self._spawn(handle_message, context, message)\n\n    def _spawn(self, func: Callable, *args, **kwargs):\n        \"\"\"Spawn a handler function.\n\n        This function is overridden in subclasses to provide concurrency.\n\n        In the base implementation, it simply calls the supplied function\n        ``func`` with ``*args`` and ``**kwargs``. This results in a\n        single-threaded, single-process, synchronous server.\n\n        :param func: A callable to call.\n        :param args: Arguments to ``func``.\n        :param kwargs: Keyword arguments to ``func``.\n        \"\"\"\n        func(*args, **kwargs)\n"
  },
  {
    "path": "tinyrpc/server/gevent.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Server definition.\n\nDefines and implements a single-threaded, single-process, asynchronous server.\n\"\"\"\nfrom typing import Callable\n\nimport gevent\n\nfrom . import RPCServer\n\n\nclass RPCServerGreenlets(RPCServer):\n    \"\"\"Asynchronous RPCServer.\n\n    This implementation of :py:class:`~tinyrpc.server.RPCServer` uses\n    :py:func:`gevent.spawn` to spawn new client handlers, resulting\n    in asynchronous handling of clients using greenlets.\n    \"\"\"\n    def _spawn(self, func: Callable, *args, **kwargs):\n        \"\"\"Spawn a handler function.\n\n        Spawns the supplied ``func`` with ``*args`` and ``**kwargs``\n        as a gevent greenlet.\n\n        :param callable func: A callable to call.\n        :param list args: Arguments to ``func``.\n        :param dict kwargs: Keyword arguments to ``func``.\n        \"\"\"\n        gevent.spawn(func, *args, **kwargs)\n\n    def start(self):\n        \"\"\"\n        Create a Greenlet with serve_forever so you can do a gevent.joinall of\n        several RPCServerGreenlets\n        \"\"\"\n        return gevent.spawn(self.serve_forever)\n"
  },
  {
    "path": "tinyrpc/transports/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Any, Tuple\n\n\nclass ServerTransport(object):\n    \"\"\"Abstract base class for all server transports.\n\n    The server side implementation of the transport component.\n    Requests and replies encoded by the protocol component are\n    exchanged between client and server using the :py:class:`ServerTransport`\n    and :py:class:`ClientTransport` classes.\n    \"\"\"\n    def receive_message(self) -> Tuple[Any, bytes]:\n        \"\"\"Receive a message from the transport.\n\n        Blocks until a message has been received.\n        May return an opaque context object to its caller that should be passed on to\n        :py:func:`~tinyrpc.transport.ServerTransport.send_reply` to identify\n        the transport or requester later on.\n        Use and function of the context object are entirely controlled by the transport\n        instance.\n\n        The message must be treated as a binary entity as only the protocol\n        level will know how to interpret the message.\n\n        If the transport encodes the message in some way, the opposite end\n        is responsible for decoding it before it is passed to either client\n        or server.\n\n        :return: A tuple consisting of ``(context, message)``.\n            Where ``context`` can be any valid Python type and\n            ``message`` must be a :py:class:`bytes` object.\n        \"\"\"\n        raise NotImplementedError()\n\n    def send_reply(self, context: Any, reply: bytes) -> None:\n        \"\"\"Sends a reply to a client.\n\n        The client is usually identified by passing ``context`` as returned\n        from the original :py:meth:`receive_message` call.\n\n        The reply must be a bytes object since only the protocol\n        level will know how to construct the reply.\n\n        :param any context: A context returned by :py:meth:`receive_message`.\n        :param bytes reply: The reply to return to the client.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass ClientTransport(object):\n    \"\"\"Abstract base class for all client transports.\n\n    The client side implementation of the transport component.\n    Requests and replies encoded by the protocol component are\n    exchanged between client and server using the :py:class:`ServerTransport`\n    and :py:class:`ClientTransport` classes.    \"\"\"\n    def send_message(self, message: bytes, expect_reply: bool = True) -> bytes:\n        \"\"\"Send a message to the server and possibly receive a reply.\n\n        Sends a message to the connected server.\n\n        The message must be treated as a binary entity as only the protocol\n        level will know how to interpret the message.\n\n        If the transport encodes the message in some way, the opposite end\n        is responsible for decoding it before it is passed to either client\n        or server.\n\n        This function will block until the reply has been received.\n\n        :param bytes message: The request to send to the server.\n        :param bool expect_reply: Some protocols allow notifications for which a\n            reply is not expected. When this flag is ``False`` the transport may\n            not wait for a response from the server.\n            **Note** that it is still the responsibility of the transport layer how\n            to implement this. It is still possible that the server sends some form\n            of reply regardless the value of this flag.\n        :return: The servers reply to the request.\n        :rtype: bytes\n        \"\"\"\n        raise NotImplementedError\n"
  },
  {
    "path": "tinyrpc/transports/callback.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom typing import Callable, Tuple, Any\n\nfrom . import ServerTransport\n\n\nclass CallbackServerTransport(ServerTransport):\n    \"\"\"Callback server transport.\n\n    The :py:class:`CallbackServerTransport` uses the provided callbacks to implement\n    communication with the counterpart.\n\n    Used when tinyrpc is part of a system where it cannot directly attach\n    to a socket or stream. The methods :py:meth:`receive_message` and\n    :py:meth:`send_reply` are implemented by callback functions that are\n    set when constructed.\n\n    :param callable reader: Called when the transport wants to receive a new request.\n\n        :returns: The RPC request.\n        :rtype: bytes\n\n    :param callable writer(reply): Called to return the response to the client.\n\n        :param bytes reply: The response to the request.\n    \"\"\"\n    def __init__(\n            self, reader: Callable[[], bytes], writer: Callable[[bytes], None]\n    ) -> None:\n        super(CallbackServerTransport, self).__init__()\n        self.reader = reader\n        self.writer = writer\n\n    def receive_message(self) -> Tuple[Any, bytes]:\n        \"\"\"Receive a message from the transport.\n\n        Uses the callback function :py:attr:`reader` to obtain a :py:class:`bytes` ``message``.\n        May return a context opaque to clients that should be passed on to\n        :py:meth:`send_reply` to identify the client later on.\n\n        :return: A tuple consisting of ``(context, message)``.\n        \"\"\"\n        return None, self.reader()\n\n    def send_reply(self, context: Any, reply: bytes):\n        \"\"\"Sends a reply to a client.\n\n        The client is usually identified by passing ``context`` as returned\n        from the original :py:meth:`receive_message` call.\n\n        Uses the callback function :py:attr:`writer` to forward the reply.\n\n        :param any context: A context returned by :py:meth:`receive_message`.\n        :param bytes reply: The reply.\n        \"\"\"\n\n        self.writer(reply)\n"
  },
  {
    "path": "tinyrpc/transports/cgi.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport os\nimport sys\nimport urllib.parse as urlparse\nfrom typing import Any, Tuple\n\nfrom . import ServerTransport\n\n\nclass CGIServerTransport(ServerTransport):\n    \"\"\"CGI transport.\n\n    The CGIServerTransport adds CGI as a supported server protocol.\n    It can be used with the regular HTTP client.\n\n    Reading stdin is blocking but, given that we've been called, something is\n    waiting.  The transport accepts only POST requests.\n\n    A POST request provides the entire JSON-RPC request in the body of the HTTP\n    request.\n    \"\"\"\n    def receive_message(self) -> Tuple[Any, bytes]:\n        \"\"\"Receive a message from the transport.\n\n        Blocks until a message has been received. May return a context\n        opaque to clients that should be passed to :py:func:`send_reply`\n        to identify the client later on.\n\n        :return: A tuple consisting of ``(context, message)``.\n        \"\"\"\n\n        if not ('REQUEST_METHOD' in os.environ\n                and os.environ['REQUEST_METHOD'] == 'POST'):\n            print(\"Status: 405 Method not Allowed; only POST is accepted\")\n            exit(0)\n\n        # POST\n        content_length = int(os.environ['CONTENT_LENGTH'])\n        request_json = sys.stdin.read(content_length)\n        request_json = urlparse.unquote(request_json)\n        # context isn't used with cgi\n        return None, request_json\n\n    def send_reply(self, context: Any, reply: bytes) -> None:\n        \"\"\"Sends a reply to a client.\n\n        The client is usually identified by passing ``context`` as returned\n        from the original :py:func:`receive_message` call.\n\n        Messages must be bytes, it is up to the sender to convert the message\n        beforehand. A non-bytes value raises a :py:exc:`TypeError`.\n\n        :param any context: A context returned by :py:func:`receive_message`.\n        :param bytes reply: A binary to send back as the reply.\n        \"\"\"\n\n        # context isn't used with cgi\n        # Using sys.stdout.buffer.write() fails as stdout is on occasion monkey patched\n        # to AsyncFile which doesn't support the buffer attribute.\n        print(\"Status: 200 OK\")\n        print(\"Content-Type: application/json\")\n        print(\"Cache-Control: no-cache\")\n        print(\"Pragma: no-cache\")\n        print(\"Content-Length: %d\" % len(reply))\n        print()\n        print(reply.decode())\n"
  },
  {
    "path": "tinyrpc/transports/http.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Callable, Dict\n\nimport requests\n\nfrom . import ClientTransport\n\n\nclass HttpPostClientTransport(ClientTransport):\n    \"\"\"HTTP POST based client transport.\n\n    Requires :py:mod:`requests`. Submits messages to a server using the body of\n    an ``HTTP`` ``POST`` request. Replies are taken from the responses body.\n\n    :param str endpoint: The URL to send ``POST`` data to.\n    :param callable post_method: allows to replace `requests.post` with another method,\n        e.g. the post method of a `requests.Session()` instance.\n    :param dict kwargs: Additional parameters for :py:func:`requests.post`.\n    \"\"\"\n    def __init__(\n            self, endpoint: str, post_method: Callable = None, **kwargs: Dict\n    ):\n        self.endpoint = endpoint\n        self.request_kwargs = kwargs\n        if post_method is None:\n            self.post = requests.post\n        else:\n            self.post = post_method\n\n    def send_message(self, message: bytes, expect_reply: bool = True):\n        if not isinstance(message, bytes):\n            raise TypeError('message must by of type bytes')\n\n        r = self.post(self.endpoint, data=message, **self.request_kwargs)\n\n        if expect_reply:\n            # Note that this is not strictly conforming to the (JSON RPC) standard since\n            # even notifications may, under certain conditions, return an\n            # error message which is completely ignored by this implementation.\n            return r.content\n"
  },
  {
    "path": "tinyrpc/transports/rabbitmq.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom typing import Tuple, Any\n\nimport pika\n\nfrom . import ServerTransport, ClientTransport\n\n\nclass RabbitMQServerTransport(ServerTransport):\n    \"\"\"Server transport based on a :py:class:`pika.BlockingConnection`.\n\n    The transport assumes a RabbitMQ topology has already been established.\n\n    :param connection: A :py:class:`pika.BlockingConnection` instance.\n    :param queue: The RabbitMQ queue to consume messages from.\n    :param exchange: The RabbitMQ exchange to use.\n    \"\"\"\n\n    def __init__(self, connection: pika.BlockingConnection, queue: str, exchange: str = '') -> None:\n        self.connection = connection\n        self.queue = queue\n        self.exchange = exchange\n\n        self.channel = self.connection.channel()\n        self.channel.queue_declare(queue=self.queue)\n        self.channel.basic_consume(queue=self.queue, on_message_callback=self.on_receive)\n        self.message_received = False\n\n    def receive_message(self) -> Tuple[Any, bytes]:\n        while not self.message_received:\n            self.connection.process_data_events()\n        return self.context, self.message\n\n    def send_reply(self, context: Any, reply: bytes) -> None:\n        ch, method, props = context\n        ch.basic_publish(exchange=self.exchange,\n                     routing_key=props.reply_to,\n                     properties=pika.BasicProperties(correlation_id = props.correlation_id),\n                     body=reply)\n        ch.basic_ack(delivery_tag=method.delivery_tag)\n        self.message_received = False # message processed, reset status\n\n    def on_receive(self, ch, method, props, body):\n        self.context = (ch, method, props)\n        self.message = body\n        self.message_received = True\n\n    @classmethod\n    def create(cls, host: str, queue: str, exchange: str = '') -> 'RabbitMQServerTransport':\n        \"\"\"Create new server transport.\n\n        Instead of creating the BlockingConnection yourself, you can call this function and\n        pass in the host name, queue, and exchange.\n\n        :param host: The host clients will connect to.\n        :param queue: The RabbitMQ queue to consume messages from.\n        :param exchange: The RabbitMQ exchange to use.\n        \"\"\"\n        connection = pika.BlockingConnection(pika.ConnectionParameters(host))\n        return cls(connection, queue, exchange)\n\n\nclass RabbitMQClientTransport(ClientTransport):\n    \"\"\"Client transport based on a :py:class:`pika.BlockingConnection`.\n\n    The transport assumes a RabbitMQ topology has already been established.\n\n    :param connection: A :py:class:`pika.BlockingConnection` instance.\n    :param routing_key: The RabbitMQ routing key to direct messages.\n    :param exchange: The RabbitMQ exchange to use.\n    \"\"\"\n\n    def __init__(self, connection: pika.BlockingConnection, routing_key: str, exchange: str = '') -> None:\n        self.connection = connection\n        self.routing_key = routing_key\n        self.exchange = exchange\n        self._id_counter = 1000\n\n        self.channel = self.connection.channel()\n        qd_result = self.channel.queue_declare(queue='', exclusive=True)\n        self.callback_queue = qd_result.method.queue\n\n        self.channel.basic_consume(\n            queue=self.callback_queue,\n            on_message_callback=self.on_response,\n            auto_ack=True\n        )\n\n    def _get_unique_id(self) -> int:\n        self._id_counter += 1\n        return self._id_counter\n\n    def send_message(self, message: bytes, expect_reply: bool = True) -> bytes:\n        self.response_data = None\n        self.corr_id = str(self._get_unique_id())\n        self.channel.basic_publish(\n            exchange=self.exchange,\n            routing_key=self.routing_key,\n            properties=pika.BasicProperties(\n                reply_to=self.callback_queue,\n                correlation_id=self.corr_id,\n            ),\n            body=message)\n\n        if expect_reply:\n            while self.response_data is None:\n                self.connection.process_data_events()\n            return self.response_data\n\n    def on_response(self, ch, method, props, body):\n        if self.corr_id == props.correlation_id:\n            self.response_data = body\n\n    @classmethod\n    def create(cls, host: str, routing_key: str, exchange: str = '') -> 'RabbitMQClientTransport':\n        \"\"\"Create new client transport.\n\n        Instead of creating the BlockingConnection yourself, you can call this function and\n        pass in the host name, routing key, and exchange.\n\n        :param host: The host clients will connect to.\n        :param routing_key: The RabbitMQ routing key to direct messages.\n        :param exchange: The RabbitMQ exchange to use.\n        \"\"\"\n        connection = pika.BlockingConnection(pika.ConnectionParameters(host))\n        return cls(connection, routing_key, exchange)\n"
  },
  {
    "path": "tinyrpc/transports/websocket.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport queue\nfrom typing import Callable, Tuple, Any\n\nfrom . import ServerTransport\nfrom geventwebsocket.resource import WebSocketApplication, Resource\n\n\nclass WSServerTransport(ServerTransport):\n    \"\"\"\n    Requires :py:mod:`geventwebsocket`.\n\n    Due to the nature of WS, this transport has a few peculiarities: It must\n    be run in a thread, greenlet or some other form of concurrent execution\n    primitive.\n\n    This is due to\n    :py:attr:`~tinyrpc.transports.websocket.WSServerTransport.handle` which is\n    a :py:class:`geventwebsocket.resource.Resource` that joins a wsgi handler\n    for the / and a WebSocket handler for the /ws path. These resource is\n    used in combination with a :py:class:`geventwebsocket.server.WebSocketServer`\n    that blocks while waiting for a call to\n    :py:func:`~tinyrpc.transports.wsgi.WSServerTransport.send_reply`.\n\n    The parameter ``queue_class`` must be used to supply a proper queue class\n    for the chosen concurrency mechanism (i.e. when using :py:mod:`gevent`,\n    set it to :py:class:`gevent.queue.Queue`).\n\n    :param queue_class: The queue class to use.\n    :param wsgi_handler: Can be used to change the standard response to a\n        http request to the /\n    \"\"\"\n    def __init__(\n            self,\n            queue_class: queue.Queue = queue.Queue,\n            wsgi_handler: Callable[[], str] = None\n    ) -> None:\n        self._queue_class = queue_class\n        self.messages = queue_class()\n\n        def static_wsgi_app(environ, start_response) -> str:\n            start_response(\"200 OK\", [(\"Content-Type\", \"text/html\")])\n            return 'Ready for WebSocket connection in /ws'\n\n        self.handle = Resource({\n            '/':\n            static_wsgi_app if wsgi_handler is None else wsgi_handler,\n            '/ws':\n            WSApplicationFactory(self.messages, queue_class)\n        })\n\n    def receive_message(self) -> Tuple[Any, bytes]:\n        return self.messages.get()\n\n    def send_reply(self, context: Any, reply: bytes) -> None:\n        context.put(reply)\n\n\nclass WSApplicationFactory(object):\n    \"\"\"\n    Creates WebSocketApplications with a messages queue and the queue_class\n    needed for the communication with the WSServerTransport.\n    \"\"\"\n    def __init__(self, messages: queue.Queue, queue_class):\n        self.messages = messages\n        self._queue_class = queue_class\n\n    def __call__(self, ws):\n        \"\"\"\n        The fake __init__ for the WSApplication\n        \"\"\"\n        app = WSApplication(ws)\n        app.messages = self.messages\n        app._queue_class = self._queue_class\n        return app\n\n    @classmethod\n    def protocol(cls):\n        return WebSocketApplication.protocol()\n\n\nclass WSApplication(WebSocketApplication):\n    \"\"\"\n    This class is the bridge between the WSServerTransport and the WebSocket\n    protocol implemented by\n    :py:class:`geventwebsocket.resource.WebSocketApplication`\n    \"\"\"\n    def on_message(self, msg, *args, **kwargs):\n        # create new context\n        context = self._queue_class()\n        self.messages.put((context, msg))\n        response = context.get()\n        self.ws.send(response, *args, **kwargs)\n"
  },
  {
    "path": "tinyrpc/transports/websocketclient.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Dict\n\nimport geventwebsocket as websocket\n\nfrom . import ClientTransport\n\n\nclass HttpWebSocketClientTransport(ClientTransport):\n    \"\"\"HTTP WebSocket based client transport.\n\n    Requires :py:mod:`websocket-python`. Submits messages to a server using the body of\n    an ``HTTP`` ``WebSocket`` message. Replies are taken from the response of the websocket.\n\n    The connection is establish on the ``__init__`` because the protocol is connection oriented,\n    you need to close the connection calling the close method.\n\n    :param endpoint: The URL to connect the websocket.\n    :param kwargs: Additional parameters for :py:func:`websocket.send`.\n    \"\"\"\n    def __init__(self, endpoint: str, **kwargs: Dict):\n        self.endpoint = endpoint\n        self.request_kwargs = kwargs\n        self.ws = websocket.create_connection(self.endpoint, **kwargs)\n\n    def send_message(self, message: bytes, expect_reply: bool = True) -> bytes:\n        if not isinstance(message, bytes):\n            raise TypeError('message must by of type bytes')\n        self.ws.send(message)\n        r = self.ws.recv()\n        if expect_reply:\n            return r\n\n    def close(self) -> None:\n        \"\"\"Terminate the connection.\n\n        Since WebSocket maintains an open connection over multiple calls\n        it must be closed explicitly.\n        \"\"\"\n        if self.ws is not None:\n            self.ws.close()\n"
  },
  {
    "path": "tinyrpc/transports/wsgi.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport queue as Queue\nfrom typing import Tuple, Any\n\nfrom werkzeug.wrappers import Response, Request\n\nfrom . import ServerTransport\n\n\nclass WsgiServerTransport(ServerTransport):\n    \"\"\"WSGI transport.\n\n    Requires :py:mod:`werkzeug`.\n\n    Due to the nature of WSGI, this transport has a few peculiarities: It must\n    be run in a thread, greenlet or some other form of concurrent execution\n    primitive.\n\n    This is due to\n    :py:func:`~tinyrpc.transports.wsgi.WsgiServerTransport.handle` blocking\n    while waiting for a call to\n    :py:func:`~tinyrpc.transports.wsgi.WsgiServerTransport.send_reply`.\n\n    The parameter ``queue_class`` must be used to supply a proper queue class\n    for the chosen concurrency mechanism (i.e. when using :py:mod:`gevent`,\n    set it to :py:class:`gevent.queue.Queue`).\n\n    :param max_content_length: The maximum request content size allowed. Should\n                               be set to a sane value to prevent DoS-Attacks.\n    :param queue_class: The Queue class to use.\n    :param allow_origin: The ``Access-Control-Allow-Origin`` header. Defaults\n                         to ``*`` (so change it if you need actual security).\n    \"\"\"\n    def __init__(\n            self,\n            max_content_length: int = 4096,\n            queue_class: Queue.Queue = Queue.Queue,\n            allow_origin: str = '*'\n    ):\n        self._queue_class = queue_class\n        self.messages = queue_class()\n        self.max_content_length = max_content_length\n        self.allow_origin = allow_origin\n\n    def receive_message(self) -> Tuple[Any, bytes]:\n        return self.messages.get()\n\n    def send_reply(self, context: Any, reply: bytes):\n        context.put(reply)\n\n    def handle(self, environ, start_response):\n        \"\"\"WSGI handler function.\n\n        The transport will serve a request by reading the message and putting\n        it into an internal buffer. It will then block until another\n        concurrently running function sends a reply using :py:meth:`send_reply`.\n\n        The reply will then be sent to the client being handled and handle will\n        return.\n        \"\"\"\n        request = Request(environ)\n        request.max_content_length = self.max_content_length\n\n        access_control_headers = {\n            'Access-Control-Allow-Methods':\n            'POST',\n            'Access-Control-Allow-Origin':\n            self.allow_origin,\n            'Access-Control-Allow-Headers':\n            'Content-Type, X-Requested-With, Accept, Origin'\n        }\n\n        post_headers = {\n            'Content-Type': 'application/json'\n        }\n\n        if request.method == 'OPTIONS':\n            response = Response(headers=access_control_headers)\n\n        elif request.method == 'POST':\n            # message is encoded in POST, read it...\n            msg = request.stream.read()\n\n            # create new context\n            context = self._queue_class()\n\n            self.messages.put((context, msg))\n\n            # collect and combine all headers\n            response_headers = dict(**access_control_headers, **post_headers)\n\n            # ...and send the reply\n            response = Response(context.get(), headers=response_headers)\n        else:\n            # nothing else supported at the moment\n            response = Response('Only POST supported', 405)\n\n        return response(environ, start_response)\n"
  },
  {
    "path": "tinyrpc/transports/zmq.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import absolute_import  # needed for zmq import\n\nfrom typing import Tuple, Any, Dict\n\nimport zmq\n\nfrom . import ServerTransport, ClientTransport\nfrom .. import exc\n\n\nclass ZmqServerTransport(ServerTransport):\n    \"\"\"Server transport based on a :py:const:`zmq.ROUTER` socket.\n\n    :param socket: A :py:const:`zmq.ROUTER` socket instance, bound to an\n                   endpoint.\n    \"\"\"\n\n    def __init__(self, socket: zmq.Socket) -> None:\n        self.socket = socket\n\n    def receive_message(self) -> Tuple[Any, bytes]:\n        msg = self.socket.recv_multipart()\n        return msg[:-1], msg[-1]\n\n    def send_reply(self, context: Any, reply: bytes) -> None:\n        self.socket.send_multipart(context + [reply])\n\n    @classmethod\n    def create(cls, zmq_context: zmq.Context, endpoint: str) -> 'ZmqServerTransport':\n        \"\"\"Create new server transport.\n\n        Instead of creating the socket yourself, you can call this function and\n        merely pass the :py:class:`zmq.core.context.Context` instance.\n\n        By passing a context imported from :py:mod:`zmq.green`, you can use\n        green (gevent) 0mq sockets as well.\n\n        :param zmq_context: A 0mq context.\n        :param endpoint: The endpoint clients will connect to.\n        \"\"\"\n        socket = zmq_context.socket(zmq.ROUTER)\n        socket.bind(endpoint)\n        return cls(socket)\n\n\nclass ZmqClientTransport(ClientTransport):\n    \"\"\"Client transport based on a :py:const:`zmq.REQ` socket.\n\n    :param socket: A :py:const:`zmq.REQ` socket instance, connected to the\n                   server socket.\n    :param timeout: An optional float. When set it defines the time period\n                    in seconds to wait for a reply.\n                    It will generate a :py:class:`exc.TimeoutError` exception\n                    if no reply was received in time.\n    \"\"\"\n\n    def __init__(self, socket: zmq.Socket, timeout: float = None) -> None:\n        self.socket = socket\n        self.timeout = timeout\n\n    def send_message(self, message: bytes, expect_reply: bool = True) -> bytes:\n        self.socket.send(message)\n\n        # zmq contains a state machine preventing a new request\n        # until the previous one is answered, so always receive\n        if self.timeout is None:\n            reply = self.socket.recv()\n        else:\n            poller = zmq.Poller()\n            poller.register(self.socket, zmq.POLLIN)\n            ready = dict(poller.poll(int(self.timeout * 1000)))\n            if ready.get(self.socket) == zmq.POLLIN:\n                reply = self.socket.recv()\n            else:\n                raise exc.TimeoutError()\n        if expect_reply:\n            return reply\n\n    @classmethod\n    def create(cls, zmq_context: zmq.Context, endpoint: str, timeout: float = None) -> 'ZmqClientTransport':\n        \"\"\"Create new client transport.\n\n        Instead of creating the socket yourself, you can call this function and\n        merely pass the :py:class:`zmq.core.context.Context` instance.\n\n        By passing a context imported from :py:mod:`zmq.green`, you can use\n        green (gevent) 0mq sockets as well.\n\n        :param zmq_context: A 0mq context.\n        :param endpoint: The endpoint the server is bound to.\n        :param timeout: Optional period in seconds to wait for reply\n        \"\"\"\n        socket = zmq_context.socket(zmq.REQ)\n        socket.connect(endpoint)\n        return cls(socket, timeout)\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\n#envlist = py38\nenvlist = py34, py35, py36, py37, py38, py39, py310, py311\n\n[testenv]\ndeps = -rrequirements.txt\ncommands=\n    pytest -rs\n    pytest --cov=tinyrpc/ --cov-report=term --cov-report=html\n"
  }
]