diff --git a/.travis.yml b/.travis.yml index a4f2163..67c2d20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,32 @@ +dist: trusty +sudo: false language: python python: + - 2.6 - 2.7 + - 3.4 + - 3.5 env: matrix: - - PYZMQ='pyzmq>=14.3' - - PYZMQ='pyzmq>=14.2,<14.3' - - PYZMQ='pyzmq>=14.1,<14.2' - - PYZMQ='pyzmq>=14.0,<14.1' - - PYZMQ='pyzmq<14' + - PYZMQ='pyzmq>=15' + - PYZMQ='pyzmq>=14,<15' + - PYZMQ='pyzmq>=13,<14' matrix: fast_finish: true -script: - - flake8 --ignore=E501,E128 zerorpc bin - - ZPC_TEST_TIME_FACTOR=0.1 nosetests -before_install: - - sudo apt-get update - - sudo apt-get install python-dev libevent-dev +addons: + apt: + packages: + - python-dev + - libevent-dev install: - - pip install flake8 - - "pip install nose gevent msgpack-python $PYZMQ" - - pip install . --no-deps + - if [ $TRAVIS_PYTHON_VERSION != '2.6' ]; then + pip install flake8; + fi + - "pip install nose $PYZMQ" + - pip install . + - pip freeze +script: + - if [ $TRAVIS_PYTHON_VERSION != '2.6' ]; then + flake8 --ignore=E501,E128 zerorpc bin; + fi + - pytest -v diff --git a/README.rst b/README.rst index 1210822..3538015 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ zerorpc ======= -.. image:: https://secure.travis-ci.org/0rpc/zerorpc-python.png - :target: http://travis-ci.org/0rpc/zerorpc-python +.. image:: https://travis-ci.org/0rpc/zerorpc-python.svg?branch=master + :target: https://travis-ci.org/0rpc/zerorpc-python Mailing list: zerorpc@googlegroups.com (https://groups.google.com/d/forum/zerorpc) @@ -16,6 +16,14 @@ with a convenient script, "zerorpc", allowing to: * expose Python modules without modifying a single line of code, * call those modules remotely through the command line. +Installation +------------ + +On most systems, its a matter of:: + + $ pip install zerorpc + +Depending of the support from Gevent and PyZMQ on your system, you might need to install `libev` (for gevent) and `libzmq` (for pyzmq) with the development files. Create a server with a one-liner -------------------------------- @@ -28,7 +36,7 @@ we will expose the Python "time" module:: .. note:: The bind address uses the zeromq address format. You are not limited to TCP transport: you could as well specify ipc:///tmp/time to use - host-local sockets, for instance. "tcp://*:1234" is a short-hand to + host-local sockets, for instance. "tcp://\*:1234" is a short-hand to "tcp://0.0.0.0:1234" and means "listen on TCP port 1234, accepting connections on all IP addresses". @@ -129,7 +137,7 @@ the "--bind" option:: $ zerorpc --server --bind tcp://*:1234 --bind ipc:///tmp/time time -You can then connect to it using either "zerorpc tcp://*:1234" or +You can then connect to it using either "zerorpc tcp://\*:1234" or "zerorpc ipc:///tmp/time". Wait, there is more! You can even mix "--bind" and "--connect". That means diff --git a/bin/zerorpc b/bin/zerorpc index f0e1172..c9d31d3 100755 --- a/bin/zerorpc +++ b/bin/zerorpc @@ -27,7 +27,7 @@ import os import sys sys.path.append(os.path.dirname(os.path.dirname(sys.argv[0]))) -from zerorpc import cli +from zerorpc import cli # NOQA if __name__ == "__main__": exit(cli.main()) diff --git a/doc/protocol.md b/doc/protocol.md index a5ee1ff..cfc83d2 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -207,7 +207,7 @@ exception is raised), we use the ERR event. - Name of the error (it should be the exception class name, or another meaningful keyword). - Human representation of the error (preferably in english). - - If possible a pretty printed traceback of the call stack when the error occured. + - If possible a pretty printed traceback of the call stack when the error occurred. > A future version of the protocol will probably add a structured version of the > traceback, allowing machine-to-machine stack walking and better cross-language @@ -231,7 +231,7 @@ FIXME we should rather standardize about the basic introspection calls. At the protocol level, streaming is straightforward. When a server wants to stream some data, instead of sending a "OK" message, it sends a "STREAM" message. The client will know that it needs to keep waiting for more. -At the end of the sream, a "STREAM_DONE" message is expected. +At the end of the stream, a "STREAM_DONE" message is expected. Formal definitions follow. diff --git a/setup.py b/setup.py index 1a22102..b07ebcb 100644 --- a/setup.py +++ b/setup.py @@ -22,11 +22,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -# execfile() doesn't exist in Python 3, this way we are compatible with both. -exec(compile(open('zerorpc/version.py').read(), 'zerorpc/version.py', 'exec')) - import sys +if sys.version_info < (3, 0): + execfile('zerorpc/version.py') +else: + exec(compile(open('zerorpc/version.py', encoding='utf8').read(), 'zerorpc/version.py', 'exec')) try: from setuptools import setup @@ -35,24 +36,36 @@ requirements = [ - 'gevent>=1.0', - 'msgpack-python', - 'pyzmq>=13.1.0' + 'msgpack>=0.5.2', + 'pyzmq>=13.1.0', + 'future', ] + if sys.version_info < (2, 7): requirements.append('argparse') +if sys.version_info < (2, 7): + requirements.append('gevent>=1.1.0,<1.2.0') +elif sys.version_info < (3, 0): + requirements.append('gevent>=1.0') +else: + requirements.append('gevent>=1.1') + +with open("README.rst", "r") as fh: + long_description = fh.read() setup( name='zerorpc', version=__version__, description='zerorpc is a flexible RPC based on zeromq.', + long_description=long_description, + long_description_content_type='text/x-rst', author=__author__, url='https://github.com/0rpc/zerorpc-python', packages=['zerorpc'], install_requires=requirements, - tests_require=['nose'], - test_suite='nose.collector', + tests_require=['pytest'], + test_suite='pytest.collector', zip_safe=False, entry_points={'console_scripts': ['zerorpc = zerorpc.cli:main']}, license='MIT', @@ -62,5 +75,8 @@ 'Natural Language :: English', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', ), ) diff --git a/tests/test_buffered_channel.py b/tests/test_buffered_channel.py index adb27d6..20b8173 100644 --- a/tests/test_buffered_channel.py +++ b/tests/test_buffered_channel.py @@ -23,13 +23,17 @@ # SOFTWARE. -from nose.tools import assert_raises +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + +import pytest import gevent import sys from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_close_server_bufchan(): @@ -54,14 +58,14 @@ def test_close_server_bufchan(): server_bufchan.recv() gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE SERVER SOCKET!!!' + print('CLOSE SERVER SOCKET!!!') server_bufchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_bufchan.recv) + pytest.raises(zerorpc.LostRemote, client_bufchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_bufchan.recv() - print 'CLIENT LOST SERVER :)' + print('CLIENT LOST SERVER :)') client_bufchan.close() server.close() client.close() @@ -89,14 +93,14 @@ def test_close_client_bufchan(): server_bufchan.recv() gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE CLIENT SOCKET!!!' + print('CLOSE CLIENT SOCKET!!!') client_bufchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_bufchan.recv) + pytest.raises(zerorpc.LostRemote, client_bufchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_bufchan.recv() - print 'SERVER LOST CLIENT :)' + print('SERVER LOST CLIENT :)') server_bufchan.close() server.close() client.close() @@ -122,14 +126,14 @@ def test_heartbeat_can_open_channel_server_close(): server_bufchan = zerorpc.BufferedChannel(server_hbchan) gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE SERVER SOCKET!!!' + print('CLOSE SERVER SOCKET!!!') server_bufchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_bufchan.recv) + pytest.raises(zerorpc.LostRemote, client_bufchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_bufchan.recv() - print 'CLIENT LOST SERVER :)' + print('CLIENT LOST SERVER :)') client_bufchan.close() server.close() client.close() @@ -162,15 +166,15 @@ def server_fn(): server_coro = gevent.spawn(server_fn) gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE CLIENT SOCKET!!!' + print('CLOSE CLIENT SOCKET!!!') client_bufchan.close() client.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, server_coro.get()) + pytest.raises(zerorpc.LostRemote, server_coro.get) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): server_coro.get() - print 'SERVER LOST CLIENT :)' + print('SERVER LOST CLIENT :)') server.close() @@ -189,7 +193,7 @@ def client_do(): client_channel = client.channel() client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) client_bufchan = zerorpc.BufferedChannel(client_hbchan) - for x in xrange(20): + for x in range(20): client_bufchan.emit('add', (x, x * x)) event = client_bufchan.recv() assert event.name == 'OK' @@ -205,7 +209,7 @@ def server_do(): server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) server_bufchan = zerorpc.BufferedChannel(server_hbchan) - for x in xrange(20): + for x in range(20): event = server_bufchan.recv() assert event.name == 'add' server_bufchan.emit('OK', (sum(event.args),)) @@ -229,20 +233,20 @@ def test_do_some_req_rep_lost_server(): client = zerorpc.ChannelMultiplexer(client_events, ignore_broadcast=True) def client_do(): - print 'running' + print('running') client_channel = client.channel() client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) client_bufchan = zerorpc.BufferedChannel(client_hbchan) - for x in xrange(10): + for x in range(10): client_bufchan.emit('add', (x, x * x)) event = client_bufchan.recv() assert event.name == 'OK' assert list(event.args) == [x + x * x] client_bufchan.emit('add', (x, x * x)) if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_bufchan.recv) + pytest.raises(zerorpc.LostRemote, client_bufchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_bufchan.recv() client_bufchan.close() @@ -254,7 +258,7 @@ def server_do(): server_channel = server.channel(event) server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) server_bufchan = zerorpc.BufferedChannel(server_hbchan) - for x in xrange(10): + for x in range(10): event = server_bufchan.recv() assert event.name == 'add' server_bufchan.emit('OK', (sum(event.args),)) @@ -282,7 +286,7 @@ def client_do(): client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) client_bufchan = zerorpc.BufferedChannel(client_hbchan) - for x in xrange(10): + for x in range(10): client_bufchan.emit('add', (x, x * x)) event = client_bufchan.recv() assert event.name == 'OK' @@ -298,15 +302,15 @@ def server_do(): server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) server_bufchan = zerorpc.BufferedChannel(server_hbchan) - for x in xrange(10): + for x in range(10): event = server_bufchan.recv() assert event.name == 'add' server_bufchan.emit('OK', (sum(event.args),)) if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, server_bufchan.recv) + pytest.raises(zerorpc.LostRemote, server_bufchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): server_bufchan.recv() server_bufchan.close() @@ -334,15 +338,15 @@ def client_do(): if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(10): + for x in range(10): client_bufchan.emit('sleep', (x,)) event = client_bufchan.recv(timeout=TIME_FACTOR * 3) assert event.name == 'OK' assert list(event.args) == [x] - assert_raises(zerorpc.TimeoutExpired, _do_with_assert_raises) + pytest.raises(zerorpc.TimeoutExpired, _do_with_assert_raises) else: - with assert_raises(zerorpc.TimeoutExpired): - for x in xrange(10): + with pytest.raises(zerorpc.TimeoutExpired): + for x in range(10): client_bufchan.emit('sleep', (x,)) event = client_bufchan.recv(timeout=TIME_FACTOR * 3) assert event.name == 'OK' @@ -360,15 +364,15 @@ def server_do(): if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(20): + for x in range(20): event = server_bufchan.recv() assert event.name == 'sleep' gevent.sleep(TIME_FACTOR * event.args[0]) server_bufchan.emit('OK', event.args) - assert_raises(zerorpc.LostRemote, _do_with_assert_raises) + pytest.raises(zerorpc.LostRemote, _do_with_assert_raises) else: - with assert_raises(zerorpc.LostRemote): - for x in xrange(20): + with pytest.raises(zerorpc.LostRemote): + for x in range(20): event = server_bufchan.recv() assert event.name == 'sleep' gevent.sleep(TIME_FACTOR * event.args[0]) @@ -393,18 +397,17 @@ def test_congestion_control_server_pushing(): client_events.connect(endpoint) client = zerorpc.ChannelMultiplexer(client_events, ignore_broadcast=True) - read_cnt = 0 + read_cnt = type('Dummy', (object,), { "value": 0 }) def client_do(): client_channel = client.channel() client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) client_bufchan = zerorpc.BufferedChannel(client_hbchan, inqueue_size=100) - for x in xrange(200): + for x in range(200): event = client_bufchan.recv() assert event.name == 'coucou' assert event.args == x - global read_cnt - read_cnt += 1 + read_cnt.value += 1 client_bufchan.close() coro_pool = gevent.pool.Pool() @@ -417,24 +420,24 @@ def server_do(): server_bufchan = zerorpc.BufferedChannel(server_hbchan, inqueue_size=100) if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(200): + for x in range(200): server_bufchan.emit('coucou', x, timeout=0) # will fail when x == 1 - assert_raises(zerorpc.TimeoutExpired, _do_with_assert_raises) + pytest.raises(zerorpc.TimeoutExpired, _do_with_assert_raises) else: - with assert_raises(zerorpc.TimeoutExpired): - for x in xrange(200): + with pytest.raises(zerorpc.TimeoutExpired): + for x in range(200): server_bufchan.emit('coucou', x, timeout=0) # will fail when x == 1 server_bufchan.emit('coucou', 1) # block until receiver is ready if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(2, 200): + for x in range(2, 200): server_bufchan.emit('coucou', x, timeout=0) # will fail when x == 100 - assert_raises(zerorpc.TimeoutExpired, _do_with_assert_raises) + pytest.raises(zerorpc.TimeoutExpired, _do_with_assert_raises) else: - with assert_raises(zerorpc.TimeoutExpired): - for x in xrange(2, 200): + with pytest.raises(zerorpc.TimeoutExpired): + for x in range(2, 200): server_bufchan.emit('coucou', x, timeout=0) # will fail when x == 100 - for x in xrange(read_cnt, 200): + for x in range(read_cnt.value, 200): server_bufchan.emit('coucou', x) # block until receiver is ready server_bufchan.close() @@ -446,3 +449,61 @@ def _do_with_assert_raises(): finally: client.close() server.close() + + +def test_on_close_if(): + """ + Test that the on_close_if method does not cause exceptions when the client + is slow to recv() data. + """ + endpoint = random_ipc_endpoint() + server_events = zerorpc.Events(zmq.ROUTER) + server_events.bind(endpoint) + server = zerorpc.ChannelMultiplexer(server_events) + + client_events = zerorpc.Events(zmq.DEALER) + client_events.connect(endpoint) + client = zerorpc.ChannelMultiplexer(client_events, ignore_broadcast=True) + + client_channel = client.channel() + client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=2) + client_bufchan = zerorpc.BufferedChannel(client_hbchan, inqueue_size=10) + + event = server.recv() + server_channel = server.channel(event) + server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=2) + server_bufchan = zerorpc.BufferedChannel(server_hbchan, inqueue_size=10) + + seen = [] + + def is_stream_done(event): + return event.name == 'done' + + def client_do(): + while True: + event = client_bufchan.recv() + if event.name == 'done': + return + seen.append(event.args) + gevent.sleep(0.1) + + def server_do(): + for i in range(0, 10): + server_bufchan.emit('blah', (i)) + server_bufchan.emit('done', ('bye')) + + client_bufchan.on_close_if = is_stream_done + + coro_pool = gevent.pool.Pool() + g1 = coro_pool.spawn(client_do) + g2 = coro_pool.spawn(server_do) + + g1.get() # Re-raise any exceptions... + g2.get() + + assert seen == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + + client_bufchan.close() + server_bufchan.close() + client.close() + server.close() diff --git a/tests/test_channel.py b/tests/test_channel.py index 18d2d8d..1d59b1e 100644 --- a/tests/test_channel.py +++ b/tests/test_channel.py @@ -23,9 +23,13 @@ # SOFTWARE. +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint +from .testutils import teardown, random_ipc_endpoint def test_events_channel_client_side(): @@ -42,12 +46,12 @@ def test_events_channel_client_side(): client_channel.emit('someevent', (42,)) event = server.recv() - print event + print(event) assert list(event.args) == [42] assert event.identity is not None reply_event = server.new_event('someanswer', (21,), - xheader=dict(response_to=event.header['message_id'])) + xheader={'response_to': event.header['message_id']}) reply_event.identity = event.identity server.emit_event(reply_event) event = client_channel.recv() @@ -68,16 +72,16 @@ def test_events_channel_client_side_server_send_many(): client_channel.emit('giveme', (10,)) event = server.recv() - print event + print(event) assert list(event.args) == [10] assert event.identity is not None - for x in xrange(10): + for x in range(10): reply_event = server.new_event('someanswer', (x,), - xheader=dict(response_to=event.header['message_id'])) + xheader={'response_to': event.header['message_id']}) reply_event.identity = event.identity server.emit_event(reply_event) - for x in xrange(10): + for x in range(10): event = client_channel.recv() assert list(event.args) == [x] @@ -96,7 +100,7 @@ def test_events_channel_both_side(): client_channel.emit('openthat', (42,)) event = server.recv() - print event + print(event) assert list(event.args) == [42] assert event.name == 'openthat' diff --git a/tests/test_client.py b/tests/test_client.py index 7a954ba..6a692b3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,10 +23,11 @@ # SOFTWARE. +from __future__ import absolute_import import gevent import zerorpc -from testutils import teardown, random_ipc_endpoint +from .testutils import teardown, random_ipc_endpoint def test_client_connect(): endpoint = random_ipc_endpoint() diff --git a/tests/test_client_async.py b/tests/test_client_async.py index 93645fe..ced4b1f 100644 --- a/tests/test_client_async.py +++ b/tests/test_client_async.py @@ -23,13 +23,15 @@ # SOFTWARE. -from nose.tools import assert_raises +from __future__ import print_function +from __future__ import absolute_import +import pytest import gevent import sys from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_client_server_client_timeout_with_async(): @@ -51,15 +53,15 @@ def add(self, a, b): client = zerorpc.Client(timeout=TIME_FACTOR * 2) client.connect(endpoint) - async_result = client.add(1, 4, async=True) + async_result = client.add(1, 4, async_=True) if sys.version_info < (2, 7): def _do_with_assert_raises(): - print async_result.get() - assert_raises(zerorpc.TimeoutExpired, _do_with_assert_raises) + print(async_result.get()) + pytest.raises(zerorpc.TimeoutExpired, _do_with_assert_raises) else: - with assert_raises(zerorpc.TimeoutExpired): - print async_result.get() + with pytest.raises(zerorpc.TimeoutExpired): + print(async_result.get()) client.close() srv.close() @@ -82,8 +84,8 @@ def add(self, a, b): client = zerorpc.Client() client.connect(endpoint) - async_result = client.lolita(async=True) + async_result = client.lolita(async_=True) assert async_result.get() == 42 - async_result = client.add(1, 4, async=True) + async_result = client.add(1, 4, async_=True) assert async_result.get() == 5 diff --git a/tests/test_client_heartbeat.py b/tests/test_client_heartbeat.py index 1d2936a..6b552a4 100644 --- a/tests/test_client_heartbeat.py +++ b/tests/test_client_heartbeat.py @@ -23,10 +23,15 @@ # SOFTWARE. +from __future__ import print_function +from __future__ import absolute_import +from builtins import next +from builtins import range + import gevent import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_client_server_hearbeat(): @@ -48,7 +53,7 @@ def slow(self): client.connect(endpoint) assert client.lolita() == 42 - print 'GOT ANSWER' + print('GOT ANSWER') def test_client_server_activate_heartbeat(): @@ -69,7 +74,7 @@ def lolita(self): client.connect(endpoint) assert client.lolita() == 42 - print 'GOT ANSWER' + print('GOT ANSWER') def test_client_server_passive_hearbeat(): @@ -93,7 +98,7 @@ def slow(self): client.connect(endpoint) assert client.slow() == 2 - print 'GOT ANSWER' + print('GOT ANSWER') def test_client_hb_doesnt_linger_on_streaming(): @@ -103,7 +108,7 @@ class MySrv(zerorpc.Server): @zerorpc.stream def iter(self): - return xrange(42) + return range(42) srv = MySrv(heartbeat=TIME_FACTOR * 1, context=zerorpc.Context()) srv.bind(endpoint) @@ -112,8 +117,8 @@ def iter(self): client1 = zerorpc.Client(endpoint, heartbeat=TIME_FACTOR * 1, context=zerorpc.Context()) def test_client(): - assert list(client1.iter()) == list(xrange(42)) - print 'sleep 3s' + assert list(client1.iter()) == list(range(42)) + print('sleep 3s') gevent.sleep(TIME_FACTOR * 3) gevent.spawn(test_client).join() @@ -158,10 +163,10 @@ def iter(self): client1 = zerorpc.Client(endpoint, heartbeat=TIME_FACTOR * 1, context=zerorpc.Context()) def test_client(): - print 'grab iter' + print('grab iter') i = client1.iter() - print 'sleep 3s' + print('sleep 3s') gevent.sleep(TIME_FACTOR * 3) gevent.spawn(test_client).join() @@ -174,7 +179,7 @@ class MySrv(zerorpc.Server): @zerorpc.stream def iter(self): - return xrange(500) + return range(500) srv = MySrv(heartbeat=TIME_FACTOR * 1, context=zerorpc.Context()) srv.bind(endpoint) @@ -183,13 +188,13 @@ def iter(self): client1 = zerorpc.Client(endpoint, heartbeat=TIME_FACTOR * 1, context=zerorpc.Context()) def test_client(): - print 'grab iter' + print('grab iter') i = client1.iter() - print 'consume some' - assert list(next(i) for x in xrange(142)) == list(xrange(142)) + print('consume some') + assert list(next(i) for x in range(142)) == list(range(142)) - print 'sleep 3s' + print('sleep 3s') gevent.sleep(TIME_FACTOR * 3) gevent.spawn(test_client).join() diff --git a/tests/test_events.py b/tests/test_events.py index e3d1616..7acf98e 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -23,11 +23,15 @@ # SOFTWARE. +from __future__ import print_function, absolute_import +from builtins import str, bytes +from builtins import range, object + from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint +from .testutils import teardown, random_ipc_endpoint -class MokupContext(): +class MokupContext(object): _next_id = 0 def new_msgid(self): @@ -44,46 +48,46 @@ def test_context(): def test_event(): context = MokupContext() event = zerorpc.Event('mylittleevent', (None,), context=context) - print event + print(event) assert event.name == 'mylittleevent' assert event.header['message_id'] == 0 assert event.args == (None,) event = zerorpc.Event('mylittleevent2', ('42',), context=context) - print event + print(event) assert event.name == 'mylittleevent2' assert event.header['message_id'] == 1 assert event.args == ('42',) event = zerorpc.Event('mylittleevent3', ('a', 42), context=context) - print event + print(event) assert event.name == 'mylittleevent3' assert event.header['message_id'] == 2 assert event.args == ('a', 42) - event = zerorpc.Event('mylittleevent4', ('b', 21), context=context) - print event + event = zerorpc.Event('mylittleevent4', ('', 21), context=context) + print(event) assert event.name == 'mylittleevent4' assert event.header['message_id'] == 3 - assert event.args == ('b', 21) + assert event.args == ('', 21) packed = event.pack() unpacked = zerorpc.Event.unpack(packed) - print unpacked + print(unpacked) assert unpacked.name == 'mylittleevent4' assert unpacked.header['message_id'] == 3 - assert list(unpacked.args) == ['b', 21] + assert list(unpacked.args) == ['', 21] event = zerorpc.Event('mylittleevent5', ('c', 24, True), header={'lol': 'rofl'}, context=None) - print event + print(event) assert event.name == 'mylittleevent5' assert event.header['lol'] == 'rofl' assert event.args == ('c', 24, True) event = zerorpc.Event('mod', (42,), context=context) - print event + print(event) assert event.name == 'mod' assert event.header['message_id'] == 4 assert event.args == (42,) @@ -102,7 +106,7 @@ def test_events_req_rep(): client.emit('myevent', ('arg1',)) event = server.recv() - print event + print(event) assert event.name == 'myevent' assert list(event.args) == ['arg1'] @@ -115,16 +119,16 @@ def test_events_req_rep2(): client = zerorpc.Events(zmq.REQ) client.connect(endpoint) - for i in xrange(10): + for i in range(10): client.emit('myevent' + str(i), (i,)) event = server.recv() - print event + print(event) assert event.name == 'myevent' + str(i) assert list(event.args) == [i] server.emit('answser' + str(i * 2), (i * 2,)) event = client.recv() - print event + print(event) assert event.name == 'answser' + str(i * 2) assert list(event.args) == [i * 2] @@ -137,10 +141,10 @@ def test_events_dealer_router(): client = zerorpc.Events(zmq.DEALER) client.connect(endpoint) - for i in xrange(6): + for i in range(6): client.emit('myevent' + str(i), (i,)) event = server.recv() - print event + print(event) assert event.name == 'myevent' + str(i) assert list(event.args) == [i] @@ -148,7 +152,7 @@ def test_events_dealer_router(): reply_event.identity = event.identity server.emit_event(reply_event) event = client.recv() - print event + print(event) assert event.name == 'answser' + str(i * 2) assert list(event.args) == [i * 2] @@ -161,11 +165,35 @@ def test_events_push_pull(): client = zerorpc.Events(zmq.PUSH) client.connect(endpoint) - for x in xrange(10): + for x in range(10): client.emit('myevent', (x,)) - for x in xrange(10): + for x in range(10): event = server.recv() - print event + print(event) assert event.name == 'myevent' assert list(event.args) == [x] + + +def test_msgpack(): + context = zerorpc.Context() + event = zerorpc.Event(u'myevent', (u'a',), context=context) + print(event) + # note here that str is an unicode string in all Python version (thanks to + # the builtin str import). + assert isinstance(event.name, str) + for key in event.header.keys(): + assert isinstance(key, str) + assert isinstance(event.header[u'message_id'], bytes) + assert isinstance(event.header[u'v'], int) + assert isinstance(event.args[0], str) + + packed = event.pack() + event = event.unpack(packed) + print(event) + assert isinstance(event.name, str) + for key in event.header.keys(): + assert isinstance(key, str) + assert isinstance(event.header[u'message_id'], bytes) + assert isinstance(event.header[u'v'], int) + assert isinstance(event.args[0], str) diff --git a/tests/test_heartbeat.py b/tests/test_heartbeat.py index 0eee2b5..14c66fd 100644 --- a/tests/test_heartbeat.py +++ b/tests/test_heartbeat.py @@ -23,13 +23,17 @@ # SOFTWARE. -from nose.tools import assert_raises +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + +import pytest import gevent import sys from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_close_server_hbchan(): @@ -52,14 +56,14 @@ def test_close_server_hbchan(): server_hbchan.recv() gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE SERVER SOCKET!!!' + print('CLOSE SERVER SOCKET!!!') server_hbchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_hbchan.recv) + pytest.raises(zerorpc.LostRemote, client_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_hbchan.recv() - print 'CLIENT LOST SERVER :)' + print('CLIENT LOST SERVER :)') client_hbchan.close() server.close() client.close() @@ -85,14 +89,14 @@ def test_close_client_hbchan(): server_hbchan.recv() gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE CLIENT SOCKET!!!' + print('CLOSE CLIENT SOCKET!!!') client_hbchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, server_hbchan.recv) + pytest.raises(zerorpc.LostRemote, server_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): server_hbchan.recv() - print 'SERVER LOST CLIENT :)' + print('SERVER LOST CLIENT :)') server_hbchan.close() server.close() client.close() @@ -116,14 +120,14 @@ def test_heartbeat_can_open_channel_server_close(): server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE SERVER SOCKET!!!' + print('CLOSE SERVER SOCKET!!!') server_hbchan.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_hbchan.recv) + pytest.raises(zerorpc.LostRemote, client_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_hbchan.recv() - print 'CLIENT LOST SERVER :)' + print('CLIENT LOST SERVER :)') client_hbchan.close() server.close() client.close() @@ -147,15 +151,15 @@ def test_heartbeat_can_open_channel_client_close(): server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) gevent.sleep(TIME_FACTOR * 3) - print 'CLOSE CLIENT SOCKET!!!' + print('CLOSE CLIENT SOCKET!!!') client_hbchan.close() client.close() if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, server_hbchan.recv) + pytest.raises(zerorpc.LostRemote, server_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): server_hbchan.recv() - print 'SERVER LOST CLIENT :)' + print('SERVER LOST CLIENT :)') server_hbchan.close() server.close() @@ -178,7 +182,7 @@ def test_do_some_req_rep(): server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 4) def client_do(): - for x in xrange(20): + for x in range(20): client_hbchan.emit('add', (x, x * x)) event = client_hbchan.recv() assert event.name == 'OK' @@ -188,7 +192,7 @@ def client_do(): client_task = gevent.spawn(client_do) def server_do(): - for x in xrange(20): + for x in range(20): event = server_hbchan.recv() assert event.name == 'add' server_hbchan.emit('OK', (sum(event.args),)) @@ -213,19 +217,19 @@ def test_do_some_req_rep_lost_server(): client = zerorpc.ChannelMultiplexer(client_events, ignore_broadcast=True) def client_do(): - print 'running' + print('running') client_channel = client.channel() client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) - for x in xrange(10): + for x in range(10): client_hbchan.emit('add', (x, x * x)) event = client_hbchan.recv() assert event.name == 'OK' assert list(event.args) == [x + x * x] client_hbchan.emit('add', (x, x * x)) if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, client_hbchan.recv) + pytest.raises(zerorpc.LostRemote, client_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): client_hbchan.recv() client_hbchan.close() @@ -235,7 +239,7 @@ def server_do(): event = server.recv() server_channel = server.channel(event) server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) - for x in xrange(10): + for x in range(10): event = server_hbchan.recv() assert event.name == 'add' server_hbchan.emit('OK', (sum(event.args),)) @@ -263,7 +267,7 @@ def client_do(): client_channel = client.channel() client_hbchan = zerorpc.HeartBeatOnChannel(client_channel, freq=TIME_FACTOR * 2) - for x in xrange(10): + for x in range(10): client_hbchan.emit('add', (x, x * x)) event = client_hbchan.recv() assert event.name == 'OK' @@ -277,15 +281,15 @@ def server_do(): server_channel = server.channel(event) server_hbchan = zerorpc.HeartBeatOnChannel(server_channel, freq=TIME_FACTOR * 2) - for x in xrange(10): + for x in range(10): event = server_hbchan.recv() assert event.name == 'add' server_hbchan.emit('OK', (sum(event.args),)) if sys.version_info < (2, 7): - assert_raises(zerorpc.LostRemote, server_hbchan.recv) + pytest.raises(zerorpc.LostRemote, server_hbchan.recv) else: - with assert_raises(zerorpc.LostRemote): + with pytest.raises(zerorpc.LostRemote): server_hbchan.recv() server_hbchan.close() @@ -313,15 +317,15 @@ def client_do(): if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(10): + for x in range(10): client_hbchan.emit('sleep', (x,)) event = client_hbchan.recv(timeout=TIME_FACTOR * 3) assert event.name == 'OK' assert list(event.args) == [x] - assert_raises(zerorpc.TimeoutExpired, _do_with_assert_raises) + pytest.raises(zerorpc.TimeoutExpired, _do_with_assert_raises) else: - with assert_raises(zerorpc.TimeoutExpired): - for x in xrange(10): + with pytest.raises(zerorpc.TimeoutExpired): + for x in range(10): client_hbchan.emit('sleep', (x,)) event = client_hbchan.recv(timeout=TIME_FACTOR * 3) assert event.name == 'OK' @@ -337,15 +341,15 @@ def server_do(): if sys.version_info < (2, 7): def _do_with_assert_raises(): - for x in xrange(20): + for x in range(20): event = server_hbchan.recv() assert event.name == 'sleep' gevent.sleep(TIME_FACTOR * event.args[0]) server_hbchan.emit('OK', event.args) - assert_raises(zerorpc.LostRemote, _do_with_assert_raises) + pytest.raises(zerorpc.LostRemote, _do_with_assert_raises) else: - with assert_raises(zerorpc.LostRemote): - for x in xrange(20): + with pytest.raises(zerorpc.LostRemote): + for x in range(20): event = server_hbchan.recv() assert event.name == 'sleep' gevent.sleep(TIME_FACTOR * event.args[0]) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index c97270a..3163a3a 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -23,7 +23,12 @@ # SOFTWARE. -from nose.tools import assert_raises +from __future__ import print_function +from __future__ import absolute_import +from builtins import str +from future.utils import tobytes + +import pytest import gevent import gevent.local import random @@ -32,7 +37,7 @@ from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_resolve_endpoint(): @@ -47,16 +52,16 @@ def resolve(endpoint): cnt = c.register_middleware({ 'resolve_endpoint': resolve }) - print 'registered_count:', cnt + print('registered_count:', cnt) assert cnt == 1 - print 'resolve titi:', c.hook_resolve_endpoint('titi') + print('resolve titi:', c.hook_resolve_endpoint('titi')) assert c.hook_resolve_endpoint('titi') == test_endpoint - print 'resolve toto:', c.hook_resolve_endpoint('toto') + print('resolve toto:', c.hook_resolve_endpoint('toto')) assert c.hook_resolve_endpoint('toto') == 'toto' - class Resolver(): + class Resolver(object): def resolve_endpoint(self, endpoint): if endpoint == 'toto': @@ -64,18 +69,18 @@ def resolve_endpoint(self, endpoint): return endpoint cnt = c.register_middleware(Resolver()) - print 'registered_count:', cnt + print('registered_count:', cnt) assert cnt == 1 - print 'resolve titi:', c.hook_resolve_endpoint('titi') + print('resolve titi:', c.hook_resolve_endpoint('titi')) assert c.hook_resolve_endpoint('titi') == test_endpoint - print 'resolve toto:', c.hook_resolve_endpoint('toto') + print('resolve toto:', c.hook_resolve_endpoint('toto')) assert c.hook_resolve_endpoint('toto') == test_endpoint c2 = zerorpc.Context() - print 'resolve titi:', c2.hook_resolve_endpoint('titi') + print('resolve titi:', c2.hook_resolve_endpoint('titi')) assert c2.hook_resolve_endpoint('titi') == 'titi' - print 'resolve toto:', c2.hook_resolve_endpoint('toto') + print('resolve toto:', c2.hook_resolve_endpoint('toto')) assert c2.hook_resolve_endpoint('toto') == 'toto' @@ -83,7 +88,7 @@ def test_resolve_endpoint_events(): test_endpoint = random_ipc_endpoint() c = zerorpc.Context() - class Resolver(): + class Resolver(object): def resolve_endpoint(self, endpoint): if endpoint == 'some_service': return test_endpoint @@ -91,14 +96,14 @@ def resolve_endpoint(self, endpoint): class Srv(zerorpc.Server): def hello(self): - print 'heee' + print('heee') return 'world' srv = Srv(heartbeat=TIME_FACTOR * 1, context=c) if sys.version_info < (2, 7): - assert_raises(zmq.ZMQError, srv.bind, 'some_service') + pytest.raises(zmq.ZMQError, srv.bind, 'some_service') else: - with assert_raises(zmq.ZMQError): + with pytest.raises(zmq.ZMQError): srv.bind('some_service') cnt = c.register_middleware(Resolver()) @@ -114,7 +119,7 @@ def hello(self): srv.close() -class Tracer: +class Tracer(object): '''Used by test_task_context_* tests''' def __init__(self, identity): self._identity = identity @@ -127,19 +132,19 @@ def trace_id(self): def load_task_context(self, event_header): self._locals.trace_id = event_header.get('trace_id', None) - print self._identity, 'load_task_context', self.trace_id + print(self._identity, 'load_task_context', self.trace_id) self._log.append(('load', self.trace_id)) def get_task_context(self): if self.trace_id is None: # just an ugly code to generate a beautiful little hash. self._locals.trace_id = '<{0}>'.format(hashlib.md5( - str(random.random())[3:] + tobytes(str(random.random())[3:]) ).hexdigest()[0:6].upper()) - print self._identity, 'get_task_context! [make a new one]', self.trace_id + print(self._identity, 'get_task_context! [make a new one]', self.trace_id) self._log.append(('new', self.trace_id)) else: - print self._identity, 'get_task_context! [reuse]', self.trace_id + print(self._identity, 'get_task_context! [reuse]', self.trace_id) self._log.append(('reuse', self.trace_id)) return { 'trace_id': self.trace_id } @@ -154,7 +159,7 @@ def test_task_context(): cli_tracer = Tracer('[client]') cli_ctx.register_middleware(cli_tracer) - class Srv: + class Srv(object): def echo(self, msg): return msg @@ -201,7 +206,7 @@ def test_task_context_relay(): cli_tracer = Tracer('[client]') cli_ctx.register_middleware(cli_tracer) - class Srv: + class Srv(object): def echo(self, msg): return msg @@ -212,7 +217,7 @@ def echo(self, msg): c_relay = zerorpc.Client(context=srv_relay_ctx) c_relay.connect(endpoint1) - class SrvRelay: + class SrvRelay(object): def echo(self, msg): return c_relay.echo('relay' + msg) + 'relayed' @@ -257,7 +262,7 @@ def test_task_context_relay_fork(): cli_tracer = Tracer('[client]') cli_ctx.register_middleware(cli_tracer) - class Srv: + class Srv(object): def echo(self, msg): return msg @@ -268,15 +273,15 @@ def echo(self, msg): c_relay = zerorpc.Client(context=srv_relay_ctx) c_relay.connect(endpoint1) - class SrvRelay: + class SrvRelay(object): def echo(self, msg): def dothework(msg): return c_relay.echo(msg) + 'relayed' g = gevent.spawn(zerorpc.fork_task_context(dothework, srv_relay_ctx), 'relay' + msg) - print 'relaying in separate task:', g + print('relaying in separate task:', g) r = g.get() - print 'back to main task' + print('back to main task') return r srv_relay = zerorpc.Server(SrvRelay(), context=srv_relay_ctx) @@ -321,7 +326,7 @@ def test_task_context_pushpull(): trigger = gevent.event.Event() - class Puller: + class Puller(object): def echo(self, msg): trigger.set() @@ -359,7 +364,7 @@ def test_task_context_pubsub(): trigger = gevent.event.Event() - class Subscriber: + class Subscriber(object): def echo(self, msg): trigger.set() @@ -381,9 +386,9 @@ def echo(self, msg): subscriber.stop() subscriber_task.join() - print publisher_tracer._log + print(publisher_tracer._log) assert ('new', publisher_tracer.trace_id) in publisher_tracer._log - print subscriber_tracer._log + print(subscriber_tracer._log) assert ('load', publisher_tracer.trace_id) in subscriber_tracer._log diff --git a/tests/test_middleware_before_after_exec.py b/tests/test_middleware_before_after_exec.py index 07821bc..5dafeb0 100644 --- a/tests/test_middleware_before_after_exec.py +++ b/tests/test_middleware_before_after_exec.py @@ -22,10 +22,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import absolute_import +from builtins import range + import gevent import zerorpc -from testutils import random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR class EchoModule(object): @@ -42,7 +45,7 @@ def echo(self, msg): @zerorpc.stream def echoes(self, msg): self.last_msg = 'echo: ' + msg - for i in xrange(0, 3): + for i in range(0, 3): yield self.last_msg class ServerBeforeExecMiddleware(object): diff --git a/tests/test_middleware_client.py b/tests/test_middleware_client.py index c65eae9..943985e 100644 --- a/tests/test_middleware_client.py +++ b/tests/test_middleware_client.py @@ -22,10 +22,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import absolute_import +from builtins import range + import gevent import zerorpc -from testutils import random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR class EchoModule(object): @@ -42,7 +45,7 @@ def echo(self, msg): @zerorpc.stream def echoes(self, msg): self.last_msg = "echo: " + msg - for i in xrange(0, 3): + for i in range(0, 3): yield self.last_msg def crash(self, msg): diff --git a/tests/test_pubpush.py b/tests/test_pubpush.py index ac93711..a99f9b4 100644 --- a/tests/test_pubpush.py +++ b/tests/test_pubpush.py @@ -23,11 +23,15 @@ # SOFTWARE. +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + import gevent import gevent.event import zerorpc -from testutils import teardown, random_ipc_endpoint +from .testutils import teardown, random_ipc_endpoint def test_pushpull_inheritance(): @@ -39,7 +43,7 @@ def test_pushpull_inheritance(): class Puller(zerorpc.Puller): def lolita(self, a, b): - print 'lolita', a, b + print('lolita', a, b) assert a + b == 3 trigger.set() @@ -50,7 +54,7 @@ def lolita(self, a, b): trigger.clear() pusher.lolita(1, 2) trigger.wait() - print 'done' + print('done') def test_pubsub_inheritance(): @@ -62,7 +66,7 @@ def test_pubsub_inheritance(): class Subscriber(zerorpc.Subscriber): def lolita(self, a, b): - print 'lolita', a, b + print('lolita', a, b) assert a + b == 3 trigger.set() @@ -73,10 +77,10 @@ def lolita(self, a, b): trigger.clear() # We need this retry logic to wait that the subscriber.run coroutine starts # reading (the published messages will go to /dev/null until then). - for attempt in xrange(0, 10): + for attempt in range(0, 10): publisher.lolita(1, 2) if trigger.wait(0.2): - print 'done' + print('done') return raise RuntimeError("The subscriber didn't receive any published message") @@ -87,7 +91,7 @@ def test_pushpull_composite(): class Puller(object): def lolita(self, a, b): - print 'lolita', a, b + print('lolita', a, b) assert a + b == 3 trigger.set() @@ -102,7 +106,7 @@ def lolita(self, a, b): trigger.clear() pusher.lolita(1, 2) trigger.wait() - print 'done' + print('done') def test_pubsub_composite(): @@ -111,7 +115,7 @@ def test_pubsub_composite(): class Subscriber(object): def lolita(self, a, b): - print 'lolita', a, b + print('lolita', a, b) assert a + b == 3 trigger.set() @@ -126,10 +130,10 @@ def lolita(self, a, b): trigger.clear() # We need this retry logic to wait that the subscriber.run coroutine starts # reading (the published messages will go to /dev/null until then). - for attempt in xrange(0, 10): + for attempt in range(0, 10): publisher.lolita(1, 2) if trigger.wait(0.2): - print 'done' + print('done') return raise RuntimeError("The subscriber didn't receive any published message") diff --git a/tests/test_reqstream.py b/tests/test_reqstream.py index 276d5dd..71e1511 100644 --- a/tests/test_reqstream.py +++ b/tests/test_reqstream.py @@ -23,10 +23,21 @@ # SOFTWARE. +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + import gevent import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR + +try: + # Try collections.abc for 3.4+ + from collections.abc import Iterator +except ImportError: + # Fallback to collections for Python 2 + from collections import Iterator def test_rcp_streaming(): @@ -36,11 +47,11 @@ class MySrv(zerorpc.Server): @zerorpc.rep def range(self, max): - return range(max) + return list(range(max)) @zerorpc.stream def xrange(self, max): - return xrange(max) + return range(max) srv = MySrv(heartbeat=TIME_FACTOR * 4) srv.bind(endpoint) @@ -53,10 +64,10 @@ def xrange(self, max): assert list(r) == list(range(10)) r = client.xrange(10) - assert getattr(r, 'next', None) is not None + assert isinstance(r, Iterator) l = [] - print 'wait 4s for fun' + print('wait 4s for fun') gevent.sleep(TIME_FACTOR * 4) for x in r: l.append(x) - assert l == range(10) + assert l == list(range(10)) diff --git a/tests/test_server.py b/tests/test_server.py index 548b1e4..86997a9 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -23,13 +23,17 @@ # SOFTWARE. -from nose.tools import assert_raises +from __future__ import print_function +from __future__ import absolute_import +from builtins import range + +import pytest import gevent import sys from zerorpc import zmq import zerorpc -from testutils import teardown, random_ipc_endpoint, TIME_FACTOR +from .testutils import teardown, random_ipc_endpoint, TIME_FACTOR def test_server_manual(): @@ -83,10 +87,10 @@ def add(self, a, b): client = zerorpc.Client() client.connect(endpoint) - print client.lolita() + print(client.lolita()) assert client.lolita() == 42 - print client.add(1, 4) + print(client.add(1, 4)) assert client.add(1, 4) == 5 @@ -110,10 +114,10 @@ def add(self, a, b): client.connect(endpoint) if sys.version_info < (2, 7): - assert_raises(zerorpc.TimeoutExpired, client.add, 1, 4) + pytest.raises(zerorpc.TimeoutExpired, client.add, 1, 4) else: - with assert_raises(zerorpc.TimeoutExpired): - print client.add(1, 4) + with pytest.raises(zerorpc.TimeoutExpired): + print(client.add(1, 4)) client.close() srv.close() @@ -135,12 +139,12 @@ def raise_something(self, a): if sys.version_info < (2, 7): def _do_with_assert_raises(): - print client.raise_something(42) - assert_raises(zerorpc.RemoteError, _do_with_assert_raises) + print(client.raise_something(42)) + pytest.raises(zerorpc.RemoteError, _do_with_assert_raises) else: - with assert_raises(zerorpc.RemoteError): - print client.raise_something(42) - assert client.raise_something(range(5)) == 4 + with pytest.raises(zerorpc.RemoteError): + print(client.raise_something(42)) + assert client.raise_something(list(range(5))) == 4 client.close() srv.close() @@ -162,17 +166,17 @@ def raise_error(self): if sys.version_info < (2, 7): def _do_with_assert_raises(): - print client.raise_error() - assert_raises(zerorpc.RemoteError, _do_with_assert_raises) + print(client.raise_error()) + pytest.raises(zerorpc.RemoteError, _do_with_assert_raises) else: - with assert_raises(zerorpc.RemoteError): - print client.raise_error() + with pytest.raises(zerorpc.RemoteError): + print(client.raise_error()) try: client.raise_error() except zerorpc.RemoteError as e: - print 'got that:', e - print 'name', e.name - print 'msg', e.msg + print('got that:', e) + print('name', e.name) + print('msg', e.msg) assert e.name == 'RuntimeError' assert e.msg == 'oops!' @@ -197,20 +201,20 @@ class MySrv(zerorpc.Server): rpccall = client.channel() rpccall.emit('donotexist', tuple()) event = rpccall.recv() - print event + print(event) assert event.name == 'ERR' (name, msg, tb) = event.args - print 'detailed error', name, msg, tb + print('detailed error', name, msg, tb) assert name == 'NameError' assert msg == 'donotexist' rpccall = client.channel() - rpccall.emit('donotexist', tuple(), xheader=dict(v=1)) + rpccall.emit('donotexist', tuple(), xheader={'v': 1}) event = rpccall.recv() - print event + print(event) assert event.name == 'ERR' (msg,) = event.args - print 'msg only', msg + print('msg only', msg) assert msg == "NameError('donotexist',)" client_events.close() diff --git a/tests/test_zmq.py b/tests/test_zmq.py index 723c941..1e7b4dd 100644 --- a/tests/test_zmq.py +++ b/tests/test_zmq.py @@ -23,10 +23,12 @@ # SOFTWARE. +from __future__ import print_function +from __future__ import absolute_import import gevent from zerorpc import zmq -from testutils import random_ipc_endpoint +from .testutils import teardown, random_ipc_endpoint def test1(): @@ -36,11 +38,11 @@ def server(): s = c.socket(zmq.REP) s.bind(endpoint) while True: - print 'srv recving...' + print(b'srv recving...') r = s.recv() - print 'srv', r - print 'srv sending...' - s.send('world') + print('srv', r) + print('srv sending...') + s.send(b'world') s.close() c.term() @@ -50,11 +52,11 @@ def client(): s = c.socket(zmq.REQ) s.connect(endpoint) - print 'cli sending...' - s.send('hello') - print 'cli recving...' + print('cli sending...') + s.send(b'hello') + print('cli recving...') r = s.recv() - print 'cli', r + print('cli', r) s.close() c.term() diff --git a/tests/testutils.py b/tests/testutils.py index 5b73bc5..85b6a96 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -22,8 +22,11 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import print_function +from builtins import str + import functools -import nose.exc +import pytest import random import os @@ -38,7 +41,7 @@ def random_ipc_endpoint(): def teardown(): global _tmpfiles for tmpfile in _tmpfiles: - print 'unlink', tmpfile + print('unlink', tmpfile) try: os.unlink(tmpfile) except Exception: @@ -49,12 +52,12 @@ def skip(reason): def _skip(test): @functools.wraps(test) def wrap(): - raise nose.exc.SkipTest(reason) + raise pytest.SkipTest(reason) return wrap return _skip try: TIME_FACTOR = float(os.environ.get('ZPC_TEST_TIME_FACTOR')) - print 'ZPC_TEST_TIME_FACTOR:', TIME_FACTOR except TypeError: - TIME_FACTOR = 1.0 + TIME_FACTOR = 0.2 +print('ZPC_TEST_TIME_FACTOR:', TIME_FACTOR) diff --git a/tests/zmqbug.py b/tests/zmqbug.py index 3544aab..1d102a2 100644 --- a/tests/zmqbug.py +++ b/tests/zmqbug.py @@ -25,6 +25,8 @@ # Based on https://github.com/traviscline/gevent-zeromq/blob/master/gevent_zeromq/core.py +from __future__ import print_function + import zmq import gevent.event @@ -79,7 +81,7 @@ def send(self, data, flags=0, copy=True, track=False): while True: try: return super(ZMQSocket, self).send(data, flags, copy, track) - except zmq.ZMQError, e: + except zmq.ZMQError as e: if e.errno != zmq.EAGAIN: raise self._writable.clear() @@ -92,14 +94,14 @@ def recv(self, flags=0, copy=True, track=False): while True: try: return super(ZMQSocket, self).recv(flags, copy, track) - except zmq.ZMQError, e: + except zmq.ZMQError as e: if e.errno != zmq.EAGAIN: raise self._readable.clear() while not self._readable.wait(timeout=10): events = self.getsockopt(zmq.EVENTS) if bool(events & zmq.POLLIN): - print "here we go, nobody told me about new messages!" + print("here we go, nobody told me about new messages!") global STOP_EVERYTHING STOP_EVERYTHING = True raise gevent.GreenletExit() @@ -111,7 +113,7 @@ def server(): socket = ZMQSocket(zmq_context, zmq.REP) socket.bind('ipc://zmqbug') - class Cnt: + class Cnt(object): responded = 0 cnt = Cnt() @@ -125,7 +127,7 @@ def responder(): gevent.spawn(responder) while not STOP_EVERYTHING: - print "cnt.responded=", cnt.responded + print("cnt.responded=", cnt.responded) gevent.sleep(0.5) @@ -133,7 +135,7 @@ def client(): socket = ZMQSocket(zmq_context, zmq.DEALER) socket.connect('ipc://zmqbug') - class Cnt: + class Cnt(object): recv = 0 send = 0 @@ -156,7 +158,7 @@ def sendmsg(): gevent.spawn(sendmsg) while not STOP_EVERYTHING: - print "cnt.recv=", cnt.recv, "cnt.send=", cnt.send + print("cnt.recv=", cnt.recv, "cnt.send=", cnt.send) gevent.sleep(0.5) gevent.spawn(server) diff --git a/tox.ini b/tox.ini index 6f68c02..96bace8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,16 @@ [tox] -envlist = py26,py27,py31,py32 +envlist = py26,py27,py34,py35 [testenv] deps = flake8 - nose + pytest commands = - flake8 --ignore=E501,E128 zerorpc bin - nosetests + flake8 zerorpc bin + pytest -v +passenv = ZPC_TEST_TIME_FACTOR + +[flake8] +ignore = E501,E128 +filename = *.py,zerorpc +exclude = tests,.git,dist,doc,*.egg-info,__pycache__,setup.py diff --git a/zerorpc/channel.py b/zerorpc/channel.py index 7a43f7a..ad21c27 100644 --- a/zerorpc/channel.py +++ b/zerorpc/channel.py @@ -32,9 +32,8 @@ from .exceptions import TimeoutExpired from .channel_base import ChannelBase -from logging import getLogger -logger = getLogger(__name__) +logger = logging.getLogger(__name__) class ChannelMultiplexer(ChannelBase): @@ -80,7 +79,7 @@ def _channel_dispatcher(self): except Exception: logger.exception('zerorpc.ChannelMultiplexer ignoring error on recv') continue - channel_id = event.header.get('response_to', None) + channel_id = event.header.get(u'response_to', None) queue = None if channel_id is not None: @@ -120,10 +119,10 @@ def __init__(self, multiplexer, from_event=None): self._zmqid = None self._queue = gevent.queue.Queue(maxsize=1) if from_event is not None: - self._channel_id = from_event.header['message_id'] + self._channel_id = from_event.header[u'message_id'] self._zmqid = from_event.identity self._multiplexer._active_channels[self._channel_id] = self - logging.debug('<-- new channel %s', self._channel_id) + logger.debug('<-- new channel %s', self._channel_id) self._queue.put(from_event) @property @@ -137,17 +136,17 @@ def emit_is_supported(self): def close(self): if self._channel_id is not None: del self._multiplexer._active_channels[self._channel_id] - logging.debug('-x- closed channel %s', self._channel_id) + logger.debug('-x- closed channel %s', self._channel_id) self._channel_id = None def new_event(self, name, args, xheader=None): event = self._multiplexer.new_event(name, args, xheader) if self._channel_id is None: - self._channel_id = event.header['message_id'] + self._channel_id = event.header[u'message_id'] self._multiplexer._active_channels[self._channel_id] = self - logging.debug('--> new channel %s', self._channel_id) + logger.debug('--> new channel %s', self._channel_id) else: - event.header['response_to'] = self._channel_id + event.header[u'response_to'] = self._channel_id event.identity = self._zmqid return event @@ -206,7 +205,7 @@ def close(self): def _recver(self): while True: event = self._channel.recv() - if event.name == '_zpc_more': + if event.name == u'_zpc_more': try: self._remote_queue_open_slots += int(event.args[0]) except Exception: @@ -240,11 +239,14 @@ def emit_event(self, event, timeout=None): def _request_data(self): open_slots = self._input_queue_size - self._input_queue_reserved self._input_queue_reserved += open_slots - self._channel.emit('_zpc_more', (open_slots,)) + self._channel.emit(u'_zpc_more', (open_slots,)) def recv(self, timeout=None): - if self._verbose: - if self._input_queue_reserved < self._input_queue_size / 2: + # self._channel can be set to None by an 'on_close_if' callback if it + # sees a suitable message from the remote end... + # + if self._verbose and self._channel: + if self._input_queue_reserved < self._input_queue_size // 2: self._request_data() else: self._verbose = True diff --git a/zerorpc/cli.py b/zerorpc/cli.py index 1766b08..3fd6350 100644 --- a/zerorpc/cli.py +++ b/zerorpc/cli.py @@ -24,6 +24,9 @@ # SOFTWARE. +from __future__ import print_function +from builtins import map + import argparse import json import sys @@ -34,6 +37,13 @@ import zerorpc +try: + # Try collections.abc for 3.4+ + from collections.abc import Iterator +except ImportError: + # Fallback to collections for Python 2 + from collections import Iterator + parser = argparse.ArgumentParser( description='Make a zerorpc call to a remote service.' @@ -57,6 +67,8 @@ parser.add_argument('--heartbeat', default=5, metavar='seconds', type=int, help='heartbeat frequency. You should always use \ the same frequency as the server. (default: 5s)') +parser.add_argument('--pool-size', default=None, metavar='count', type=int, + help='size of worker pool. --server only.') parser.add_argument('-j', '--json', default=False, action='store_true', help='arguments are in JSON format and will be be parsed \ before being sent to the remote') @@ -86,7 +98,7 @@ def setup_links(args, socket): if args.bind: for endpoint in args.bind: - print 'binding to "{0}"'.format(endpoint) + print('binding to "{0}"'.format(endpoint), file=sys.stderr) socket.bind(endpoint) addresses = [] if args.address: @@ -94,7 +106,7 @@ def setup_links(args, socket): if args.connect: addresses.extend(args.connect) for endpoint in addresses: - print 'connecting to "{0}"'.format(endpoint) + print('connecting to "{0}"'.format(endpoint), file=sys.stderr) socket.connect(endpoint) @@ -112,11 +124,11 @@ def run_server(args): if callable(server_obj): server_obj = server_obj() - server = zerorpc.Server(server_obj, heartbeat=args.heartbeat) + server = zerorpc.Server(server_obj, heartbeat=args.heartbeat, pool_size=args.pool_size) if args.debug: server.debug = True setup_links(args, server) - print 'serving "{0}"'.format(server_obj_path) + print('serving "{0}"'.format(server_obj_path), file=sys.stderr) return server.run() @@ -222,7 +234,7 @@ def zerorpc_inspect(client, method=None, long_doc=True, include_argspec=True): if not isinstance(remote_methods, dict): (longest_name_len, detailled_methods) = zerorpc_inspect_python_argspecs( - remote_methods, method, long_doc, include_argspec) + remote_methods, method, long_doc, include_argspec) (longest_name_len, detailled_methods) = zerorpc_inspect_generic( remote_methods, method, long_doc, include_argspec) @@ -239,29 +251,29 @@ def run_client(args): if not args.command: (longest_name_len, detailled_methods, service) = zerorpc_inspect(client, long_doc=False, include_argspec=args.inspect) - print '[{0}]'.format(service) + print('[{0}]'.format(service)) if args.inspect: for (name, doc) in detailled_methods: - print name + print(name) else: for (name, doc) in detailled_methods: - print '{0} {1}'.format(name.ljust(longest_name_len), doc) + print('{0} {1}'.format(name.ljust(longest_name_len), doc)) return if args.inspect: (longest_name_len, detailled_methods, service) = zerorpc_inspect(client, method=args.command) if detailled_methods: (name, doc) = detailled_methods[0] - print '[{0}]\n{1}\n\n{2}\n'.format(service, name, doc) + print('[{0}]\n{1}\n\n{2}\n'.format(service, name, doc)) else: - print '[{0}]\nNo documentation for "{1}".'.format(service, args.command) + print('[{0}]\nNo documentation for "{1}".'.format(service, args.command)) return if args.json: call_args = [json.loads(x) for x in args.params] else: call_args = args.params results = client(args.command, *call_args) - if getattr(results, 'next', None) is None: + if not isinstance(results, Iterator): if args.print_json: json.dump(results, sys.stdout) else: diff --git a/zerorpc/context.py b/zerorpc/context.py index 327f1ff..debce26 100644 --- a/zerorpc/context.py +++ b/zerorpc/context.py @@ -23,10 +23,13 @@ # SOFTWARE. +from __future__ import absolute_import +from future.utils import tobytes + import uuid import random -import gevent_zmq as zmq +from . import gevent_zmq as zmq class Context(zmq.Context): @@ -100,7 +103,7 @@ def get_instance(): return Context._instance def _reset_msgid(self): - self._msg_id_base = str(uuid.uuid4())[8:] + self._msg_id_base = tobytes(uuid.uuid4().hex)[8:] self._msg_id_counter = random.randrange(0, 2 ** 32) self._msg_id_counter_stop = random.randrange(self._msg_id_counter, 2 ** 32) @@ -109,12 +112,12 @@ def new_msgid(self): self._reset_msgid() else: self._msg_id_counter = (self._msg_id_counter + 1) - return '{0:08x}{1}'.format(self._msg_id_counter, self._msg_id_base) + return tobytes('{0:08x}'.format(self._msg_id_counter)) + self._msg_id_base def register_middleware(self, middleware_instance): registered_count = 0 self._middlewares.append(middleware_instance) - for hook in self._hooks.keys(): + for hook in self._hooks: functor = getattr(middleware_instance, hook, None) if functor is None: try: diff --git a/zerorpc/core.py b/zerorpc/core.py index 0bd820c..ec2e008 100644 --- a/zerorpc/core.py +++ b/zerorpc/core.py @@ -23,6 +23,11 @@ # SOFTWARE. +from __future__ import absolute_import +from builtins import str +from builtins import zip +from future.utils import iteritems + import sys import traceback import gevent.pool @@ -31,14 +36,14 @@ import gevent.local import gevent.lock -import gevent_zmq as zmq +from . import gevent_zmq as zmq from .exceptions import TimeoutExpired, RemoteError, LostRemote from .channel import ChannelMultiplexer, BufferedChannel from .socket import SocketBase from .heartbeat import HeartBeatOnChannel from .context import Context from .decorators import DecoratorBase, rep -import patterns +from . import patterns from logging import getLogger logger = getLogger(__name__) @@ -62,7 +67,7 @@ def __init__(self, channel, methods=None, name=None, context=None, self._inject_builtins() self._heartbeat_freq = heartbeat - for (k, functor) in self._methods.items(): + for (k, functor) in iteritems(self._methods): if not isinstance(functor, DecoratorBase): self._methods[k] = rep(functor) @@ -73,9 +78,8 @@ def _filter_methods(cls, self, methods): server_methods = set(k for k in dir(cls) if not k.startswith('_')) return dict((k, getattr(methods, k)) for k in dir(methods) - if callable(getattr(methods, k)) - and not k.startswith('_') - and k not in server_methods + if callable(getattr(methods, k)) and + not k.startswith('_') and k not in server_methods ) @staticmethod @@ -98,11 +102,11 @@ def _format_args_spec(self, args_spec, r=None): return r def _zerorpc_inspect(self): - methods = dict((m, f) for m, f in self._methods.items() + methods = dict((m, f) for m, f in iteritems(self._methods) if not m.startswith('_')) detailled_methods = dict((m, dict(args=self._format_args_spec(f._zerorpc_args()), - doc=f._zerorpc_doc())) for (m, f) in methods.items()) + doc=f._zerorpc_doc())) for (m, f) in iteritems(methods)) return {'name': self._name, 'methods': detailled_methods} @@ -134,7 +138,7 @@ def _print_traceback(self, protocol_v1, exc_infos): return (name, human_msg, human_traceback) def _async_task(self, initial_event): - protocol_v1 = initial_event.header.get('v', 1) < 2 + protocol_v1 = initial_event.header.get(u'v', 1) < 2 channel = self._multiplexer.channel(initial_event) hbchan = HeartBeatOnChannel(channel, freq=self._heartbeat_freq, passive=protocol_v1) @@ -153,7 +157,7 @@ def _async_task(self, initial_event): except Exception: exc_infos = list(sys.exc_info()) human_exc_infos = self._print_traceback(protocol_v1, exc_infos) - reply_event = bufchan.new_event('ERR', human_exc_infos, + reply_event = bufchan.new_event(u'ERR', human_exc_infos, self._context.hook_get_task_context()) self._context.hook_server_inspect_exception(event, reply_event, exc_infos) bufchan.emit_event(reply_event) @@ -197,7 +201,7 @@ def close(self): def _handle_remote_error(self, event): exception = self._context.hook_client_handle_remote_error(event) if not exception: - if event.header.get('v', 1) >= 2: + if event.header.get(u'v', 1) >= 2: (name, msg, traceback) = event.args exception = RemoteError(name, msg, traceback) else: @@ -234,6 +238,23 @@ def raise_error(ex): reply_event, self._handle_remote_error) def __call__(self, method, *args, **kargs): + # here `method` is either a string of bytes or an unicode string in + # Python2 and Python3. Python2: str aka a byte string containing ASCII + # (unless the user explicitly provide an unicode string). Python3: str + # aka an unicode string (unless the user explicitly provide a byte + # string). + # zerorpc protocol requires an utf-8 encoded string at the msgpack + # level. msgpack will encode any unicode string object to UTF-8 and tag + # it `string`, while a bytes string will be tagged `bin`. + # + # So when we get a bytes string, we assume it to be an UTF-8 string + # (ASCII is contained in UTF-8) that we decode to an unicode string. + # Right after, msgpack-python will re-encode it as UTF-8. Yes this is + # terribly inefficient with Python2 because most of the time `method` + # will already be an UTF-8 encoded bytes string. + if isinstance(method, bytes): + method = method.decode('utf-8') + timeout = kargs.get('timeout', self._timeout) channel = self._multiplexer.channel() hbchan = HeartBeatOnChannel(channel, freq=self._heartbeat_freq, @@ -245,7 +266,10 @@ def __call__(self, method, *args, **kargs): self._context.hook_client_before_request(request_event) bufchan.emit_event(request_event) - if kargs.get('async', False) is False: + # In python 3.7, "async" is a reserved keyword, clients should now use + # "async_": support both for the time being + if (kargs.get('async', False) is False and + kargs.get('async_', False) is False): return self._process_response(request_event, bufchan, timeout) async_result = gevent.event.AsyncResult() @@ -366,7 +390,7 @@ class Subscriber(Puller): def __init__(self, methods=None, context=None): super(Subscriber, self).__init__(methods=methods, context=context, zmq_socket=zmq.SUB) - self._events.setsockopt(zmq.SUBSCRIBE, '') + self._events.setsockopt(zmq.SUBSCRIBE, b'') def fork_task_context(functor, context=None): @@ -382,7 +406,7 @@ def fork_task_context(functor, context=None): - task1 is created to handle this event this task will be linked to the initial event context. zerorpc.Server does that for you. - task1 make use of some zerorpc.Client instances, the initial - event context is transfered on every call. + event context is transferred on every call. - task1 spawn a new task2. - task2 make use of some zerorpc.Client instances, it's a fresh @@ -391,7 +415,7 @@ def fork_task_context(functor, context=None): - task1 spawn a new fork_task_context(task3). - task3 make use of some zerorpc.Client instances, the initial - event context is transfered on every call. + event context is transferred on every call. A real use case is a distributed tracer. Each time a new event is created, a trace_id is injected in it or copied from the current task diff --git a/zerorpc/decorators.py b/zerorpc/decorators.py index 60cf1f8..43dfa64 100644 --- a/zerorpc/decorators.py +++ b/zerorpc/decorators.py @@ -24,7 +24,7 @@ import inspect -from .patterns import * # noqa +from .patterns import ReqRep, ReqStream class DecoratorBase(object): @@ -33,7 +33,7 @@ class DecoratorBase(object): def __init__(self, functor): self._functor = functor self.__doc__ = functor.__doc__ - self.__name__ = functor.__name__ + self.__name__ = getattr(functor, "__name__", str(functor)) def __get__(self, instance, type_instance=None): if instance is None: diff --git a/zerorpc/events.py b/zerorpc/events.py index ae82fb1..f87d0b5 100644 --- a/zerorpc/events.py +++ b/zerorpc/events.py @@ -23,6 +23,10 @@ # SOFTWARE. +from __future__ import absolute_import +from builtins import str +from builtins import range + import msgpack import gevent.pool import gevent.queue @@ -30,13 +34,29 @@ import gevent.local import gevent.lock import logging +import sys -import gevent_zmq as zmq +from . import gevent_zmq as zmq from .exceptions import TimeoutExpired from .context import Context from .channel_base import ChannelBase +if sys.version_info < (2, 7): + def get_pyzmq_frame_buffer(frame): + return frame.buffer[:] +else: + def get_pyzmq_frame_buffer(frame): + return frame.buffer + +# gevent <= 1.1.0.rc5 is missing the Python3 __next__ method. +if sys.version_info >= (3, 0) and gevent.version_info <= (1, 1, 0, 'rc', '5'): + setattr(gevent.queue.Channel, '__next__', gevent.queue.Channel.next) + + +logger = logging.getLogger(__name__) + + class SequentialSender(object): def __init__(self, socket): @@ -44,7 +64,7 @@ def __init__(self, socket): def _send(self, parts): e = None - for i in xrange(len(parts) - 1): + for i in range(len(parts) - 1): try: self._socket.send(parts[i], copy=False, flags=zmq.SNDMORE) except (gevent.GreenletExit, gevent.Timeout) as e: @@ -146,11 +166,15 @@ class Event(object): __slots__ = ['_name', '_args', '_header', '_identity'] + # protocol details: + # - `name` and `header` keys must be unicode strings. + # - `message_id` and 'response_to' values are opaque bytes string. + # - `v' value is an integer. def __init__(self, name, args, context, header=None): self._name = name self._args = args if header is None: - self._header = {'message_id': context.new_msgid(), 'v': 3} + self._header = {u'message_id': context.new_msgid(), u'v': 3} else: self._header = header self._identity = None @@ -180,11 +204,13 @@ def identity(self, v): self._identity = v def pack(self): - return msgpack.Packer().pack((self._header, self._name, self._args)) + payload = (self._header, self._name, self._args) + r = msgpack.Packer(use_bin_type=True).pack(payload) + return r @staticmethod def unpack(blob): - unpacker = msgpack.Unpacker() + unpacker = msgpack.Unpacker(raw=False) unpacker.feed(blob) unpacked_msg = unpacker.unpack() @@ -249,17 +275,17 @@ def __del__(self): try: if not self._socket.closed: self.close() - except AttributeError: + except (AttributeError, TypeError): pass def close(self): try: self._send.close() - except AttributeError: + except (AttributeError, TypeError, gevent.GreenletExit): pass try: self._recv.close() - except AttributeError: + except (AttributeError, TypeError, gevent.GreenletExit): pass self._socket.close() @@ -272,9 +298,9 @@ def debug(self, v): if v != self._debug: self._debug = v if self._debug: - logging.debug('debug enabled') + logger.debug('debug enabled') else: - logging.debug('debug disabled') + logger.debug('debug disabled') def _resolve_endpoint(self, endpoint, resolve=True): if resolve: @@ -290,14 +316,21 @@ def connect(self, endpoint, resolve=True): r = [] for endpoint_ in self._resolve_endpoint(endpoint, resolve): r.append(self._socket.connect(endpoint_)) - logging.debug('connected to %s (status=%s)', endpoint_, r[-1]) + logger.debug('connected to %s (status=%s)', endpoint_, r[-1]) return r def bind(self, endpoint, resolve=True): r = [] for endpoint_ in self._resolve_endpoint(endpoint, resolve): r.append(self._socket.bind(endpoint_)) - logging.debug('bound to %s (status=%s)', endpoint_, r[-1]) + logger.debug('bound to %s (status=%s)', endpoint_, r[-1]) + return r + + def disconnect(self, endpoint, resolve=True): + r = [] + for endpoint_ in self._resolve_endpoint(endpoint, resolve): + r.append(self._socket.disconnect(endpoint_)) + logger.debug('disconnected from %s (status=%s)', endpoint_, r[-1]) return r def new_event(self, name, args, xheader=None): @@ -308,12 +341,12 @@ def new_event(self, name, args, xheader=None): def emit_event(self, event, timeout=None): if self._debug: - logging.debug('--> %s', event) + logger.debug('--> %s', event) if event.identity: parts = list(event.identity or list()) - parts.extend(['', event.pack()]) + parts.extend([b'', event.pack()]) elif self._zmq_socket_type in (zmq.DEALER, zmq.ROUTER): - parts = ('', event.pack()) + parts = (b'', event.pack()) else: parts = (event.pack(),) self._send(parts, timeout) @@ -329,10 +362,10 @@ def recv(self, timeout=None): else: identity = None blob = parts[0] - event = Event.unpack(blob) + event = Event.unpack(get_pyzmq_frame_buffer(blob)) event.identity = identity if self._debug: - logging.debug('<-- %s', event) + logger.debug('<-- %s', event) return event def setsockopt(self, *args): diff --git a/zerorpc/gevent_zmq.py b/zerorpc/gevent_zmq.py index b4c89e3..54420ae 100644 --- a/zerorpc/gevent_zmq.py +++ b/zerorpc/gevent_zmq.py @@ -27,6 +27,16 @@ # We want to act like zmq from zmq import * # noqa +try: + # Try to import enums for pyzmq >= 23.0.0 + from zmq.constants import * # noqa +except ImportError: + pass + + +# Explicit import to please flake8 +from zmq import ZMQError + # A way to access original zmq import zmq as _zmq @@ -100,7 +110,7 @@ def connect(self, *args, **kwargs): while True: try: return super(Socket, self).connect(*args, **kwargs) - except _zmq.ZMQError, e: + except _zmq.ZMQError as e: if e.errno not in (_zmq.EAGAIN, errno.EINTR): raise @@ -122,7 +132,7 @@ def send(self, data, flags=0, copy=True, track=False): # send and recv on the socket. self._on_state_changed() return msg - except _zmq.ZMQError, e: + except _zmq.ZMQError as e: if e.errno not in (_zmq.EAGAIN, errno.EINTR): raise self._writable.clear() @@ -162,7 +172,7 @@ def recv(self, flags=0, copy=True, track=False): # send and recv on the socket. self._on_state_changed() return msg - except _zmq.ZMQError, e: + except _zmq.ZMQError as e: if e.errno not in (_zmq.EAGAIN, errno.EINTR): raise self._readable.clear() diff --git a/zerorpc/heartbeat.py b/zerorpc/heartbeat.py index 5ac9206..23b974d 100644 --- a/zerorpc/heartbeat.py +++ b/zerorpc/heartbeat.py @@ -30,7 +30,7 @@ import gevent.local import gevent.lock -from .exceptions import * # noqa +from .exceptions import LostRemote, TimeoutExpired from .channel_base import ChannelBase @@ -81,7 +81,7 @@ def _heartbeat(self): gevent.kill(self._parent_coroutine, self._lost_remote_exception()) break - self._channel.emit('_zpc_hb', (0,)) # 0 -> compat with protocol v2 + self._channel.emit(u'_zpc_hb', (0,)) # 0 -> compat with protocol v2 def _start_heartbeat(self): if self._heartbeat_task is None and self._heartbeat_freq is not None and not self._closed: @@ -91,12 +91,12 @@ def _recver(self): while True: event = self._channel.recv() if self._compat_v2 is None: - self._compat_v2 = event.header.get('v', 0) < 3 - if event.name == '_zpc_hb': + self._compat_v2 = event.header.get(u'v', 0) < 3 + if event.name == u'_zpc_hb': self._remote_last_hb = time.time() self._start_heartbeat() if self._compat_v2: - event.name = '_zpc_more' + event.name = u'_zpc_more' self._input_queue.put(event) else: self._input_queue.put(event) @@ -106,8 +106,8 @@ def _lost_remote_exception(self): self._heartbeat_freq * 2)) def new_event(self, name, args, header=None): - if self._compat_v2 and name == '_zpc_more': - name = '_zpc_hb' + if self._compat_v2 and name == u'_zpc_more': + name = u'_zpc_hb' return self._channel.new_event(name, args, header) def emit_event(self, event, timeout=None): diff --git a/zerorpc/patterns.py b/zerorpc/patterns.py index bf6ede5..3623e17 100644 --- a/zerorpc/patterns.py +++ b/zerorpc/patterns.py @@ -23,23 +23,23 @@ # SOFTWARE. -class ReqRep: +class ReqRep(object): def process_call(self, context, channel, req_event, functor): context.hook_server_before_exec(req_event) result = functor(*req_event.args) - rep_event = channel.new_event('OK', (result,), + rep_event = channel.new_event(u'OK', (result,), context.hook_get_task_context()) context.hook_server_after_exec(req_event, rep_event) channel.emit_event(rep_event) def accept_answer(self, event): - return event.name in ('OK', 'ERR') + return event.name in (u'OK', u'ERR') def process_answer(self, context, channel, req_event, rep_event, handle_remote_error): try: - if rep_event.name == 'ERR': + if rep_event.name == u'ERR': exception = handle_remote_error(rep_event) context.hook_client_after_request(req_event, rep_event, exception) raise exception @@ -49,39 +49,39 @@ def process_answer(self, context, channel, req_event, rep_event, channel.close() -class ReqStream: +class ReqStream(object): def process_call(self, context, channel, req_event, functor): context.hook_server_before_exec(req_event) xheader = context.hook_get_task_context() for result in iter(functor(*req_event.args)): - channel.emit('STREAM', result, xheader) - done_event = channel.new_event('STREAM_DONE', None, xheader) + channel.emit(u'STREAM', result, xheader) + done_event = channel.new_event(u'STREAM_DONE', None, xheader) # NOTE: "We" made the choice to call the hook once the stream is done, - # the other choice was to call it at each iteration. I don't think that - # one choice is better than the other, so I'm fine with changing this + # the other choice was to call it at each iteration. I donu't think that + # one choice is better than the other, so Iu'm fine with changing this # or adding the server_after_iteration and client_after_iteration hooks. context.hook_server_after_exec(req_event, done_event) channel.emit_event(done_event) def accept_answer(self, event): - return event.name in ('STREAM', 'STREAM_DONE') + return event.name in (u'STREAM', u'STREAM_DONE') def process_answer(self, context, channel, req_event, rep_event, handle_remote_error): def is_stream_done(rep_event): - return rep_event.name == 'STREAM_DONE' + return rep_event.name == u'STREAM_DONE' channel.on_close_if = is_stream_done def iterator(req_event, rep_event): try: - while rep_event.name == 'STREAM': + while rep_event.name == u'STREAM': # Like in process_call, we made the choice to call the # after_exec hook only when the stream is done. yield rep_event.args rep_event = channel.recv() - if rep_event.name == 'ERR': + if rep_event.name == u'ERR': exception = handle_remote_error(rep_event) context.hook_client_after_request(req_event, rep_event, exception) raise exception diff --git a/zerorpc/socket.py b/zerorpc/socket.py index 51f99c2..35cb7e4 100644 --- a/zerorpc/socket.py +++ b/zerorpc/socket.py @@ -42,6 +42,9 @@ def connect(self, endpoint, resolve=True): def bind(self, endpoint, resolve=True): return self._events.bind(endpoint, resolve) + def disconnect(self, endpoint, resolve=True): + return self._events.disconnect(endpoint, resolve) + @property def debug(self): return self._events.debug diff --git a/zerorpc/version.py b/zerorpc/version.py index 3208b41..4324a5a 100644 --- a/zerorpc/version.py +++ b/zerorpc/version.py @@ -23,7 +23,7 @@ # SOFTWARE. __title__ = 'zerorpc' -__version__ = '0.5.1' +__version__ = '0.6.3' __author__ = 'François-Xavier Bourlet .' __license__ = 'MIT' __copyright__ = 'Copyright 2015 François-Xavier Bourlet .'