Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial import

  • Loading branch information...
commit 410dfdad0afc1995494b3a4164cfb288bccfcf67 0 parents
@rep authored
37 LICENSE
@@ -0,0 +1,37 @@
+All files in evnet are Copyright (C) 2010,2011 Mark Schloesser.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Alternatively, the contents of this package may be used under the terms
+of the GNU General Public License ("GPL") version 2 or any later version,
+in which case the provisions of the GPL are applicable instead of the
+above. If you wish to allow the use of your version of this package only
+under the terms of the GPL and not to allow others to use your version of
+this file under the BSD license, indicate your decision by deleting the
+provisions above and replace them with the notice and other provisions
+required by the GPL in this and the other files of this package. If you do
+not delete the provisions above, a recipient may use your version of this
+file under either the BSD or the GPL.
+
14 README.md
@@ -0,0 +1,14 @@
+evnet
+===========
+Efficient non-blocking/asynchronous networking library built on top of libev through its pyev bindings.
+
+## Getting Started
+
+See examples/.
+
+## Learn More
+
+ - [libev](http://software.schmorp.de/pkg/libev.html)
+ - [pyev](http://code.google.com/p/pyev/)
+ - [Repository at github](http://github.com/rep/evnet)
+
571 evnet/__init__.py
@@ -0,0 +1,571 @@
+
+import traceback
+import signal
+import socket
+import logging
+import errno
+import threading
+import Queue
+
+import pyev
+from OpenSSL import SSL
+
+from util import EventGen, WeakMethod
+
+default_loop = pyev.default_loop()
+shutdown_callbacks = set()
+scheduled_calls = []
+timed_calls = set()
+try:
+ hints = set([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")])
+except:
+ hints = set()
+
+class EVException(Exception):
+ """Eventloop Exceptions"""
+
+def shutdown_callback(cb):
+ shutdown_callbacks.add(WeakMethod(cb))
+
+def _sigint_cb(watcher, events):
+ for cb in shutdown_callbacks:
+ if cb.alive():
+ cb()
+
+ watcher.loop.unloop()
+
+def hint(sock):
+ ip = sock.getsockname()[0]
+ if not ip.startswith("127."):
+ hints.add(ip)
+
+def loop():
+ sigint_watcher = pyev.Signal(signal.SIGINT, default_loop, _sigint_cb)
+ sigint_watcher.start()
+
+ default_loop.loop()
+
+def unloop():
+ for cb in shutdown_callbacks:
+ if cb.alive():
+ cb()
+
+ default_loop.unloop()
+
+def connectssl(host, port, cert='cert2.pem'):
+ return ClientConnection((host,port), cert=cert)
+
+def connectplain(host, port):
+ return PlainClientConnection((host,port))
+
+def listensock(host='', port=0, backlog_limit=5):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((host, port))
+ sock.listen(backlog_limit)
+ return sock
+
+def listenssl(host='', port=0, backlog_limit=5, cert='cert.pem'):
+ sock = listensock(host, port, backlog_limit)
+ l = ListenerSSL(sock, cert=cert)
+ return l
+
+def listenplain(host='', port=0, backlog_limit=5):
+ sock = listensock(host, port, backlog_limit)
+ l = ListenerPlain(sock)
+ return l
+
+def schedule(cb, *args, **kwargs):
+ scheduled_calls.append((cb, args, kwargs))
+ schedule_timer.start()
+
+def schedule_cb(w, e):
+ l = len(scheduled_calls)
+ c = 0
+ for cb, args, kwargs in scheduled_calls:
+ try:
+ cb(*args, **kwargs)
+ except:
+ traceback.print_exc()
+ c += 1
+ if c == l:
+ break
+ del scheduled_calls[:c]
+
+schedule_timer = pyev.Timer(0.0, 0.0, default_loop, schedule_cb, data=None)
+
+def later(seconds, cb, *args, **kwargs):
+ def wrap(watcher, events):
+ args, kwargs = watcher.data
+ timed_calls.remove(watcher)
+ try:
+ cb(*args, **kwargs)
+ except:
+ traceback.print_exc()
+
+ t = pyev.Timer(seconds, 0.0, default_loop, wrap, data=(args, kwargs))
+ timed_calls.add(t)
+ t.start()
+
+
+class Listener(EventGen):
+ def __init__(self, sock):
+ EventGen.__init__(self)
+ self.sock = sock
+ self.sock.setblocking(False)
+ self.read_watcher = pyev.Io(self.sock, pyev.EV_READ, default_loop, self._readable)
+ self.read_watcher.start()
+
+ def _readable(self, watcher, events):
+ try:
+ sock, addr = self.sock.accept()
+ c = self.connclass(sock, addr)
+ self._event('connection', c, addr)
+ except IOError as e:
+ print 'ERROR', e
+ self.read_watcher.stop()
+ self.sock.close()
+ self._event('close', self)
+
+ def connclass(self, sock, addr):
+ raise Exception('Override this!')
+
+ def close(self):
+ if self.read_watcher.active:
+ self.read_watcher.stop()
+ self.sock.close()
+ self._event('close', self)
+
+
+class ListenerPlain(Listener):
+ def connclass(self, sock, addr):
+ return PlainServerConnection(addr, sock)
+
+
+class ListenerSSL(Listener):
+ def __init__(self, sock, cert):
+ self.cert = cert
+ Listener.__init__(self, sock)
+
+ def connclass(self, sock, addr):
+ return ServerConnection(addr, sock, cert=self.cert)
+
+
+# helper class for ssl style buffer requirements when writes fail
+class SSLbuf(object):
+ def __init__(self):
+ self.fail = None
+ self.buf = bytearray()
+ self.tmp = None
+ self.size = 0
+
+ def put(self, data):
+ self.buf.extend(data)
+ self.size += len(data)
+ #print 'put, len(data)', len(data), self.size
+
+ def get(self):
+ #print 'get, len(buf)', len(self.buf), self.fail and len(self.fail) or 'no fail'
+ if self.fail:
+ return self.fail
+ self.tmp = buffer(self.buf, 0)
+ return self.tmp
+
+ def failed(self):
+ self.fail, self.buf = self.tmp, bytearray()
+
+ # this must be called on successful write
+ def success(self, length):
+ #print 'success, length', length
+ self.size -= length
+ if self.fail:
+ self.buf, self.fail = bytearray(self.fail[length:])+self.buf, None
+ else:
+ del self.buf[:length]
+
+ def __len__(self):
+ return self.size
+
+
+class Connection(EventGen):
+ def __init__(self, addr, sock=None, cert=None):
+ EventGen.__init__(self)
+ if not sock:
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ else:
+ self.sock = sock
+ self.sock.setblocking(False)
+ #self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ self.addr = addr
+ self.cert = cert
+
+ self.sslsock = None
+ self.ctx = None
+ self.buf = SSLbuf()
+ self._closed = False
+ self._writing = False
+ self.peerfp = None
+ self.readbytes = 0
+ self.writebytes = 0
+
+ self.write_watcher = pyev.Io(self.sock, pyev.EV_WRITE, default_loop, self._writable)
+ self.read_watcher = pyev.Io(self.sock, pyev.EV_READ, default_loop, self._readable)
+ self.write_readwatcher = pyev.Io(self.sock, pyev.EV_READ, default_loop, self._writable)
+ self.read_writewatcher = pyev.Io(self.sock, pyev.EV_WRITE, default_loop, self._readable)
+
+ #self.ssl_shake()
+ self.initiate()
+
+ def initiate(self):
+ raise EVException('Use subclass of Connection!')
+ def set_ssl_state(self):
+ raise EVException('Use subclass of Connection!')
+
+ def _connected(self, watcher=None, events=None):
+ self.write_watcher.stop()
+ self.write_watcher.callback = self._writable
+ serr = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if serr == 0:
+ hint(self.sock)
+ self.ctx = SSL.Context(SSL.SSLv23_METHOD)
+ self.ctx.use_privatekey_file(self.cert)
+ self.ctx.use_certificate_file(self.cert)
+ self.ctx.set_verify(SSL.VERIFY_PEER, self.verify_cb)
+ self.sslsock = SSL.Connection(self.ctx, self.sock)
+ self.sslsock.setblocking(False)
+ self.set_ssl_state()
+ self._sslshake()
+ else:
+ self._close('SO_ERROR: {0}'.format(errno.errorcode[serr]))
+
+ def verify_cb(self, ok, store, *args, **kwargs):
+ for cb in self._event_subscribers['verify']:
+ if not cb(ok, store, *args, **kwargs):
+ return 0
+ return 1
+
+ def _sslshake(self, watcher=None, events=None):
+ try:
+ self.sslsock.do_handshake()
+ except SSL.WantWriteError:
+ self.write_watcher.callback = self._sslshake
+ self.write_watcher.start()
+ except SSL.WantReadError:
+ self.write_readwatcher.callback = self._sslshake
+ self.write_readwatcher.start()
+ except SSL.ZeroReturnError:
+ self._close(EVException('Connection closed (ZeroReturn).'))
+ except SSL.Error as e:
+ self._close(EVException('SSLError {0}'.format(e)))
+ else:
+ self.write_readwatcher.callback = self._writable
+ self.write_watcher.callback = self._writable
+ pc = self.sslsock.get_peer_certificate()
+ self.peerfp = pc.digest('sha1').replace(':', '').lower()
+ self.read_watcher.start()
+ self._event('ready')
+
+ def stop(self):
+ if self._closed:
+ raise EVException('Already closed.')
+
+ if self.read_watcher.active: self.read_watcher.stop()
+ if self.write_watcher.active: self.write_watcher.stop()
+ if self.read_writewatcher.active: self.read_writewatcher.stop()
+ if self.write_readwatcher.active: self.write_readwatcher.stop()
+
+ def write(self, data):
+ if self._closed:
+ raise EVException('Already closed.')
+
+ if not isinstance(data, bytes):
+ data = bytes(data)
+
+ self.buf.put(data)
+ self.writebytes += len(data)
+
+ if not self.write_watcher.active and not self._writing:
+ self._writeloop()
+
+ def _writable(self, watcher, events):
+ if self.write_readwatcher.active:
+ self.write_readwatcher.stop()
+ if self.write_watcher.active:
+ self.write_watcher.stop()
+
+ try:
+ self._writeloop()
+ except:
+ traceback.print_exc()
+ self._close(EVException('DEBUGWRAP'))
+
+ def _writeloop(self):
+ self._writing = True
+ count = 0
+ while not self._closed and len(self.buf) and count < 5:
+ count += 1
+ data = self.buf.get()
+ try:
+ ret = self.sslsock.send(data)
+ except SSL.WantWriteError:
+ self.buf.failed()
+ self.write_watcher.start()
+ return
+ except SSL.WantReadError:
+ self.buf.failed()
+ self.write_readwatcher.start()
+ return
+ except SSL.ZeroReturnError:
+ self._close(EVException('Connection closed (ZeroReturn).'))
+ except SSL.Error as e:
+ self._close(EVException('SSLError {0}'.format(e)))
+ except Exception as e:
+ self._close(EVException('Exception {0}'.format(e)))
+ else:
+ self.buf.success(ret)
+ if len(self.buf) < 16384*2:
+ self._event('writable')
+
+ if len(self.buf) and not self.write_watcher.active:
+ self.write_watcher.start()
+
+ self._writing = False
+
+
+ def _readable(self, watcher, events):
+ if self.read_writewatcher.active:
+ self.read_writewatcher.stop()
+ self.read_watcher.start()
+
+ count = 0
+ while not self._closed and count < 5:
+ count += 1
+ try:
+ data = self.sslsock.recv(16384)
+ except SSL.WantWriteError:
+ self.read_writewatcher.start()
+ self.read_watcher.stop()
+ break
+ except SSL.WantReadError:
+ break
+ except SSL.ZeroReturnError:
+ self._close(EVException('Connection closed (ZeroReturn).'))
+ except SSL.Error as e:
+ self._close(EVException('SSLError {0}'.format(e)))
+
+ else:
+ if not data:
+ self._close(EVException('Connection closed. not data'))
+ elif len(data) == 0:
+ self._close(EVException('Connection closed. len data = 0'))
+ else:
+ self.readbytes += len(data)
+ try:
+ self._event('read', data)
+ except:
+ traceback.print_exc()
+
+ def _close(self, e):
+ self.stop()
+ try:
+ if self.sslsock: self.sslsock.shutdown()
+ except:
+ pass
+ self.sock.close()
+ self._closed = True
+ self._event('close', e)
+
+ def close(self):
+ self._close(EVException('Connection closed.'))
+
+
+class ClientConnection(Connection):
+ def set_ssl_state(self):
+ self.sslsock.set_connect_state()
+
+ def initiate(self):
+ eno = self.sock.connect_ex(self.addr)
+ if eno == errno.EINPROGRESS:
+ self.write_watcher.callback = self._connected
+ self.write_watcher.start()
+ else:
+ logging.critical('socket.error != EINPROGRESS: {0}'.format(eno))
+ self._close('Exception.')
+
+
+class ServerConnection(Connection):
+ def set_ssl_state(self):
+ self.sslsock.set_accept_state()
+
+ def initiate(self):
+ self._connected()
+
+
+class PlainConnection(EventGen):
+ def __init__(self, addr, sock=None):
+ EventGen.__init__(self)
+ if not sock:
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ else:
+ self.sock = sock
+ self.sock.setblocking(False)
+ #self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ self.addr = addr
+
+ self.buf = bytearray()
+ self._closed = False
+ self._writing = False
+
+ self.write_watcher = pyev.Io(self.sock, pyev.EV_WRITE, default_loop, self._writable)
+ self.read_watcher = pyev.Io(self.sock, pyev.EV_READ, default_loop, self._readable)
+
+ self.initiate()
+
+ def initiate(self):
+ raise EVException('Use subclass of Connection!')
+
+ def _connected(self, watcher=None, events=None):
+ self.write_watcher.stop()
+ self.write_watcher.callback = self._writable
+ serr = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if serr == 0:
+ hint(self.sock)
+ self.read_watcher.start()
+ self._event('ready')
+ else:
+ self._close('SO_ERROR: {0}'.format(errno.errorcode[serr]))
+
+ def stop(self):
+ if self._closed:
+ raise EVException('Already closed.')
+
+ if self.read_watcher.active: self.read_watcher.stop()
+ if self.write_watcher.active: self.write_watcher.stop()
+
+ def write(self, data):
+ if self._closed:
+ raise EVException('Already closed.')
+
+ if not isinstance(data, bytes):
+ data = bytes(data)
+
+ self.buf.extend(data)
+
+ if not self.write_watcher.active and not self._writing:
+ self._writeloop()
+
+ def _writable(self, watcher, events):
+ self.write_watcher.stop()
+
+ try:
+ self._writeloop()
+ except:
+ traceback.print_exc()
+ self._close(EVException('DEBUGWRAP'))
+
+ def _writeloop(self):
+ self._writing = True
+ while not self._closed and len(self.buf):
+ try:
+ ret = self.sock.send(self.buf)
+ except socket.error as e:
+ if e.errno == errno.EAGAIN:
+ self.write_watcher.start()
+ return
+ else:
+ self._close(EVException('Exception {0}'.format(e)))
+ except Exception as e:
+ self._close(EVException('Exception {0}'.format(e)))
+ else:
+ del self.buf[:ret]
+ if len(self.buf) < 16384*2:
+ self._event('writable')
+
+ self._writing = False
+
+ def _readable(self, watcher, events):
+ count = 0
+ while not self._closed and count < 5:
+ count += 1
+ try:
+ data = self.sock.recv(16384)
+ except socket.error as e:
+ if e.errno == errno.EAGAIN:
+ return
+ else:
+ self._close(EVException('Exception {0}'.format(e)))
+ except Exception as e:
+ self._close(EVException('Exception {0}'.format(e)))
+ else:
+ if not data:
+ self._close(EVException('Connection closed. not data'))
+ elif len(data) == 0:
+ self._close(EVException('Connection closed. len data = 0'))
+ else:
+ try:
+ self._event('read', data)
+ except:
+ traceback.print_exc()
+
+
+ def _close(self, e):
+ self.stop()
+ self.sock.close()
+ self._closed = True
+ self._event('close', e)
+
+ def close(self):
+ self._close(EVException('Connection closed.'))
+
+
+class PlainClientConnection(PlainConnection):
+ def initiate(self):
+ eno = self.sock.connect_ex(self.addr)
+ if eno == errno.EINPROGRESS:
+ self.write_watcher.callback = self._connected
+ self.write_watcher.start()
+ else:
+ logging.critical('socket.error != EINPROGRESS: {0}'.format(eno))
+ self._close('Exception.')
+
+
+class PlainServerConnection(PlainConnection):
+ def initiate(self):
+ self._connected()
+
+
+class pyevThread(threading.Thread):
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self.aw = pyev.Async(default_loop, self.process)
+ self.aw.start()
+ self.calls = []
+
+ def run(self):
+ default_loop.loop()
+
+ def process(self, w, e):
+ l = len(self.calls)
+ c = 0
+ for f, args, kwargs in self.calls:
+ try:
+ f(*args, **kwargs)
+ except:
+ traceback.print_exc()
+ c += 1
+ if c == l:
+ break
+ del self.calls[:c]
+ if self.calls:
+ schedule(self.process, w, e)
+
+ def blockingCall(self, f, *a, **kw):
+ q = Queue.Queue()
+ def tmpcaller():
+ r = f(*a, **kw)
+ r._when(q.put)
+ r._except(q.put)
+ self.calls.append((tmpcaller, [], {}))
+ self.aw.send()
+ r = q.get()
+ return r
47 evnet/util.py
@@ -0,0 +1,47 @@
+
+import collections
+import weakref
+
+class EventGen(object):
+ def __init__(self):
+ self._event_subscribers = collections.defaultdict(list)
+
+ def _on(self, name, cb):
+ #self._event_subscribers[name].append(WeakMethod(cb))
+ self._event_subscribers[name].append(cb)
+
+ def _event(self, name, *args):
+ for cb in self._event_subscribers[name]:
+ #if cb.alive():
+ cb(*args)
+
+# some support classes for weakly referencing methods
+class WeakMethodBound:
+ def __init__(self, f):
+ self.f = f.im_func
+ self.c = weakref.ref(f.im_self)
+ def __call__(self, *args):
+ if self.c() == None:
+ raise TypeError, 'Method called on dead object'
+ apply(self.f, (self.c(),) + args)
+ def alive(self):
+ return (not self.c() == None)
+
+class WeakMethodFree:
+ def __init__(self, f):
+ self.f = weakref.ref(f)
+ def __call__(self, *args):
+ if self.f() == None:
+ raise TypeError, 'Function no longer exist'
+ apply(self.f(), args)
+ def alive(self):
+ return (not self.f() == None)
+
+def WeakMethod(f):
+ try :
+ f.im_func
+ except AttributeError :
+ return WeakMethodFree(f)
+ return WeakMethodBound(f)
+
+
18 examples/evnet-ssl-client.py
@@ -0,0 +1,18 @@
+from evnet import ClientConnection, loop, unloop
+
+c = ClientConnection(('127.0.0.1', 20001), cert='./cert2.pem')
+
+def readycb(fp):
+ print 'ready, remote fp: %s' % fp
+ c.write('test\n')
+ c.close()
+
+def closecb(r):
+ print 'closed', r
+ unloop()
+
+c._on('close', closecb)
+c._on('ready', readycb)
+
+loop()
+
45 examples/evnetcat.py
@@ -0,0 +1,45 @@
+
+import sys
+import logging
+import traceback
+logging.basicConfig(level=logging.DEBUG)
+
+from pwrcall.pyevloop import PlainClientConnection, loop, unloop
+
+def filegen(fobj):
+ d = fobj.read(1024**2)
+ while d:
+ yield d
+ d = fobj.read(1024**2)
+
+class Filesender(object):
+ def __init__(self, host, port, fp):
+ self.fobj = open(fp, 'rb')
+ self.c = PlainClientConnection((host, port))
+ self.c._on('writable', self.send_data)
+ self.c._on('ready', self.ready)
+ self.c._on('close', self.closed)
+
+ def ready(self):
+ print 'connection ready, pumping'
+ self.send_data()
+
+ def send_data(self):
+ d = self.fobj.read(16384)
+ if not d:
+ print 'EOF, closing'
+ self.fobj.close()
+ self.c.close()
+ unloop()
+ return
+ self.c.write(d)
+ def closed(self, e):
+ traceback.print_exc()
+ print e
+ self.fobj.close()
+ unloop()
+
+a = Filesender(sys.argv[1], int(sys.argv[2]), sys.argv[3])
+
+loop()
+
62 examples/evnetcatsrv.py
@@ -0,0 +1,62 @@
+
+import sys
+import logging
+import random
+import struct
+logging.basicConfig(level=logging.DEBUG)
+
+from pwrcall.pyevloop import loop, unloop, listenplain
+from pwrcall.promise import Promise
+from pwrcall.util import EventGen
+
+
+class WebSockListener(EventGen):
+ def __init__(self, port):
+ EventGen.__init__(self)
+
+ self.l = listenplain(host='0.0.0.0', port=port)
+ self.l._on('connection', self.connection)
+
+ def connection(self, c, addr):
+ print 'cli', addr,
+ self._event('connection', WebSock(c, addr))
+
+class WebSock(EventGen):
+ def __init__(self, c, addr):
+ EventGen.__init__(self)
+ self.c, self.addr = c, addr
+
+ self.c._on('ready', self.ready)
+ self.c._on('close', self.closed)
+ self.c._on('read', self.read)
+
+ def ready(self):
+ self._event('ready')
+
+ def closed(self, e):
+ self._event('close', e)
+
+ def read(self, d):
+ print 'read', repr(d)
+
+
+if __name__ == '__main__':
+ a = WebSockListener(int(sys.argv[2]))
+
+ def new_conn(c):
+ def onready():
+ def dbgprint(r):
+ print 'dbgprint', r
+
+ def closed(e):
+ pass
+ #print e
+
+ c._on('ready', onready)
+ c._on('close', closed)
+
+ a._on('connection', new_conn)
+
+ loop()
+
+
22 setup.py
@@ -0,0 +1,22 @@
+from setuptools import setup
+
+import sys
+
+extra = {}
+if sys.version_info >= (3,):
+ extra['use_2to3'] = True
+
+setup(
+ name='evnet',
+ version = '1.0',
+ description='evnet is a networking library built on top of pyev (libev)',
+ author='Mark Schloesser',
+ author_email='ms@mwcollect.org',
+ license = "MIT/BSD/GPL",
+ keywords = "evnet pyev network asynchronous nonblocking event",
+ url = "https://github.com/rep/evnet",
+ install_requires = ['pyev>=0.5.3-3.8'],
+ packages = ['evnet',],
+ **extra
+)
+
Please sign in to comment.
Something went wrong with that request. Please try again.