Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial release

  • Loading branch information...
commit 6d50438d96f458f196c3e002260fbff9449ceadb 0 parents
@benoitc authored
25 .gitignore
@@ -0,0 +1,25 @@
+*.gem
+*.swp
+*.pyc
+*#*
+build
+dist
+setuptools-*
+.svn/*
+.DS_Store
+*.so
+.Python
+distribute-0.6.8-py2.6.egg
+distribute-0.6.8.tar.gz
+*.egg-info
+nohup.out
+.coverage
+doc/.sass-cache
+docs/_build
+bin/
+lib/
+man/
+include/
+html/
+.tox
+htmlcov
22 LICENSE
@@ -0,0 +1,22 @@
+2012 (c) Benoît Chesneau <benoitc@e-engura.org>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
8 MANIFEST.in
@@ -0,0 +1,8 @@
+include NOTICE
+include LICENSE
+include README.rst
+include THANKS
+include UNLICENSE
+recursive-include docs *
+recursive-include examples *
+recursive-include tests *
66 NOTICE
@@ -0,0 +1,66 @@
+gaffer
+------
+
+2012 (c) Benoît Chesneau <benoitc@e-engura.org>
+
+gaffer is available in the public domain (see UNLICENSE). gaffer
+is also optionally available under the MIT License (see LICENSE), meant
+especially for jurisdictions that do not recognize public domain works.
+
+
+
+tornado_pyuv
+------------
+
+Copyright (C) 2011 by Saúl Ibarra Corretgé
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+pidfile.py
+----------
+
+2009,2012 (c) Benoît Chesneau <benoitc@e-engura.org>
+2009,2012 (c) Paul J. Davis <paul.joseph.davis@gmail.com>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+gafferctl.py
+------------
+
+Based and couchapp and placed in the domain public by Benoît Chesneau.
4 README.rst
@@ -0,0 +1,4 @@
+uzmq
+====
+
+libuv interface for ZeroMQ.
24 UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
62 setup.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -
+#
+# This file is part of uvzmq. See the NOTICE for more information.
+
+import os
+import sys
+
+from setuptools import setup, find_packages
+
+from uzmq import __version__
+
+py_version = sys.version_info[:2]
+
+if py_version < (2, 6):
+ raise RuntimeError('On Python 2, Gaffer requires Python 2.6 or better')
+
+
+CLASSIFIERS = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.0',
+ 'Programming Language :: Python :: 3.1',
+ 'Programming Language :: Python :: 3.2',
+ 'Topic :: System :: Boot',
+ 'Topic :: System :: Monitoring',
+ 'Topic :: System :: Systems Administration',
+ 'Topic :: Software Development :: Libraries']
+
+
+# read long description
+with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
+ long_description = f.read()
+
+DATA_FILES = [
+ ('uzmq', ["LICENSE", "MANIFEST.in", "NOTICE", "README.rst",
+ "THANKS", "UNLICENSE"])
+ ]
+
+
+setup(name='uzmq',
+ version = __version__,
+ description = 'libuv interface for ZeroMQ',
+ long_description = long_description,
+ classifiers = CLASSIFIERS,
+ license = 'BSD',
+ url = 'http://github.com/benoitc/uzmq',
+ author = 'Benoit Chesneau',
+ author_email = 'benoitc@e-engura.org',
+ packages=find_packages(),
+ install_requires = [
+ 'pyuv>=0.8.3',
+ 'pyzmq'
+ ],
+ data_files = DATA_FILES)
127 test/test_poll.py
@@ -0,0 +1,127 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2010-2012 Brian Granger, Min Ragan-Kelley
+#
+# This file is part of pyzmq
+#
+# Distributed under the terms of the New BSD License. The full license is in
+# the file COPYING.BSD, distributed as part of this software.
+#-----------------------------------------------------------------------------
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import time
+import os
+import threading
+
+import pyuv
+import zmq
+from zmq.tests import BaseZMQTestCase
+
+from uzmq import ZMQPoll
+
+
+#-----------------------------------------------------------------------------
+# Tests
+#-----------------------------------------------------------------------------
+
+class TestPoll(BaseZMQTestCase):
+
+ def test_simple(self):
+ """Tornado poller implementation maps events correctly"""
+ req,rep = self.create_bound_pair(zmq.REQ, zmq.REP)
+
+ loop = pyuv.Loop.default_loop()
+ poll = ZMQPoll(loop, rep)
+
+ r = []
+ def cb(handle, ev, error):
+ r.append(ev & pyuv.UV_READABLE)
+ r.append(rep.recv())
+
+ poll.start(pyuv.UV_READABLE, cb)
+ req.send(b'req')
+ t = pyuv.Timer(loop)
+
+ def stop(h):
+ poll.stop()
+
+ t = pyuv.Timer(loop)
+ t.start(stop, 0.4, 0.0)
+ loop.run()
+
+ assert r == [1, b'req']
+
+ def test_poll_rw(self):
+ """Tornado poller implementation maps events correctly"""
+ req,rep = self.create_bound_pair(zmq.REQ, zmq.REP)
+
+ loop = pyuv.Loop.default_loop()
+ poll = ZMQPoll(loop, rep)
+ poll1 = ZMQPoll(loop, req)
+
+ r = []
+ def cb(handle, ev, error):
+ r.append(ev & pyuv.UV_READABLE)
+ r.append(rep.recv())
+
+ def cb1(handle, ev, error):
+ handle.stop()
+ r.append(ev & pyuv.UV_WRITABLE)
+ req.send(b'req')
+
+ poll.start(pyuv.UV_READABLE, cb)
+ poll1.start(pyuv.UV_WRITABLE, cb1)
+
+ t = pyuv.Timer(loop)
+
+ def stop(h):
+ poll.stop()
+ poll1.close()
+
+ t = pyuv.Timer(loop)
+ t.start(stop, 0.4, 0.0)
+ loop.run()
+
+ assert r == [2, 1, b'req']
+
+ def test_multiple_write(self):
+ """Tornado poller implementation maps events correctly"""
+ req,rep = self.create_bound_pair(zmq.REQ, zmq.REP)
+
+ loop = pyuv.Loop.default_loop()
+ poll = ZMQPoll(loop, rep)
+ poll1 = ZMQPoll(loop, req)
+
+ r = []
+ r1 = []
+ r2 = []
+ def cb(handle, ev, error):
+ r.append(ev & pyuv.UV_READABLE)
+
+ data = rep.recv()
+ r.append(data)
+ rep.send(data)
+ if len(r1) == 2:
+ handle.stop()
+
+
+ def cb1(handle, ev, error):
+ if ev & pyuv.UV_WRITABLE:
+ r1.append(ev & pyuv.UV_WRITABLE)
+ req.send(b'req')
+ else:
+ r2.append(req.recv())
+
+ if len(r2) == 2:
+ handle.stop()
+
+ poll.start(pyuv.UV_READABLE, cb)
+ poll1.start(pyuv.UV_READABLE | pyuv.UV_WRITABLE, cb1)
+
+ loop.run()
+
+ assert r == [1, b'req', 1, b'req']
+ assert r1 == [2, 2]
+ assert r2 == [b'req', b'req']
36 test1.py
@@ -0,0 +1,36 @@
+
+from uzmq import ZMQPoll
+import pyuv
+
+req,rep = self.create_bound_pair(zmq.REQ, zmq.REP)
+
+loop = pyuv.Loop.default_loop()
+poll = ZMQPoll(loop, rep)
+poll1 = ZMQPoll(loop, req)
+
+r = []
+r1 = []
+def cb(handle, ev, error):
+ r.append(ev & pyuv.UV_READABLE)
+
+ data = rep.recv()
+ r.append(data)
+ rep.send(data)
+
+ if len(r1) == 2:
+ handle.stop()
+
+def cb1(handle, ev, error):
+ if len(r1) == 2:
+ handle.stop()
+
+ r1.append(ev & pyuv.UV_WRITABLE)
+ req.send(b'req')
+
+poll.start(pyuv.UV_READABLE, cb)
+poll1.start(pyuv.UV_WRITABLE, cb1)
+
+loop.run()
+
+assert r == [1, b'req', 1, b'req']
+assert r1 == [2, 2]
9 uzmq/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -
+#
+# This file is part of uzmq. See the NOTICE for more information.
+
+version_info = (0, 1, 0)
+__version__ = ".".join(map(str, version_info))
+
+from uzmq.poll import ZMQPoll
+from uzmq.sock import ZMQ
158 uzmq/poll.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -
+#
+# This file is part of gaffer. See the NOTICE for more information.
+
+"""
+ZMQPoll: ZMQ Poll handle
+
+"""
+
+import errno
+import logging
+
+import pyuv
+import six
+import zmq
+
+class ZMQPoll(object):
+
+ def __init__(self, loop, socket):
+ """
+ :param loop: loop object where this handle runs (accessible
+ through :py:attr:`Poll.loop`). :param int fd: File descriptor
+ to be monitored for readibility or writability.
+
+ ``ZMQPoll`` ZMQPoll handles can be used to monitor an arbitrary file
+ descritor for readability or writability. On Unix any file
+ descriptor is supported but on Windows only sockets are
+ supported.
+
+ .. py:attribute:: loop
+
+ *Read only*
+
+ :py:class:`pyuv.Loop` object where this handle runs.
+
+ .. py:attribute:: active
+
+ *Read only*
+
+ Indicates if this handle is active.
+
+ .. py:attribute:: closed
+
+ *Read only*
+
+ Indicates if this handle is closing or already closed.
+
+ """
+ self.loop = loop
+ self.socket = socket
+
+ self.active = False
+ self.closed = False
+
+ # initialize private variable
+ self._poller = zmq.Poller()
+ self._timer = pyuv.Timer(loop)
+ self._callback = None
+ self._started = False
+
+
+ def start(self, events, callback):
+ """
+ :param int events: Mask of events that will be detected. The
+ possible events are `pyuv.UV_READABLE` or `pyuv.UV_WRITABLE`.
+
+ :param callable callback: Function that will be called when the
+ ``Poll`` handle receives events.
+
+ Start or update the event mask of the ``ZMQPoll`` handle.
+
+ Callback signature: ``callback(poll_handle, events,
+ errorno)``.
+ """
+
+ assert self.active == False
+
+ if not six.callable(callback):
+ raise TypeError("a callable is required")
+
+ self._callback = callback
+
+ z_events = 0
+ if events & pyuv.UV_READABLE:
+ z_events |= zmq.POLLIN
+
+ if events & pyuv.UV_WRITABLE:
+ z_events |= zmq.POLLOUT
+
+ if self._started:
+ self._poller.modify(self.socket, z_events)
+ else:
+ self._poller.register(self.socket, z_events)
+ self._timer.start(self._poll, 0.1, 0.1)
+ self._started = True
+
+ def stop(self):
+ """ Stop the ``Poll`` handle. """
+ self._timer.stop()
+ self.active = False
+
+ def close(self, callback=None):
+ """
+ :param callable callback: Function that will be called after the
+ ``ZMQPoll`` handle is closed.
+
+ Close the ``ZMQPoll`` handle. After a handle has been closed no other
+ operations can be performed on it.
+ """
+
+ self.active = False
+ self.closed = True
+ self._poller.unregister(self.socket)
+ self._timer.close()
+ self._started = False
+ if six.callable(callback):
+ callback(self)
+
+ def _poll(self, handle):
+ try:
+ results = self._poller.poll(100)
+ except Exception as e:
+ print(e)
+ if (getattr(e, 'errno', None) == errno.EINTR or
+ (isinstance(getattr(e, 'args', None), tuple) and
+ len(e.args) == 2 and e.args[0] == errno.EINTR)):
+ return
+ except getattr(e, 'errno', None) == zmq.ETERM:
+ self.close()
+ self._callback(self, 0, e.errno)
+
+
+ print(results)
+ for fd, z_events in results:
+ events = 0
+ if z_events & zmq.POLLIN:
+ events |= pyuv.UV_READABLE
+ print("read")
+
+ if z_events & zmq.POLLOUT:
+ events |= pyuv.UV_WRITABLE
+ print("write")
+
+ try:
+ self._callback(self, events, None)
+ except (OSError, IOError) as e:
+ if e.args[0] == errno.EPIPE:
+ # Happens when the client closes the connection
+ print("fuck")
+ pass
+ else:
+ logging.error("Exception in I/O handler for fd %s",
+ fd, exc_info=True)
+
+ except Exception:
+ logging.error("Exception in I/O handler for fd %s",
+ fd, exc_info=True)
+
137 uzmq/sock.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -
+#
+# This file is part of zmq. See the NOTICE for more information.
+
+from collections import deque
+
+import pyuv
+import six
+import zmq
+
+from .poll import ZMQPoll
+
+class ZMQ(object):
+
+ def __init__(self, socket, loop=None):
+ self.loop = loop or pyuv.Loop()
+ self.socket = socket
+ self.active = False
+ self.closed = True
+
+ # shortcircuit some socket methods
+ self.bind = self.socket.bind
+ self.bind_to_random_port = self.socket.bind_to_random_port
+ self.setsockopt = self.socket.setsockopt
+ self.getsockopt = self.socket.getsockopt
+ self.setsockopt_string = self.socket.setsockopt_string
+ self.getsockopt_string = self.socket.getsockopt_string
+ self.setsockopt_unicode = self.socket.setsockopt_unicode
+ self.getsockopt_unicode = self.socket.getsockopt_unicode
+
+ self._poll = Poll(loop, socket)
+ self._events = 0
+
+ self._send_queue = deque()
+ self._read_cb = None
+ self._read_copy = True
+
+ def connect(self, addr, callback=None):
+ self.socket.connect(addr)
+ if six.callable(callback):
+ callback(self)
+
+
+ def start_read(self, callbacki, copy=True):
+ """
+ :param callback: callable
+ callback must take exactly one argument, which will be a
+ list, as returned by socket.recv_multipart()
+ if callback is None, recv callbacks are disabled.
+ :param copy: bool
+ copy is passed directly to recv, so if copy is False,
+ callback will receive Message objects. If copy is True,
+ then callback will receive bytes/str objects.
+
+ Start reading for incoming messages from the remote endpoint.
+ """
+ if not six.callable(callback):
+ raise TypeError("a callable is required")
+
+ if not self._events & pyuv.UV_READABLE:
+ self._events |= pyuv.UV_READABLE
+ self._poll.start(self._events, self._on_event)
+ self._read_cb = callback
+ self._read_copy = copy
+
+
+ def stop_read(self):
+ self._events = self._events & (~pyuv.UV_READABLE)
+ self._poll.start(self._events, self._on_event)
+
+ def write(self, msg, flags=0, copy=True, track=False,
+ callback=None):
+ return self.write_multipart([msg], flags=flags, copy=copy,
+ track=track, callback=callback)
+
+ def write_multipart(self, msg, flags=0, copy=True, track=False,
+ callback=None):
+
+ kwargs = dict(flags=flags, copy=copy, track=track)
+ self._send_queue.put((msg, kwargs, callback))
+
+ if not self._events & pyuv.UV_WRITABLE:
+ self._events |= pyuv.UV_WRITABLE
+ self._poll.start(self._events, self._on_event)
+
+ def close(self):
+ self._poll.close()
+
+ def flush(self):
+ if self._send_queue:
+ while True:
+ try:
+ self._send()
+ except IndexError:
+ break
+
+ def _send(self):
+ (msg, kwargs, cb) = self._send_queue.popleft()
+ try:
+ status = self.socket.send_multipart(msg, **kwargs)
+ except zmq.ZMQError as e:
+ logging.error("SEND Error: %s", e)
+ status = e
+
+ if six.callable(cb):
+ cb(self, msg, status)
+
+
+ def _on_events(self, handle, events, err):
+ if events & pyuv.UV_READABLE:
+ self._on_read()
+
+ if events & pyuv.UV_WRITEABLE:
+ self._on_write()
+
+ def _on_read(self):
+ if self.closed or not self.active:
+ return
+
+ try:
+ msg = self.socket.recv_multipart(zmq.NOBLOCK,
+ copy=self._read_copu)
+ except zmq.ZMQError as e:
+ if e.errno == zmq.EAGAIN:
+ # state changed since poll event
+ return
+ else:
+ logging.error("RECV Error: %s" % zmq.strerror(e.errno))
+ self._read_cb(self, None, e.errno)
+ else:
+ self._read_cb(self, msg, None)
+
+ def _on_write(self):
+ try:
+ self._send()
+ except IndexError:
+ pass
Please sign in to comment.
Something went wrong with that request. Please try again.