Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-tinyrpc for openSUSE:Factory checked in at 2022-10-01 17:44:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tinyrpc (Old) and /work/SRC/openSUSE:Factory/.python-tinyrpc.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tinyrpc" Sat Oct 1 17:44:11 2022 rev:10 rq:1007454 version:1.1.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tinyrpc/python-tinyrpc.changes 2020-03-11 18:54:55.331663502 +0100 +++ /work/SRC/openSUSE:Factory/.python-tinyrpc.new.2275/python-tinyrpc.changes 2022-10-01 17:44:36.873829230 +0200 @@ -1,0 +2,8 @@ +Sat Oct 1 13:51:06 UTC 2022 - Dirk M??ller <[email protected]> + +- update to 1.1.4: + * fix undesired sharing of ID generators between protocol instances + * add timeout to ZmqClientTransport + * changed zmq client timeout to seconds + +------------------------------------------------------------------- Old: ---- 1.0.4.tar.gz New: ---- 1.1.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tinyrpc.spec ++++++ --- /var/tmp/diff_new_pack.hliV8u/_old 2022-10-01 17:44:37.293829993 +0200 +++ /var/tmp/diff_new_pack.hliV8u/_new 2022-10-01 17:44:37.301830008 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-tinyrpc # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-tinyrpc -Version: 1.0.4 +Version: 1.1.4 Release: 0 Summary: A modular transport and protocol neutral RPC library License: MIT @@ -28,6 +28,7 @@ BuildRequires: %{python_module Werkzeug} BuildRequires: %{python_module gevent} BuildRequires: %{python_module msgpack} +BuildRequires: %{python_module pika >= 1.2.0} BuildRequires: %{python_module pytest} BuildRequires: %{python_module pyzmq} BuildRequires: %{python_module requests} ++++++ 1.0.4.tar.gz -> 1.1.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/.github/workflows/python-tox.yml new/tinyrpc-1.1.4/.github/workflows/python-tox.yml --- old/tinyrpc-1.0.4/.github/workflows/python-tox.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/tinyrpc-1.1.4/.github/workflows/python-tox.yml 2022-01-30 14:56:51.000000000 +0100 @@ -0,0 +1,32 @@ +# 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.6, 3.7, 3.8, 3.9] + + 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/.travis.yml new/tinyrpc-1.1.4/.travis.yml --- old/tinyrpc-1.0.4/.travis.yml 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/.travis.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,17 +0,0 @@ -language: python - -python: - - 3.6 - -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true - -install: - - pip install tox-travis - -script: - - tox - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/.vscode/settings.json new/tinyrpc-1.1.4/.vscode/settings.json --- old/tinyrpc-1.0.4/.vscode/settings.json 1970-01-01 01:00:00.000000000 +0100 +++ new/tinyrpc-1.1.4/.vscode/settings.json 2022-01-30 14:56:51.000000000 +0100 @@ -0,0 +1,9 @@ +{ + "restructuredtext.confPath": "${workspaceFolder}/docs" + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/*/**": true, + ".tox/**": true + } +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/README.rst new/tinyrpc-1.1.4/README.rst --- old/tinyrpc-1.0.4/README.rst 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/README.rst 2022-01-30 14:56:51.000000000 +0100 @@ -3,8 +3,8 @@ .. image:: https://readthedocs.org/projects/tinyrpc/badge/?version=latest :target: https://tinyrpc.readthedocs.io/en/latest -.. image:: https://travis-ci.org/mbr/tinyrpc.svg?branch=master - :target: https://travis-ci.org/mbr/tinyrpc +.. 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/ @@ -128,6 +128,8 @@ +------------+-------------------------------------------------------+ | jsonext | optional in JSONRPCProtocol | +------------+-------------------------------------------------------+ +| rabbitmq | RabbitMQServerTransport, RabbitMQClientTransport | ++------------+-------------------------------------------------------+ | websocket | WSServerTransport | +------------+-------------------------------------------------------+ | wsgi | WsgiServerTransport | @@ -135,6 +137,11 @@ | zmq | ZmqServerTransport, ZmqClientTransport | +------------+-------------------------------------------------------+ +New in version 1.1.0 +-------------------- + +Tinyrpc supports RabbitMQ has transport medium. + New in version 1.0.4 -------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/docs/conf.py new/tinyrpc-1.1.4/docs/conf.py --- old/tinyrpc-1.0.4/docs/conf.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/docs/conf.py 2022-01-30 14:56:51.000000000 +0100 @@ -42,16 +42,16 @@ # General information about the project. project = u'tinyrpc' -copyright = u'2013 - 2019, Marc Brinkmann, Leo Noordergraaf' +copyright = u'2013 - 2021, 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.0' +version = '1.1' # The full version, including alpha/beta/rc tags. -release = '1.0.4' +release = '1.1.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/docs/index.rst new/tinyrpc-1.1.4/docs/index.rst --- old/tinyrpc-1.0.4/docs/index.rst 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/docs/index.rst 2022-01-30 14:56:51.000000000 +0100 @@ -63,6 +63,8 @@ +------------+-------------------------------------------------------+ | msgpack | required by MSGPACKRPCProtocol | +------------+-------------------------------------------------------+ +| rabbitmq | RabbitMQServerTransport, RabbitMQClientTransport | ++------------+-------------------------------------------------------+ | websocket | WSServerTransport, HttpWebSocketClientTransport | +------------+-------------------------------------------------------+ | wsgi | WsgiServerTransport | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/docs/protocols.rst new/tinyrpc-1.1.4/docs/protocols.rst --- old/tinyrpc-1.0.4/docs/protocols.rst 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/docs/protocols.rst 2022-01-30 14:56:51.000000000 +0100 @@ -88,6 +88,38 @@ :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 ------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/docs/transports.rst new/tinyrpc-1.1.4/docs/transports.rst --- old/tinyrpc-1.0.4/docs/transports.rst 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/docs/transports.rst 2022-01-30 14:56:51.000000000 +0100 @@ -94,6 +94,21 @@ :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 ~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/optional_features.pip new/tinyrpc-1.1.4/optional_features.pip --- old/tinyrpc-1.0.4/optional_features.pip 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/optional_features.pip 2022-01-30 14:56:51.000000000 +0100 @@ -7,4 +7,4 @@ pyzmq jsonext msgpack - +pika diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/requirements.txt new/tinyrpc-1.1.4/requirements.txt --- old/tinyrpc-1.0.4/requirements.txt 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/requirements.txt 2022-01-30 14:56:51.000000000 +0100 @@ -1,21 +1,11 @@ -atomicwrites==1.1.5 -attrs==18.1.0 -coverage==4.5.1 -gevent==1.3.4 +gevent==21.1.2 gevent-websocket==0.10.1 -greenlet==0.4.13 -more-itertools==4.2.0 -msgpack==0.6.2 -packaging==17.1 -pluggy==0.6.0 -py==1.5.4 -pyparsing==2.2.0 -pytest==3.6.3 -pytest-cov==2.5.1 -pyzmq==17.1.0 -requests>=2.20.0 -six==1.11.0 -tox==3.1.2 -virtualenv==16.0.0 -Werkzeug==0.15.5 -zmq==0.0.0 +msgpack==1.0.2 +pika==1.2.0 +pytest==6.2.4 +pytest-cov==2.11.1 +pyzmq==22.0.3 +requests==2.25.1 +six==1.16.0 +Werkzeug==2.0.0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/setup.py new/tinyrpc-1.1.4/setup.py --- old/tinyrpc-1.0.4/setup.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/setup.py 2022-01-30 14:56:51.000000000 +0100 @@ -9,7 +9,7 @@ setup( name='tinyrpc', - version='1.0.4', + version='1.1.4', description='A small, modular, transport and protocol neutral RPC ' 'library that, among other things, supports JSON-RPC and zmq.', long_description=read('README.rst'), @@ -30,6 +30,7 @@ 'websocket': ['gevent-websocket'], 'wsgi': ['werkzeug'], 'zmq': ['pyzmq'], - 'jsonext': ['jsonext'] + 'jsonext': ['jsonext'], + 'rabbitmq': ['pika'] } ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_client.py new/tinyrpc-1.1.4/tests/test_client.py --- old/tinyrpc-1.0.4/tests/test_client.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_client.py 2022-01-30 14:56:51.000000000 +0100 @@ -129,7 +129,7 @@ assert isinstance(args[0], RPCErrorResponse) assert args[0].error == 'foo' print(mock_protocol.mock_calls) -# mock_protocol.raise_error.assert_called_with('foo') + mock_protocol.raise_error.assert_called_with(error_response) def test_client_raises_indirect_error_replies( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_dispatch.py new/tinyrpc-1.1.4/tests/test_dispatch.py --- old/tinyrpc-1.0.4/tests/test_dispatch.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_dispatch.py 2022-01-30 14:56:51.000000000 +0100 @@ -1,8 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import _compat -from six.moves.mock import Mock, MagicMock +from unittest.mock import Mock import pytest import inspect @@ -21,7 +20,7 @@ return RPCDispatcher() [email protected]() + def mock_request(method='subtract', args=None, kwargs=None): mock_request = Mock(RPCRequest) mock_request.method = method @@ -30,6 +29,9 @@ return mock_request [email protected](name="mock_request") +def mock_request_fixture(): + return mock_request() def test_function_decorating_without_paramters(dispatch): @dispatch.public diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_jsonrpc.py new/tinyrpc-1.1.4/tests/test_jsonrpc.py --- old/tinyrpc-1.0.4/tests/test_jsonrpc.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_jsonrpc.py 2022-01-30 14:56:51.000000000 +0100 @@ -107,12 +107,25 @@ ), ]) 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 [email protected](('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'), @@ -230,7 +243,8 @@ def test_jsonrpc_spec_v2_example1(prot): # reset id counter - prot._id_counter = 0 + from tinyrpc.protocols import default_id_generator + prot._id_generator = default_id_generator(1) request = prot.create_request('subtract', [42, 23]) @@ -264,7 +278,8 @@ def test_jsonrpc_spec_v2_example2(prot): # reset id counter - prot._id_counter = 2 + from tinyrpc.protocols import default_id_generator + prot._id_generator = default_id_generator(3) request = prot.create_request('subtract', kwargs={'subtrahend': 23, 'minuend': 42}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_msgpackrpc.py new/tinyrpc-1.1.4/tests/test_msgpackrpc.py --- old/tinyrpc-1.0.4/tests/test_msgpackrpc.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_msgpackrpc.py 2022-01-30 14:56:51.000000000 +0100 @@ -210,7 +210,8 @@ def test_jsonrpc_spec_v2_example1(prot): # reset id counter - prot._id_counter = 0 + from tinyrpc.protocols import default_id_generator + prot._id_generator = default_id_generator(1) request = prot.create_request("subtract", [42, 23]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_protocols.py new/tinyrpc-1.1.4/tests/test_protocols.py --- old/tinyrpc-1.0.4/tests/test_protocols.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_protocols.py 2022-01-30 14:56:51.000000000 +0100 @@ -58,3 +58,10 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_rabbitmq_transport.py new/tinyrpc-1.1.4/tests/test_rabbitmq_transport.py --- old/tinyrpc-1.0.4/tests/test_rabbitmq_transport.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_rabbitmq_transport.py 2022-01-30 14:56:51.000000000 +0100 @@ -0,0 +1,90 @@ +#!/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) + [email protected] +def dummy_blockingconnection(): + return DummyBlockingConnection() + [email protected] +def rabbitmq_server(dummy_blockingconnection): + return RabbitMQServerTransport(dummy_blockingconnection, TEST_QUEUE) + [email protected] +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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tests/test_server.py new/tinyrpc-1.1.4/tests/test_server.py --- old/tinyrpc-1.0.4/tests/test_server.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tests/test_server.py 2022-01-30 14:56:51.000000000 +0100 @@ -2,8 +2,7 @@ # -*- coding: utf-8 -*- import pytest -import _compat -from six.moves.mock import Mock, call +from unittest.mock import Mock, call from tinyrpc.server import RPCServer from tinyrpc.transports import ServerTransport diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/client.py new/tinyrpc-1.1.4/tinyrpc/client.py --- old/tinyrpc-1.0.4/tinyrpc/client.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/client.py 2022-01-30 14:56:51.000000000 +0100 @@ -49,7 +49,7 @@ tport = self.transport if transport is None else transport # sends ... - reply = tport.send_message(req.serialize()) + reply = tport.send_message(req.serialize(), expect_reply=(not one_way)) if one_way: # ... and be done diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/dispatch/__init__.py new/tinyrpc-1.1.4/tinyrpc/dispatch/__init__.py --- old/tinyrpc-1.0.4/tinyrpc/dispatch/__init__.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/dispatch/__init__.py 2022-01-30 14:56:51.000000000 +0100 @@ -10,13 +10,24 @@ """ import inspect -from typing import Callable, Any, Dict, List, Union +from typing import Callable, Any, Dict, List, Optional, TypeVar, Union, overload from tinyrpc import RPCRequest, RPCResponse, RPCBatchRequest, RPCBatchResponse from .. import exc -def public(name: str = None) -> Callable: +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. @@ -67,7 +78,15 @@ self.method_map = {} self.subdispatchers = {} - def public(self, name: str = None) -> Callable: + @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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/exc.py new/tinyrpc-1.1.4/tinyrpc/exc.py --- old/tinyrpc-1.0.4/tinyrpc/exc.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/exc.py 2022-01-30 14:56:51.000000000 +0100 @@ -34,6 +34,10 @@ 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.""" @@ -44,3 +48,6 @@ class ServerError(RPCError, ABC): """An internal error in the RPC system occurred.""" + +class TimeoutError(Exception): + """No reply received within the timeout period.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/protocols/__init__.py new/tinyrpc-1.1.4/tinyrpc/protocols/__init__.py --- old/tinyrpc-1.0.4/tinyrpc/protocols/__init__.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/protocols/__init__.py 2022-01-30 14:56:51.000000000 +0100 @@ -5,7 +5,8 @@ Defines the abstract base classes from which a protocol definition must be constructed. """ from abc import ABC -from typing import Any, List, Dict, Union, Optional +from typing import Any, Generator, List, Dict, Union, Optional +import itertools from tinyrpc import exc @@ -324,3 +325,16 @@ :rtype: :py:class:`RPCBatchRequest` """ raise NotImplementedError() + + +def default_id_generator(start: int = 1) -> Generator[int, None, None]: + """Generates sequential integers from `start`. + + e.g. 1, 2, 3, .. 9, 10, 11, ... + + :param start: The first value to start with.` + :type start: int + :return: A generator that yields a sequence of integers. + :rtype: :py:class:`Generator[int, None, None]` + """ + return itertools.count(start) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/protocols/jsonrpc.py new/tinyrpc-1.1.4/tinyrpc/protocols/jsonrpc.py --- old/tinyrpc-1.0.4/tinyrpc/protocols/jsonrpc.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/protocols/jsonrpc.py 2022-01-30 14:56:51.000000000 +0100 @@ -12,12 +12,14 @@ import json import sys -from typing import Dict, Any, Union, Optional, List, Tuple, Callable +from tinyrpc.exc import UnexpectedIDError +from typing import Dict, Any, Union, Optional, List, Tuple, Callable, Generator +from . import default_id_generator from .. import ( RPCBatchProtocol, RPCRequest, RPCResponse, RPCErrorResponse, InvalidRequestError, MethodNotFoundError, InvalidReplyError, RPCError, - RPCBatchRequest, RPCBatchResponse, InvalidParamsError + RPCBatchRequest, RPCBatchResponse, InvalidParamsError, ) if 'jsonext' in sys.modules: @@ -168,11 +170,12 @@ ) -> None: if isinstance(error, JSONRPCErrorResponse): super(JSONRPCError, self).__init__(error.error) + self.message = error.error self._jsonrpc_error_code = error._jsonrpc_error_code if hasattr(error, 'data'): self.data = error.data else: - super(JSONRPCError, self).__init__() + super(JSONRPCError, self).__init__(error.message) self.message = error['message'] self._jsonrpc_error_code = error['code'] if 'data' in error: @@ -244,7 +247,7 @@ :type: Any type that can be serialized by the protocol. - .. py:attribute:: _json_rpc_error + .. py:attribute:: _jsonrpc_error_code The numeric error code. @@ -504,13 +507,18 @@ _ALLOWED_REPLY_KEYS = sorted(['id', 'jsonrpc', 'error', 'result']) _ALLOWED_REQUEST_KEYS = sorted(['id', 'jsonrpc', 'method', 'params']) - def __init__(self, *args, **kwargs) -> None: + def __init__( + self, + id_generator: Optional[Generator[object, None, None]] = None, + *args, + **kwargs + ) -> None: super(JSONRPCProtocol, self).__init__(*args, **kwargs) - self._id_counter = 0 + self._id_generator = id_generator or default_id_generator() + self._pending_replies = [] - def _get_unique_id(self) -> int: - self._id_counter += 1 - return self._id_counter + def _get_unique_id(self) -> object: + return next(self._id_generator) def request_factory(self) -> 'JSONRPCRequest': """Factory for request objects. @@ -567,10 +575,13 @@ if not one_way: request.unique_id = self._get_unique_id() + self._pending_replies.append(request.unique_id) request.method = method - request.args = args - request.kwargs = kwargs + if args is not None: + request.args = args + if kwargs is not None: + request.kwargs = kwargs return request @@ -626,6 +637,12 @@ response.result = rep.get('result', None) response.unique_id = rep['id'] + if response.unique_id not in self._pending_replies: + raise UnexpectedIDError( + 'Reply id does not correspond to any sent requests.' + ) + else: + self._pending_replies.remove(response.unique_id) return response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/protocols/msgpackrpc.py new/tinyrpc-1.1.4/tinyrpc/protocols/msgpackrpc.py --- old/tinyrpc-1.0.4/tinyrpc/protocols/msgpackrpc.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/protocols/msgpackrpc.py 2022-01-30 14:56:51.000000000 +0100 @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from . import default_id_generator from .. import ( RPCError, RPCErrorResponse, @@ -15,7 +16,7 @@ import msgpack import six -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union, Generator class FixedErrorMessageMixin(object): @@ -236,13 +237,17 @@ class MSGPACKRPCProtocol(RPCProtocol): """MSGPACKRPC protocol implementation.""" - def __init__(self, *args, **kwargs): + def __init__( + self, + id_generator: Optional[Generator[object, None, None]] = None, + *args, + **kwargs + ) -> None: super(MSGPACKRPCProtocol, self).__init__(*args, **kwargs) - self._id_counter = 0 + self._id_generator = id_generator or default_id_generator() def _get_unique_id(self): - self._id_counter += 1 - return self._id_counter + return next(self._id_generator) def request_factory(self) -> "MSGPACKRPCRequest": """Factory for request objects. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/transports/rabbitmq.py new/tinyrpc-1.1.4/tinyrpc/transports/rabbitmq.py --- old/tinyrpc-1.0.4/tinyrpc/transports/rabbitmq.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/transports/rabbitmq.py 2022-01-30 14:56:51.000000000 +0100 @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from typing import Tuple, Any + +import pika + +from . import ServerTransport, ClientTransport + + +class RabbitMQServerTransport(ServerTransport): + """Server transport based on a :py:class:`pika.BlockingConnection`. + + The transport assumes a RabbitMQ topology has already been established. + + :param connection: A :py:class:`pika.BlockingConnection` instance. + :param queue: The RabbitMQ queue to consume messages from. + :param exchange: The RabbitMQ exchange to use. + """ + + def __init__(self, connection: pika.BlockingConnection, queue: str, exchange: str = '') -> None: + self.connection = connection + self.queue = queue + self.exchange = exchange + + self.channel = self.connection.channel() + self.channel.queue_declare(queue=self.queue) + self.channel.basic_consume(queue=self.queue, on_message_callback=self.on_receive) + self.message_received = False + + def receive_message(self) -> Tuple[Any, bytes]: + while not self.message_received: + self.connection.process_data_events() + return self.context, self.message + + def send_reply(self, context: Any, reply: bytes) -> None: + ch, method, props = context + ch.basic_publish(exchange=self.exchange, + routing_key=props.reply_to, + properties=pika.BasicProperties(correlation_id = props.correlation_id), + body=reply) + ch.basic_ack(delivery_tag=method.delivery_tag) + self.message_received = False # message processed, reset status + + def on_receive(self, ch, method, props, body): + self.context = (ch, method, props) + self.message = body + self.message_received = True + + @classmethod + def create(cls, host: str, queue: str, exchange: str = '') -> 'RabbitMQServerTransport': + """Create new server transport. + + Instead of creating the BlockingConnection yourself, you can call this function and + pass in the host name, queue, and exchange. + + :param host: The host clients will connect to. + :param queue: The RabbitMQ queue to consume messages from. + :param exchange: The RabbitMQ exchange to use. + """ + connection = pika.BlockingConnection(pika.ConnectionParameters(host)) + return cls(connection, queue, exchange) + + +class RabbitMQClientTransport(ClientTransport): + """Client transport based on a :py:class:`pika.BlockingConnection`. + + The transport assumes a RabbitMQ topology has already been established. + + :param connection: A :py:class:`pika.BlockingConnection` instance. + :param routing_key: The RabbitMQ routing key to direct messages. + :param exchange: The RabbitMQ exchange to use. + """ + + def __init__(self, connection: pika.BlockingConnection, routing_key: str, exchange: str = '') -> None: + self.connection = connection + self.routing_key = routing_key + self.exchange = exchange + self._id_counter = 1000 + + self.channel = self.connection.channel() + qd_result = self.channel.queue_declare(queue='', exclusive=True) + self.callback_queue = qd_result.method.queue + + self.channel.basic_consume( + queue=self.callback_queue, + on_message_callback=self.on_response, + auto_ack=True + ) + + def _get_unique_id(self) -> int: + self._id_counter += 1 + return self._id_counter + + def send_message(self, message: bytes, expect_reply: bool = True) -> bytes: + self.response_data = None + self.corr_id = str(self._get_unique_id()) + self.channel.basic_publish( + exchange=self.exchange, + routing_key=self.routing_key, + properties=pika.BasicProperties( + reply_to=self.callback_queue, + correlation_id=self.corr_id, + ), + body=message) + + if expect_reply: + while self.response_data is None: + self.connection.process_data_events() + return self.response_data + + def on_response(self, ch, method, props, body): + if self.corr_id == props.correlation_id: + self.response_data = body + + @classmethod + def create(cls, host: str, routing_key: str, exchange: str = '') -> 'RabbitMQClientTransport': + """Create new client transport. + + Instead of creating the BlockingConnection yourself, you can call this function and + pass in the host name, routing key, and exchange. + + :param host: The host clients will connect to. + :param routing_key: The RabbitMQ routing key to direct messages. + :param exchange: The RabbitMQ exchange to use. + """ + connection = pika.BlockingConnection(pika.ConnectionParameters(host)) + return cls(connection, routing_key, exchange) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/transports/wsgi.py new/tinyrpc-1.1.4/tinyrpc/transports/wsgi.py --- old/tinyrpc-1.0.4/tinyrpc/transports/wsgi.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/transports/wsgi.py 2022-01-30 14:56:51.000000000 +0100 @@ -72,6 +72,10 @@ 'Content-Type, X-Requested-With, Accept, Origin' } + post_headers = { + 'Content-Type': 'application/json' + } + if request.method == 'OPTIONS': response = Response(headers=access_control_headers) @@ -84,8 +88,11 @@ self.messages.put((context, msg)) + # collect and combine all headers + response_headers = dict(**access_control_headers, **post_headers) + # ...and send the reply - response = Response(context.get(), headers=access_control_headers) + response = Response(context.get(), headers=response_headers) else: # nothing else supported at the moment response = Response('Only POST supported', 405) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tinyrpc/transports/zmq.py new/tinyrpc-1.1.4/tinyrpc/transports/zmq.py --- old/tinyrpc-1.0.4/tinyrpc/transports/zmq.py 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tinyrpc/transports/zmq.py 2022-01-30 14:56:51.000000000 +0100 @@ -3,11 +3,12 @@ from __future__ import absolute_import # needed for zmq import -from typing import Tuple, Any +from typing import Tuple, Any, Dict import zmq from . import ServerTransport, ClientTransport +from .. import exc class ZmqServerTransport(ServerTransport): @@ -50,19 +51,36 @@ :param socket: A :py:const:`zmq.REQ` socket instance, connected to the server socket. + :param timeout: An optional float. When set it defines the time period + in seconds to wait for a reply. + It will generate a :py:class:`exc.TimeoutError` exception + if no reply was received in time. """ - def __init__(self, socket: zmq.Socket) -> None: + def __init__(self, socket: zmq.Socket, timeout: float = None) -> None: self.socket = socket + self.timeout = timeout def send_message(self, message: bytes, expect_reply: bool = True) -> bytes: self.socket.send(message) + # zmq contains a state machine preventing a new request + # until the previous one is answered, so always receive + if self.timeout is None: + reply = self.socket.recv() + else: + poller = zmq.Poller() + poller.register(self.socket, zmq.POLLIN) + ready = dict(poller.poll(int(self.timeout * 1000))) + if ready.get(self.socket) == zmq.POLLIN: + reply = self.socket.recv() + else: + raise exc.TimeoutError() if expect_reply: - return self.socket.recv() + return reply @classmethod - def create(cls, zmq_context: zmq.Context, endpoint: str) -> 'ZmqClientTransport': + def create(cls, zmq_context: zmq.Context, endpoint: str, timeout: float = None) -> 'ZmqClientTransport': """Create new client transport. Instead of creating the socket yourself, you can call this function and @@ -73,7 +91,8 @@ :param zmq_context: A 0mq context. :param endpoint: The endpoint the server is bound to. + :param timeout: Optional period in seconds to wait for reply """ socket = zmq_context.socket(zmq.REQ) socket.connect(endpoint) - return cls(socket) + return cls(socket, timeout) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tinyrpc-1.0.4/tox.ini new/tinyrpc-1.1.4/tox.ini --- old/tinyrpc-1.0.4/tox.ini 2019-11-03 14:42:07.000000000 +0100 +++ new/tinyrpc-1.1.4/tox.ini 2022-01-30 14:56:51.000000000 +0100 @@ -1,19 +1,9 @@ [tox] -envlist = py37 +#envlist = py38 +envlist = py34, py35, py36, py37, py38, py39 [testenv] -;usedevelop = true -;deps= -; six -; pytest -; pytest-cov -; werkzeug -; greenlet -; gevent-websocket -; gevent -; requests==2.4.3 -; zmq deps = -rrequirements.txt commands= pytest -rs -; pytest --cov=tinyrpc/ --cov-report=term --cov-report=html + pytest --cov=tinyrpc/ --cov-report=term --cov-report=html
