Full Code of mbr/tinyrpc for AI

master d77f1218da49 cached
60 files
262.1 KB
67.7k tokens
365 symbols
1 requests
Download .txt
Showing preview only (278K chars total). Download the full file or copy to clipboard to get everything.
Repository: mbr/tinyrpc
Branch: master
Commit: d77f1218da49
Files: 60
Total size: 262.1 KB

Directory structure:
gitextract_i25jxbas/

├── .github/
│   └── workflows/
│       └── python-tox.yml
├── .gitignore
├── .readthedocs.yaml
├── .style.yapf
├── .vscode/
│   └── settings.json
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── _static/
│   │   └── uml.xmi
│   ├── client.rst
│   ├── conf.py
│   ├── dispatch.rst
│   ├── examples.rst
│   ├── exceptions.rst
│   ├── index.rst
│   ├── jsonrpc.rst
│   ├── make.bat
│   ├── msgpackrpc.rst
│   ├── protocols.rst
│   ├── server.rst
│   ├── structure.rst
│   └── transports.rst
├── examples/
│   ├── http_client_example.py
│   ├── http_server_example.py
│   ├── zmq_client_example.py
│   └── zmq_server_example.py
├── optional_features.pip
├── pyproject.toml
├── requirements.txt
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── test_client.py
│   ├── test_dispatch.py
│   ├── test_jsonrpc.py
│   ├── test_msgpackrpc.py
│   ├── test_protocols.py
│   ├── test_rabbitmq_transport.py
│   ├── test_server.py
│   ├── test_transport.py
│   └── test_wsgi_transport.py
├── tinyrpc/
│   ├── __init__.py
│   ├── client.py
│   ├── dispatch/
│   │   └── __init__.py
│   ├── exc.py
│   ├── protocols/
│   │   ├── __init__.py
│   │   ├── jsonrpc.py
│   │   └── msgpackrpc.py
│   ├── server/
│   │   ├── __init__.py
│   │   └── gevent.py
│   └── transports/
│       ├── __init__.py
│       ├── callback.py
│       ├── cgi.py
│       ├── http.py
│       ├── rabbitmq.py
│       ├── websocket.py
│       ├── websocketclient.py
│       ├── wsgi.py
│       └── zmq.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/python-tox.yml
================================================
# This workflow will install tox and run tox for each version of Python defined in the matrix


name: Unit tests

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install tox
      run: |
        python -m pip install --upgrade pip
        python -m pip install tox
    - name: Run tox
      run: tox -e py


================================================
FILE: .gitignore
================================================
.cache
.tox
.idea
__pycache__
*.pyc
*egg-info
docs/_build
.coverage
.pytest_cache
_env
build/
dist/
htmlcov/


================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the version of Python and other tools you might need
build:
  os: ubuntu-22.04
  tools:
    python: "3.11"

# Build documentation in the docs/ directory with Sphinx
sphinx:
  configuration: docs/conf.py

# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
#   install:
#   - requirements: docs/requirements.txt

================================================
FILE: .style.yapf
================================================
[style]
# Align closing bracket with visual indentation.
align_closing_bracket_with_visual_indent=True

# Allow dictionary keys to exist on multiple lines. For example:
#
#   x = {
#       ('this is the first element of a tuple',
#        'this is the second element of a tuple'):
#            value,
#   }
allow_multiline_dictionary_keys=False

# Allow lambdas to be formatted on more than one line.
allow_multiline_lambdas=True

# Allow splits before the dictionary value.
allow_split_before_dict_value=True

# Number of blank lines surrounding top-level function and class
# definitions.
blank_lines_around_top_level_definition=2

# Insert a blank line before a class-level docstring.
blank_line_before_class_docstring=False

# Insert a blank line before a 'def' or 'class' immediately nested
# within another 'def' or 'class'. For example:
#
#   class Foo:
#                      # <------ this blank line
#     def method():
#       ...
blank_line_before_nested_class_or_def=False

# Do not split consecutive brackets. Only relevant when
# dedent_closing_brackets is set. For example:
#
#    call_func_that_takes_a_dict(
#        {
#            'key1': 'value1',
#            'key2': 'value2',
#        }
#    )
#
# would reformat to:
#
#    call_func_that_takes_a_dict({
#        'key1': 'value1',
#        'key2': 'value2',
#    })
coalesce_brackets=True

# The column limit.
column_limit=79

# The style for continuation alignment. Possible values are:
#
# - SPACE: Use spaces for continuation alignment. This is default behavior.
# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns
#   (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation
#   alignment.
# - LESS: Slightly left if cannot vertically align continuation lines with
#   indent characters.
# - VALIGN-RIGHT: Vertically align continuation lines with indent
#   characters. Slightly right (one more indent character) if cannot
#   vertically align continuation lines with indent characters.
#
# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is
# enabled.
continuation_align_style=SPACE

# Indent width used for line continuations.
continuation_indent_width=4

# Put closing brackets on a separate line, dedented, if the bracketed
# expression can't fit in a single line. Applies to all kinds of brackets,
# including function definitions and calls. For example:
#
#   config = {
#       'key1': 'value1',
#       'key2': 'value2',
#   }        # <--- this bracket is dedented and on a separate line
#
#   time_series = self.remote_client.query_entity_counters(
#       entity='dev3246.region1',
#       key='dns.query_latency_tcp',
#       transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
#       start_ts=now()-timedelta(days=3),
#       end_ts=now(),
#   )        # <--- this bracket is dedented and on a separate line
dedent_closing_brackets=True

# Place each dictionary entry onto its own line.
each_dict_entry_on_separate_line=True

# The regex for an i18n comment. The presence of this comment stops
# reformatting of that line, because the comments are required to be
# next to the string they translate.
i18n_comment=

# The i18n function call names. The presence of this function stops
# reformattting on that line, because the string it has cannot be moved
# away from the i18n comment.
i18n_function_call=

# Indent the dictionary value if it cannot fit on the same line as the
# dictionary key. For example:
#
#   config = {
#       'key1':
#           'value1',
#       'key2': value1 +
#               value2,
#   }
indent_dictionary_value=False

# The number of columns to use for indentation.
indent_width=4

# Join short lines into one line. E.g., single line 'if' statements.
join_multiple_lines=True

# Do not include spaces around selected binary operators. For example:
#
#   1 + 2 * 3 - 4 / 5
#
# will be formatted as follows when configured with a value "*,/":
#
#   1 + 2*3 - 4/5
#
no_spaces_around_selected_binary_operators=set()

# Use spaces around default or named assigns.
spaces_around_default_or_named_assign=False

# Use spaces around the power operator.
spaces_around_power_operator=False

# The number of spaces required before a trailing comment.
spaces_before_comment=2

# Insert a space between the ending comma and closing bracket of a list,
# etc.
space_between_ending_comma_and_closing_bracket=True

# Split before arguments if the argument list is terminated by a
# comma.
split_arguments_when_comma_terminated=False

# Set to True to prefer splitting before '&', '|' or '^' rather than
# after.
split_before_bitwise_operator=True

# Split before the closing bracket if a list or dict literal doesn't fit on
# a single line.
split_before_closing_bracket=True

# Split before a dictionary or set generator (comp_for). For example, note
# the split before the 'for':
#
#   foo = {
#       variable: 'Hello world, have a nice day!'
#       for variable in bar if variable != 42
#   }
split_before_dict_set_generator=True

# Split after the opening paren which surrounds an expression if it doesn't
# fit on a single line.
split_before_expression_after_opening_paren=False

# If an argument / parameter list is going to be split, then split before
# the first argument.
split_before_first_argument=True

# Set to True to prefer splitting before 'and' or 'or' rather than
# after.
split_before_logical_operator=True

# Split named assignments onto individual lines.
split_before_named_assigns=True

# Set to True to split list comprehensions and generators that have
# non-trivial expressions and multiple clauses before each of these
# clauses. For example:
#
#   result = [
#       a_long_var + 100 for a_long_var in xrange(1000)
#       if a_long_var % 10]
#
# would reformat to something like:
#
#   result = [
#       a_long_var + 100
#       for a_long_var in xrange(1000)
#       if a_long_var % 10]
split_complex_comprehension=True

# The penalty for splitting right after the opening bracket.
split_penalty_after_opening_bracket=30

# The penalty for splitting the line after a unary operator.
split_penalty_after_unary_operator=10000

# The penalty for splitting right before an if expression.
split_penalty_before_if_expr=0

# The penalty of splitting the line around the '&', '|', and '^'
# operators.
split_penalty_bitwise_operator=300

# The penalty for splitting a list comprehension or generator
# expression.
split_penalty_comprehension=80

# The penalty for characters over the column limit.
split_penalty_excess_character=4500

# The penalty incurred by adding a line split to the unwrapped line. The
# more line splits added the higher the penalty.
split_penalty_for_added_line_split=30

# The penalty of splitting a list of "import as" names. For example:
#
#   from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
#                                                             long_argument_2,
#                                                             long_argument_3)
#
# would reformat to something like:
#
#   from a_very_long_or_indented_module_name_yada_yad import (
#       long_argument_1, long_argument_2, long_argument_3)
split_penalty_import_names=0

# The penalty of splitting the line around the 'and' and 'or'
# operators.
split_penalty_logical_operator=300

# Use the Tab character for indentation.
use_tabs=False



================================================
FILE: .vscode/settings.json
================================================
{
    "restructuredtext.confPath": "${workspaceFolder}/docs"
    "files.watcherExclude": {
        "**/.git/objects/**": true,
        "**/.git/subtree-cache/**": true,
        "**/node_modules/*/**": true,
        ".tox/**": true
    }
}

================================================
FILE: LICENSE
================================================
Copyright (c) 2013 Marc Brinkmann

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include README.rst
include LICENSE


================================================
FILE: README.rst
================================================
tinyrpc: A small and modular way of handling web-related RPC
============================================================

.. image:: https://readthedocs.org/projects/tinyrpc/badge/?version=latest
    :target: https://tinyrpc.readthedocs.io/en/latest
.. image:: https://github.com/mbr/tinyrpc/actions/workflows/python-tox.yml/badge.svg
    :target: https://github.com/mbr/tinyrpc/actions/workflows/python-tox.yml
.. image:: https://badge.fury.io/py/tinyrpc.svg
    :target: https://pypi.org/project/tinyrpc/

Note
----

Tinyrpc has been revised.

The current version will support Python3 only.
Have a look at the 0.9.x version if you need Python2 support.
Python2 support will be dropped completely when Python2 retires,
somewhere in 2020.

Motivation
----------

As of this writing (in Jan 2013) there are a few jsonrpc_ libraries already out
there on PyPI_, most of them handling one specific use case (e.g. json via
WSGI, using Twisted, or TCP-sockets).

None of the libraries, however, makes it easy to reuse the jsonrpc_-parsing bits
and substitute a different transport (i.e. going from json_ via TCP_ to an
implementation using WebSockets_ or 0mq_).

In the end, all these libraries have their own dispatching interfaces and a
custom implementation of handling jsonrpc_.  Today (march 2019) that hasn't changed.

``tinyrpc`` aims to do better by dividing the problem into cleanly
interchangeable parts that allow easy addition of new transport methods, RPC
protocols or dispatchers.

Example:

To create a server process receiving and handling JSONRPC requests do:

.. code-block:: python

    import gevent
    import gevent.pywsgi
    import gevent.queue
    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
    from tinyrpc.transports.wsgi import WsgiServerTransport
    from tinyrpc.server.gevent import RPCServerGreenlets
    from tinyrpc.dispatch import RPCDispatcher

    dispatcher = RPCDispatcher()
    transport = WsgiServerTransport(queue_class=gevent.queue.Queue)

    # start wsgi server as a background-greenlet
    wsgi_server = gevent.pywsgi.WSGIServer(('127.0.0.1', 5000), transport.handle)
    gevent.spawn(wsgi_server.serve_forever)

    rpc_server = RPCServerGreenlets(transport, JSONRPCProtocol(), dispatcher)

    @dispatcher.public
    def reverse_string(s):
        return s[::-1]

    # in the main greenlet, run our rpc_server
    rpc_server.serve_forever()

The corresponding client code looks like:

.. code-block:: python

    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
    from tinyrpc.transports.http import HttpPostClientTransport
    from tinyrpc import RPCClient

    rpc_client = RPCClient(
        JSONRPCProtocol(),
        HttpPostClientTransport('http://127.0.0.1:5000/'))

    remote_server = rpc_client.get_proxy()

    # call a method called 'reverse_string' with a single string argument
    result = remote_server.reverse_string('Hello, World!')

    print("Server answered:", result)

Documentation
-------------

You'll quickly find that ``tinyrpc`` has more documentation and tests than core
code, hence the name. See the documentation at
<https://tinyrpc.readthedocs.org> for more details, especially the
Structure-section to get a birds-eye view.

Installation
------------

.. code-block:: sh

   pip install tinyrpc

will install ``tinyrpc`` with its default dependencies.

Optional dependencies
---------------------

Depending on the protocols and transports you want to use additional dependencies
are required. You can instruct pip to install these dependencies by specifying
extras to the basic install command.

.. code-block:: sh

   pip install tinyrpc[httpclient, wsgi]

will install ``tinyrpc`` with dependencies for the httpclient and wsgi transports.

Available extras are:

+------------+-------------------------------------------------------+
| Option     |  Needed to use objects of class                       |
+============+=======================================================+
| gevent     | optional in RPCClient, required by RPCServerGreenlets |
+------------+-------------------------------------------------------+
| httpclient | HttpPostClientTransport, HttpWebSocketClientTransport |
+------------+-------------------------------------------------------+
| msgpack    | implements MSGPACKRPCProtocol                         |
+------------+-------------------------------------------------------+
| jsonext    | optional in JSONRPCProtocol                           |
+------------+-------------------------------------------------------+
| rabbitmq   | RabbitMQServerTransport, RabbitMQClientTransport      |
+------------+-------------------------------------------------------+
| websocket  | WSServerTransport                                     |
+------------+-------------------------------------------------------+
| wsgi       | WsgiServerTransport                                   |
+------------+-------------------------------------------------------+
| zmq        | ZmqServerTransport, ZmqClientTransport                |
+------------+-------------------------------------------------------+

New in version 1.1.0
--------------------

Tinyrpc supports RabbitMQ has transport medium.

New in version 1.0.4
--------------------

Tinyrpc now supports the MSGPACK RPC protocol in addition to JSON-RPC.


.. _jsonrpc: http://www.jsonrpc.org/
.. _PyPI: http://pypi.python.org
.. _json: http://www.json.org/
.. _TCP: http://en.wikipedia.org/wiki/Transmission_Control_Protocol
.. _WebSockets: http://en.wikipedia.org/wiki/WebSocket
.. _0mq: http://www.zeromq.org/


================================================
FILE: docs/Makefile
================================================
# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = _build

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext

help:
	@echo "Please use \`make <target>' where <target> is one of"
	@echo "  html       to make standalone HTML files"
	@echo "  dirhtml    to make HTML files named index.html in directories"
	@echo "  singlehtml to make a single large HTML file"
	@echo "  pickle     to make pickle files"
	@echo "  json       to make JSON files"
	@echo "  htmlhelp   to make HTML files and a HTML help project"
	@echo "  qthelp     to make HTML files and a qthelp project"
	@echo "  devhelp    to make HTML files and a Devhelp project"
	@echo "  epub       to make an epub"
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
	@echo "  text       to make text files"
	@echo "  man        to make manual pages"
	@echo "  texinfo    to make Texinfo files"
	@echo "  info       to make Texinfo files and run them through makeinfo"
	@echo "  gettext    to make PO message catalogs"
	@echo "  changes    to make an overview of all changed/added/deprecated items"
	@echo "  linkcheck  to check all external links for integrity"
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"

clean:
	-rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

singlehtml:
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
	@echo
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/tinyrpc.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/tinyrpc.qhc"

devhelp:
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
	@echo
	@echo "Build finished."
	@echo "To view the help file:"
	@echo "# mkdir -p $$HOME/.local/share/devhelp/tinyrpc"
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tinyrpc"
	@echo "# devhelp"

epub:
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
	@echo
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
	      "(use \`make latexpdf' here to do that automatically)."

latexpdf:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo "Running LaTeX files through pdflatex..."
	$(MAKE) -C $(BUILDDIR)/latex all-pdf
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."

text:
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
	@echo
	@echo "Build finished. The text files are in $(BUILDDIR)/text."

man:
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
	@echo
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."

texinfo:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo
	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
	@echo "Run \`make' in that directory to run these through makeinfo" \
	      "(use \`make info' here to do that automatically)."

info:
	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
	@echo "Running Texinfo files through makeinfo..."
	make -C $(BUILDDIR)/texinfo info
	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."

gettext:
	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
	@echo
	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."


================================================
FILE: docs/_static/uml.xmi
================================================
<?xml version="1.0" encoding="UTF-8"?>
<XMI verified="false" xmi.version="1.2" timestamp="2019-01-19T17:13:06" xmlns:UML="http://schema.omg.org/spec/UML/1.3">
 <XMI.header>
  <XMI.documentation>
   <XMI.exporter>umbrello uml modeller http://umbrello.kde.org</XMI.exporter>
   <XMI.exporterVersion>1.6.9</XMI.exporterVersion>
   <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>
  </XMI.documentation>
  <XMI.metamodel xmi.version="1.3" href="UML.xml" xmi.name="UML"/>
 </XMI.header>
 <XMI.content>
  <UML:Model isSpecification="false" isAbstract="false" isLeaf="false" xmi.id="m1" isRoot="false" name="UML Model">
   <UML:Namespace.ownedElement>
    <UML:Stereotype visibility="public" isSpecification="false" namespace="m1" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="folder" name="folder"/>
    <UML:Stereotype visibility="public" isSpecification="false" namespace="m1" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="datatype" name="datatype"/>
    <UML:Model stereotype="folder" visibility="public" isSpecification="false" namespace="m1" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Logical View" name="Logical View">
     <UML:Namespace.ownedElement>
      <UML:Package stereotype="folder" visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Datatypes" name="Datatypes">
       <UML:Namespace.ownedElement>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="OjpkERcLwQ38" name="array"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="vcBNGYol65kB" name="bool"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="msV7kE98ywqA" name="tuple"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="NQVJqXRl9b6O" name="float"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="9v9FyrOkiuqS" name="int"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="6za4uOEHoiZd" name="long"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="F0MMZTkiMrvK" name="dict"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="hK2PCsrIqPLk" name="object"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="lZ1XFYAUXXp6" name="set"/>
        <UML:DataType stereotype="datatype" visibility="public" isSpecification="false" namespace="Datatypes" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="yXpKPic62eTE" name="string"/>
       </UML:Namespace.ownedElement>
      </UML:Package>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="d85AfuEbS1vp" name="RPCClient">
       <UML:Classifier.feature>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="EPqcqu9AWUkQ" type="95NxQdnjLPTQ" name="protocol"/>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="suWOOGpkneAV" type="FM5KzcTsgUmV" name="transport"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Qyx1eBYVYGsw" name="call"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="laB8nntFEPTJ" name="call_all"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="2ClpW2nwykwc" name="get_proxy"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="vU6TiwNSWZaH" name="batch_all"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="gRGItEb9GKnL" name="RPCDispatcher">
       <UML:Classifier.feature>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="2ILlIYmTV1NY" type="ZRYc8HBwIjgt" name="validator"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="yYQFVDA8Z305" name="public"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="XJE2KtsHI6VS" name="add_subdispatch"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="m5IKJCjFi7SD" name="add_method"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="KcJrfJwBc8Dw" name="get_method"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="13BKAgkuj2E2" name="register_instance"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="CxFu2h5MIODv" name="dispatch"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="f86FGHCGJLYR" name="validate_parameters"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Jkej48LjYa1P" name="ServerTransport">
       <UML:Classifier.feature>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="v43upCaD8gak" name="receive_message"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="9n2pFEijk20K" name="send_reply"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="95NxQdnjLPTQ" name="RPCProtocol">
       <UML:Classifier.feature>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="EsgVEEwEWGzn" type="vcBNGYol65kB" name="supports_out_of_order"/>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="ojGHjbnQ2gNq" type="vcBNGYol65kB" name="raises_errors"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="kFHuCzMRs3w4" name="create_request"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="FrIk7HXtznAi" name="parse_request"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Oy8PcTDKiUEv" name="parse_reply"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="TYKJZENn5fXA" name="raise_error"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Dependency visibility="public" isSpecification="false" namespace="Logical View" supplier="I2lf5iGgqMoT" xmi.id="MZXhsJS1Onwp" client="gRGItEb9GKnL" name=""/>
      <UML:Association visibility="public" isSpecification="false" namespace="Logical View" xmi.id="RcpqOlfnT9XZ" name="">
       <UML:Association.connection>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="9eDtrtxB3CDC" type="Jkej48LjYa1P" name="" aggregation="composite"/>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="cpgXxqNPTADr" type="I2lf5iGgqMoT" name="" aggregation="none"/>
       </UML:Association.connection>
      </UML:Association>
      <UML:Association visibility="public" isSpecification="false" namespace="Logical View" xmi.id="vqcMz7w6l7zd" name="">
       <UML:Association.connection>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="false" isSpecification="false" xmi.id="0vo27zEinuDk" type="Jkej48LjYa1P" name="" aggregation="none"/>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="la7CkK1eAdy8" type="d85AfuEbS1vp" name="" aggregation="none"/>
       </UML:Association.connection>
      </UML:Association>
      <UML:Association visibility="public" isSpecification="false" namespace="Logical View" xmi.id="3db2Fj9wB5IA" name="">
       <UML:Association.connection>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="false" isSpecification="false" xmi.id="y1trlVLTC706" type="gRGItEb9GKnL" name="" aggregation="none"/>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="dOS1cL4dwdf9" type="I2lf5iGgqMoT" name="" aggregation="none"/>
       </UML:Association.connection>
      </UML:Association>
      <UML:Package visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="MjZAD10HkUF5" name="X">
       <UML:Namespace.ownedElement/>
      </UML:Package>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="I2lf5iGgqMoT" name="RPCServer">
       <UML:Classifier.feature>
        <UML:Attribute visibility="public" isSpecification="false" xmi.id="q3XlaI1x1Y5D" type="ZRYc8HBwIjgt" name="trace"/>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="KSVZrysrAdfe" type="Jkej48LjYa1P" name="transport"/>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="dSYZAUA3wGWf" type="95NxQdnjLPTQ" name="protocol"/>
        <UML:Attribute visibility="private" isSpecification="false" xmi.id="A326JWjTOo8n" type="gRGItEb9GKnL" name="dispatcher"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="UpEqKrwKoUUp" name="serve_forever"/>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="3iU0NBVNudIG" name="receive_one_message"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="a3SIWhN9Qofn" name="trace"/>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="ZRYc8HBwIjgt" name="callable"/>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="BbKoeiZzFEaT" name="str"/>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="hRszn4uTCziS" name="function"/>
      <UML:Class visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="FM5KzcTsgUmV" name="ClientTransport">
       <UML:Classifier.feature>
        <UML:Operation visibility="public" isSpecification="false" isQuery="false" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="kfzVgxnvJsDJ" name="send_message"/>
       </UML:Classifier.feature>
      </UML:Class>
      <UML:Package visibility="public" isSpecification="false" namespace="Logical View" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="LeMauwpQ19ve" name="Transports">
       <UML:Namespace.ownedElement/>
      </UML:Package>
      <UML:Association visibility="public" isSpecification="false" namespace="Logical View" xmi.id="z1nOqt9ItgrK" name="">
       <UML:Association.connection>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="UL8SZP9EnEiq" type="Jkej48LjYa1P" name="" aggregation="composite"/>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="DB4FeMYeNJ0D" type="I2lf5iGgqMoT" name="" aggregation="none"/>
       </UML:Association.connection>
      </UML:Association>
      <UML:Association visibility="public" isSpecification="false" namespace="Logical View" xmi.id="A9T3qJNIcTkC" name="">
       <UML:Association.connection>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="ssOd67UHGWWt" type="I2lf5iGgqMoT" name="" aggregation="composite"/>
        <UML:AssociationEnd changeability="changeable" visibility="public" isNavigable="true" isSpecification="false" xmi.id="s3XFMVRXeuaS" type="Jkej48LjYa1P" name="" aggregation="none"/>
       </UML:Association.connection>
      </UML:Association>
     </UML:Namespace.ownedElement>
     <XMI.extension xmi.extender="umbrello">
      <diagrams>
       <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">
        <widgets>
         <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"/>
         <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"/>
         <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"/>
         <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"/>
         <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"/>
         <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"/>
        </widgets>
        <messages/>
        <associations>
         <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">
          <linepath layout="Polyline">
           <startpoint startx="-1085.470588235297" starty="-393.7058823529407"/>
           <endpoint endx="-975.7647058823527" endy="-393.7058823529407"/>
          </linepath>
         </assocwidget>
         <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">
          <linepath layout="Polyline">
           <startpoint startx="-1129.411764705885" starty="-351.882352941176"/>
           <endpoint endx="-1129.411764705885" endy="-230.5882352941177"/>
          </linepath>
         </assocwidget>
         <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">
          <linepath layout="Polyline">
           <startpoint startx="-733.4117647058823" starty="-393.7058823529407"/>
           <endpoint endx="-827.7647058823527" endy="-393.7058823529407"/>
          </linepath>
         </assocwidget>
         <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">
          <linepath layout="Polyline">
           <startpoint startx="-733.4117647058823" starty="-350.9411764705877"/>
           <endpoint endx="-822.2352941176472" endy="-293.6470588235293"/>
          </linepath>
         </assocwidget>
         <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">
          <linepath layout="Polyline">
           <startpoint startx="-652.5882352941173" starty="-350.9411764705877"/>
           <endpoint endx="-652.5882352941173" endy="-242.2941176470588"/>
          </linepath>
         </assocwidget>
        </associations>
       </diagram>
       <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">
        <widgets/>
        <messages/>
        <associations/>
       </diagram>
      </diagrams>
     </XMI.extension>
    </UML:Model>
    <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">
     <UML:Namespace.ownedElement/>
    </UML:Model>
    <UML:Model stereotype="folder" visibility="public" isSpecification="false" namespace="m1" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Component View" name="Component View">
     <UML:Namespace.ownedElement/>
    </UML:Model>
    <UML:Model stereotype="folder" visibility="public" isSpecification="false" namespace="m1" isAbstract="false" isLeaf="false" isRoot="false" xmi.id="Deployment View" name="Deployment View">
     <UML:Namespace.ownedElement/>
    </UML:Model>
    <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">
     <UML:Namespace.ownedElement/>
    </UML:Model>
   </UML:Namespace.ownedElement>
  </UML:Model>
 </XMI.content>
 <XMI.extensions xmi.extender="umbrello">
  <docsettings viewid="WYGz5jAMLtj4" uniqueid="s3XFMVRXeuaS" documentation=""/>
  <listview>
   <listitem open="1" type="800" id="Views">
    <listitem open="1" type="821" id="Component View"/>
    <listitem open="1" type="827" id="Deployment View"/>
    <listitem open="1" type="836" id="Entity Relationship Model"/>
    <listitem open="1" type="801" id="Logical View">
     <listitem open="1" type="813" id="ZRYc8HBwIjgt"/>
     <listitem open="0" type="807" id="WYGz5jAMLtj4" label="class diagram"/>
     <listitem open="1" type="813" id="FM5KzcTsgUmV">
      <listitem open="0" type="815" id="kfzVgxnvJsDJ"/>
     </listitem>
     <listitem open="0" type="806" id="XKVLhq5SN4zd" label="collaboration diagram"/>
     <listitem open="0" type="830" id="Datatypes">
      <listitem open="1" type="829" id="OjpkERcLwQ38"/>
      <listitem open="1" type="829" id="vcBNGYol65kB"/>
      <listitem open="1" type="829" id="F0MMZTkiMrvK"/>
      <listitem open="1" type="829" id="NQVJqXRl9b6O"/>
      <listitem open="1" type="829" id="9v9FyrOkiuqS"/>
      <listitem open="1" type="829" id="6za4uOEHoiZd"/>
      <listitem open="1" type="829" id="hK2PCsrIqPLk"/>
      <listitem open="1" type="829" id="lZ1XFYAUXXp6"/>
      <listitem open="1" type="829" id="yXpKPic62eTE"/>
      <listitem open="1" type="829" id="msV7kE98ywqA"/>
     </listitem>
     <listitem open="1" type="813" id="hRszn4uTCziS"/>
     <listitem open="1" type="817" id="d85AfuEbS1vp">
      <listitem open="0" type="815" id="vU6TiwNSWZaH"/>
      <listitem open="0" type="815" id="laB8nntFEPTJ"/>
      <listitem open="0" type="815" id="Qyx1eBYVYGsw"/>
      <listitem open="0" type="815" id="2ClpW2nwykwc"/>
      <listitem open="0" type="814" id="EPqcqu9AWUkQ"/>
      <listitem open="0" type="814" id="suWOOGpkneAV"/>
     </listitem>
     <listitem open="1" type="817" id="gRGItEb9GKnL">
      <listitem open="0" type="815" id="m5IKJCjFi7SD"/>
      <listitem open="0" type="815" id="XJE2KtsHI6VS"/>
      <listitem open="0" type="815" id="CxFu2h5MIODv"/>
      <listitem open="0" type="815" id="KcJrfJwBc8Dw"/>
      <listitem open="0" type="815" id="yYQFVDA8Z305"/>
      <listitem open="0" type="815" id="13BKAgkuj2E2"/>
      <listitem open="0" type="815" id="f86FGHCGJLYR"/>
      <listitem open="0" type="814" id="2ILlIYmTV1NY"/>
     </listitem>
     <listitem open="1" type="817" id="95NxQdnjLPTQ">
      <listitem open="0" type="815" id="kFHuCzMRs3w4"/>
      <listitem open="0" type="815" id="Oy8PcTDKiUEv"/>
      <listitem open="0" type="815" id="FrIk7HXtznAi"/>
      <listitem open="0" type="815" id="TYKJZENn5fXA"/>
      <listitem open="0" type="814" id="ojGHjbnQ2gNq"/>
      <listitem open="0" type="814" id="EsgVEEwEWGzn"/>
     </listitem>
     <listitem open="1" type="817" id="I2lf5iGgqMoT">
      <listitem open="0" type="814" id="A326JWjTOo8n"/>
      <listitem open="0" type="814" id="dSYZAUA3wGWf"/>
      <listitem open="0" type="815" id="3iU0NBVNudIG"/>
      <listitem open="0" type="815" id="UpEqKrwKoUUp"/>
      <listitem open="0" type="814" id="q3XlaI1x1Y5D"/>
      <listitem open="0" type="814" id="KSVZrysrAdfe"/>
     </listitem>
     <listitem open="1" type="817" id="Jkej48LjYa1P">
      <listitem open="0" type="815" id="v43upCaD8gak"/>
      <listitem open="0" type="815" id="9n2pFEijk20K"/>
     </listitem>
     <listitem open="1" type="813" id="BbKoeiZzFEaT"/>
     <listitem open="1" type="813" id="a3SIWhN9Qofn"/>
     <listitem open="1" type="818" id="LeMauwpQ19ve"/>
     <listitem open="1" type="818" id="MjZAD10HkUF5"/>
    </listitem>
    <listitem open="1" type="802" id="Use Case View"/>
   </listitem>
  </listview>
  <codegeneration>
   <codegenerator language="Python"/>
  </codegeneration>
 </XMI.extensions>
</XMI>


================================================
FILE: docs/client.rst
================================================
RPC Client
==========

:py:class:`~tinyrpc.client.RPCClient` instances are high-level handlers for
making remote procedure calls to servers. Other than
:py:class:`~tinyrpc.client.RPCProxy` objects, they are what most user
applications interact with.

Clients needs to be instantiated with a protocol and a transport to function.
Proxies are syntactic sugar for using clients.

.. autoclass:: tinyrpc.client.RPCClient
    :members:
    :show-inheritance:
    :noindex:

.. autoclass:: tinyrpc.client.RPCProxy
    :members:
    :show-inheritance:
    :noindex:

.. automodule:: tinyrpc.client
    :members: RPCCall, RPCCallTo
    :show-inheritance:
    :noindex:


================================================
FILE: docs/conf.py
================================================
# -*- coding: utf-8 -*-
#
# tinyrpc documentation build configuration file, created by
# sphinx-quickstart on Wed Jan 23 19:15:13 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys, os

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('..'))

# -- General configuration -----------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'tinyrpc'
copyright = u'2013 - 2023, Marc Brinkmann, Leo Noordergraaf'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.1.7'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []


# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#html_theme = 'default'
html_theme = 'sphinx_rtd_theme'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
    'prev_next_buttons_location': 'both',
    'collapse_navigation': True,
    'sticky_navigation': True,
    'navigation_depth': 4
}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'tinyrpcdoc'


# -- Options for LaTeX output --------------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
  ('index', 'tinyrpc.tex', u'tinyrpc Documentation',
   u'Marc Brinkmann, Leo Noordergraaf', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True


# -- Options for manual page output --------------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'tinyrpc', u'tinyrpc Documentation',
     [u'Marc Brinkmann, Leo Noordergraaf'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False


# -- Options for Texinfo output ------------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'tinyrpc', u'tinyrpc Documentation',
   u'Marc Brinkmann, Leo Noordergraaf', 'tinyrpc', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'


# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
    'https://docs.python.org/3/': None,
    'https://pyzmq.readthedocs.io/en/latest/': None,
    'http://docs.python-requests.org/en/latest/': None,
    'http://werkzeug.pocoo.org/docs/': None,
    'http://www.gevent.org/': None,
}

autoclass_content = "both"

autodoc_mock_imports = ["msgpack", "zmq", "werkzeug", "pika", "geventwebsocket"]


================================================
FILE: docs/dispatch.rst
================================================
Dispatching
===========

Dispatching in ``tinyrpc`` is very similiar to url-routing in web frameworks.
Functions are registered with a specific name and made public, i.e. callable,
to remote clients.

Examples
--------

Exposing a few functions:
~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

   from tinyrpc.dispatch import RPCDispatcher

   dispatch = RPCDispatcher()

   @dispatch.public
   def foo():
       # ...

   @dispatch.public
   def bar(arg):
       # ...

   # later on, assuming we know we want to call foo(*args, **kwargs):

   f = dispatch.get_method('foo')
   f(*args, **kwargs)

Using prefixes and instance registration:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

   from tinyrpc.dispatch import public

   class SomeWebsite(object):
       def __init__(self, ...):
           # note: this method will not be exposed

       def secret(self):
           # another unexposed method

       @public
       def get_user_info(self, user):
           # ...

       # using a different name
       @public('get_user_comment')
       def get_comment(self, comment_id):
           # ...

The code above declares an RPC interface for ``SomeWebsite`` objects,
consisting of two visible methods: ``get_user_info(user)`` and
``get_user_comment(comment_id)``.

These can be used with a dispatcher now:

.. code-block:: python

   def hello():
       # ...

   website1 = SomeWebsite(...)
   website2 = SomeWebsite(...)

   from tinyrpc.dispatch import RPCDispatcher

   dispatcher = RPCDispatcher()

   # directly register version method
   @dispatcher.public
   def version():
       # ...

   # add earlier defined method
   dispatcher.add_method(hello)

   # register the two website instances
   dispatcher.register_instance(website1, 'sitea.')
   dispatcher.register_instance(website2, 'siteb.')

In the example above, the :py:class:`~tinyrpc.dispatch.RPCDispatcher` now knows
a total of six registered methods: ``version``, ``hello``,
``sitea.get_user_info``, ``sitea.get_user_comment``, ``siteb.get_user_info``,
``siteb.get_user_comment``.

Automatic dispatching
~~~~~~~~~~~~~~~~~~~~~

When writing a server application, a higher level dispatching method is
available with :py:func:`~tinyrpc.dispatch.RPCDispatcher.dispatch`:

.. code-block:: python

   from tinyrpc.dispatch import RPCDispatcher

   dispatcher = RPCDispatcher()

   # register methods like in the examples above
   # ...
   # now assumes that a valid RPCRequest has been obtained, as `request`

   response = dispatcher.dispatch(request)

   # response can be directly processed back to the client, all Exceptions have
   # been handled already

Class, static and unbound method dispatching
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Although you will only rarely use these method types they *do* work and here we show you how.

Class methods do not have `self` as the initial parameter but rather a reference to their class.
You may want to use such methods to instantiate class instances.

.. code-block:: python

    class ShowClassMethod:
        @classmethod
        @public
        def func(cls, a, b):
            return a-b

Note the ordering of the decorators.
Ordering them differently will not work.
You call dispatch to the `func` method just as you would dispatch to any other method.

Static methods have neither a class nor instance reference as first parameter:

.. code-block:: python

    class ShowStaticMethod:
        @staticmethod
        @public
        def func(a, b):
            return a-b

Again the ordering of the decorators is critical and you dispatch them as any other method.

Finally it is possible to dispatch to unbound methods but I strongly advise against it.
If you really want to do that see the tests to learn how. Everyone else should use static methods instead.

API reference
-------------

.. autoclass:: tinyrpc.dispatch.RPCDispatcher
    :members:
    :show-inheritance:
    :member-order: bysource

Classes can be made to support an RPC interface without coupling it to a
dispatcher using a decorator:

.. autofunction:: tinyrpc.dispatch.public


================================================
FILE: docs/examples.rst
================================================
Quickstart examples
===================

The source contains all of these examples in a working fashion in the examples
subfolder.

HTTP based
----------

A client making JSONRPC calls via HTTP (this requires :py:mod:`requests` to be
installed):

.. code-block:: python

   from tinyrpc import RPCClient
   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
   from tinyrpc.transports.http import HttpPostClientTransport

   rpc_client = RPCClient(
       JSONRPCProtocol(),
       HttpPostClientTransport('http://localhost')
   )

   str_server = rpc_client.get_proxy()

   # ...

   # call a method called 'reverse_string' with a single string argument
   result = str_server.reverse_string('Simple is better.')

   print("Server answered:", result)

This call can be answered by a server implemented as follows:

.. code-block:: python

   import gevent
   import gevent.pywsgi
   import gevent.queue

   from tinyrpc.server.gevent import RPCServerGreenlets
   from tinyrpc.dispatch import RPCDispatcher
   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
   from tinyrpc.transports.wsgi import WsgiServerTransport

   dispatcher = RPCDispatcher()
   transport = WsgiServerTransport(queue_class=gevent.queue.Queue)

   # start wsgi server as a background-greenlet
   wsgi_server = gevent.pywsgi.WSGIServer(('127.0.0.1', 80), transport.handle)
   gevent.spawn(wsgi_server.serve_forever)

   rpc_server = RPCServerGreenlets(
       transport,
       JSONRPCProtocol(),
       dispatcher
   )

   @dispatcher.public
   def reverse_string(s):
       return s[::-1]

   # in the main greenlet, run our rpc_server
   rpc_server.serve_forever()


0mq
---

An example using :py:mod:`zmq` is very similiar, differing only in the
instantiation of the transport:

.. code-block:: python

  import zmq

  from tinyrpc import RPCClient
  from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
  from tinyrpc.transports.zmq import ZmqClientTransport

  ctx = zmq.Context()

  rpc_client = RPCClient(
      JSONRPCProtocol(),
      ZmqClientTransport.create(ctx, 'tcp://127.0.0.1:5001')
  )

  str_server = rpc_client.get_proxy()

  # call a method called 'reverse_string' with a single string argument
  result = str_server.reverse_string('Hello, World!')

  print("Server answered:", result)


Matching server:

.. code-block:: python

   import zmq

   from tinyrpc.server import RPCServer
   from tinyrpc.dispatch import RPCDispatcher
   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
   from tinyrpc.transports.zmq import ZmqServerTransport

   ctx = zmq.Context()
   dispatcher = RPCDispatcher()
   transport = ZmqServerTransport.create(ctx, 'tcp://127.0.0.1:5001')

   rpc_server = RPCServer(
       transport,
       JSONRPCProtocol(),
       dispatcher
   )

   @dispatcher.public
   def reverse_string(s):
       return s[::-1]

   rpc_server.serve_forever()



Further examples
----------------

In :doc:`protocols`, you can find client and server examples on how
to use just the protocol parsing parts of ``tinyrpc``.

The :py:class:`~tinyrpc.dispatch.RPCDispatcher` should be useful on its own (or
at least easily replaced with one of your choosing), see :doc:`dispatch` for
details.





================================================
FILE: docs/exceptions.rst
================================================
The Exceptions hierarchy
========================

All exceptions are rooted in the :py:class:`Exception` class.
The :py:class:`~tinyrpc.exc.RPCError` class derives from it and forms the basis of all tinyrpc exceptions.

Abstract exceptions
-------------------
These exceptions, most of them will be overridden, define errors concerning the transport and structure
of messages.

.. autoclass:: tinyrpc.exc.RPCError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.BadRequestError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.exc.BadReplyError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.InvalidRequestError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.InvalidReplyError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.MethodNotFoundError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.InvalidParamsError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.ServerError
    :members:
    :show-inheritance:
    :member-order: bysource

Protocol exceptions
-------------------
Each protocol provides its own concrete implementations of these exceptions.

JSON-RPC
^^^^^^^^

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCParseError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInternalError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCServerError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

This last exception is a client side exception designed to represent the server side error in the client.

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:


MSGPACK-RPC
^^^^^^^^^^^

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCParseError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidRequestError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCMethodNotFoundError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidParamsError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInternalError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCServerError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:

This last exception is a client side exception designed to represent the server side error in the client.

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCError
    :members:
    :show-inheritance:
    :member-order: bysource
    :noindex:


================================================
FILE: docs/index.rst
================================================
tinyrpc: A modular RPC library
==============================

``tinyrpc`` is a framework for constructing remote procedure call (RPC) services in Python.

In ``tinyrpc`` all components (transport, protocol and dispatcher) that together make an
RPC service are independently replacable.

Although its initial scope is handling jsonrpc_ it is easy to add further protocols or
add additional transports (one such example is msgpackrpc_, which is now fully supported).
If so desired it is even possible to replace the default method dispatcher.


Table of contents
-----------------

.. toctree::
    :maxdepth: 2

    examples
    structure
    dispatch
    protocols
    jsonrpc
    msgpackrpc
    transports
    client
    server
    exceptions

Installation
------------

.. code-block:: sh

   pip install tinyrpc

will install ``tinyrpc`` with its default dependencies.

Optional dependencies
+++++++++++++++++++++

Depending on the protocols and transports you want to use additional dependencies
are required. You can instruct pip to install these dependencies by specifying
extras to the basic install command.

.. code-block:: sh

   pip install tinyrpc[httpclient, wsgi]

will install ``tinyrpc`` with dependencies for the httpclient and wsgi transports.

Available extras are:

+------------+-------------------------------------------------------+
| Option     |  Needed to use objects of class                       |
+============+=======================================================+
| gevent     | optional in RPCClient, required by RPCServerGreenlets |
+------------+-------------------------------------------------------+
| httpclient | HttpPostClientTransport, HttpWebSocketClientTransport |
+------------+-------------------------------------------------------+
| jsonext    | optional in JSONRPCProtocol                           |
+------------+-------------------------------------------------------+
| msgpack    | required by MSGPACKRPCProtocol                        |
+------------+-------------------------------------------------------+
| rabbitmq   | RabbitMQServerTransport, RabbitMQClientTransport      |
+------------+-------------------------------------------------------+
| websocket  | WSServerTransport, HttpWebSocketClientTransport       |
+------------+-------------------------------------------------------+
| wsgi       | WsgiServerTransport                                   |
+------------+-------------------------------------------------------+
| zmq        | ZmqServerTransport, ZmqClientTransport                |
+------------+-------------------------------------------------------+

People
------

Creator
+++++++

- Marc Brinkmann: https://github.com/mbr

    As of this writing (in Jan 2013) there are a few jsonrpc_ libraries already out
    there on PyPI_, most of them handling one specific use case (e.g. json via
    WSGI, using Twisted, or TCP-sockets).

    None of the libraries, however, made it easy to reuse the jsonrpc_-parsing bits
    and substitute a different transport (i.e. going from json_ via TCP_ to an
    implementation using WebSockets_ or 0mq_).

    In the end, all these libraries have their own dispatching interfaces and a
    custom implementation of handling jsonrpc_.

    ``tinyrpc`` aims to do better by dividing the problem into cleanly
    interchangeable parts that allow easy addition of new transport methods, RPC
    protocols or dispatchers.

Maintainer
++++++++++

- Leo Noordergraaf: https://github.com/lnoor

    Looking for a Python jsonrpc_ library I found ``tinyrpc``.
    I was immediately taken by its modular concept and construction.

    After creating a couple transports and trying to get them integrated in tinyrpc,
    I learned that Marc got involved with other projects and that maintaining
    ``tinyrpc`` became too much a burden.
    I then volunteered to become its maintainer.

.. _jsonrpc: http://jsonrpc.org
.. _msgpackrpc: https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
.. _PyPI: http://pypi.python.org
.. _json: http://www.json.org/
.. _TCP: http://en.wikipedia.org/wiki/Transmission_Control_Protocol
.. _WebSockets: http://en.wikipedia.org/wiki/WebSocket
.. _0mq: http://www.zeromq.org/


================================================
FILE: docs/jsonrpc.rst
================================================
The JSON-RPC protocol
=====================

Example
-------

The following example shows how to use the
:py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol` class in a custom
application, without using any other components:

Server
++++++

.. code-block:: python

   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
   from tinyrpc import BadRequestError, RPCBatchRequest

   rpc = JSONRPCProtocol()

   # the code below is valid for all protocols, not just JSONRPC:

   def handle_incoming_message(self, data):
       try:
           request = rpc.parse_request(data)
       except BadRequestError as e:
           # request was invalid, directly create response
           response = e.error_respond(e)
       else:
           # we got a valid request
           # the handle_request function is user-defined
           # and returns some form of response
           if hasattr(request, create_batch_response):
               response = request.create_batch_response(
                   handle_request(req) for req in request
               )
           else:
               response = handle_request(request)

       # now send the response to the client
       if response != None:
            send_to_client(response.serialize())


   def handle_request(request):
       try:
           # do magic with method, args, kwargs...
           return request.respond(result)
       except Exception as e:
           # for example, a method wasn't found
           return request.error_respond(e)

Client
++++++

.. code-block:: python

   from tinyrpc.protocols.jsonrpc import JSONRPCProtocol

   rpc = JSONRPCProtocol()

   # again, code below is protocol-independent

   # assuming you want to call method(*args, **kwargs)

   request = rpc.create_request(method, args, kwargs)
   reply = send_to_server_and_get_reply(request)

   response = rpc.parse_reply(reply)

   if hasattr(response, 'error'):
       # error handling...
   else:
       # the return value is found in response.result
       do_something_with(response.result)


Another example, this time using batch requests:

.. code-block:: python

   # or using batch requests:

   requests = rpc.create_batch_request([
       rpc.create_request(method_1, args_1, kwargs_1)
       rpc.create_request(method_2, args_2, kwargs_2)
       # ...
   ])

   reply = send_to_server_and_get_reply(request)

   responses = rpc.parse_reply(reply)

   for responses in response:
       if hasattr(reponse, 'error'):
           # ...


Finally, one-way requests are requests where the client does not expect an
answer:

.. code-block:: python

   request = rpc.create_request(method, args, kwargs, one_way=True)
   send_to_server(request)

   # done


Protocol implementation
-----------------------

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCProtocol
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCRequest
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCSuccessResponse
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCErrorResponse
    :members:
    :show-inheritance:
    :member-order: bysource

Batch protocol
--------------

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCBatchRequest
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCBatchResponse
    :members:
    :show-inheritance:
    :member-order: bysource


Errors and error handling
-------------------------

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCParseError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidRequestError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCMethodNotFoundError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInvalidParamsError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCInternalError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCServerError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.jsonrpc.JSONRPCError
    :members:
    :show-inheritance:
    :member-order: bysource

Adding custom exceptions
------------------------

.. note:: As per the specification_ you should use error codes -32000 to
    -32099 when adding server specific error messages.
    Error codes outside the range -32768 to -32000 are available for
    application specific error codes.

To add custom errors you need to combine an :py:class:`Exception` subclass
with the :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin` class to
create your exception object which you can raise.

So a version of the reverse string example that dislikes palindromes could
look like:


.. code-block:: python

    from tinyrpc.protocols.jsonrpc import FixedErrorMessageMixin, JSONRPCProtocol
    from tinyrpc.dispatch import RPCDispatcher

    dispatcher = RPCDispatcher()

    class PalindromeError(FixedErrorMessageMixin, Exception):
        jsonrpc_error_code = 99
        message = "Ah, that's cheating!"


    @dispatcher.public
    def reverse_string(s):
        r = s[::-1]
        if r == s:
            raise PalindromeError()
        return r

Error with data
---------------

The specification_ states that the ``error`` element of a reply may contain
an optional ``data`` property. This property is now available for your use.

There are two ways that you can use to pass additional data with an :py:class:`Exception`.
It depends whether your application generates regular exceptions or exceptions derived
from :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin`.

When using ordinary exceptions you normally pass a single parameter (an error message)
to the :py:class:`Exception` constructor.
By passing two parameters, the second parameter is assumed to be the data element.

.. code-block:: python

    @public
    def fn():
        raise Exception('error message', {'msg': 'structured data', 'lst': [1, 2, 3]})

This will produce the reply message::

    {   "jsonrpc": "2.0",
        "id": <some id>,
        "error": {
            "code": -32000,
            "message": "error message",
            "data": {"msg": "structured data", "lst": [1, 2, 3]}
        }
    }

When using :py:class:`~tinyrpc.protocols.jsonrpc.FixedErrorMessageMixin` based exceptions the data is passed using
a keyword parameter.

.. code-block:: python

    class MyException(FixedErrorMessageMixin, Exception):
        jsonrcp_error_code = 99
        message = 'standard message'

    @public
    def fn():
        raise MyException(data={'msg': 'structured data', 'lst': [1, 2, 3]})

This will produce the reply message::

    {   "jsonrpc": "2.0",
        "id": <some id>,
        "error": {
            "code": 99,
            "message": "standard message",
            "data": {"msg": "structured data", "lst": [1, 2, 3]}
        }
    }


.. _specification: http://www.jsonrpc.org/specification#error_object


================================================
FILE: docs/make.bat
================================================
@ECHO OFF

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)

if "%1" == "" goto help

if "%1" == "help" (
	:help
	echo.Please use `make ^<target^>` where ^<target^> is one of
	echo.  html       to make standalone HTML files
	echo.  dirhtml    to make HTML files named index.html in directories
	echo.  singlehtml to make a single large HTML file
	echo.  pickle     to make pickle files
	echo.  json       to make JSON files
	echo.  htmlhelp   to make HTML files and a HTML help project
	echo.  qthelp     to make HTML files and a qthelp project
	echo.  devhelp    to make HTML files and a Devhelp project
	echo.  epub       to make an epub
	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
	echo.  text       to make text files
	echo.  man        to make manual pages
	echo.  texinfo    to make Texinfo files
	echo.  gettext    to make PO message catalogs
	echo.  changes    to make an overview over all changed/added/deprecated items
	echo.  linkcheck  to check all external links for integrity
	echo.  doctest    to run all doctests embedded in the documentation if enabled
	goto end
)

if "%1" == "clean" (
	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
	del /q /s %BUILDDIR%\*
	goto end
)

if "%1" == "html" (
	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
	goto end
)

if "%1" == "dirhtml" (
	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
	goto end
)

if "%1" == "singlehtml" (
	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
	goto end
)

if "%1" == "pickle" (
	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can process the pickle files.
	goto end
)

if "%1" == "json" (
	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can process the JSON files.
	goto end
)

if "%1" == "htmlhelp" (
	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
	goto end
)

if "%1" == "qthelp" (
	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\tinyrpc.qhcp
	echo.To view the help file:
	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\tinyrpc.ghc
	goto end
)

if "%1" == "devhelp" (
	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished.
	goto end
)

if "%1" == "epub" (
	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The epub file is in %BUILDDIR%/epub.
	goto end
)

if "%1" == "latex" (
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
	goto end
)

if "%1" == "text" (
	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The text files are in %BUILDDIR%/text.
	goto end
)

if "%1" == "man" (
	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The manual pages are in %BUILDDIR%/man.
	goto end
)

if "%1" == "texinfo" (
	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
	goto end
)

if "%1" == "gettext" (
	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
	if errorlevel 1 exit /b 1
	echo.
	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
	goto end
)

if "%1" == "changes" (
	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
	if errorlevel 1 exit /b 1
	echo.
	echo.The overview file is in %BUILDDIR%/changes.
	goto end
)

if "%1" == "linkcheck" (
	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
	if errorlevel 1 exit /b 1
	echo.
	echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
	goto end
)

if "%1" == "doctest" (
	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
	if errorlevel 1 exit /b 1
	echo.
	echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
	goto end
)

:end


================================================
FILE: docs/msgpackrpc.rst
================================================
The MSGPACK-RPC protocol
========================

Example
-------

The following example shows how to use the
:py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol` class in a custom
application, without using any other components:

Server
++++++

.. code-block:: python

   from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol
   from tinyrpc import BadRequestError, RPCRequest

   rpc = MSGPACKRPCProtocol()

   # the code below is valid for all protocols, not just MSGPACKRPCProtocol,
   # as long as you don't need to handle batch RPC requests:

   def handle_incoming_message(self, data):
       try:
           request = rpc.parse_request(data)
       except BadRequestError as e:
           # request was invalid, directly create response
           response = e.error_respond(e)
       else:
           # we got a valid request
           # the handle_request function is user-defined
           # and returns some form of response
           response = handle_request(request)

       # now send the response to the client
       if response != None:
            send_to_client(response.serialize())


   def handle_request(request):
       try:
           # do magic with method, args, kwargs...
           return request.respond(result)
       except Exception as e:
           # for example, a method wasn't found
           return request.error_respond(e)

Client
++++++

.. code-block:: python

   from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol

   rpc = MSGPACKRPCProtocol()

   # again, code below is protocol-independent

   # assuming you want to call method(*args, **kwargs)

   request = rpc.create_request(method, args, kwargs)
   reply = send_to_server_and_get_reply(request)

   response = rpc.parse_reply(reply)

   if hasattr(response, 'error'):
       # error handling...
   else:
       # the return value is found in response.result
       do_something_with(response.result)


Finally, one-way requests are requests where the client does not expect an
answer:

.. code-block:: python

   request = rpc.create_request(method, args, kwargs, one_way=True)
   send_to_server(request)

   # done


Protocol implementation
-----------------------

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCRequest
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCSuccessResponse
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCErrorResponse
    :members:
    :show-inheritance:
    :member-order: bysource

Errors and error handling
-------------------------

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.msgpackrpc.FixedErrorMessageMixin
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCParseError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidRequestError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCMethodNotFoundError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInvalidParamsError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCInternalError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCServerError
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.msgpackrpc.MSGPACKRPCError
    :members:
    :show-inheritance:
    :member-order: bysource

Adding custom exceptions
------------------------

.. note:: Unlike JSON-RPC, the MSGPACK-RPC specification does not specify how
    the error messages should look like; the protocol allows any arbitrary
    MSGPACK object as an error object. For sake of compatibility with JSON-RPC,
    this implementation uses MSGPACK lists of length 2 (consisting of a numeric
    error code and an error description) to represent errors in the serialized
    representation. These are transparently decoded into
    :py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCError` instances as
    needed. The error codes for parsing errors, invalid requests, unknown RPC
    methods and so on match those from the `JSON-RPC specification`_.

To add custom errors you need to combine an :py:class:`Exception` subclass
with the :py:class:`~tinyrpc.protocols.msgpackrpc.FixedErrorMessageMixin` class
to create your exception object which you can raise.

So a version of the reverse string example that dislikes palindromes could
look like:


.. code-block:: python

    from tinyrpc.protocols.msgpackrpc import FixedErrorMessageMixin, MSGPACKRPCProtocol
    from tinyrpc.dispatch import RPCDispatcher

    dispatcher = RPCDispatcher()

    class PalindromeError(FixedErrorMessageMixin, Exception):
        msgpackrpc_error_code = 99
        message = "Ah, that's cheating!"


    @dispatcher.public
    def reverse_string(s):
        r = s[::-1]
        if r == s:
            raise PalindromeError()
        return r

.. _specification: https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
.. _JSON-RPC specification: http://www.jsonrpc.org/specification#error_object


================================================
FILE: docs/protocols.rst
================================================
The protocol layer
==================

Interface definition
--------------------

All protocols are implemented by deriving from :py:class:`~tinyrpc.protocols.RPCProtocol`
and implementing all of its members.

Every protocol deals with multiple kinds of structures: ``data`` arguments are
always byte strings, either messages or replies, that are sent via or received
from a transport.

Protocol-specific subclasses of :py:class:`~tinyrpc.protocols.RPCRequest` and
:py:class:`~tinyrpc.protocols.RPCResponse` represent well-formed requests and responses.

Protocol specific subclasses of :py:class:`~tinyrpc.protocols.RPCErrorResponse` represent
errors and error responses.

Finally, if an error occurs during parsing of a request, a
:py:class:`~tinyrpc.exc.BadRequestError` instance must be thrown. These need to be
subclassed for each protocol as well, since they generate error replies.

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.RPCProtocol
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.RPCRequest
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.RPCResponse
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.RPCErrorResponse
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.exc.BadRequestError
    :members:
    :show-inheritance:
    :member-order: bysource


Batch protocols
---------------

Some protocols may support batch requests. In this case, they need to derive
from :py:class:`~tinyrpc.protocols.RPCBatchProtocol`.

Batch protocols differ in that their
:py:func:`~tinyrpc.protocols.RPCProtocol.parse_request` method may return an instance of
:py:class:`~tinyrpc.protocols.RPCBatchRequest`. They also possess an addional method in
:py:func:`~tinyrpc.protocols.RPCBatchProtocol.create_batch_request`.

Handling a batch request is slightly different, while it supports
:py:func:`~tinyrpc.protocols.RPCBatchRequest.error_respond`, to make actual responses,
:py:func:`~tinyrpc.protocols.RPCBatchRequest.create_batch_response` needs to be used.

No assumptions are made whether or not it is okay for batch requests to be
handled in parallel. This is up to the server/dispatch implementation, which
must be chosen appropriately.

API Reference
+++++++++++++

.. autoclass:: tinyrpc.protocols.RPCBatchProtocol
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.RPCBatchRequest
    :members:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.protocols.RPCBatchResponse
    :members:
    :show-inheritance:
    :member-order: bysource


ID Generators
-------------

By default, the :py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol` 
and :py:class:`~tinyrpc.protocols.msgpackrpc.MSGPACKRPCProtocol` classes
generates ids as sequential integers starting at 1.
If alternative id generation is needed, you may supply your own
generator.

Example
-------

The following example shows how to use alternative id generators in a protocol
that supports them.

.. code-block:: python

    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
    
    def collatz_generator():
        """A sample generator for demonstration purposes ONLY."""
        n = 27
        while True:
            if n % 2 != 0:
                n = 3*n + 1
            else:
                n = n / 2
            yield n

    rpc = JSONRPCProtocol(id_generator=collatz_generator())


Supported protocols
-------------------

Any supported protocol is used by instantiating its class and calling the
interface of :py:class:`~tinyrpc.protocols.RPCProtocol`. Note that constructors
are not part of the interface, any protocol may have specific arguments for its
instances.

Protocols usually live in their own module because they may need to import
optional modules that needn't be a dependency for all of ``tinyrpc``.

.. _jsonrpc: http://jsonrpc.org


================================================
FILE: docs/server.rst
================================================
Server implementations
======================

Like :doc:`client`, servers are top-level instances that most user code should
interact with. They provide runnable functions that are combined with
transports, protocols and dispatchers to form a complete RPC system.

.. automodule:: tinyrpc.server
   :members:
   :noindex:

.. py:class:: tinyrpc.server.gevent.RPCServerGreenlets

   Asynchronous RPCServer.

   This implementation of :py:class:`~tinyrpc.server.RPCServer` uses
   :py:func:`gevent.spawn` to spawn new client handlers, result in asynchronous
   handling of clients using greenlets.


================================================
FILE: docs/structure.rst
================================================
Structure of tinyrpc
====================

Architecture
------------

``tinyrpc`` is constructed around the :py:class:`~tinyrpc.server.RPCServer` and
:py:class:`~tinyrpc.client.RPCClient` classes.

They in turn depend on the :py:class:`~tinyrpc.dispatch.RPCDispatcher`,
:py:class:`~tinyrpc.protocols.RPCProtocol`, :py:class:`~tinyrpc.transports.ServerTransport`
and :py:class:`~tinyrpc.transports.ClientTransport` classes as visualized in the image below.

.. image:: _static/uml.png

Of these :py:class:`~tinyrpc.protocols.RPCProtocol`,
:py:class:`~tinyrpc.transports.ServerTransport` and
:py:class:`~tinyrpc.transports.ClientTransport` are abstract base classes.

Each layer is useful "on its own" and can be used separately.
If you just need to decode a jsonrpc_ message, without passing it on or sending it through
a transport, the :py:class:`~tinyrpc.protocols.jsonrpc.JSONRPCProtocol`-class is completely usable
on its own.

Likewise the :py:class:`~tinyrpc.dispatch.RPCDispatcher` could be used to dispatch calls in a
commandline REPL like application.

Transport
---------

The transport classes are responsible for receiving and sending messages.
No assumptions are made about messages, except that they are of a fixed size.
Messages are received and possibly passed on as Python :py:class:`bytes` objects.

In an RPC context, messages coming in (containing requests) are simply called
messages, a message sent in reply is called a reply. Replies are always
serialized responses.

Protocol
--------

The protocol class(es) are responsible for two tasks:

* they implement the protocol, defining how method names, method parameters and errors are represented in requests and responses.
* they serialize the requests and responses into messages and deserialize messages back into requests and responses.


Dispatcher
----------

:doc:`dispatch` performs the actual method calling determining with method to call and how to
pass it the parameters.
The result of the method call, or the exception if the call failed is assembled and made available
to the protocol for serialization.

Client and Server
-----------------

The client and server classes tie all components together to provide the application interface.

.. _jsonrpc: http://jsonrpc.org


================================================
FILE: docs/transports.rst
================================================
Transports
==========

Transports are somewhat low level interface concerned with transporting
messages across through different means. "Messages" in this case are simple
strings. All transports need to support two different interfaces:

.. autoclass:: tinyrpc.transports.ServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.transports.ClientTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

Note that these transports are of relevance when using ``tinyrpc``-built in
facilities. They can be coopted for any other purpose, if you simply need
reliable server-client message passing as well.

Also note that the client transport interface is not designed for asynchronous
use. For simple use cases (sending multiple concurrent requests) monkey patching
with gevent may get the job done.


Transport implementations
-------------------------

A few transport implementations are included with ``tinyrpc``:

0mq
~~~

Based on :py:mod:`zmq`, supports 0mq based sockets. Highly recommended:

.. autoclass:: tinyrpc.transports.zmq.ZmqServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.transports.zmq.ZmqClientTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

HTTP
~~~~

There is only an HTTP client, no server (use WSGI instead).

.. autoclass:: tinyrpc.transports.http.HttpPostClientTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. note:: To set a timeout on your client transport provide a ``timeout``
    keyword parameter like::

        transport = HttpPostClientTransport(endpoint, timeout=0.1)

    It will result in a ``requests.exceptions.Timeout`` exception when a
    timeout occurs.

WSGI
~~~~

.. autoclass:: tinyrpc.transports.wsgi.WsgiServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

CGI
~~~

.. autoclass:: tinyrpc.transports.cgi.CGIServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

Callback
~~~~~~~~

.. autoclass:: tinyrpc.transports.callback.CallbackServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

RabbitMQ
~~~~~~~~

.. autoclass:: tinyrpc.transports.rabbitmq.RabbitMQServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.transports.rabbitmq.RabbitMQClientTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

WebSocket
~~~~~~~~~

.. autoclass:: tinyrpc.transports.websocket.WSServerTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.transports.websocket.WSApplication
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource

.. autoclass:: tinyrpc.transports.websocketclient.HttpWebSocketClientTransport
    :members:
    :noindex:
    :show-inheritance:
    :member-order: bysource



================================================
FILE: examples/http_client_example.py
================================================
#!/usr/bin/env python3

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.http import HttpPostClientTransport
from tinyrpc import RPCClient

rpc_client = RPCClient(
    JSONRPCProtocol(),
    HttpPostClientTransport('http://127.0.0.1:5000/')
)

remote_server = rpc_client.get_proxy()

# call a method called 'reverse_string' with a single string argument
result = remote_server.reverse_string('Hello, World!')

print("Server answered:", result)


================================================
FILE: examples/http_server_example.py
================================================
#!/usr/bin/env python3

import gevent
import gevent.wsgi
import gevent.queue
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.wsgi import WsgiServerTransport
from tinyrpc.server.gevent import RPCServerGreenlets
from tinyrpc.dispatch import RPCDispatcher

dispatcher = RPCDispatcher()
transport = WsgiServerTransport(queue_class=gevent.queue.Queue)

# start wsgi server as a background-greenlet
wsgi_server = gevent.wsgi.WSGIServer(('127.0.0.1', 5000), transport.handle)
gevent.spawn(wsgi_server.serve_forever)

rpc_server = RPCServerGreenlets(
    transport,
    JSONRPCProtocol(),
    dispatcher
)

@dispatcher.public
def reverse_string(s):
    return s[::-1]

# in the main greenlet, run our rpc_server
rpc_server.serve_forever()


================================================
FILE: examples/zmq_client_example.py
================================================
#!/usr/bin/env python3

import zmq

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.zmq import ZmqClientTransport
from tinyrpc import RPCClient

ctx = zmq.Context()

rpc_client = RPCClient(
    JSONRPCProtocol(),
    ZmqClientTransport.create(ctx, 'tcp://127.0.0.1:5001')
)

remote_server = rpc_client.get_proxy()

# call a method called 'reverse_string' with a single string argument
result = remote_server.reverse_string('Hello, World!')

print("Server answered:", result)


================================================
FILE: examples/zmq_server_example.py
================================================
#!/usr/bin/env python3

import zmq

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc.transports.zmq import ZmqServerTransport
from tinyrpc.server import RPCServer
from tinyrpc.dispatch import RPCDispatcher

ctx = zmq.Context()
dispatcher = RPCDispatcher()
transport = ZmqServerTransport.create(ctx, 'tcp://127.0.0.1:5001')

rpc_server = RPCServer(
    transport,
    JSONRPCProtocol(),
    dispatcher
)

@dispatcher.public
def reverse_string(s):
    return s[::-1]

rpc_server.serve_forever()


================================================
FILE: optional_features.pip
================================================
requests
werkzeug
gevent
pyzmq
websocket-client
gevent-websocket
pyzmq
jsonext
msgpack
pika


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"



================================================
FILE: requirements.txt
================================================
gevent==22.10.2
gevent-websocket==0.10.1
msgpack==1.0.2
pika==1.2.0
pytest==6.2.4
pytest-cov==2.11.1
pyzmq==23.2.1
requests==2.31.0
Werkzeug==2.2.3


================================================
FILE: setup.py
================================================
import os

from setuptools import setup, find_packages


def read(fname):
    return open(os.path.join(os.path.dirname(__file__), fname)).read()


setup(
    name='tinyrpc',
    version='1.1.7',
    description='A small, modular, transport and protocol neutral RPC '
                'library that, among other things, supports JSON-RPC and zmq.',
    long_description=read('README.rst'),
    long_description_content_type="text/x-rst",
    packages=find_packages(exclude=['examples']),
    keywords='json rpc json-rpc jsonrpc 0mq zmq zeromq',
    author='Marc Brinkmann',
    author_email='git@marcbrinkmann.de',
    maintainer='Leo Noordergraaf',
    maintainer_email='leo@noordergraaf.net',
    url='http://github.com/mbr/tinyrpc',
    license='MIT',
    extras_require={
        'gevent': ['gevent'],
        'httpclient': ['requests', 'websocket-client', 'gevent-websocket'],
        'msgpack': ['msgpack'],
        'websocket': ['gevent-websocket'],
        'wsgi': ['werkzeug'],
        'zmq': ['pyzmq'],
        'jsonext': ['jsonext'],
        'rabbitmq': ['pika']
    }
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/test_client.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest
from unittest.mock import Mock

from tinyrpc.exc import RPCError
from tinyrpc.client import RPCClient, RPCProxy
from tinyrpc.protocols import RPCProtocol, RPCResponse, RPCErrorResponse, RPCRequest
from tinyrpc.transports import ClientTransport


@pytest.fixture(params=['test_method1', 'method2', 'CamelCasedMethod'])
def method_name(request):
    return request.param


@pytest.fixture(params=[(), ('foo', None, 42), (1, )])
def method_args(request):
    return request.param


@pytest.fixture(
    params=[(), (('foo', 'bar'), ('x', None), ('y', 42)), (('q', 1), )]
)
def method_kwargs(request):
    return dict(request.param or {})


@pytest.fixture(params=['', 'NoDot', 'dot.'])
def prefix(request):
    return request.param


@pytest.fixture(params=[True, False])
def one_way_setting(request):
    return request.param


@pytest.fixture
def mock_client():
    return Mock(RPCClient)


@pytest.fixture
def mock_protocol():
    mproto = Mock(RPCProtocol)

    foo = Mock(RPCResponse)
    foo.result = None

    mproto.parse_reply = Mock(return_value=foo)

    return mproto


@pytest.fixture
def mock_transport():
    return Mock(ClientTransport)


@pytest.fixture()
def client(mock_protocol, mock_transport):
    return RPCClient(mock_protocol, mock_transport)


@pytest.fixture
def m_proxy(mock_client, prefix, one_way_setting):
    return RPCProxy(mock_client, prefix, one_way_setting)


def test_proxy_calls_correct_method(
        m_proxy, mock_client, prefix, method_kwargs, method_args, method_name,
        one_way_setting
):

    getattr(m_proxy, method_name)(*method_args, **method_kwargs)

    mock_client.call.assert_called_with(
        prefix + method_name,
        method_args,
        method_kwargs,
        one_way=one_way_setting
    )


def test_client_uses_correct_protocol(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting
):
    client.call(method_name, method_args, method_kwargs, one_way_setting)

    assert mock_protocol.create_request.called


def test_client_uses_correct_transport(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting, mock_transport
):
    client.call(method_name, method_args, method_kwargs, one_way_setting)
    assert mock_transport.send_message.called


def test_client_passes_correct_reply(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting, mock_transport
):
    transport_return = '023hoisdfh'
    mock_transport.send_message = Mock(return_value=transport_return)
    client.call(method_name, method_args, method_kwargs, one_way_setting)
    if one_way_setting:
        mock_protocol.parse_reply.assert_not_called()
    else:
        mock_protocol.parse_reply.assert_called_with(transport_return)


def test_client_raises_error_replies(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting
):
    error_response = RPCErrorResponse()
    error_response.error = 'foo'
    mock_protocol.parse_reply = Mock(return_value=error_response)

    if not one_way_setting:
        client.call(method_name, method_args, method_kwargs, one_way_setting)
        assert mock_protocol.raise_error.call_args is not None
        args, kwargs = mock_protocol.raise_error.call_args
        assert isinstance(args[0], RPCErrorResponse)
        assert args[0].error == 'foo'
        print(mock_protocol.mock_calls)
        mock_protocol.raise_error.assert_called_with(error_response)


def test_client_raises_indirect_error_replies(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting
):
    class MockException(Exception):
        pass

    def raise_error(error):
        raise MockException(error)

    error_response = RPCErrorResponse()
    error_response.error = 'foo'
    mock_protocol.parse_reply = Mock(return_value=error_response)
    mock_protocol.raise_error = raise_error

    if not one_way_setting:
        with pytest.raises(MockException):
            client.call(
                method_name, method_args, method_kwargs, one_way_setting
            )


def test_client_produces_good_proxy(client, prefix, one_way_setting):
    proxy = client.get_proxy(prefix, one_way_setting)
    assert proxy.client == client
    assert proxy.prefix == prefix
    assert proxy.one_way == one_way_setting
    assert callable(proxy.foobar)


@pytest.mark.skip(
    'no longer performs automatic conversion, serialize() always returns bytes'
)
def test_client_send_binary_message(
        client, mock_protocol, method_name, method_args, method_kwargs,
        one_way_setting, mock_transport
):
    req = Mock(RPCRequest)
    req.serialize.return_value = u'unicode not acceptable'
    mock_protocol.create_request.return_value = req
    client.call(method_name, method_args, method_kwargs, one_way_setting)
    assert mock_transport.send_message.called
    assert isinstance(mock_transport.send_message.call_args[0][0], bytes)


================================================
FILE: tests/test_dispatch.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from unittest.mock import Mock
import pytest
import inspect

from tinyrpc.dispatch import RPCDispatcher, public
from tinyrpc import RPCRequest, RPCBatchRequest, RPCBatchResponse
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol, JSONRPCInvalidParamsError
from tinyrpc.exc import *

@pytest.fixture
def dispatch():
    return RPCDispatcher()


@pytest.fixture()
def subdispatch():
    return RPCDispatcher()



def mock_request(method='subtract', args=None, kwargs=None):
    mock_request = Mock(RPCRequest)
    mock_request.method = method
    mock_request.args = args or [4, 6]
    mock_request.kwargs = kwargs or {}

    return mock_request

@pytest.fixture(name="mock_request")
def mock_request_fixture():
    return mock_request()

def test_function_decorating_without_paramters(dispatch):
    @dispatch.public
    def foo(bar):
        pass

    assert dispatch.get_method('foo') == foo


def test_function_decorating_with_empty_paramters(dispatch):
    @dispatch.public()
    def foo(bar):
        pass

    assert dispatch.get_method('foo') == foo


def test_function_decorating_with_paramters(dispatch):
    @dispatch.public(name='baz')
    def foo(bar):
        pass

    with pytest.raises(MethodNotFoundError):
        dispatch.get_method('foo')


    assert dispatch.get_method('baz') == foo


def test_subdispatchers(dispatch, subdispatch):
    @dispatch.public()
    def foo(bar):
        pass

    @subdispatch.public(name='foo')
    def subfoo(bar):
        pass

    dispatch.add_subdispatch(subdispatch, 'sub.')

    assert dispatch.get_method('foo') == foo
    assert dispatch.get_method('sub.foo') == subfoo


def test_object_method_marking():
    class Foo(object):
        def foo1(self):
            pass

        @public
        def foo2(self):
            pass

        @public(name='baz')
        def foo3(self):
            pass

    f = Foo()

    assert not hasattr(f.foo1, '_rpc_public_name')
    assert f.foo2._rpc_public_name == 'foo2'
    assert f.foo3._rpc_public_name == 'baz'


def test_object_method_register(dispatch):
    class Foo(object):
        def foo1(self):
            pass

        @public
        def foo2(self):
            pass

        @public(name='baz')
        def foo3(self):
            pass

    f = Foo()
    dispatch.register_instance(f)

    with pytest.raises(MethodNotFoundError):
        assert dispatch.get_method('foo1')

    assert dispatch.get_method('foo2') == f.foo2
    assert dispatch.get_method('baz') == f.foo3


def test_object_method_register_with_prefix(dispatch):
    class Foo(object):
        def foo1(self):
            pass

        @public
        def foo2(self):
            pass

        @public(name='baz')
        def foo3(self):
            pass

    f = Foo()
    dispatch.register_instance(f, 'myprefix')

    with pytest.raises(MethodNotFoundError):
        assert dispatch.get_method('foo1')

    with pytest.raises(MethodNotFoundError):
        assert dispatch.get_method('myprefixfoo1')

    with pytest.raises(MethodNotFoundError):
        assert dispatch.get_method('foo2')

    with pytest.raises(MethodNotFoundError):
        assert dispatch.get_method('foo3')

    assert dispatch.get_method('myprefixfoo2') == f.foo2
    assert dispatch.get_method('myprefixbaz') == f.foo3


def test_dispatch_calls_method_and_responds(dispatch, mock_request):
    m = Mock()
    m.subtract = Mock(return_value=-2)

    dispatch.add_method(m.subtract, 'subtract')
    response = dispatch.dispatch(mock_request)

    assert m.subtract.called

    mock_request.respond.assert_called_with(-2)


def test_dispatch_handles_in_function_exceptions(dispatch, mock_request):
    m = Mock()
    m.subtract = Mock(return_value=-2)

    class MockError(Exception):
        pass

    m.subtract.side_effect = MockError('mock error')

    dispatch.add_method(m.subtract, 'subtract')
    response = dispatch.dispatch(mock_request)

    assert m.subtract.called

    mock_request.error_respond.assert_called_with(m.subtract.side_effect)


def test_batch_dispatch(dispatch):
    method1 = Mock(return_value='rv1')
    method2 = Mock(return_value=None)

    dispatch.add_method(method1, 'method1')
    dispatch.add_method(method2, 'method2')

    batch_request = RPCBatchRequest()
    batch_request.error_respond = Mock(return_value='ERROR')
    batch_request.append(mock_request('method1', args=[1,2]))
    batch_request.append(mock_request('non_existant_method', args=[5,6]))
    batch_request.append(mock_request('method2', args=[3,4]))

    batch_request.create_batch_response = lambda: RPCBatchResponse()

    assert batch_request.error_respond.call_count == 0

    response = dispatch.dispatch(batch_request)

    # assert all methods are called
    method1.assert_called_with(1, 2)
    method2.assert_called_with(3, 4)

    # FIXME: could use better checking?


def test_dispatch_raises_key_error(dispatch):
    with pytest.raises(MethodNotFoundError):
        dispatch.get_method('foo')

@pytest.fixture(params=[
    ('fn_a', [4, 6], {}, -2),
    ('fn_a', [4], {}, InvalidParamsError),
    # InvalidParamsError instead of JSONRPCInvalidParamsError due to mocking
    ('fn_a', [], {'a':4, 'b':6}, -2),
    ('fn_a', [4], {'b':6}, -2),
    ('fn_b', [4, 6], {}, -2),
    ('fn_b', [], {'a':4, 'b':6}, InvalidParamsError),
    ('fn_b', [4], {}, IndexError),
    # a[1] doesn't exist, can't be detected beforehand
    ('fn_c', [4, 6], {}, InvalidParamsError),
    ('fn_c', [], {'a':4, 'b':6}, -2),
    ('fn_c', [], {'a':4}, KeyError)
    # a['b'] doesn't exist, can't be detected beforehand
])
def invoke_with(request):
    return request.param

def test_argument_error(dispatch, invoke_with):
    method, args, kwargs, result = invoke_with

    protocol = JSONRPCProtocol()

    @dispatch.public
    def fn_a(a, b):
        return a-b

    @dispatch.public
    def fn_b(*a):
        return a[0]-a[1]

    @dispatch.public
    def fn_c(**a):
        return a['a']-a['b']

    mock_request = Mock(RPCRequest)
    mock_request.args = args
    mock_request.kwargs = kwargs
    mock_request.method = method
    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))
    if inspect.isclass(result) and issubclass(result, Exception):
        assert type(mock_request.error_respond.call_args[0][0]) == result
    else:
        mock_request.respond.assert_called_with(result)

def test_call_argument_validation(dispatch):
    def f(a,b):
        return a+b

    dispatch.validate_parameters(f, [1, 2], {})
    with pytest.raises(InvalidParamsError):
        dispatch.validate_parameters(f, [1], {})
    dispatch.validate_parameters(dir, [], {})
    # should skip validation, will produce error otherwise

def test_bound_method_argument_error(dispatch, invoke_with):
    method, args, kwargs, result = invoke_with

    protocol = JSONRPCProtocol()

    class Test:
        c = 0
        @public
        def fn_a(self, a, b):
            return a-b+self.c

        @public
        def fn_b(self, *a):
            return a[0]-a[1]+self.c

        @public
        def fn_c(self, **a):
            return a['a']-a['b']+self.c

    test=Test()
    dispatch.register_instance(test)
    mock_request = Mock(RPCRequest)
    mock_request.args = args
    mock_request.kwargs = kwargs
    mock_request.method = method
    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))
    if inspect.isclass(result) and issubclass(result, Exception):
        assert type(mock_request.error_respond.call_args[0][0]) == result
    else:
        mock_request.respond.assert_called_with(result)

def test_bound_method_validation(dispatch):
    class Test:
        def f(self, a, b):
            return a+b
    inst = Test()

    dispatch.validate_parameters(inst.f, [1, 2], {})
    with pytest.raises(InvalidParamsError):
        dispatch.validate_parameters(inst.f, [1], {})

def test_unbound_method_argument_error(dispatch, invoke_with):
    method, args, kwargs, result = invoke_with

    protocol = JSONRPCProtocol()

    class Test:
        c = 0
        @public
        def fn_a(a, b):
            return a-b

        @public
        def fn_b(*a):
            return a[0]-a[1]

        @public
        def fn_c(**a):
            return a['a']-a['b']

    dispatch.register_instance(Test)
    mock_request = Mock(RPCRequest)
    mock_request.args = args
    mock_request.kwargs = kwargs
    mock_request.method = method
    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))
    if inspect.isclass(result) and issubclass(result, Exception):
        assert type(mock_request.error_respond.call_args[0][0]) == result
    else:
        mock_request.respond.assert_called_with(result)

def test_unbound_method_validation(dispatch):
    class Test:
        def f(a, b):
            return a+b

    dispatch.validate_parameters(Test.f, [1, 2], {})
    with pytest.raises(InvalidParamsError):
        dispatch.validate_parameters(Test.f, [1], {})

def test_static_method_argument_error(dispatch, invoke_with):
    method, args, kwargs, result = invoke_with

    protocol = JSONRPCProtocol()

    class Test:
        c = 0
        @staticmethod
        @public
        def fn_a(a, b):
            return a-b

        @staticmethod
        @public
        def fn_b(*a):
            return a[0]-a[1]

        @staticmethod
        @public
        def fn_c(**a):
            return a['a']-a['b']

    test=Test()
    dispatch.register_instance(test)
    mock_request = Mock(RPCRequest)
    mock_request.args = args
    mock_request.kwargs = kwargs
    mock_request.method = method
    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))
    if inspect.isclass(result) and issubclass(result, Exception):
        assert type(mock_request.error_respond.call_args[0][0]) == result
    else:
        mock_request.respond.assert_called_with(result)

def test_static_method_validation(dispatch):
    class Test:
        @staticmethod
        def f(a, b):
            return a+b
    inst = Test()

    dispatch.validate_parameters(inst.f, [1, 2], {})
    with pytest.raises(InvalidParamsError):
        dispatch.validate_parameters(inst.f, [1], {})

def test_class_method_argument_error(dispatch, invoke_with):
    method, args, kwargs, result = invoke_with

    protocol = JSONRPCProtocol()

    class Test:
        c = 0
        @classmethod
        @public
        def fn_a(cls, a, b):
            return a-b-cls.c

        @classmethod
        @public
        def fn_b(cls, *a):
            return a[0]-a[1]-cls.c

        @classmethod
        @public
        def fn_c(cls, **a):
            return a['a']-a['b']-cls.c

    test=Test()
    dispatch.register_instance(test)
    mock_request = Mock(RPCRequest)
    mock_request.args = args
    mock_request.kwargs = kwargs
    mock_request.method = method
    dispatch._dispatch(mock_request, getattr(protocol, '_caller', None))
    if inspect.isclass(result) and issubclass(result, Exception):
        assert type(mock_request.error_respond.call_args[0][0]) == result
    else:
        mock_request.respond.assert_called_with(result)

def test_class_method_validation(dispatch):
    class Test:
        @classmethod
        def f(cls, a, b):
            return a+b
    inst = Test()

    dispatch.validate_parameters(inst.f, [1, 2], {})
    with pytest.raises(InvalidParamsError):
        dispatch.validate_parameters(inst.f, [1], {})



================================================
FILE: tests/test_jsonrpc.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json

import pytest

from tinyrpc import MethodNotFoundError, InvalidRequestError, ServerError, \
                    RPCError, RPCResponse, InvalidReplyError
from tinyrpc.protocols.jsonrpc import JSONRPCParseError, \
                                      JSONRPCInvalidRequestError, \
                                      JSONRPCMethodNotFoundError, \
                                      JSONRPCInvalidParamsError, \
                                      JSONRPCInternalError,\
                                      JSONRPCErrorResponse


def _json_equal(a, b):
    da = json.loads(a.decode() if isinstance(a, bytes) else a)
    db = json.loads(b.decode() if isinstance(b, bytes) else b)

    return da == db


@pytest.fixture
def prot():
    from tinyrpc.protocols.jsonrpc import JSONRPCProtocol

    return JSONRPCProtocol()


@pytest.mark.parametrize(('data', 'attrs'), [
    # examples from the spec, parsing only
    ("""{"jsonrpc": "2.0", "method": "subtract",
       "params": [42, 23], "id": 1}""",
     {'method': 'subtract', 'args': [42, 23], 'unique_id': 1}
    ),

    ("""{"jsonrpc": "2.0", "method": "subtract", "params":
        [23, 42], "id": 2}""",
     {'method': 'subtract', 'args': [23, 42], 'unique_id': 2}
    ),

    ("""{"jsonrpc": "2.0", "method": "subtract", "params":
        {"subtrahend": 23, "minuend": 42}, "id": 3}""",
     {'method': 'subtract', 'kwargs': {'subtrahend': 23, 'minuend': 42},
      'unique_id': 3}
    ),

    ("""{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42,
        "subtrahend": 23}, "id": 4}""",
     {'method': 'subtract', 'kwargs': {'minuend': 42, 'subtrahend': 23},
      'unique_id': 4},
    ),

    ("""{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}""",
     {'method': 'update', 'args': [1, 2, 3, 4, 5]}
    ),

    ("""{"jsonrpc": "2.0", "method": "foobar"}""",
     {'method': 'foobar'}
    ),
])
def test_parsing_good_request_samples(prot, data, attrs):
    req = prot.parse_request(data)

    for k, v in attrs.items():
        assert getattr(req, k) == v


@pytest.mark.parametrize('invalid_json', [
    '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]',
    'garbage',
])
def test_parsing_invalid_json(prot, invalid_json):
    with pytest.raises(JSONRPCParseError):
        prot.parse_request(invalid_json)


def test_parsing_invalid_arguments(prot):
    with pytest.raises(JSONRPCInvalidParamsError):
        prot.parse_request(
            """{"jsonrpc": "2.0", "method": "update", "params": 9}"""
        )


@pytest.mark.parametrize(('data', 'id', 'result'), [
    ("""{"jsonrpc": "2.0", "result": 19, "id": 1}""",
     1,
     19,
    ),

    ("""{"jsonrpc": "2.0", "result": -19, "id": 2}""",
     2,
     -19,
    ),

    ("""{"jsonrpc": "2.0", "result": 19, "id": 3}""",
     3,
     19,
    ),

    ("""{"jsonrpc": "2.0", "result": 19, "id": 4}""",
     4,
     19,
    ),
])
def test_good_reply_samples(prot, data, id, result):
    # assume the protocol is awaiting a response for
    # a request with `id`
    prot._pending_replies = [id]
    
    reply = prot.parse_reply(data)

    assert reply.unique_id == id
    assert reply.result == result


@pytest.mark.parametrize(('data'), [
    """{"jsonrpc": "2.0", "result": 19, "id": 9001}"""
])
def test_unsolicited_reply_raises_error(prot, data):
    prot._pending_replies = [4]
    with pytest.raises(InvalidReplyError):
        reply = prot.parse_reply(data)


@pytest.mark.parametrize(('exc', 'code', 'message'), [
    (JSONRPCParseError, -32700, 'Parse error'),
    (JSONRPCInvalidRequestError, -32600, 'Invalid Request'),
    (JSONRPCMethodNotFoundError, -32601, 'Method not found'),
    (JSONRPCInvalidParamsError, -32602, 'Invalid params'),
    (JSONRPCInternalError, -32603, 'Internal error'),

    # generic errors
    #(InvalidRequestError, -32600, 'Invalid Request'),
    #(MethodNotFoundError, -32601, 'Method not found'),
    #(ServerError, -32603, 'Internal error'),
])
def test_proper_construction_of_error_codes(prot, exc, code, message):
    request = prot.parse_request(
        """{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4],
           "id": "1"}"""
    )
    reply = exc().error_respond().serialize()
    assert isinstance(reply, bytes)
    reply = reply.decode()

    err = json.loads(reply)

    assert err['error']['code'] == code
    assert err['error']['message'] == message


def test_notification_yields_None_response(prot):
    data = """{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}"""

    req = prot.parse_request(data)

    assert req.one_way == True

    # updates should never cause retries
    assert req.respond(True) == None


def test_batch_empty_array(prot):
    with pytest.raises(JSONRPCInvalidRequestError):
        prot.parse_request("""[]""")


def test_batch_invalid_array(prot):
    assert isinstance(prot.parse_request("""[1]""")[0],
                      JSONRPCInvalidRequestError)


def test_batch_invalid_batch(prot):
    for r in prot.parse_request("""[1, 2, 3]"""):
        assert isinstance(r, JSONRPCInvalidRequestError)


def test_batch_good_examples(prot):
    data = """
    [
        {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
        {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
        {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
        {"foo": "boo"},
        {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
        {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
    ]
    """

    results = prot.parse_request(data)

    assert isinstance(results, list)
    assert results[0].method == 'sum'
    assert results[0].args == [1, 2, 4]
    assert results[0].unique_id == "1"

    assert results[1].method == 'notify_hello'
    assert results[1].args == [7]
    assert results[1].unique_id == None

    assert results[2].method == 'subtract'
    assert results[2].args == [42, 23]
    assert results[2].unique_id == "2"

    assert isinstance(results[3], JSONRPCInvalidRequestError)

    assert results[4].method == 'foo.get'
    assert results[4].kwargs == {'name': 'myself'}
    assert results[4].unique_id == "5"

    assert results[5].method == 'get_data'
    assert results[5].args == []
    assert results[5].kwargs == {}
    assert results[5].unique_id == "9"


def test_unique_ids(prot):
    req1 = prot.create_request('foo', [1, 2])
    req2 = prot.create_request('foo', [1, 2])

    assert req1.unique_id != req2.unique_id


def test_out_of_order(prot):
    req = prot.create_request('foo', ['a', 'b'], None)
    rep = req.respond(1)

    assert req.unique_id == rep.unique_id


def test_request_generation(prot):
    jdata = json.loads(prot.create_request('subtract', [42, 23]).serialize().decode())

    assert jdata['method'] == 'subtract'
    assert jdata['params'] == [42, 23]
    assert jdata['id'] != None
    assert jdata['jsonrpc'] == '2.0'


def test_jsonrpc_spec_v2_example1(prot):
    # reset id counter
    from tinyrpc.protocols import default_id_generator
    prot._id_generator = default_id_generator(1)

    request = prot.create_request('subtract', [42, 23])

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id":
        1}""",
        request.serialize()
    )

    reply = request.respond(19)

    assert _json_equal(
        """{"jsonrpc": "2.0", "result": 19, "id": 1}""",
        reply.serialize()
    )

    request = prot.create_request('subtract', [23, 42])

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}""",
        request.serialize()
    )

    reply = request.respond(-19)

    assert _json_equal(
        """{"jsonrpc": "2.0", "result": -19, "id": 2}""",
        reply.serialize()
    )


def test_jsonrpc_spec_v2_example2(prot):
    # reset id counter
    from tinyrpc.protocols import default_id_generator
    prot._id_generator = default_id_generator(3)

    request = prot.create_request('subtract',
                                  kwargs={'subtrahend': 23, 'minuend': 42})

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "subtract", "params":
           {"subtrahend": 23, "minuend": 42}, "id": 3}""",
        request.serialize()
    )

    reply = request.respond(19)

    assert _json_equal(
        """{"jsonrpc": "2.0", "result": 19, "id": 3}""",
        reply.serialize()
    )

    request = prot.create_request('subtract',
                                  kwargs={'subtrahend': 23, 'minuend': 42})

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend":
           42, "subtrahend": 23}, "id": 4}""",
        request.serialize()
    )

    reply = request.respond(-19)

    assert _json_equal(
        """{"jsonrpc": "2.0", "result": -19, "id": 4}""",
        reply.serialize()
    )


def test_jsonrpc_spec_v2_example3(prot):
    request = prot.create_request('update', [1, 2, 3, 4, 5], one_way=True)

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}""",
        request.serialize()
    )

    request = prot.create_request('foobar', one_way=True)

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "foobar"}""",
        request.serialize()
    )


def test_jsonrpc_spec_v2_example4(prot):
    request = prot.create_request('foobar')
    request.unique_id = str(1)

    assert _json_equal(
        """{"jsonrpc": "2.0", "method": "foobar", "id": "1"}""",
        request.serialize()
    )

    response = request.error_respond(MethodNotFoundError('foobar'))

    assert _json_equal(
        """{"jsonrpc": "2.0", "error": {"code": -32601, "message":
           "Method not found"}, "id": "1"}""",
           response.serialize()
    )


def test_jsonrpc_spec_v2_example5(prot):
    try:
        prot.parse_request(
            """{"jsonrpc": "2.0", "method": "foobar, "params":
            "bar", "baz]""")
        assert False  # parsing must fail
    except JSONRPCParseError as error:
        e = error

    response = e.error_respond()

    assert _json_equal(
            """{"jsonrpc": "2.0", "error": {"code": -32700, "message":
            "Parse error"}, "id": null}""",
            response.serialize()
    )


def test_jsonrpc_spec_v2_example6(prot):
    try:
        prot.parse_request(
            """{"jsonrpc": "2.0", "method": 1, "params": "bar"}""")
        assert False  # parsing must fail
    except JSONRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _json_equal(
            """{"jsonrpc": "2.0", "error": {"code": -32600, "message":
            "Invalid Request"}, "id": null}""",
            response.serialize()
    )


def test_jsonrpc_spec_v2_example6_with_request_id(prot):
    try:
        prot.parse_request(
            """{"jsonrpc": "2.0", "id": 42, "method": 1, "params": "bar"}""")
        assert False  # parsing must fail
    except JSONRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _json_equal(
            """{"jsonrpc": "2.0", "error": {"code": -32600, "message":
            "Invalid Request"}, "id": 42}""",
            response.serialize()
    )


def test_jsonrpc_spec_v2_example7(prot):
    try:
        prot.parse_request("""[
            {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
            {"jsonrpc": "2.0", "method" ]""")
        assert False
    except JSONRPCParseError as error:
        e = error

    response = e.error_respond()

    assert _json_equal(
        """{"jsonrpc": "2.0", "error": {"code": -32700, "message":
           "Parse error"}, "id": null}""",
           response.serialize()
    )


def test_jsonrpc_spec_v2_example8(prot):
    try:
        prot.parse_request("""[]""")
        assert False
    except JSONRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _json_equal("""{"jsonrpc": "2.0", "error": {"code": -32600,
    "message": "Invalid Request"}, "id": null}""",
           response.serialize())


def test_jsonrpc_spec_v2_example9(prot):
    requests = prot.parse_request("""[1]""")

    assert isinstance(requests[0], JSONRPCInvalidRequestError)

    responses = requests.create_batch_response()
    responses.append(requests[0].error_respond())

    assert _json_equal("""[ {"jsonrpc": "2.0", "error": {"code": -32600,
                       "message": "Invalid Request"}, "id": null} ]""",
           responses.serialize())


def test_jsonrpc_spec_v2_example10(prot):
    requests = prot.parse_request("""[1, 2, 3]""")

    assert isinstance(requests[0], JSONRPCInvalidRequestError)
    assert isinstance(requests[1], JSONRPCInvalidRequestError)
    assert isinstance(requests[2], JSONRPCInvalidRequestError)

    responses = requests.create_batch_response()
    responses.append(requests[0].error_respond())
    responses.append(requests[1].error_respond())
    responses.append(requests[2].error_respond())

    assert _json_equal("""[
  {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
  {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
  {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
]""",
           responses.serialize())


def test_jsonrpc_spec_v2_example11(prot):
    requests = prot.parse_request("""[
        {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
        {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
        {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
        {"foo": "boo"},
        {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
        {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
    ]""")

    assert isinstance(requests[3], JSONRPCInvalidRequestError)

    responses = requests.create_batch_response()
    responses.append(requests[0].respond(7))
    responses.append(requests[2].respond(19))
    responses.append(requests[3].error_respond())
    responses.append(requests[4].error_respond(MethodNotFoundError('foo.get')))
    responses.append(requests[5].respond(['hello', 5]))

    assert _json_equal("""[
        {"jsonrpc": "2.0", "result": 7, "id": "1"},
        {"jsonrpc": "2.0", "result": 19, "id": "2"},
        {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
        {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
        {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
    ]""",
        responses.serialize())


def test_jsonrpc_spec_v2_example12(prot):
    reqs = []
    reqs.append(prot.create_request('notify_sum', [1, 2, 4], one_way=True))
    reqs.append(prot.create_request('notify_hello', [7], one_way=True))

    request = prot.create_batch_request(reqs)

    assert request.create_batch_response() == None


def test_can_get_custom_error_messages_out(prot):
    request = prot.create_request('foo')

    custom_msg = 'join the army, they said. see the world, they said.'

    e = Exception(custom_msg)

    response = request.error_respond(e)

    jstr = response.serialize()
    assert isinstance(jstr, bytes)
    jstr = jstr.decode()

    data = json.loads(jstr)

    assert data['error']['message'] == custom_msg


def test_accepts_empty_but_not_none_args_kwargs(prot):
    request = prot.create_request('foo', args=[], kwargs={})


def test_missing_jsonrpc_version_on_request(prot):
    with pytest.raises(JSONRPCInvalidRequestError):
        prot.parse_request('{"method": "sum", "params": [1,2,4], "id": "1"}')

def test_missing_jsonrpc_version_on_reply(prot):
    with pytest.raises(InvalidReplyError):
        prot.parse_reply('{"result": 7, "id": "1"}')

def test_pass_error_data_with_standard_exception(prot):
    request = prot.create_request('foo')

    custom_msg = 'join the army, they said. see the world, they said.'
    data = {'pi': 3.14, 'lst': ['a', 'b', 'c']}

    e = Exception(custom_msg, data)

    response = request.error_respond(e)
    jmsg = response.serialize()
    assert isinstance(jmsg, bytes)
    jmsg = jmsg.decode()

    decoded = json.loads(jmsg)
    print("decoded=", decoded)
    assert decoded['error']['code'] == -32000
    assert decoded['error']['message'] == custom_msg
    assert decoded['error']['data'] == data

    # on the client side, when reply is parsed
    parsed_reply = prot.parse_reply(jmsg)
    serialized_reply = parsed_reply.serialize().decode("utf-8")
    decoded_reply = json.loads(serialized_reply)
    print("decoded_reply=", decoded_reply)
    assert isinstance(parsed_reply, JSONRPCErrorResponse)
    assert hasattr(parsed_reply, "data")
    assert serialized_reply == jmsg
    assert decoded_reply == decoded

def test_pass_error_data_with_custom_exception(prot):
    # type: (JSONRPCProtocol) -> None
    request = prot.create_request('foo')

    data = {'pi': 3.14, 'lst': ['a', 'b', 'c']}

    e = JSONRPCParseError(data=data)

    response = request.error_respond(e)
    jmsg = response.serialize()
    assert isinstance(jmsg, bytes)
    jmsg = jmsg.decode()

    decoded = json.loads(jmsg)
    print("decoded=", decoded)
    assert decoded['error']['code'] == -32700
    assert decoded['error']['message'] == JSONRPCParseError.message
    assert decoded['error']['data'] == data

    # on the client side, when reply is parsed
    parsed_reply = prot.parse_reply(jmsg)
    serialized_reply = parsed_reply.serialize().decode("utf-8")
    decoded_reply = json.loads(serialized_reply)
    print("decoded_reply=", decoded_reply)
    assert isinstance(parsed_reply, JSONRPCErrorResponse)
    assert hasattr(parsed_reply, "data")
    assert serialized_reply == jmsg
    assert decoded_reply == decoded


================================================
FILE: tests/test_msgpackrpc.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import msgpack
import pytest

from tinyrpc import InvalidReplyError, MethodNotFoundError
from tinyrpc.protocols.msgpackrpc import (
    MSGPACKRPCParseError,
    MSGPACKRPCInvalidRequestError,
    MSGPACKRPCMethodNotFoundError,
    MSGPACKRPCInvalidParamsError,
    MSGPACKRPCInternalError,
)


def _msgpack_equal(a, b):
    return msgpack.unpackb(a) == msgpack.unpackb(b)


@pytest.fixture
def prot():
    from tinyrpc.protocols.msgpackrpc import MSGPACKRPCProtocol

    return MSGPACKRPCProtocol()


@pytest.mark.parametrize(
    ("data", "attrs"),
    [
        # examples from the JSON-RPC spec, translated to MSGPACK, parsing only
        (
            b"\x94\x00\x01\xa8subtract\x92*\x17",
            {"method": "subtract", "args": [42, 23], "unique_id": 1},
        ),
        (
            b"\x94\x00\x02\xa8subtract\x92\x17*",
            {"method": "subtract", "args": [23, 42], "unique_id": 2},
        ),
        (
            b"\x93\x02\xa6update\x95\x01\x02\x03\x04\x05",
            {"method": "update", "args": [1, 2, 3, 4, 5]},
        ),
        (b"\x93\x02\xa6foobar\x90", {"method": "foobar", "args": []}),
    ],
)
def test_parsing_good_request_samples(prot, data, attrs):
    req = prot.parse_request(data)

    for k, v in attrs.items():
        assert getattr(req, k) == v


@pytest.mark.parametrize(
    "invalid_msgpack",
    [
        b"\x81\xa3\x66\x6f\x6f\xa4\x62\x61\x72",
        b"\x94\x00\x01\x81\xa3aaa\xa3bb",
        b"garbage",
    ],
)
def test_parsing_invalid_msgpack(prot, invalid_msgpack):
    with pytest.raises(MSGPACKRPCParseError):
        prot.parse_request(invalid_msgpack)


@pytest.mark.parametrize(
    "data",
    [
        b"\xc0",  # None
        b"\x94\x00\xc0\xa3aaa\x90",  # [0, None, "aaa", []] - request ID not int
        b"\x95\x00\x02\xa3aaa\x90\xc0",  # [0, 2, "aaa", [], None] - too long
        b"\x94\x02\xa3aaa\x90\xc0",  # [2, "aaa", [], None] - too long
        b"\x93\x02\x01\x90",  # [2, 1, []] - method name not string
    ],
)
def test_parsing_valid_msgpack_but_invalid_rpc_message(prot, data):
    with pytest.raises(MSGPACKRPCInvalidRequestError):
        prot.parse_request(data)


@pytest.mark.parametrize(
    "invalid_args",
    [
        b"\x94\x00\x03\xa6update\t",  # [0, 3, "update", 9]
        b"\x94\x00\x03\xa6foobar\xc0",  # [0, 3, "foobar", None]
        b"\x93\x02\xa3aaa\xc0",  # [2, "aaa", None]
    ],
)
def test_parsing_invalid_arguments(prot, invalid_args):
    with pytest.raises(MSGPACKRPCInvalidParamsError):
        prot.parse_request(invalid_args)


@pytest.mark.parametrize(
    ("data", "id", "result"),
    [
        (b"\x94\x01\x01\xc0\x13", 1, 19),  # [1, 1, None, 19]
        (b"\x94\x01\x02\xc0\xed", 2, -19),  # [1, 2, None, -19]
        (b"\x94\x01\x03\xc0\x13", 3, 19),  # [1, 3, None, 19]
        (b"\x94\x01\x04\xc0\x13", 4, 19),  # [1, 4, None, 19]
    ],
)
def test_good_reply_samples(prot, data, id, result):
    reply = prot.parse_reply(data)

    assert reply.unique_id == id
    assert reply.result == result


@pytest.mark.parametrize(
    ("data", "id", "code", "message"),
    [
        # Neovim-style
        (b"\x94\x01\x05\x92\xcd\x04\xd2\xa5Error\xc0", 5, 1234, "Error"),
        # Ordinary error string
        (b"\x94\x01\x05\xa5Error\xc0", 5, None, "Error"),
        # Two-item list but the types don't match Neovim's style
        (b"\x94\x01\x05\x92\xa41234\xa5Error\xc0", 5, None, ["1234", "Error"]),
    ],
)
def test_good_error_reply_samples(prot, data, id, code, message):
    reply = prot.parse_reply(data)

    assert reply.unique_id == id
    assert reply._msgpackrpc_error_code == code
    assert reply.error == message


@pytest.mark.parametrize(
    ("exc", "code", "message"),
    [
        (MSGPACKRPCParseError, -32700, "Parse error"),
        (MSGPACKRPCInvalidRequestError, -32600, "Invalid request"),
        (MSGPACKRPCMethodNotFoundError, -32601, "Method not found"),
        (MSGPACKRPCInvalidParamsError, -32602, "Invalid params"),
        (MSGPACKRPCInternalError, -32603, "Internal error"),
    ],
)
def test_proper_construction_of_error_codes(prot, exc, code, message):
    reply = exc().error_respond().serialize()
    assert isinstance(reply, bytes)

    err = msgpack.unpackb(reply, raw=False)

    assert err[0] == 1
    assert err[2] == [code, message]


def test_notification_yields_None_response(prot):
    # [2, "update", [1,2,3,4,5]]
    data = b"\x93\x02\xa6update\x95\x01\x02\x03\x04\x05"

    req = prot.parse_request(data)

    assert req.one_way is True

    # updates should never cause retries
    assert req.respond(True) is None


@pytest.mark.parametrize(
    "data",
    [
        b"\x90",  # \x90 = []
        b"\x91\x01",  # \x91\x01 = [1]
        b"\x93\x01\x02\x03",  # \x93\x01\x02\x03 = [1, 2, 3]
        (
            b"\x95\x94\x00\x01\xa3sum\x93\x01\x02\x04"
            b"\x93\x02\xacnotify_hello\x91\x07"
            b"\x94\x00\x02\xa8subtract\x92*\x17"
            b"\x94\x00\x05\xa7foo.get\x81\xa4name\xa6myself"
            b"\x94\x00\t\xa8get_data\xc0"
        ),
    ],
)
def test_batch_examples(prot, data):
    with pytest.raises(MSGPACKRPCInvalidRequestError):
        prot.parse_request(data)


def test_unique_ids(prot):
    req1 = prot.create_request("foo", [1, 2])
    req2 = prot.create_request("foo", [1, 2])

    assert req1.unique_id != req2.unique_id


def test_out_of_order(prot):
    req = prot.create_request("foo", ["a", "b"], None)
    rep = req.respond(1)

    assert req.unique_id == rep.unique_id


def test_request_generation(prot):
    data = msgpack.unpackb(
        prot.create_request("subtract", [42, 23]).serialize(), raw=False
    )

    assert data[0] == 0
    assert isinstance(data[1], int)
    assert data[2] == "subtract"
    assert data[3] == [42, 23]


# The tests below are adapted from the JSON-RPC specification, hence their names


def test_jsonrpc_spec_v2_example1(prot):
    # reset id counter
    from tinyrpc.protocols import default_id_generator
    prot._id_generator = default_id_generator(1)

    request = prot.create_request("subtract", [42, 23])

    assert request.serialize() == b"\x94\x00\x01\xa8subtract\x92*\x17"

    reply = request.respond(19)

    assert reply.serialize() == b"\x94\x01\x01\xc0\x13"

    request = prot.create_request("subtract", [23, 42])

    assert request.serialize() == b"\x94\x00\x02\xa8subtract\x92\x17*"

    reply = request.respond(-19)

    assert reply.serialize() == b"\x94\x01\x02\xc0\xed"


def test_jsonrpc_spec_v2_example3(prot):
    request = prot.create_request("update", [1, 2, 3, 4, 5], one_way=True)

    assert request.serialize() == b"\x93\x02\xa6update\x95\x01\x02\x03\x04\x05"

    request = prot.create_request("foobar", one_way=True)

    assert request.serialize() == b"\x93\x02\xa6foobar\x90"


def test_jsonrpc_spec_v2_example4(prot):
    request = prot.create_request("foobar")
    request.unique_id = 1

    assert request.serialize() == b"\x94\x00\x01\xa6foobar\x90"

    response = request.error_respond(MethodNotFoundError("foobar"))

    assert _msgpack_equal(
        b"\x94\x01\x01\x92\xd1\x80\xa7\xb0Method not found\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example5(prot):
    try:
        prot.parse_request(b"\x94\x00\x01\x81\xa3aaa\xa3bb")
        assert False  # parsing must fail
    except MSGPACKRPCParseError as error:
        e = error

    response = e.error_respond()

    # TODO(ntamas): here we are sending None as the request ID because
    # obviously we could not parse it from a malformed request. We need to
    # decide whether this is valid MSGPACK or not.
    assert _msgpack_equal(
        b"\x94\x01\xc0\x92\xd1\x80D\xabParse error\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example6(prot):
    try:
        prot.parse_request(b"\x94\x00\x01\x01\xa3bar")
        assert False  # parsing must fail
    except MSGPACKRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _msgpack_equal(
        b"\x94\x01\x01\x92\xd1\x80\xa8\xafInvalid request\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example8(prot):
    try:
        prot.parse_request(b"\x90")
        assert False
    except MSGPACKRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _msgpack_equal(
        b"\x94\x01\xc0\x92\xd1\x80\xa8\xafInvalid request\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example9(prot):
    try:
        prot.parse_request(b"\x91\x01")
        assert False
    except MSGPACKRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _msgpack_equal(
        b"\x94\x01\xc0\x92\xd1\x80\xa8\xafInvalid request\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example10(prot):
    try:
        prot.parse_request(b"\x93\x01\x02\x03")
        assert False
    except MSGPACKRPCInvalidRequestError as error:
        e = error

    response = e.error_respond()

    assert _msgpack_equal(
        b"\x94\x01\xc0\x92\xd1\x80\xa8\xafInvalid request\xc0", response.serialize()
    )


def test_jsonrpc_spec_v2_example11(prot):
    # Since MSGPACK does not support batched request, we test the requests
    # one by one
    requests = []

    for data in [
        b"\x94\x00\x01\xa3sum\x93\x01\x02\x04",  # [0, 1, "sum", [1,2,4]]
        b"\x93\x02\xacnotify_hello\x91\x07",  # [2, "notify_hello", [7]
        b"\x94\x00\x02\xa8subtract\x92*\x17",  # [0, 2, "subtract", [42,23]]
        b"\x92\xa3foo\xa3boo",  # ["foo", "boo"]
        b"\x94\x00\x05\xa7foo.get\x92\xa4name\xa6myself",  # [0, 5, "foo.get", ["name", "myself"]]
        b"\x94\x00\t\xa8get_data\x90",  # [0, 9, "get_data", []]
    ]:
        try:
            requests.append(prot.parse_request(data))
        except Exception as ex:
            requests.append(ex)

    assert isinstance(requests[3], MSGPACKRPCInvalidRequestError)

    responses = []
    responses.append(requests[0].respond(7))
    responses.append(requests[1].error_respond(MethodNotFoundError("notify_hello")))
    responses.append(requests[2].respond(19))
    responses.append(requests[3].error_respond())
    responses.append(requests[4].error_respond(MethodNotFoundError("foo.get")))
    responses.append(requests[5].respond(["hello", 5]))

    responses = [
        response.serialize() if response else response for response in responses
    ]

    assert responses[0] == b"\x94\x01\x01\xc0\x07"
    assert responses[1] is None
    assert responses[2] == b"\x94\x01\x02\xc0\x13"
    assert responses[3] == b"\x94\x01\xc0\x92\xd1\x80\xa8\xafInvalid request\xc0"
    assert responses[4] == b"\x94\x01\x05\x92\xd1\x80\xa7\xb0Method not found\xc0"
    assert responses[5] == b"\x94\x01\t\xc0\x92\xa5hello\x05"


def test_can_get_custom_error_messages_out(prot):
    request = prot.create_request("foo")

    custom_msg = "join the army, they said. see the world, they said."

    e = Exception(custom_msg)

    response = request.error_respond(e)

    data = response.serialize()
    assert isinstance(data, bytes)

    decoded = msgpack.unpackb(data, raw=False)

    assert decoded[0] == 1
    assert decoded[1] == request.unique_id
    assert isinstance(decoded[2], list)
    assert decoded[2][1] == custom_msg


def test_accepts_empty_but_not_none_args(prot):
    prot.create_request("foo", args=[])


def test_rejects_nonempty_kwargs(prot):
    with pytest.raises(MSGPACKRPCInvalidRequestError):
        prot.create_request("foo", kwargs={"foo": "bar"})


def test_accepts_empty_kwargs(prot):
    prot.create_request("foo", kwargs={})


@pytest.mark.parametrize(
    "data",
    [
        b"\x97\x01",  # complete garbage
        b"\x93\x01\xc0\xa5hello",  # too short
        b"\x94\x00\x01\xc0\xa5hello",  # not a reply (message type is request)
        b"\x94\x01\xc0\xc0\xa5hello",  # missing message ID in response
        b"\x94\x01\x01\xa5hello\xa5hello",  # contains error _and_ result
    ],
)
def test_invalid_replies(prot, data):
    with pytest.raises(InvalidReplyError):
        prot.parse_reply(data)


================================================
FILE: tests/test_protocols.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest

from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
from tinyrpc import RPCErrorResponse


@pytest.fixture(params=['jsonrpc'])
def protocol(request):
    if 'jsonrpc':
        return JSONRPCProtocol()

    raise RuntimeError('Bad protocol name in test case')


def test_protocol_returns_bytes(protocol):
    req = protocol.create_request('foo', ['bar'])

    assert isinstance(req.serialize(), bytes)

def test_procotol_responds_bytes(protocol):
    req = protocol.create_request('foo', ['bar'])
    rep = req.respond(42)
    err_rep = req.error_respond(Exception('foo'))

    assert isinstance(rep.serialize(), bytes)
    assert isinstance(err_rep.serialize(), bytes)


def test_one_way(protocol):
    req = protocol.create_request('foo', None, {'a': 'b'}, True)

    assert req.respond(None) == None


def test_raises_on_args_and_kwargs(protocol):
    with pytest.raises(Exception):
        protocol.create_request('foo', ['arg1', 'arg2'], {'kw_key': 'kw_value'})


def test_supports_no_args(protocol):
        protocol.create_request('foo')


def test_creates_error_response(protocol):
    req = protocol.create_request('foo', ['bar'])
    err_rep = req.error_respond(Exception('foo'))

    assert hasattr(err_rep, 'error')


def test_parses_error_response(protocol):
    req = protocol.create_request('foo', ['bar'])
    err_rep = req.error_respond(Exception('foo'))

    parsed = protocol.parse_reply(err_rep.serialize())

    assert hasattr(parsed, 'error')

def test_default_id_generator():
    from tinyrpc.protocols import default_id_generator
    g = default_id_generator(1)
    assert next(g) == 1
    assert next(g) == 2
    assert next(g) == 3


================================================
FILE: tests/test_rabbitmq_transport.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest
from unittest.mock import patch

from tinyrpc.transports.rabbitmq import RabbitMQServerTransport, RabbitMQClientTransport

FAKE_REQUEST_MSG = b'a fake request message'
FAKE_RESPONSE_MSG = b'a fake response message'
FAKE_MESSAGE_DATA = b'some fake message data'
TEST_QUEUE = 'test_queue'
TEST_ROUTE = 'test_route'

class DummyBlockingConnection:
    class DummyChannel:
        class GenericObject(object):
            pass

        def __init__(self):
            self.properties = self.GenericObject()
            self.properties.reply_to = "reply_to"
            self.properties.correlation_id = "correlation_id"

        def queue_declare(self, *args, **kwargs):
            result = self.GenericObject()
            result.method = self.GenericObject()
            result.method.queue = "queue_id"
            return result

        def basic_consume(self, on_message_callback, *args, **kwargs):
            self.on_message_callback = on_message_callback

        def basic_publish(self, properties, *args, **kwargs):
            self.properties = properties

        def basic_ack(self, *args, **kwargs):
            pass

    def __init__(self, *args, **kwargs):
        pass

    def channel(self):
        self.channel = self.DummyChannel()
        return self.channel

    def process_data_events(self):
        fake_response = FAKE_MESSAGE_DATA
        method = self.DummyChannel.GenericObject()
        method.delivery_tag = "delivery_tag"
        self.channel.on_message_callback(self.channel, method, self.channel.properties, fake_response)

@pytest.fixture
def dummy_blockingconnection():
    return DummyBlockingConnection()

@pytest.fixture
def rabbitmq_server(dummy_blockingconnection):
    return RabbitMQServerTransport(dummy_blockingconnection, TEST_QUEUE)

@pytest.fixture
def rabbitmq_client(dummy_blockingconnection):
    return RabbitMQClientTransport(dummy_blockingconnection, TEST_ROUTE)

@patch('pika.BlockingConnection', DummyBlockingConnection)
def test_can_create_rabbitmq_server():
    RabbitMQServerTransport.create("localhost", TEST_QUEUE)

@patch('pika.BlockingConnection', DummyBlockingConnection)
def test_can_create_rabbitmq_client():
    RabbitMQClientTransport.create("localhost", TEST_ROUTE)

def test_server_can_receive_message(rabbitmq_server):
    context, message = rabbitmq_server.receive_message()
    assert context
    assert message == FAKE_MESSAGE_DATA

def test_server_can_send_reply(rabbitmq_server):
    context, message = rabbitmq_server.receive_message()
    assert context
    assert message == FAKE_MESSAGE_DATA
    rabbitmq_server.send_reply(context, FAKE_RESPONSE_MSG)

def test_client_can_send_message(rabbitmq_client):
    response = rabbitmq_client.send_message(FAKE_REQUEST_MSG, expect_reply=False)
    assert response is None

def test_client_can_send_message_and_get_reply(rabbitmq_client):
    response = rabbitmq_client.send_message(FAKE_REQUEST_MSG, expect_reply=True)
    assert response == FAKE_MESSAGE_DATA


================================================
FILE: tests/test_server.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest
from unittest.mock import Mock, call

from tinyrpc.server import RPCServer
from tinyrpc.transports import ServerTransport
from tinyrpc.protocols import RPCProtocol, RPCResponse
from tinyrpc.dispatch import RPCDispatcher


CONTEXT='sapperdeflap'
RECMSG='out of receive_message'
PARMSG='out of parse_request'
SERMSG='out of serialize'

@pytest.fixture
def transport():
    transport = Mock(ServerTransport)
    transport.receive_message = Mock(return_value=(CONTEXT, RECMSG))
    return transport

@pytest.fixture
def protocol():
    protocol = Mock(RPCProtocol)
    protocol.parse_request = Mock(return_value=PARMSG)
    return protocol

@pytest.fixture()
def response():
    response = Mock(RPCResponse)
    response.serialize = Mock(return_value=SERMSG)
    return response

@pytest.fixture
def dispatcher(response):
    dispatcher = Mock(RPCDispatcher)
    dispatcher.dispatch = Mock(return_value=response)
    return dispatcher

def test_handle_message(transport, protocol, dispatcher):
    server = RPCServer(transport, protocol, dispatcher)
    server.receive_one_message()

    transport.receive_message.assert_called()
    protocol.parse_request.assert_called_with(RECMSG)
    dispatcher.dispatch.assert_called_with(PARMSG, None)
    dispatcher.dispatch().serialize.assert_called()
    transport.send_reply.assert_called_with(CONTEXT, SERMSG)

def test_handle_message_callback(transport, protocol, dispatcher):
    server = RPCServer(transport, protocol, dispatcher)
    server.trace = Mock(return_value=None)
    server.receive_one_message()

    assert server.trace.call_args_list == [call('-->', CONTEXT, RECMSG), call('<--', CONTEXT, SERMSG)]
    server.trace.assert_called()


================================================
FILE: tests/test_transport.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest

import zmq
import zmq.green

from tinyrpc.transports import ServerTransport, ClientTransport
from tinyrpc.transports.zmq import ZmqServerTransport, ZmqClientTransport


class DummyServerTransport(ServerTransport):
    def __init__(self):
        self.messages = []
        self.clients = {}

    def receive_message(self):
        return self.messages.pop()

    def send_reply(self, context, message):
        if not isinstance(message, str):
            raise TypeError('Message must be str().')
        self.clients[context].messages.append(message)


class DummyClientTransport(ClientTransport):
    def __init__(self, server):
        self.server = server
        self.id = id(self)
        self.server.clients[self.id] = self
        self.messages = []

    def send_message(self, message):
        if not isinstance(message, str):
            raise TypeError('Message must be str().')
        self.server.messages.append((self.id, message))

    def receive_reply(self):
        return self.messages.pop()


ZMQ_ENDPOINT = 'inproc://example2'


@pytest.fixture(scope='session')
def zmq_context(request):
    ctx = zmq.Context()
    def fin():
        request.addfinalizer(ctx.destroy())
    return ctx


@pytest.fixture(scope='session')
def zmq_green_context(request):
    ctx = zmq.Context()
    def fin():
        request.addfinalizer(ctx.destroy())
    return ctx


# zmq and zmq.green fail on python3
SERVERS=['dummy']

@pytest.fixture(params=SERVERS)
def transport(request, zmq_context, zmq_green_context):
    if request.param == 'dummy':
        server = DummyServerTransport()
        client = DummyClientTransport(server)
    elif request.param in ('zmq', 'zmq.green'):
        ctx = zmq_context if request.param == 'zmq' else zmq_green_context

        server = ZmqServerTransport.create(ctx, ZMQ_ENDPOINT)
        client = ZmqClientTransport.create(ctx, ZMQ_ENDPOINT)

        def fin():
            server.socket.close()
            client.socket.close()

        request.addfinalizer(fin)
    else:
        raise ValueError('Invalid transport.')
    return (client, server)

SAMPLE_MESSAGES = ['asdf', 'loremipsum' * 1500, '', '\x00', 'b\x00a', '\r\n',
                   '\n', '\u1234'.encode('utf8')]
BAD_MESSAGES = [b'asdf', b'', 1234, 1.2, None, True, False, ('foo',)]


@pytest.fixture(scope='session',
                params=SAMPLE_MESSAGES)
def sample_msg(request):
    return request.param


@pytest.fixture(scope='session',
                params=SAMPLE_MESSAGES)
def sample_msg2(request):
    return request.param


@pytest.fixture(scope='session',
                params=BAD_MESSAGES)
def bad_msg(request):
    return request.param

def test_transport_rejects_bad_values(transport, bad_msg):
    client, server = transport
   
    with pytest.raises(TypeError):
        client.send_message(bad_msg)


# FIXME: these tests need to be rethought, as they no longer work properly with
# the change to the interface of ClientTransport

# FIXME: the actual client needs tests as well


================================================
FILE: tests/test_wsgi_transport.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import pytest


import gevent
import gevent.queue
import gevent.monkey
from gevent.pywsgi import WSGIServer
import requests

from importlib import reload

from tinyrpc.transports.wsgi import WsgiServerTransport
from tinyrpc.transports.http import HttpPostClientTransport

TEST_SERVER_ADDR = ('127.0.0.1', 49294)


@pytest.fixture(scope='module', autouse=True)
def monkey_patches(request):
    # ugh? ugh. ugh. ugh!
    import socket
    gevent.monkey.patch_all(
        socket=True,
        dns=False,
        time=False,
        select=False,
        thread=False,
        os=True,
        httplib=False,
        ssl=False,
        aggressive=False)

    def fin():
        reload(socket)

    request.addfinalizer(fin)


@pytest.fixture()
def wsgi_server(request):
    app = WsgiServerTransport(queue_class=gevent.queue.Queue)

    server = WSGIServer(TEST_SERVER_ADDR, app.handle)

    def fin():
        server.stop()
        server_greenlet.join()

    request.addfinalizer(fin)
    server_greenlet = gevent.spawn(server.serve_forever)
    gevent.sleep(0)  # wait for server to come up

    return (app, 'http://%s:%d' % TEST_SERVER_ADDR)


def test_server_supports_post_only(wsgi_server):
    transport, addr = wsgi_server

    r = requests.get(addr)

    # we expect a "not supported" response
    assert r.status_code == 405

    r = requests.head(addr)

    # we expect a "not supported" response
    assert r.status_code == 405


@pytest.mark.parametrize(('msg',),
    [(b'foo',), (b'',), (b'bar',), (b'1234',), (b'{}',), (b'{',), (b'\x00\r\n',)])
def test_server_receives_messages(wsgi_server, msg):
    transport, addr = wsgi_server

    def consumer():
        context, received_msg = transport.receive_message()
        assert received_msg == msg
        reply = b'reply:' + msg
        transport.send_reply(context, reply)

    gevent.spawn(consumer)

    r = requests.post(addr, data=msg)

    assert r.content == b'reply:' + msg


@pytest.fixture
def sessioned_client():
    session = requests.Session()
    adapter = requests.adapters.HTTPAdapter(pool_maxsize=100)
    session.mount('http://', adapter)
    client = HttpPostClientTransport(
        'http://%s:%d' % TEST_SERVER_ADDR,
        post_method=session.post
    )
    return client


@pytest.fixture
def non_sessioned_client():
    client = HttpPostClientTransport('http://%s:%d' % TEST_SERVER_ADDR)
    return client


@pytest.mark.parametrize(('msg',),
    [(b'foo',), (b'',), (b'bar',), (b'1234',), (b'{}',), (b'{',), (b'\x00\r\n',)])
def test_sessioned_http_sessioned_client(wsgi_server, sessioned_client, msg):
    transport, addr = wsgi_server

    def consumer():
        context, received_msg = transport.receive_message()
        assert received_msg == msg
        reply = b'reply:' + msg
        transport.send_reply(context, reply)

    gevent.spawn(consumer)

    result = sessioned_client.send_message(msg)
    assert result == b'reply:' + msg


@pytest.mark.skip('somehow fails on travis')
def test_exhaust_ports(wsgi_server, non_sessioned_client):
    """
    This raises a
    > ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=49294):
    >    Max retries exceeded with url: / (Caused by
    >    NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection
    >    object at 0x7f6f86246210>: Failed to establish a new connection:
    >    [Errno 99] Cannot assign requested address',))
    """

    transport, addr = wsgi_server

    def consumer():
        context, received_msg = transport.receive_message()
        reply = b'reply:' + received_msg
        transport.send_reply(context, reply)

    def send_and_receive(i):
        try:
            gevent.spawn(consumer)
            msg = b'msg_%s' % i
            result = non_sessioned_client.send_message(msg)
            return result == b'reply:' + msg
        except Exception as e:
            return e

    pool = gevent.pool.Pool(500)

    with pytest.raises(requests.ConnectionError):
        for result in pool.imap_unordered(send_and_receive, range(55000)):
            assert result
            if isinstance(result, Exception):
                raise result


================================================
FILE: tinyrpc/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from .protocols import *
from .exc import *
from .client import *


================================================
FILE: tinyrpc/client.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from collections import namedtuple
from typing import List, Any, Dict, Callable, Optional

from .transports import ClientTransport
from .exc import RPCError
from .protocols import RPCErrorResponse, RPCProtocol, RPCRequest, RPCResponse, RPCBatchResponse

RPCCall = namedtuple('RPCCall', 'method args kwargs')
"""Defines the elements of an RPC call.

RPCCall is used with :py:meth:`~tinyrpc.client.RPCClient.call_all`
to provide the list of requests to be processed. Each request contains the
elements defined in this tuple.
"""

RPCCallTo = namedtuple('RPCCallTo', 'transport method args kwargs')
"""Defines the elements of a RPC call directed to multiple transports.

RPCCallTo is used with :py:meth:`~tinyrpc.client.RPCClient.call_all`
to provide the list of requests to be processed.
"""


class RPCClient(object):
    """Client for making RPC calls to connected servers.

    :param protocol: An :py:class:`~tinyrpc.RPCProtocol` instance.
    :type protocol: RPCProtocol
    :param transport: The data transport mechanism
    :type transport: ClientTransport
    """
    def __init__(
            self, protocol: RPCProtocol, transport: ClientTransport
    ) -> None:
        self.protocol = protocol
        self.transport = transport

    def _send_and_handle_reply(
            self,
            req: RPCRequest,
            one_way: bool = False,
            transport: ClientTransport = None,
            no_exception: bool = False
    ) -> Optional[RPCResponse]:
        tport = self.transport if transport is None else transport

        # sends ...
        reply = tport.send_message(req.serialize(), expect_reply=(not one_way))

        if one_way:
            # ... and be done
            return

        # ... or process the reply
        response = self.protocol.parse_reply(reply)

        if not no_exception and isinstance(response, RPCErrorResponse):
            if hasattr(self.protocol, 'raise_error') and callable(
                    self.protocol.raise_error):
                response = self.protocol.raise_error(response)
            else:
                raise RPCError(
                    'Error calling remote procedure: %s' % response.error
                )

        return response

    def call(
            self, method: str, args: List, kwargs: Dict, one_way: bool = False
    ) -> Any:
        """Calls the requested method and returns the result.

        If an error occurred, an :py:class:`~tinyrpc.exc.RPCError` instance
        is raised.

        :param str method: Name of the method to call.
        :param list args: Arguments to pass to the method.
        :param dict kwargs: Keyword arguments to pass to the method.
        :param bool one_way: Whether or not a reply is desired.
        :return: The result of the call
        :rtype: any
        """
        req = self.protocol.create_request(method, args, kwargs, one_way)

        rep = self._send_and_handle_reply(req, one_way)

        if one_way:
            return

        return rep.result

    def call_all(self, requests: List[RPCCall]) -> List[Any]:
        """Calls the methods in the request in parallel.

        When the :py:mod:`gevent` module is already loaded it is assumed to be
        correctly initialized, including monkey patching if necessary.
        In that case the RPC calls defined by ``requests`` are performed in
        parallel otherwise the methods are called sequentially.

        :param requests: A list of either :py:class:`~tinyrpc.client.RPCCall` or :py:class:`~tinyrpc.client.RPCCallTo`
                         elements.
                         When RPCCallTo is used each element defines a transport.
                         Otherwise the default transport set when RPCClient is
                         created is used.
        :return: A list with replies matching the order of the requests.
        """
        threads = []

        if 'gevent' in sys.modules:
            # assume that gevent is available and functional, make calls in parallel
            import gevent
            for r in requests:
                req = self.protocol.create_request(r.method, r.args, r.kwargs)
                tr = r.transport.transport if len(r) == 4 else None
                threads.append(
                    gevent.spawn(
                        self._send_and_handle_reply, req, False, tr, True
                    )
                )
            gevent.joinall(threads)
            return [t.value for t in threads]
        else:
            # call serially
            for r in requests:
                req = self.protocol.create_request(r.method, r.args, r.kwargs)
                tr = r.transport.transport if len(r) == 4 else None
                threads.append(
                    self._send_and_handle_reply(req, False, tr, True)
                )
            return threads

    def get_proxy(self, prefix: str = '', one_way: bool = False) -> 'RPCProxy':
        """Convenience method for creating a proxy.

        :param prefix: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :param one_way: Passed on to :py:class:`~tinyrpc.client.RPCProxy`.
        :return: :py:class:`~tinyrpc.client.RPCProxy` instance.
        """
        return RPCProxy(self, prefix, one_way)

    def batch_call(self, calls: List[RPCCallTo]) -> RPCBatchResponse:
        """Experimental, use at your own peril."""
        req = self.protocol.create_batch_request()

        for call_args in calls:
            req.append(self.protocol.create_request(*call_args))

        return self._send_and_handle_reply(req)


class RPCProxy(object):
    """Create a new remote proxy object.

    Proxies allow calling of methods through a simpler interface. See the
    documentation for an example.

    :param client: An :py:class:`~tinyrpc.client.RPCClient` instance.
    :param prefix: Prefix to prepend to every method name.
    :param one_way: Passed to every call of
                    :py:func:`~tinyrpc.client.call`.
    """
    def __init__(
            self, client: RPCClient, prefix: str = '', one_way: bool = False
    ) -> None:
        self.client = client
        self.prefix = prefix
        self.one_way = one_way

    def __getattr__(self, name: str) -> Callable:
        """Returns a proxy function that, when called, will call a function
        name ``name`` on the client associated with the proxy.
        """
        proxy_func = lambda *args, **kwargs: self.client.call(
            self.prefix + name, args, kwargs, one_way=self.one_way
        )
        return proxy_func


================================================
FILE: tinyrpc/dispatch/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Dispatcher
==========

Given an RPC request the dispatcher will try to locate a server function that
implements the request and will call that function returning its return value
to the caller.
"""

import inspect
from typing import Callable, Any, Dict, List, Optional, TypeVar, Union, overload

from tinyrpc import RPCRequest, RPCResponse, RPCBatchRequest, RPCBatchResponse
from .. import exc


T = TypeVar("T")


@overload
def public(name: Callable[..., T]) -> Callable[..., T]:
    ...

@overload
def public(name: Optional[str] = None) -> Callable[[Callable[..., T]], Callable[..., T]]:
    ...

def public(name = None):
    # noinspection SpellCheckingInspection
    """Decorator. Mark a method as eligible for registration by a dispatcher.

        The dispatchers :py:func:`~tinyrpc.dispatch.RPCDispatcher.register_instance` function
        will do the actual registration of the marked method.

        The difference with :py:func:`~tinyrpc.dispatch.RPCDispatcher.public` is that this decorator does
        not register with a dispatcher, therefore binding the marked methods with a dispatcher is delayed
        until runtime.
        It also becomes possible to bind with multiple dispatchers.

        :param name: The name to register the function with.

        Example:

        .. code-block:: python

            def class Baz(object):
                def not_exposed(self);
                    # ...

                @public('do_something')
                def visible_method(self, arg1):
                    # ...

            baz = Baz()
            dispatch = RPCDispatcher()
            dispatch.register_instance(baz, 'bazzies`)
            # Baz.visible_method is now callable via RPC as bazzies.do_something('hello')

        ``@public`` is a shortcut for ``@public()``.
        """
    if callable(name):
        f = name
        f._rpc_public_name = f.__name__
        return f

    def _(f):
        f._rpc_public_name = name or f.__name__
        return f

    return _


class RPCDispatcher(object):
    """Stores name-to-method mappings."""
    def __init__(self) -> None:
        self.method_map = {}
        self.subdispatchers = {}

    @overload
    def public(self, name: Callable[..., T]) -> Callable[..., T]:
        ...

    @overload
    def public(self, name: Optional[str] = None) -> Callable[[Callable[..., T]], Callable[..., T]]:
        ...

    def public(self, name = None):
        """Convenient decorator.

        Allows easy registering of functions to this dispatcher. Example:

        .. code-block:: python

            dispatch = RPCDispatcher()

            @dispatch.public
            def foo(bar):
                # ...

            class Baz(object):
                def not_exposed(self):
                    # ...

                @dispatch.public(name='do_something')
                def visible_method(arg1)
                    # ...

        :param str name: Name to register callable with.
        """
        if callable(name):
            self.add_method(name)
            return name

        def _(f):
            self.add_method(f, name=name)
            return f

        return _

    def add_subdispatch(self, dispatcher: 'RPCDispatcher', prefix: str = ''):
        """Adds a subdispatcher, possibly in its own namespace.

        :param dispatcher: The dispatcher to add as a subdispatcher.
        :type dispatcher: RPCDispatcher
        :param str prefix: A prefix. All of the new subdispatchers methods will be
                       available as prefix + their original name.
        """
        self.subdispatchers.setdefault(prefix, []).append(dispatcher)

    def add_method(self, f: Callable, name: str = None) -> None:
        """Add a method to the dispatcher.

        :param f: Callable to be added.
        :type f: callable
        :param str name: Name to register it with. If ``None``, ``f.__name__`` will
                     be used.
        :raises ~tinyrpc.exc.RPCError: When the `name` is already registered.
        """
        assert callable(f), "method argument must be callable"
        # catches a few programming errors that are
        # commonly silently swallowed otherwise
        if not name:
            name = f.__name__

        if name in self.method_map:
            raise exc.RPCError('Name \'{}\' already registered'.format(name))

        self.method_map[name] = f

    def get_method(self, name: str) -> Callable:
        """Retrieve a previously registered method.

        Checks if a method matching ``name`` has been registered.

        If :py:func:`get_method` cannot find a method, every subdispatcher
        with a prefix matching the method name is checked as well.

        :param str name: Function to find.
        :returns: The callable implementing the function.
        :rtype: callable
        :raises: :py:exc:`~tinyrpc.exc.MethodNotFoundError`
        """
        if name in self.method_map:
            return self.method_map[name]

        for prefix, subdispatchers in self.subdispatchers.items():
            if name.startswith(prefix):
                for sd in subdispatchers:
                    try:
                        return sd.get_method(name[len(prefix):])
                    except exc.MethodNotFoundError:
                        pass

        raise exc.MethodNotFoundError(name)

    def register_instance(self, obj: object, prefix: str = '') -> None:
        """Create new subdispatcher and register all public object methods on
        it.

        To be used in conjunction with the :py:func:`public`
        decorator (*not* :py:func:`RPCDispatcher.public`).

        :param obj: The object whose public methods should be made available.
        :type obj: object
        :param str prefix: A prefix for the new subdispatcher.
        """
        dispatch = self.__class__()  # type: 'RPCDispatcher'
        for name, f in inspect.getmembers(
                obj, lambda f: callable(f) and hasattr(f, '_rpc_public_name')):
            dispatch.add_method(f, f._rpc_public_name)

        # add to dispatchers
        self.add_subdispatch(dispatch, prefix)

    def dispatch(
            self,
            request: Union[RPCRequest, RPCBatchRequest],
            caller: Callable = None
    ) -> Union[RPCResponse, RPCBatchResponse]:
        """Fully handle request.

        The dispatch method determines which method to call, calls it and
        returns a response containing a result.

        No exceptions will be thrown, rather, every exception will be turned
        into a response using :py:func:`~tinyrpc.RPCRequest.error_respond`.

        If a method isn't found, a :py:exc:`~tinyrpc.exc.MethodNotFoundError`
        response will be returned. If any error occurs outside of the requested
        method, a :py:exc:`~tinyrpc.exc.ServerError` without any error
        information will be returned.

        If the method is found and called but throws an exception, the
        exception thrown is used as a response instead. This is the only case
        in which information from the exception is possibly propagated back to
        the client, as the exception is part of the requested method.

        :py:class:`~tinyrpc.RPCBatchRequest` instances are handled by handling
        all its children in order and collecting the results, then returning an
        :py:class:`~tinyrpc.RPCBatchResponse` with the results.

        :param request: The request containing the function to be called and its parameters.
        :type request: ~tinyrpc.protocols.RPCRequest or ~tinyrpc.protocols.RPCBatchRequest
        :param caller: An optional callable used to invoke the method.
        :type caller: callable
        :return: The result produced by calling the requested function.
        :rtype: ~tinyrpc.protocols.RPCResponse or ~tinyrpc.protocols.RPCBatchResponse
        :raises ~exc.MethodNotFoundError: If the requested function is not published.
        :raises ~exc.ServerError: If some other error occurred.

        .. Note::

            The :py:exc:`~tinyrpc.exc.ServerError` is raised for any kind of exception not
            raised by the called function itself or :py:exc:`~tinyrpc.exc.MethodNotFoundError`.
        """
        if hasattr(request, 'create_batch_response'):
            results = [self._dispatch(req, caller) for req in request]

            response = request.create_batch_response()
            if response is not None:
                response.extend(results)

            return response
        else:
            return self._dispatch(request, caller)

    def _dispatch(self, request, caller):
        try:
            method = self.get_method(request.method)
        except exc.MethodNotFoundError as e:
            return request.error_respond(e)
        except Exception:
            # unexpected error, do not let client know what happened
            return request.error_respond(exc.ServerError())

        # we found the method
        try:
            if self.validator is not None:
                self.validator(method, request.args, request.kwargs)
            if caller is not None:
                result = caller(method, request.args, request.kwargs)
            else:
                result = method(*request.args, **request.kwargs)
        except Exception as e:
            # an error occurred within the method, return it
            return request.error_respond(e)

        # respond with result
        return request.respond(result)

    @staticmethod
    def validate_parameters(
            method: Callable, args: List[Any], kwargs: Dict[str, Any]
    ) -> None:
        """Verify that `*args` and `**kwargs` are appropriate parameters for `method`.

        .. Warning::

            This function has changed to a static function.
            This will make it easier to replace it with a regular function instead of having to
            subclass only to replace it.

        :param method: A callable.
        :param args: List of positional arguments for `method`
        :param kwargs: Keyword arguments for `method`
        :raises ~tinyrpc.exc.InvalidParamsError:
            Raised when the provided arguments are not acceptable for `method`.
        """
        if hasattr(method, '__code__'):
            try:
                inspect.getcallargs(method, *args, **kwargs)
            except TypeError:
                raise exc.InvalidParamsError()

    validator = validate_parameters
    """Dispatched function parameter validation.

    :type: callable
    
    By default this attribute is set to :py:func:`validate_parameters`.
    The value can be set to any callable implementing the same interface
    as :py:func:`validate_parameters` or to `None` to disable validation
    entirely.
    """


================================================
FILE: tinyrpc/exc.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from abc import ABC


class RPCError(Exception, ABC):
    """Base class for all exceptions thrown by :py:mod:`tinyrpc`."""
    def error_respond(self):
        """Converts the error to an error response object.

        :returns: An error response instance or ``None`` if the protocol decides to drop the error silently.
        :rtype: :py:class:`~tinyrpc.protocols.RPCErrorResponse`
        """
        raise NotImplementedError()


class BadRequestError(RPCError, ABC):
    """Base class for all errors that caused the processing of a request to
    abort before a request object could be instantiated."""


class BadReplyError(RPCError, ABC):
    """Base class for all errors that caused processing of a reply to abort
    before it could be turned in a response object."""


class InvalidRequestError(BadRequestError, ABC):
    """A request made was malformed (i.e. violated the specification) and could
    not be parsed."""


class InvalidReplyError(BadReplyError, ABC):
    """A reply received was malformed (i.e. violated the specification) and
    could not be parsed into a response."""


class UnexpectedIDError (InvalidReplyError, ABC):
    """A reply received contained an invalid unique identifier."""


class MethodNotFoundError(RPCError, ABC):
    """The desired method was not found."""


class InvalidParamsError(RPCError, ABC):
    """The provided parameters do not match those of the desired method."""


class ServerError(RPCError, ABC):
    """An internal error in the RPC system occurred."""

class TimeoutError(Exception):
    """No reply received within the timeout period."""


================================================
FILE: tinyrpc/protocols/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Protocol definition.

Defines the abstract base classes from which a protocol definition must be constructed.
"""
from abc import ABC
from typing import Any, Generator, List, Dict, Union, Optional
import itertools

from tinyrpc import exc


class RPCRequest(object):
    """Defines a generic RPC request."""
    def __init__(self) -> None:
        self.unique_id = None
        """Correlation ID used to match request and response.

        :type: int or str or None

        Protocol specific, may or may not be set.
        This value should only be set by :py:func:`~tinyrpc.protocols.RPCProtocol.create_request`.

        When the protocol permits it this ID allows servers to respond to requests out
        of order and allows clients to relate a response to the corresponding request.

        Only supported if the protocol has its
        :py:attr:`~tinyrpc.protocols.RPCProtocol.supports_out_of_order` set to ``True``.

        Generated by the client, the server copies it from request to corresponding response.
        """

        self.method = None
        """The name of the RPC function to be called.

        :type: str

        The :py:attr:`method` attribute uses the name of the function as it is known by the public.
        The :py:class:`~tinyrpc.dispatch.RPCDispatcher` allows the use of public aliases in the
        ``@public`` decorators.
        These are the names used in the :py:attr:`method` attribute.
        """

        self.args = []
        """The positional arguments of the method call.

        :type: list

        The contents of this list are the positional parameters for the :py:attr:`method` called.
        It is eventually called as ``method(*args)``.
        """

        self.kwargs = {}
        """The keyword arguments of the method call.

        :type: dict

        The contents of this dict are the keyword parameters for the :py:attr:`method` called.
        It is eventually called as ``method(**kwargs)``.
        """
    def error_respond(self, error: Union[Exception, str]
                      ) -> Optional['RPCErrorResponse']:
        """Creates an error response.

        Create a response indicating that the request was parsed correctly,
        but an error has occurred trying to fulfill it.

        This is an abstract method that must be overridden in a derived class.

        :param error: An exception or a string describing the error.
        :type error: Exception or str
        :return: A response or ``None`` to indicate that no error should be sent out.
        :rtype: :py:class:`RPCErrorResponse`
        """
        raise NotImplementedError()

    def respond(self, result: Any) -> Optional['RPCResponse']:
        """Create a response.

        Call this to return the result of a successful method invocation.

        This creates and returns an instance of a protocol-specific subclass of
        :py:class:`~tinyrpc.RPCResponse`.

        This is an abstract method that must be overridden in a derived class.

        :param result: Passed on to new response instance.
        :type result: Any type that can be serialized by the protocol.

        :return: A response or ``None`` to indicate this request does not expect a response.
        :rtype: :py:class:`RPCResponse`
        """
        raise NotImplementedError()

    def serialize(self) -> bytes:
        """Returns a serialization of the request.

        Converts the request into a bytes object that can be passed to and by the transport layer.

        This is an abstract method that must be overridden in a derived class.

        :return: A bytes object to be passed on to a transport.
        :rtype: bytes
        """
        raise NotImplementedError()


class RPCBatchRequest(list):
    """Multiple requests batched together.

    Protocols that support multiple requests in a single message use this to group them together.
    Note that not all protocols may support batch requests.

    Handling a batch requests is done in any order, responses must be gathered
    in a batch response and be in the same order as their respective requests.

    Any item of a batch request is either an :py:class:`RPCRequest` or an
    :py:class:`~tinyrpc.exc.BadRequestError`, which indicates that there has been
    an error in parsing the request.
    """
    def create_batch_response(self) -> Optional['RPCBatchResponse']:
        """Creates a response suitable for responding to this request.

        This is an abstract method that must be overridden in a derived class.

        :return: An :py:class:`RPCBatchResponse` or None if no response is expected.
        :rtype: :py:class:`RPCBatchResponse`
        """
        raise NotImplementedError()

    def serialize(self) -> bytes:
        """Returns a serialization of the request.

        Converts the request into a bytes object that can be passed to and by the transport layer.

        This is an abstract method that must be overridden in a derived class.

        :return: A bytes object to be passed on to a transport.
        :rtype: bytes
        """
        raise NotImplementedError()


class RPCResponse(ABC):
    """Defines a generic RPC response.

    Base class for all responses.

    .. py:attribute:: id

        Correlation ID to match request and response

        :type: str or int

    .. py:attribute:: result

        When present this attribute contains the result of the RPC call.
        Otherwise the :py:attr:`error` attribute must be defined.

        :type: Any type that can be serialized by the protocol.

    .. py:attribute:: error

        When present the :py:attr:`result` attribute must be absent.
        Presence of this attribute indicates an error condition.

        :type: :py:class:`~tinyrpc.exc.RPCError`
    """
    def __init__(self) -> None:
        self.unique_id = None
        """Correlation ID used to match request and response.

        :type: int or str or None
        """
    def serialize(self) -> bytes:
        """Returns a serialization of the response.

        Converts the response into a bytes object that can be passed to and by the transport layer.

        This is an abstract method that must be overridden in a derived class.

        :return: The serialized encoded response object.
        :rtype: bytes
        """
        raise NotImplementedError()


class RPCErrorResponse(RPCResponse, ABC):
    """RPC error response class.

    Base class for all deriving responses.

    .. py:attribute:: error

        This attribute contains the fields ``message`` (str) and
        ``code`` (int) where at least ``message`` is required to contain a value.

        :type: dict
    """
    error = None


class RPCBatchResponse(list):
    """Multiple response from a batch request. See
    :py:class:`RPCBatchRequest` on how to handle.

    Items in a batch response need to be
    :py:class:`RPCResponse` instances or None, meaning no reply should
    generated for the request.
    """
    def serialize(self) -> bytes:
        """Returns a serialization of the batch response.

        Converts the response into a bytes object that can be passed to and by the transport layer.

        This is an abstract method that must be overridden in a derived class.

        :return: A bytes object to be passed on to a transport.
        :rtype: bytes
        """
        raise NotImplementedError()


class RPCProtocol(ABC):
    """Abstract base class for all protocol implementations."""

    supports_out_of_order = False
    """If true, this protocol can receive responses out of order correctly.

    Note that this usually depends on the generation of unique_ids, the
    generation of these may or may not be thread safe, depending on the
    protocol. Ideally, only one instance of RPCProtocol should be used per
    client.

    :type: bool
    """

    raises_errors = True
    """If True, this protocol instance will raise an RPCError exception.

    On receipt of an RPCErrorResponse instance an RPCError exception is raised.
    When this flag is False the RPCErrorResponse object is returned to the caller
    which is then responsible for handling the error.

    :type: bool
    """
    def create_request(
            self,
            method: str,
            args: List[Any] = None,
            kwargs: Dict[str, Any] = None,
            one_way: bool = False
    ) -> 'RPCRequest':
        """Creates a new :py:class:`RPCRequest` object.

        Called by the client when constructing a request.
        It is up to the implementing protocol whether or not ``args``,
        ``kwargs``, one of these, both at once or none of them are supported.

        :param str method: The method name to invoke.
        :param list args: The positional arguments to call the method with.
        :param dict kwargs: The keyword arguments to call the method with.
        :param bool one_way: The request is an update, i.e. it does not expect a reply.
        :return: A new request instance
        :rtype: :py:class:`RPCRequest`
        """
        raise NotImplementedError()

    def parse_request(self, data: bytes) -> 'RPCRequest':
        """De-serializes and validates a request.

        Called by the server to reconstruct the serialized :py:class:`RPCRequest`.

        :param bytes data: The data stream received by the transport layer containing the
            serialized request.
        :return: A reconstructed request.
        :rtype: :py:class:`RPCRequest`
        """
        raise NotImplementedError()

    def parse_reply(self, data: bytes) -> Union['RPCResponse', 'RPCBatchResponse']:
        """De-serializes and validates a response.

        Called by the client to reconstruct the serialized :py:class:`RPCResponse`.

        :param bytes data: The data stream received by the transport layer containing the
            serialized response.
        :return: A reconstructed response.
        :rtype: :py:class:`RPCResponse`
        """
        raise NotImplementedError()

    def raise_error(self, error: 'RPCErrorResponse') -> exc.RPCError:
        """Raises the exception in the client.

        Called by the client to convert the :py:class:`RPCErrorResponse` into an Exception
        and raise or return it depending on the :py:attr:`raises_errors` attribute.

        :param error: The error response received from the server.
        :type error: :py:class:`RPCResponse`
        :rtype: :py:exc:`~tinyrpc.exc.RPCError` when :py:attr:`raises_errors` is False.
        :raises: :py:exc:`~tinyrpc.exc.RPCError` when :py:attr:`raises_errors` is True.
        """
        ex = exc.RPCError(
            'Error calling remote procedure: %s' % error.error['message']
        )
        if self.raises_errors:
            raise ex
        return ex


class RPCBatchProtocol(RPCProtocol, ABC):
    """Abstract base class for all batch protocol implementations."""
    def create_batch_request(
            self, requests: List['RP
Download .txt
gitextract_i25jxbas/

├── .github/
│   └── workflows/
│       └── python-tox.yml
├── .gitignore
├── .readthedocs.yaml
├── .style.yapf
├── .vscode/
│   └── settings.json
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── _static/
│   │   └── uml.xmi
│   ├── client.rst
│   ├── conf.py
│   ├── dispatch.rst
│   ├── examples.rst
│   ├── exceptions.rst
│   ├── index.rst
│   ├── jsonrpc.rst
│   ├── make.bat
│   ├── msgpackrpc.rst
│   ├── protocols.rst
│   ├── server.rst
│   ├── structure.rst
│   └── transports.rst
├── examples/
│   ├── http_client_example.py
│   ├── http_server_example.py
│   ├── zmq_client_example.py
│   └── zmq_server_example.py
├── optional_features.pip
├── pyproject.toml
├── requirements.txt
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── test_client.py
│   ├── test_dispatch.py
│   ├── test_jsonrpc.py
│   ├── test_msgpackrpc.py
│   ├── test_protocols.py
│   ├── test_rabbitmq_transport.py
│   ├── test_server.py
│   ├── test_transport.py
│   └── test_wsgi_transport.py
├── tinyrpc/
│   ├── __init__.py
│   ├── client.py
│   ├── dispatch/
│   │   └── __init__.py
│   ├── exc.py
│   ├── protocols/
│   │   ├── __init__.py
│   │   ├── jsonrpc.py
│   │   └── msgpackrpc.py
│   ├── server/
│   │   ├── __init__.py
│   │   └── gevent.py
│   └── transports/
│       ├── __init__.py
│       ├── callback.py
│       ├── cgi.py
│       ├── http.py
│       ├── rabbitmq.py
│       ├── websocket.py
│       ├── websocketclient.py
│       ├── wsgi.py
│       └── zmq.py
└── tox.ini
Download .txt
SYMBOL INDEX (365 symbols across 29 files)

FILE: examples/http_server_example.py
  function reverse_string (line 25) | def reverse_string(s):

FILE: examples/zmq_server_example.py
  function reverse_string (line 21) | def reverse_string(s):

FILE: setup.py
  function read (line 6) | def read(fname):

FILE: tests/test_client.py
  function method_name (line 14) | def method_name(request):
  function method_args (line 19) | def method_args(request):
  function method_kwargs (line 26) | def method_kwargs(request):
  function prefix (line 31) | def prefix(request):
  function one_way_setting (line 36) | def one_way_setting(request):
  function mock_client (line 41) | def mock_client():
  function mock_protocol (line 46) | def mock_protocol():
  function mock_transport (line 58) | def mock_transport():
  function client (line 63) | def client(mock_protocol, mock_transport):
  function m_proxy (line 68) | def m_proxy(mock_client, prefix, one_way_setting):
  function test_proxy_calls_correct_method (line 72) | def test_proxy_calls_correct_method(
  function test_client_uses_correct_protocol (line 87) | def test_client_uses_correct_protocol(
  function test_client_uses_correct_transport (line 96) | def test_client_uses_correct_transport(
  function test_client_passes_correct_reply (line 104) | def test_client_passes_correct_reply(
  function test_client_raises_error_replies (line 117) | def test_client_raises_error_replies(
  function test_client_raises_indirect_error_replies (line 135) | def test_client_raises_indirect_error_replies(
  function test_client_produces_good_proxy (line 157) | def test_client_produces_good_proxy(client, prefix, one_way_setting):
  function test_client_send_binary_message (line 168) | def test_client_send_binary_message(

FILE: tests/test_dispatch.py
  function dispatch (line 14) | def dispatch():
  function subdispatch (line 19) | def subdispatch():
  function mock_request (line 24) | def mock_request(method='subtract', args=None, kwargs=None):
  function mock_request_fixture (line 33) | def mock_request_fixture():
  function test_function_decorating_without_paramters (line 36) | def test_function_decorating_without_paramters(dispatch):
  function test_function_decorating_with_empty_paramters (line 44) | def test_function_decorating_with_empty_paramters(dispatch):
  function test_function_decorating_with_paramters (line 52) | def test_function_decorating_with_paramters(dispatch):
  function test_subdispatchers (line 64) | def test_subdispatchers(dispatch, subdispatch):
  function test_object_method_marking (line 79) | def test_object_method_marking():
  function test_object_method_register (line 99) | def test_object_method_register(dispatch):
  function test_object_method_register_with_prefix (line 122) | def test_object_method_register_with_prefix(dispatch):
  function test_dispatch_calls_method_and_responds (line 154) | def test_dispatch_calls_method_and_responds(dispatch, mock_request):
  function test_dispatch_handles_in_function_exceptions (line 166) | def test_dispatch_handles_in_function_exceptions(dispatch, mock_request):
  function test_batch_dispatch (line 183) | def test_batch_dispatch(dispatch):
  function test_dispatch_raises_key_error (line 209) | def test_dispatch_raises_key_error(dispatch):
  function invoke_with (line 228) | def invoke_with(request):
  function test_argument_error (line 231) | def test_argument_error(dispatch, invoke_with):
  function test_call_argument_validation (line 258) | def test_call_argument_validation(dispatch):
  function test_bound_method_argument_error (line 268) | def test_bound_method_argument_error(dispatch, invoke_with):
  function test_bound_method_validation (line 299) | def test_bound_method_validation(dispatch):
  function test_unbound_method_argument_error (line 309) | def test_unbound_method_argument_error(dispatch, invoke_with):
  function test_unbound_method_validation (line 339) | def test_unbound_method_validation(dispatch):
  function test_static_method_argument_error (line 348) | def test_static_method_argument_error(dispatch, invoke_with):
  function test_static_method_validation (line 382) | def test_static_method_validation(dispatch):
  function test_class_method_argument_error (line 393) | def test_class_method_argument_error(dispatch, invoke_with):
  function test_class_method_validation (line 427) | def test_class_method_validation(dispatch):

FILE: tests/test_jsonrpc.py
  function _json_equal (line 18) | def _json_equal(a, b):
  function prot (line 26) | def prot():
  function test_parsing_good_request_samples (line 64) | def test_parsing_good_request_samples(prot, data, attrs):
  function test_parsing_invalid_json (line 75) | def test_parsing_invalid_json(prot, invalid_json):
  function test_parsing_invalid_arguments (line 80) | def test_parsing_invalid_arguments(prot):
  function test_good_reply_samples (line 108) | def test_good_reply_samples(prot, data, id, result):
  function test_unsolicited_reply_raises_error (line 122) | def test_unsolicited_reply_raises_error(prot, data):
  function test_proper_construction_of_error_codes (line 140) | def test_proper_construction_of_error_codes(prot, exc, code, message):
  function test_notification_yields_None_response (line 155) | def test_notification_yields_None_response(prot):
  function test_batch_empty_array (line 166) | def test_batch_empty_array(prot):
  function test_batch_invalid_array (line 171) | def test_batch_invalid_array(prot):
  function test_batch_invalid_batch (line 176) | def test_batch_invalid_batch(prot):
  function test_batch_good_examples (line 181) | def test_batch_good_examples(prot):
  function test_unique_ids (line 220) | def test_unique_ids(prot):
  function test_out_of_order (line 227) | def test_out_of_order(prot):
  function test_request_generation (line 234) | def test_request_generation(prot):
  function test_jsonrpc_spec_v2_example1 (line 243) | def test_jsonrpc_spec_v2_example1(prot):
  function test_jsonrpc_spec_v2_example2 (line 278) | def test_jsonrpc_spec_v2_example2(prot):
  function test_jsonrpc_spec_v2_example3 (line 316) | def test_jsonrpc_spec_v2_example3(prot):
  function test_jsonrpc_spec_v2_example4 (line 332) | def test_jsonrpc_spec_v2_example4(prot):
  function test_jsonrpc_spec_v2_example5 (line 350) | def test_jsonrpc_spec_v2_example5(prot):
  function test_jsonrpc_spec_v2_example6 (line 368) | def test_jsonrpc_spec_v2_example6(prot):
  function test_jsonrpc_spec_v2_example6_with_request_id (line 385) | def test_jsonrpc_spec_v2_example6_with_request_id(prot):
  function test_jsonrpc_spec_v2_example7 (line 402) | def test_jsonrpc_spec_v2_example7(prot):
  function test_jsonrpc_spec_v2_example8 (line 420) | def test_jsonrpc_spec_v2_example8(prot):
  function test_jsonrpc_spec_v2_example9 (line 434) | def test_jsonrpc_spec_v2_example9(prot):
  function test_jsonrpc_spec_v2_example10 (line 447) | def test_jsonrpc_spec_v2_example10(prot):
  function test_jsonrpc_spec_v2_example11 (line 467) | def test_jsonrpc_spec_v2_example11(prot):
  function test_jsonrpc_spec_v2_example12 (line 496) | def test_jsonrpc_spec_v2_example12(prot):
  function test_can_get_custom_error_messages_out (line 506) | def test_can_get_custom_error_messages_out(prot):
  function test_accepts_empty_but_not_none_args_kwargs (line 524) | def test_accepts_empty_but_not_none_args_kwargs(prot):
  function test_missing_jsonrpc_version_on_request (line 528) | def test_missing_jsonrpc_version_on_request(prot):
  function test_missing_jsonrpc_version_on_reply (line 532) | def test_missing_jsonrpc_version_on_reply(prot):
  function test_pass_error_data_with_standard_exception (line 536) | def test_pass_error_data_with_standard_exception(prot):
  function test_pass_error_data_with_custom_exception (line 565) | def test_pass_error_data_with_custom_exception(prot):

FILE: tests/test_msgpackrpc.py
  function _msgpack_equal (line 17) | def _msgpack_equal(a, b):
  function prot (line 22) | def prot():
  function test_parsing_good_request_samples (line 47) | def test_parsing_good_request_samples(prot, data, attrs):
  function test_parsing_invalid_msgpack (line 62) | def test_parsing_invalid_msgpack(prot, invalid_msgpack):
  function test_parsing_valid_msgpack_but_invalid_rpc_message (line 77) | def test_parsing_valid_msgpack_but_invalid_rpc_message(prot, data):
  function test_parsing_invalid_arguments (line 90) | def test_parsing_invalid_arguments(prot, invalid_args):
  function test_good_reply_samples (line 104) | def test_good_reply_samples(prot, data, id, result):
  function test_good_error_reply_samples (line 122) | def test_good_error_reply_samples(prot, data, id, code, message):
  function test_proper_construction_of_error_codes (line 140) | def test_proper_construction_of_error_codes(prot, exc, code, message):
  function test_notification_yields_None_response (line 150) | def test_notification_yields_None_response(prot):
  function test_batch_examples (line 177) | def test_batch_examples(prot, data):
  function test_unique_ids (line 182) | def test_unique_ids(prot):
  function test_out_of_order (line 189) | def test_out_of_order(prot):
  function test_request_generation (line 196) | def test_request_generation(prot):
  function test_jsonrpc_spec_v2_example1 (line 210) | def test_jsonrpc_spec_v2_example1(prot):
  function test_jsonrpc_spec_v2_example3 (line 232) | def test_jsonrpc_spec_v2_example3(prot):
  function test_jsonrpc_spec_v2_example4 (line 242) | def test_jsonrpc_spec_v2_example4(prot):
  function test_jsonrpc_spec_v2_example5 (line 255) | def test_jsonrpc_spec_v2_example5(prot):
  function test_jsonrpc_spec_v2_example6 (line 272) | def test_jsonrpc_spec_v2_example6(prot):
  function test_jsonrpc_spec_v2_example8 (line 286) | def test_jsonrpc_spec_v2_example8(prot):
  function test_jsonrpc_spec_v2_example9 (line 300) | def test_jsonrpc_spec_v2_example9(prot):
  function test_jsonrpc_spec_v2_example10 (line 314) | def test_jsonrpc_spec_v2_example10(prot):
  function test_jsonrpc_spec_v2_example11 (line 328) | def test_jsonrpc_spec_v2_example11(prot):
  function test_can_get_custom_error_messages_out (line 368) | def test_can_get_custom_error_messages_out(prot):
  function test_accepts_empty_but_not_none_args (line 388) | def test_accepts_empty_but_not_none_args(prot):
  function test_rejects_nonempty_kwargs (line 392) | def test_rejects_nonempty_kwargs(prot):
  function test_accepts_empty_kwargs (line 397) | def test_accepts_empty_kwargs(prot):
  function test_invalid_replies (line 411) | def test_invalid_replies(prot, data):

FILE: tests/test_protocols.py
  function protocol (line 11) | def protocol(request):
  function test_protocol_returns_bytes (line 18) | def test_protocol_returns_bytes(protocol):
  function test_procotol_responds_bytes (line 23) | def test_procotol_responds_bytes(protocol):
  function test_one_way (line 32) | def test_one_way(protocol):
  function test_raises_on_args_and_kwargs (line 38) | def test_raises_on_args_and_kwargs(protocol):
  function test_supports_no_args (line 43) | def test_supports_no_args(protocol):
  function test_creates_error_response (line 47) | def test_creates_error_response(protocol):
  function test_parses_error_response (line 54) | def test_parses_error_response(protocol):
  function test_default_id_generator (line 62) | def test_default_id_generator():

FILE: tests/test_rabbitmq_transport.py
  class DummyBlockingConnection (line 15) | class DummyBlockingConnection:
    class DummyChannel (line 16) | class DummyChannel:
      class GenericObject (line 17) | class GenericObject(object):
      method __init__ (line 20) | def __init__(self):
      method queue_declare (line 25) | def queue_declare(self, *args, **kwargs):
      method basic_consume (line 31) | def basic_consume(self, on_message_callback, *args, **kwargs):
      method basic_publish (line 34) | def basic_publish(self, properties, *args, **kwargs):
      method basic_ack (line 37) | def basic_ack(self, *args, **kwargs):
    method __init__ (line 40) | def __init__(self, *args, **kwargs):
    method channel (line 43) | def channel(self):
    method process_data_events (line 47) | def process_data_events(self):
  function dummy_blockingconnection (line 54) | def dummy_blockingconnection():
  function rabbitmq_server (line 58) | def rabbitmq_server(dummy_blockingconnection):
  function rabbitmq_client (line 62) | def rabbitmq_client(dummy_blockingconnection):
  function test_can_create_rabbitmq_server (line 66) | def test_can_create_rabbitmq_server():
  function test_can_create_rabbitmq_client (line 70) | def test_can_create_rabbitmq_client():
  function test_server_can_receive_message (line 73) | def test_server_can_receive_message(rabbitmq_server):
  function test_server_can_send_reply (line 78) | def test_server_can_send_reply(rabbitmq_server):
  function test_client_can_send_message (line 84) | def test_client_can_send_message(rabbitmq_client):
  function test_client_can_send_message_and_get_reply (line 88) | def test_client_can_send_message_and_get_reply(rabbitmq_client):

FILE: tests/test_server.py
  function transport (line 19) | def transport():
  function protocol (line 25) | def protocol():
  function response (line 31) | def response():
  function dispatcher (line 37) | def dispatcher(response):
  function test_handle_message (line 42) | def test_handle_message(transport, protocol, dispatcher):
  function test_handle_message_callback (line 52) | def test_handle_message_callback(transport, protocol, dispatcher):

FILE: tests/test_transport.py
  class DummyServerTransport (line 13) | class DummyServerTransport(ServerTransport):
    method __init__ (line 14) | def __init__(self):
    method receive_message (line 18) | def receive_message(self):
    method send_reply (line 21) | def send_reply(self, context, message):
  class DummyClientTransport (line 27) | class DummyClientTransport(ClientTransport):
    method __init__ (line 28) | def __init__(self, server):
    method send_message (line 34) | def send_message(self, message):
    method receive_reply (line 39) | def receive_reply(self):
  function zmq_context (line 47) | def zmq_context(request):
  function zmq_green_context (line 55) | def zmq_green_context(request):
  function transport (line 66) | def transport(request, zmq_context, zmq_green_context):
  function sample_msg (line 92) | def sample_msg(request):
  function sample_msg2 (line 98) | def sample_msg2(request):
  function bad_msg (line 104) | def bad_msg(request):
  function test_transport_rejects_bad_values (line 107) | def test_transport_rejects_bad_values(transport, bad_msg):

FILE: tests/test_wsgi_transport.py
  function monkey_patches (line 22) | def monkey_patches(request):
  function wsgi_server (line 43) | def wsgi_server(request):
  function test_server_supports_post_only (line 59) | def test_server_supports_post_only(wsgi_server):
  function test_server_receives_messages (line 75) | def test_server_receives_messages(wsgi_server, msg):
  function sessioned_client (line 92) | def sessioned_client():
  function non_sessioned_client (line 104) | def non_sessioned_client():
  function test_sessioned_http_sessioned_client (line 111) | def test_sessioned_http_sessioned_client(wsgi_server, sessioned_client, ...
  function test_exhaust_ports (line 127) | def test_exhaust_ports(wsgi_server, non_sessioned_client):

FILE: tinyrpc/client.py
  class RPCClient (line 28) | class RPCClient(object):
    method __init__ (line 36) | def __init__(
    method _send_and_handle_reply (line 42) | def _send_and_handle_reply(
    method call (line 72) | def call(
    method call_all (line 96) | def call_all(self, requests: List[RPCCall]) -> List[Any]:
    method get_proxy (line 136) | def get_proxy(self, prefix: str = '', one_way: bool = False) -> 'RPCPr...
    method batch_call (line 145) | def batch_call(self, calls: List[RPCCallTo]) -> RPCBatchResponse:
  class RPCProxy (line 155) | class RPCProxy(object):
    method __init__ (line 166) | def __init__(
    method __getattr__ (line 173) | def __getattr__(self, name: str) -> Callable:

FILE: tinyrpc/dispatch/__init__.py
  function public (line 23) | def public(name: Callable[..., T]) -> Callable[..., T]:
  function public (line 27) | def public(name: Optional[str] = None) -> Callable[[Callable[..., T]], C...
  function public (line 30) | def public(name = None):
  class RPCDispatcher (line 75) | class RPCDispatcher(object):
    method __init__ (line 77) | def __init__(self) -> None:
    method public (line 82) | def public(self, name: Callable[..., T]) -> Callable[..., T]:
    method public (line 86) | def public(self, name: Optional[str] = None) -> Callable[[Callable[......
    method public (line 89) | def public(self, name = None):
    method add_subdispatch (line 122) | def add_subdispatch(self, dispatcher: 'RPCDispatcher', prefix: str = ''):
    method add_method (line 132) | def add_method(self, f: Callable, name: str = None) -> None:
    method get_method (line 152) | def get_method(self, name: str) -> Callable:
    method register_instance (line 178) | def register_instance(self, obj: object, prefix: str = '') -> None:
    method dispatch (line 197) | def dispatch(
    method _dispatch (line 249) | def _dispatch(self, request, caller):
    method validate_parameters (line 274) | def validate_parameters(

FILE: tinyrpc/exc.py
  class RPCError (line 6) | class RPCError(Exception, ABC):
    method error_respond (line 8) | def error_respond(self):
  class BadRequestError (line 17) | class BadRequestError(RPCError, ABC):
  class BadReplyError (line 22) | class BadReplyError(RPCError, ABC):
  class InvalidRequestError (line 27) | class InvalidRequestError(BadRequestError, ABC):
  class InvalidReplyError (line 32) | class InvalidReplyError(BadReplyError, ABC):
  class UnexpectedIDError (line 37) | class UnexpectedIDError (InvalidReplyError, ABC):
  class MethodNotFoundError (line 41) | class MethodNotFoundError(RPCError, ABC):
  class InvalidParamsError (line 45) | class InvalidParamsError(RPCError, ABC):
  class ServerError (line 49) | class ServerError(RPCError, ABC):
  class TimeoutError (line 52) | class TimeoutError(Exception):

FILE: tinyrpc/protocols/__init__.py
  class RPCRequest (line 14) | class RPCRequest(object):
    method __init__ (line 16) | def __init__(self) -> None:
    method error_respond (line 62) | def error_respond(self, error: Union[Exception, str]
    method respond (line 78) | def respond(self, result: Any) -> Optional['RPCResponse']:
    method serialize (line 96) | def serialize(self) -> bytes:
  class RPCBatchRequest (line 109) | class RPCBatchRequest(list):
    method create_batch_response (line 122) | def create_batch_response(self) -> Optional['RPCBatchResponse']:
    method serialize (line 132) | def serialize(self) -> bytes:
  class RPCResponse (line 145) | class RPCResponse(ABC):
    method __init__ (line 170) | def __init__(self) -> None:
    method serialize (line 176) | def serialize(self) -> bytes:
  class RPCErrorResponse (line 189) | class RPCErrorResponse(RPCResponse, ABC):
  class RPCBatchResponse (line 204) | class RPCBatchResponse(list):
    method serialize (line 212) | def serialize(self) -> bytes:
  class RPCProtocol (line 225) | class RPCProtocol(ABC):
    method create_request (line 248) | def create_request(
    method parse_request (line 270) | def parse_request(self, data: bytes) -> 'RPCRequest':
    method parse_reply (line 282) | def parse_reply(self, data: bytes) -> Union['RPCResponse', 'RPCBatchRe...
    method raise_error (line 294) | def raise_error(self, error: 'RPCErrorResponse') -> exc.RPCError:
  class RPCBatchProtocol (line 313) | class RPCBatchProtocol(RPCProtocol, ABC):
    method create_batch_request (line 315) | def create_batch_request(
  function default_id_generator (line 330) | def default_id_generator(start: int = 1) -> Generator[int, None, None]:

FILE: tinyrpc/protocols/jsonrpc.py
  class FixedErrorMessageMixin (line 35) | class FixedErrorMessageMixin(object):
    method __init__ (line 96) | def __init__(self, *args, **kwargs) -> None:
    method error_respond (line 104) | def error_respond(self) -> 'JSONRPCErrorResponse':
  class JSONRPCParseError (line 120) | class JSONRPCParseError(FixedErrorMessageMixin, InvalidRequestError):
  class JSONRPCInvalidRequestError (line 126) | class JSONRPCInvalidRequestError(FixedErrorMessageMixin, InvalidRequestE...
  class JSONRPCMethodNotFoundError (line 132) | class JSONRPCMethodNotFoundError(FixedErrorMessageMixin, MethodNotFoundE...
  class JSONRPCInvalidParamsError (line 138) | class JSONRPCInvalidParamsError(FixedErrorMessageMixin, InvalidRequestEr...
  class JSONRPCInternalError (line 144) | class JSONRPCInternalError(FixedErrorMessageMixin, InvalidRequestError):
  class JSONRPCServerError (line 150) | class JSONRPCServerError(FixedErrorMessageMixin, InvalidRequestError):
  class JSONRPCError (line 156) | class JSONRPCError(FixedErrorMessageMixin, RPCError):
    method __init__ (line 168) | def __init__(
  class JSONRPCSuccessResponse (line 185) | class JSONRPCSuccessResponse(RPCResponse):
    method _to_dict (line 204) | def _to_dict(self):
    method serialize (line 211) | def serialize(self) -> bytes:
  class JSONRPCErrorResponse (line 222) | class JSONRPCErrorResponse(RPCErrorResponse):
    method _to_dict (line 263) | def _to_dict(self):
    method serialize (line 276) | def serialize(self) -> bytes:
  function _get_code_message_and_data (line 287) | def _get_code_message_and_data(error: Union[Exception, str]
  class JSONRPCRequest (line 323) | class JSONRPCRequest(RPCRequest):
    method __init__ (line 325) | def __init__(self):
    method error_respond (line 377) | def error_respond(self, error: Union[Exception, str]
    method respond (line 403) | def respond(self, result: Any) -> Optional['JSONRPCSuccessResponse']:
    method _to_dict (line 424) | def _to_dict(self):
    method serialize (line 438) | def serialize(self) -> bytes:
  class JSONRPCBatchRequest (line 449) | class JSONRPCBatchRequest(RPCBatchRequest):
    method create_batch_response (line 451) | def create_batch_response(self) -> Optional['JSONRPCBatchResponse']:
    method _expects_response (line 460) | def _expects_response(self):
    method serialize (line 469) | def serialize(self) -> bytes:
  class JSONRPCBatchResponse (line 480) | class JSONRPCBatchResponse(RPCBatchResponse):
    method serialize (line 488) | def serialize(self) -> bytes:
  class JSONRPCProtocol (line 501) | class JSONRPCProtocol(RPCBatchProtocol):
    method __init__ (line 510) | def __init__(
    method _get_unique_id (line 520) | def _get_unique_id(self) -> object:
    method request_factory (line 523) | def request_factory(self) -> 'JSONRPCRequest':
    method create_batch_request (line 532) | def create_batch_request(
    method create_request (line 547) | def create_request(
    method parse_reply (line 588) | def parse_reply(
    method _parse_subreply (line 627) | def _parse_subreply(self, rep):
    method parse_request (line 667) | def parse_request(self, data: bytes
    method _parse_subrequest (line 705) | def _parse_subrequest(self, req):
    method raise_error (line 737) | def raise_error(
    method _caller (line 760) | def _caller(

FILE: tinyrpc/protocols/msgpackrpc.py
  class FixedErrorMessageMixin (line 21) | class FixedErrorMessageMixin(object):
    method __init__ (line 22) | def __init__(self, *args, **kwargs):
    method error_respond (line 29) | def error_respond(self):
  class MSGPACKRPCParseError (line 38) | class MSGPACKRPCParseError(FixedErrorMessageMixin, InvalidRequestError):
  class MSGPACKRPCInvalidRequestError (line 43) | class MSGPACKRPCInvalidRequestError(FixedErrorMessageMixin, InvalidReque...
  class MSGPACKRPCMethodNotFoundError (line 48) | class MSGPACKRPCMethodNotFoundError(FixedErrorMessageMixin, MethodNotFou...
  class MSGPACKRPCInvalidParamsError (line 53) | class MSGPACKRPCInvalidParamsError(FixedErrorMessageMixin, InvalidReques...
  class MSGPACKRPCInternalError (line 58) | class MSGPACKRPCInternalError(FixedErrorMessageMixin, InvalidRequestError):
  class MSGPACKRPCServerError (line 63) | class MSGPACKRPCServerError(FixedErrorMessageMixin, InvalidRequestError):
  class MSGPACKRPCError (line 68) | class MSGPACKRPCError(FixedErrorMessageMixin, RPCError):
    method __init__ (line 78) | def __init__(
  class MSGPACKRPCSuccessResponse (line 89) | class MSGPACKRPCSuccessResponse(RPCResponse):
    method _to_list (line 90) | def _to_list(self):
    method serialize (line 93) | def serialize(self):
  class MSGPACKRPCErrorResponse (line 97) | class MSGPACKRPCErrorResponse(RPCErrorResponse):
    method _to_list (line 98) | def _to_list(self):
    method serialize (line 101) | def serialize(self):
  function _get_code_and_message (line 105) | def _get_code_and_message(error):
  class MSGPACKRPCRequest (line 128) | class MSGPACKRPCRequest(RPCRequest):
    method __init__ (line 131) | def __init__(self):
    method error_respond (line 175) | def error_respond(
    method respond (line 200) | def respond(self, result: Any) -> Optional["MSGPACKRPCSuccessResponse"]:
    method _to_list (line 221) | def _to_list(self):
    method serialize (line 232) | def serialize(self) -> bytes:
  class MSGPACKRPCProtocol (line 236) | class MSGPACKRPCProtocol(RPCProtocol):
    method __init__ (line 239) | def __init__(
    method _get_unique_id (line 248) | def _get_unique_id(self):
    method request_factory (line 251) | def request_factory(self) -> "MSGPACKRPCRequest":
    method create_request (line 260) | def create_request(
    method parse_reply (line 297) | def parse_reply(
    method parse_request (line 349) | def parse_request(self, data: bytes) -> "MSGPACKRPCRequest":
    method _parse_notification (line 391) | def _parse_notification(self, req):
    method _parse_request (line 409) | def _parse_request(self, req):
    method raise_error (line 428) | def raise_error(

FILE: tinyrpc/server/__init__.py
  class RPCServer (line 18) | class RPCServer(object):
    method __init__ (line 60) | def __init__(
    method serve_forever (line 69) | def serve_forever(self) -> None:
    method receive_one_message (line 78) | def receive_one_message(self) -> None:
    method _spawn (line 119) | def _spawn(self, func: Callable, *args, **kwargs):

FILE: tinyrpc/server/gevent.py
  class RPCServerGreenlets (line 14) | class RPCServerGreenlets(RPCServer):
    method _spawn (line 21) | def _spawn(self, func: Callable, *args, **kwargs):
    method start (line 33) | def start(self):

FILE: tinyrpc/transports/__init__.py
  class ServerTransport (line 6) | class ServerTransport(object):
    method receive_message (line 14) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 37) | def send_reply(self, context: Any, reply: bytes) -> None:
  class ClientTransport (line 52) | class ClientTransport(object):
    method send_message (line 59) | def send_message(self, message: bytes, expect_reply: bool = True) -> b...

FILE: tinyrpc/transports/callback.py
  class CallbackServerTransport (line 9) | class CallbackServerTransport(ServerTransport):
    method __init__ (line 29) | def __init__(
    method receive_message (line 36) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 47) | def send_reply(self, context: Any, reply: bytes):

FILE: tinyrpc/transports/cgi.py
  class CGIServerTransport (line 12) | class CGIServerTransport(ServerTransport):
    method receive_message (line 24) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 46) | def send_reply(self, context: Any, reply: bytes) -> None:

FILE: tinyrpc/transports/http.py
  class HttpPostClientTransport (line 10) | class HttpPostClientTransport(ClientTransport):
    method __init__ (line 21) | def __init__(
    method send_message (line 31) | def send_message(self, message: bytes, expect_reply: bool = True):

FILE: tinyrpc/transports/rabbitmq.py
  class RabbitMQServerTransport (line 11) | class RabbitMQServerTransport(ServerTransport):
    method __init__ (line 21) | def __init__(self, connection: pika.BlockingConnection, queue: str, ex...
    method receive_message (line 31) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 36) | def send_reply(self, context: Any, reply: bytes) -> None:
    method on_receive (line 45) | def on_receive(self, ch, method, props, body):
    method create (line 51) | def create(cls, host: str, queue: str, exchange: str = '') -> 'RabbitM...
  class RabbitMQClientTransport (line 65) | class RabbitMQClientTransport(ClientTransport):
    method __init__ (line 75) | def __init__(self, connection: pika.BlockingConnection, routing_key: s...
    method _get_unique_id (line 91) | def _get_unique_id(self) -> int:
    method send_message (line 95) | def send_message(self, message: bytes, expect_reply: bool = True) -> b...
    method on_response (line 112) | def on_response(self, ch, method, props, body):
    method create (line 117) | def create(cls, host: str, routing_key: str, exchange: str = '') -> 'R...

FILE: tinyrpc/transports/websocket.py
  class WSServerTransport (line 11) | class WSServerTransport(ServerTransport):
    method __init__ (line 35) | def __init__(
    method receive_message (line 54) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 57) | def send_reply(self, context: Any, reply: bytes) -> None:
  class WSApplicationFactory (line 61) | class WSApplicationFactory(object):
    method __init__ (line 66) | def __init__(self, messages: queue.Queue, queue_class):
    method __call__ (line 70) | def __call__(self, ws):
    method protocol (line 80) | def protocol(cls):
  class WSApplication (line 84) | class WSApplication(WebSocketApplication):
    method on_message (line 90) | def on_message(self, msg, *args, **kwargs):

FILE: tinyrpc/transports/websocketclient.py
  class HttpWebSocketClientTransport (line 10) | class HttpWebSocketClientTransport(ClientTransport):
    method __init__ (line 22) | def __init__(self, endpoint: str, **kwargs: Dict):
    method send_message (line 27) | def send_message(self, message: bytes, expect_reply: bool = True) -> b...
    method close (line 35) | def close(self) -> None:

FILE: tinyrpc/transports/wsgi.py
  class WsgiServerTransport (line 12) | class WsgiServerTransport(ServerTransport):
    method __init__ (line 36) | def __init__(
    method receive_message (line 47) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 50) | def send_reply(self, context: Any, reply: bytes):
    method handle (line 53) | def handle(self, environ, start_response):

FILE: tinyrpc/transports/zmq.py
  class ZmqServerTransport (line 14) | class ZmqServerTransport(ServerTransport):
    method __init__ (line 21) | def __init__(self, socket: zmq.Socket) -> None:
    method receive_message (line 24) | def receive_message(self) -> Tuple[Any, bytes]:
    method send_reply (line 28) | def send_reply(self, context: Any, reply: bytes) -> None:
    method create (line 32) | def create(cls, zmq_context: zmq.Context, endpoint: str) -> 'ZmqServer...
  class ZmqClientTransport (line 49) | class ZmqClientTransport(ClientTransport):
    method __init__ (line 60) | def __init__(self, socket: zmq.Socket, timeout: float = None) -> None:
    method send_message (line 64) | def send_message(self, message: bytes, expect_reply: bool = True) -> b...
    method create (line 83) | def create(cls, zmq_context: zmq.Context, endpoint: str, timeout: floa...
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (285K chars).
[
  {
    "path": ".github/workflows/python-tox.yml",
    "chars": 705,
    "preview": "# 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  "
  },
  {
    "path": ".gitignore",
    "chars": 109,
    "preview": ".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",
    "chars": 576,
    "preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
  },
  {
    "path": ".style.yapf",
    "chars": 7349,
    "preview": "[style]\n# Align closing bracket with visual indentation.\nalign_closing_bracket_with_visual_indent=True\n\n# Allow dictiona"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 238,
    "preview": "{\n    \"restructuredtext.confPath\": \"${workspaceFolder}/docs\"\n    \"files.watcherExclude\": {\n        \"**/.git/objects/**\":"
  },
  {
    "path": "LICENSE",
    "chars": 1058,
    "preview": "Copyright (c) 2013 Marc Brinkmann\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this "
  },
  {
    "path": "MANIFEST.in",
    "chars": 35,
    "preview": "include README.rst\ninclude LICENSE\n"
  },
  {
    "path": "README.rst",
    "chars": 5575,
    "preview": "tinyrpc: A small and modular way of handling web-related RPC\n==========================================================="
  },
  {
    "path": "docs/Makefile",
    "chars": 5568,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/_static/uml.xmi",
    "chars": 26388,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<XMI verified=\"false\" xmi.version=\"1.2\" timestamp=\"2019-01-19T17:13:06\" xmlns:UML"
  },
  {
    "path": "docs/client.rst",
    "chars": 661,
    "preview": "RPC Client\n==========\n\n:py:class:`~tinyrpc.client.RPCClient` instances are high-level handlers for\nmaking remote procedu"
  },
  {
    "path": "docs/conf.py",
    "chars": 8536,
    "preview": "# -*- coding: utf-8 -*-\n#\n# tinyrpc documentation build configuration file, created by\n# sphinx-quickstart on Wed Jan 23"
  },
  {
    "path": "docs/dispatch.rst",
    "chars": 4105,
    "preview": "Dispatching\n===========\n\nDispatching in ``tinyrpc`` is very similiar to url-routing in web frameworks.\nFunctions are reg"
  },
  {
    "path": "docs/examples.rst",
    "chars": 3202,
    "preview": "Quickstart examples\n===================\n\nThe source contains all of these examples in a working fashion in the examples\n"
  },
  {
    "path": "docs/exceptions.rst",
    "chars": 3679,
    "preview": "The Exceptions hierarchy\n========================\n\nAll exceptions are rooted in the :py:class:`Exception` class.\nThe :py"
  },
  {
    "path": "docs/index.rst",
    "chars": 4227,
    "preview": "tinyrpc: A modular RPC library\n==============================\n\n``tinyrpc`` is a framework for constructing remote proced"
  },
  {
    "path": "docs/jsonrpc.rst",
    "chars": 7530,
    "preview": "The JSON-RPC protocol\n=====================\n\nExample\n-------\n\nThe following example shows how to use the\n:py:class:`~tin"
  },
  {
    "path": "docs/make.bat",
    "chars": 5098,
    "preview": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUI"
  },
  {
    "path": "docs/msgpackrpc.rst",
    "chars": 5568,
    "preview": "The MSGPACK-RPC protocol\n========================\n\nExample\n-------\n\nThe following example shows how to use the\n:py:class"
  },
  {
    "path": "docs/protocols.rst",
    "chars": 4009,
    "preview": "The protocol layer\n==================\n\nInterface definition\n--------------------\n\nAll protocols are implemented by deriv"
  },
  {
    "path": "docs/server.rst",
    "chars": 597,
    "preview": "Server implementations\n======================\n\nLike :doc:`client`, servers are top-level instances that most user code s"
  },
  {
    "path": "docs/structure.rst",
    "chars": 2255,
    "preview": "Structure of tinyrpc\n====================\n\nArchitecture\n------------\n\n``tinyrpc`` is constructed around the :py:class:`~"
  },
  {
    "path": "docs/transports.rst",
    "chars": 3082,
    "preview": "Transports\n==========\n\nTransports are somewhat low level interface concerned with transporting\nmessages across through d"
  },
  {
    "path": "examples/http_client_example.py",
    "chars": 473,
    "preview": "#!/usr/bin/env python3\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.http import HttpPo"
  },
  {
    "path": "examples/http_server_example.py",
    "chars": 761,
    "preview": "#!/usr/bin/env python3\n\nimport gevent\nimport gevent.wsgi\nimport gevent.queue\nfrom tinyrpc.protocols.jsonrpc import JSONR"
  },
  {
    "path": "examples/zmq_client_example.py",
    "chars": 505,
    "preview": "#!/usr/bin/env python3\n\nimport zmq\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.zmq im"
  },
  {
    "path": "examples/zmq_server_example.py",
    "chars": 511,
    "preview": "#!/usr/bin/env python3\n\nimport zmq\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom tinyrpc.transports.zmq im"
  },
  {
    "path": "optional_features.pip",
    "chars": 92,
    "preview": "requests\nwerkzeug\ngevent\npyzmq\nwebsocket-client\ngevent-websocket\npyzmq\njsonext\nmsgpack\npika\n"
  },
  {
    "path": "pyproject.toml",
    "chars": 91,
    "preview": "[build-system]\nrequires = [\"setuptools\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n"
  },
  {
    "path": "requirements.txt",
    "chars": 148,
    "preview": "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\nreque"
  },
  {
    "path": "setup.py",
    "chars": 1080,
    "preview": "import os\n\nfrom setuptools import setup, find_packages\n\n\ndef read(fname):\n    return open(os.path.join(os.path.dirname(_"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_client.py",
    "chars": 5046,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import Mock\n\nfrom tinyrpc.exc import RPC"
  },
  {
    "path": "tests/test_dispatch.py",
    "chars": 11423,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom unittest.mock import Mock\nimport pytest\nimport inspect\n\nfrom tinyrpc"
  },
  {
    "path": "tests/test_jsonrpc.py",
    "chars": 17898,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport json\n\nimport pytest\n\nfrom tinyrpc import MethodNotFoundError, Inva"
  },
  {
    "path": "tests/test_msgpackrpc.py",
    "chars": 12111,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport msgpack\nimport pytest\n\nfrom tinyrpc import InvalidReplyError, Meth"
  },
  {
    "path": "tests/test_protocols.py",
    "chars": 1721,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\nfrom tinyrpc.protocols.jsonrpc import JSONRPCProtocol\nfrom"
  },
  {
    "path": "tests/test_rabbitmq_transport.py",
    "chars": 3039,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import patch\n\nfrom tinyrpc.transports.ra"
  },
  {
    "path": "tests/test_server.py",
    "chars": 1748,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\nfrom unittest.mock import Mock, call\n\nfrom tinyrpc.server i"
  },
  {
    "path": "tests/test_transport.py",
    "chars": 3071,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\nimport zmq\nimport zmq.green\n\nfrom tinyrpc.transports impor"
  },
  {
    "path": "tests/test_wsgi_transport.py",
    "chars": 4189,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport pytest\n\n\nimport gevent\nimport gevent.queue\nimport gevent.monkey\nfr"
  },
  {
    "path": "tinyrpc/__init__.py",
    "chars": 113,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom .protocols import *\nfrom .exc import *\nfrom .client import *\n"
  },
  {
    "path": "tinyrpc/client.py",
    "chars": 6592,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport sys\nfrom collections import namedtuple\nfrom typing import List, An"
  },
  {
    "path": "tinyrpc/dispatch/__init__.py",
    "chars": 10796,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\nDispatcher\n==========\n\nGiven an RPC request the dispatcher will try to"
  },
  {
    "path": "tinyrpc/exc.py",
    "chars": 1649,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom abc import ABC\n\n\nclass RPCError(Exception, ABC):\n    \"\"\"Base class fo"
  },
  {
    "path": "tinyrpc/protocols/__init__.py",
    "chars": 11790,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Protocol definition.\n\nDefines the abstract base classes from which a pr"
  },
  {
    "path": "tinyrpc/protocols/jsonrpc.py",
    "chars": 26224,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"JSON RPC 2.0 Protocol implementation.\n\nThis module can use the jsonext_"
  },
  {
    "path": "tinyrpc/protocols/msgpackrpc.py",
    "chars": 15220,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom . import default_id_generator\nfrom .. import (\n    RPCError,\n    RPC"
  },
  {
    "path": "tinyrpc/server/__init__.py",
    "chars": 4900,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Server definition.\n\nDefines and implements a single-threaded, single-pr"
  },
  {
    "path": "tinyrpc/server/gevent.py",
    "chars": 1109,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"Server definition.\n\nDefines and implements a single-threaded, single-pr"
  },
  {
    "path": "tinyrpc/transports/__init__.py",
    "chars": 3506,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Any, Tuple\n\n\nclass ServerTransport(object):\n    \"\"\"Abst"
  },
  {
    "path": "tinyrpc/transports/callback.py",
    "chars": 2001,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom typing import Callable, Tuple, Any\n\nfrom . import ServerTransport\n\n\n"
  },
  {
    "path": "tinyrpc/transports/cgi.py",
    "chars": 2388,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport os\nimport sys\nimport urllib.parse as urlparse\nfrom typing import A"
  },
  {
    "path": "tinyrpc/transports/http.py",
    "chars": 1516,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Callable, Dict\n\nimport requests\n\nfrom . import ClientTr"
  },
  {
    "path": "tinyrpc/transports/rabbitmq.py",
    "chars": 4851,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom typing import Tuple, Any\n\nimport pika\n\nfrom . import ServerTransport"
  },
  {
    "path": "tinyrpc/transports/websocket.py",
    "chars": 3196,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport queue\nfrom typing import Callable, Tuple, Any\n\nfrom . import Serve"
  },
  {
    "path": "tinyrpc/transports/websocketclient.py",
    "chars": 1453,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom typing import Dict\n\nimport geventwebsocket as websocket\n\nfrom . impor"
  },
  {
    "path": "tinyrpc/transports/wsgi.py",
    "chars": 3402,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport queue as Queue\nfrom typing import Tuple, Any\n\nfrom werkzeug.wrappe"
  },
  {
    "path": "tinyrpc/transports/zmq.py",
    "chars": 3466,
    "preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nfrom __future__ import absolute_import  # needed for zmq import\n\nfrom typ"
  },
  {
    "path": "tox.ini",
    "chars": 205,
    "preview": "[tox]\n#envlist = py38\nenvlist = py34, py35, py36, py37, py38, py39, py310, py311\n\n[testenv]\ndeps = -rrequirements.txt\nco"
  }
]

About this extraction

This page contains the full source code of the mbr/tinyrpc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (262.1 KB), approximately 67.7k tokens, and a symbol index with 365 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!