commit 5bfa1dc60717ef68e2a6bc0aa34ec3b6b67e230f Author: Evan Klitzke Date: Mon Jan 25 17:57:35 2010 -0800 import libevent-python-0.1a8 diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..d506194 --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,20 @@ +# libevent-python +# Copyright (c) 2006 Andy Gross . +# Copyright (c) 2006 Nick Mathewson. +# See LICENSE.txt for licensing details. + +# Installation instructions for libevent-python + +1) Install libevent (http://www.monkey.org/~provos/libevent/) + + libevent-python tracks the most-recent stable release, available from the + url above. + +2) Install libevent-python + + % python setup.py install + +3) Test libevent-python + + % python setup.py test + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..396017d --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2006 Andy Gross +# Copyright (c) 2006 Nick Mathewson +# +# 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. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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. diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..6d0daac --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,18 @@ +Metadata-Version: 1.0 +Name: libevent-python +Version: 0.1a8 +Summary: A CPython extension module wrapping the libevent library +Home-page: http://python-hpio.net/trac/wiki/LibEventPython +Author: Andy Gross +Author-email: andy@andygross.org +License: BSD +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Networking +Classifier: Topic :: Internet diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..16f6aed --- /dev/null +++ b/README.txt @@ -0,0 +1,17 @@ +# Copyright (c) 2006 Andy Gross +# Copyright (c) 2006 Nick Mathewson +# See LICENSE.txt for details. + +libevent-python +--------------- + +libevent-python is a CPython extension module that wraps the lightweight C +library 'libevent', available at http://www.monkey.org/~provos/libevent/. + +libevent provides a unified interface to a variety of IO multiplexing +mechanisms (select, poll, kqueue, epoll) and an event loop that supports +timed events and signal handlers. + +For usage examples, see the examples/ directory, which should grow as +libevent-python matures. + diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..ffb2ca3 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,5 @@ +* More documentation +* Buffered events +* Twisted integration +* More examples +* Support for libevent-CVS features diff --git a/examples/echo_server.py b/examples/echo_server.py new file mode 100644 index 0000000..082215f --- /dev/null +++ b/examples/echo_server.py @@ -0,0 +1,117 @@ +""" +A poorly-factored but kinda-working example of an echo server. +""" + +import sys +import socket +import signal +import libevent + +class BaseConnection(object): + bufferSize = 2**16 + def __init__(self, sock, addr, server): + self.sock = sock + self.addr = addr + self.server = server + self.sock.setblocking(False) + self.buf = [] + self.readEvent = libevent.createEvent( + self.sock,libevent.EV_READ|libevent.EV_PERSIST, self._doRead) + self.writeEvent = libevent.createEvent( + self.sock,libevent.EV_WRITE, self._doWrite) + self.startReading() + + def startReading(self): + self.readEvent.addToLoop() + + def stopReading(self): + self.readEvent.removeFromLoop() + + def startWriting(self): + self.writeEvent.addToLoop() + + def stopWriting(self): + self.writeEvent.removeFromLoop() + + def _doRead(self, fd, events, eventObj): + data = '' + data = self.sock.recv(self.bufferSize) + if not data: + self.server.lostClient(self) + self.stopReading() + self.stopWriting() + self.sock.close() + else: + self.gotData(data) + + def _doWrite(self, fd, events, eventObj): + data = "".join(self.buf) + nsent = self.sock.send(data) + data = data[nsent:] + if not data: + self.stopWriting() + self.buf = [] + else: + self.buf = [data] + if not self.writeEvent.pending(): + self.startWriting() + + def write(self, data): + self.buf.append(data) + self.startWriting() + + def gotData(self, data): + raise NotImplementedError + +class EchoConnection(BaseConnection): + def gotData(self, data): + self.write(data) + +class Acceptor(object): + def __init__(self, addr, port, server): + self.addr = addr + self.port = port + self.server = server + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setblocking(False) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + def listen(self): + self.sock.bind((self.addr, self.port)) + self.sock.listen(5) + events = libevent.EV_READ|libevent.EV_PERSIST + libevent.createEvent(self.sock, events, self._callback).addToLoop() + + def _callback(self, fd, events, eventObj): + sock, addr = self.sock.accept() + self.server.gotClient(sock, addr) + +class EchoServer(object): + def __init__(self, addr="127.0.0.1", port=50505): + self.acceptor = Acceptor(addr, port, self) + self.clients = dict() + self.acceptor.listen() + + def gotClient(self, sock, addr): + print "Got connection from %s:%s" % addr + client = EchoConnection(sock, addr, self) + self.clients[addr] = client + + def lostClient(self, client): + print "Lost connection from %s:%s" % client.addr + client = self.clients[client.addr] + del self.clients[client.addr] + del client + +def handleSigInt(signum, events, obj): + libevent.loopExit(0) + raise KeyboardInterrupt + +def main(): + libevent.createSignalHandler(signal.SIGINT, handleSigInt).addToLoop() + echosrv = EchoServer() + libevent.dispatch() + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/ez_setup.py b/ez_setup.py new file mode 100755 index 0000000..9252bfa --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +"""Bootstrap setuptools installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import sys +DEFAULT_VERSION = "0.6a10" +DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] + +md5_data = { + 'setuptools-0.5a13-py2.3.egg': '85edcf0ef39bab66e130d3f38f578c86', + 'setuptools-0.5a13-py2.4.egg': 'ede4be600e3890e06d4ee5e0148e092a', + 'setuptools-0.6a1-py2.3.egg': 'ee819a13b924d9696b0d6ca6d1c5833d', + 'setuptools-0.6a1-py2.4.egg': '8256b5f1cd9e348ea6877b5ddd56257d', + 'setuptools-0.6a10-py2.3.egg': '162d8357f1aff2b0349c6c247ee62987', + 'setuptools-0.6a10-py2.4.egg': '803a2d8db501c1ac3b5b6fb4e907f788', + 'setuptools-0.6a10dev_r42346-py2.3.egg': 'a7899272cfceb6aa60094ae8928b8077', + 'setuptools-0.6a10dev_r42346-py2.4.egg': '5d42a64adca9aedb409f83ecf22156a5', + 'setuptools-0.6a2-py2.3.egg': 'b98da449da411267c37a738f0ab625ba', + 'setuptools-0.6a2-py2.4.egg': 'be5b88bc30aed63fdefd2683be135c3b', + 'setuptools-0.6a3-py2.3.egg': 'ee0e325de78f23aab79d33106dc2a8c8', + 'setuptools-0.6a3-py2.4.egg': 'd95453d525a456d6c23e7a5eea89a063', + 'setuptools-0.6a4-py2.3.egg': 'e958cbed4623bbf47dd1f268b99d7784', + 'setuptools-0.6a4-py2.4.egg': '7f33c3ac2ef1296f0ab4fac1de4767d8', + 'setuptools-0.6a5-py2.3.egg': '748408389c49bcd2d84f6ae0b01695b1', + 'setuptools-0.6a5-py2.4.egg': '999bacde623f4284bfb3ea77941d2627', + 'setuptools-0.6a6-py2.3.egg': '7858139f06ed0600b0d9383f36aca24c', + 'setuptools-0.6a6-py2.4.egg': 'c10d20d29acebce0dc76219dc578d058', + 'setuptools-0.6a7-py2.3.egg': 'cfc4125ddb95c07f9500adc5d6abef6f', + 'setuptools-0.6a7-py2.4.egg': 'c6d62dab4461f71aed943caea89e6f20', + 'setuptools-0.6a8-py2.3.egg': '2f18eaaa3f544f5543ead4a68f3b2e1a', + 'setuptools-0.6a8-py2.4.egg': '799018f2894f14c9f8bcb2b34e69b391', + 'setuptools-0.6a9-py2.3.egg': '8e438ad70438b07b0d8f82cae42b278f', + 'setuptools-0.6a9-py2.4.egg': '8f6e01fc12fb1cd006dc0d6c04327ec1', +} + +import sys, os + +def _validate_md5(egg_name, data): + if egg_name in md5_data: + from md5 import md5 + digest = md5(data).hexdigest() + if digest != md5_data[egg_name]: + print >>sys.stderr, ( + "md5 validation of %s failed! (Possible download problem?)" + % egg_name + ) + sys.exit(2) + return data + + +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + download_delay=15 +): + """Automatically find/download setuptools and make it available on sys.path + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end with + a '/'). `to_dir` is the directory where setuptools will be downloaded, if + it is not already available. If `download_delay` is specified, it should + be the number of seconds that will be paused before initiating a download, + should one be required. If an older version of setuptools is installed, + this routine will print a message to ``sys.stderr`` and raise SystemExit in + an attempt to abort the calling script. + """ + try: + import setuptools + if setuptools.__version__ == '0.0.1': + print >>sys.stderr, ( + "You have an obsolete version of setuptools installed. Please\n" + "remove it from your system entirely before rerunning this script." + ) + sys.exit(2) + except ImportError: + egg = download_setuptools(version, download_base, to_dir, download_delay) + sys.path.insert(0, egg) + import setuptools; setuptools.bootstrap_install_from = egg + + import pkg_resources + try: + pkg_resources.require("setuptools>="+version) + + except pkg_resources.VersionConflict: + # XXX could we install in a subprocess here? + print >>sys.stderr, ( + "The required version of setuptools (>=%s) is not available, and\n" + "can't be installed while this script is running. Please install\n" + " a more recent version first." + ) % version + sys.exit(2) + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + delay = 15 +): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download attempt. + """ + import urllib2, shutil + egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) + url = download_base + egg_name + saveto = os.path.join(to_dir, egg_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + from distutils import log + if delay: + log.warn(""" +--------------------------------------------------------------------------- +This script requires setuptools version %s to run (even to display +help). I will attempt to download it for you (from +%s), but +you may need to enable firewall access for this script first. +I will start the download in %d seconds. + +(Note: if this machine does not have network access, please obtain the file + + %s + +and place it in this directory before rerunning this script.) +---------------------------------------------------------------------------""", + version, download_base, delay, url + ); from time import sleep; sleep(delay) + log.warn("Downloading %s", url) + src = urllib2.urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = _validate_md5(egg_name, src.read()) + dst = open(saveto,"wb"); dst.write(data) + finally: + if src: src.close() + if dst: dst.close() + return os.path.realpath(saveto) + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + + try: + import setuptools + except ImportError: + import tempfile, shutil + tmpdir = tempfile.mkdtemp(prefix="easy_install-") + try: + egg = download_setuptools(version, to_dir=tmpdir, delay=0) + sys.path.insert(0,egg) + from setuptools.command.easy_install import main + main(list(argv)+[egg]) + finally: + shutil.rmtree(tmpdir) + else: + if setuptools.__version__ == '0.0.1': + # tell the user to uninstall obsolete version + use_setuptools(version) + + req = "setuptools>="+version + import pkg_resources + try: + pkg_resources.require(req) + except pkg_resources.VersionConflict: + try: + from setuptools.command.easy_install import main + except ImportError: + from easy_install import main + main(list(argv)+[download_setuptools(delay=0)]) + sys.exit(0) # try to force an exit + else: + if argv: + from setuptools.command.easy_install import main + main(argv) + else: + print "Setuptools version",version,"or greater has been installed." + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' + + + +def update_md5(filenames): + """Update our built-in md5 registry""" + + import re + from md5 import md5 + + for name in filenames: + base = os.path.basename(name) + f = open(name,'rb') + md5_data[base] = md5(f.read()).hexdigest() + f.close() + + data = [" %r: %r,\n" % it for it in md5_data.items()] + data.sort() + repl = "".join(data) + + import inspect + srcfile = inspect.getsourcefile(sys.modules[__name__]) + f = open(srcfile, 'rb'); src = f.read(); f.close() + + match = re.search("\nmd5_data = {\n([^}]+)}", src) + if not match: + print >>sys.stderr, "Internal error!" + sys.exit(2) + + src = src[:match.start(1)] + repl + src[match.end(1):] + f = open(srcfile,'w') + f.write(src) + f.close() + + +if __name__=='__main__': + if len(sys.argv)>2 and sys.argv[1]=='--md5update': + update_md5(sys.argv[2:]) + else: + main(sys.argv[1:]) + + + + + diff --git a/libevent/__init__.py b/libevent/__init__.py new file mode 100644 index 0000000..11c9a58 --- /dev/null +++ b/libevent/__init__.py @@ -0,0 +1,22 @@ +# Copyright (c) 2006 Andy Gross +# Copyright (c) 2006 Nick Mathewson +# See LICENSE.txt for details. +from event import * + +def createEvent(fd, events, callback): + return DefaultEventBase.createEvent(fd, events, callback) + +def createTimer(callback): + return DefaultEventBase.createTimer(callback) + +def createSignalHandler(signum, callback): + return DefaultEventBase.createSignalHandler(signum, callback) + +def loop(flags=0): + return DefaultEventBase.loop(flags) + +def loopExit(seconds): + return DefaultEventBase.loopExit(seconds) + +def dispatch(): + return DefaultEventBase.dispatch() diff --git a/libevent/eventmodule.c b/libevent/eventmodule.c new file mode 100644 index 0000000..d2d9f4f --- /dev/null +++ b/libevent/eventmodule.c @@ -0,0 +1,705 @@ +/* + * eventmodule.c: a wrapper for libevent (http://monkey.org/~provos/libevent/) + * Copyright (c) 2006 Andy Gross + * Copyright (c) 2006 Nick Mathewson + * See LICENSE.txt for licensing information. + */ + +#include +#include +#include +#include +#include + +#define DEFAULT_NUM_PRIORITIES 3 + +/* + * EventBaseObject wraps a (supposedly) thread-safe libevent dispatch context. + */ +typedef struct EventBaseObject { + PyObject_HEAD + struct event_base *ev_base; +} EventBaseObject; + +/* Forward declaration of CPython type object */ +static PyTypeObject EventBase_Type; + + +/* + * EventObject wraps a libevent 'struct event' + */ +typedef struct EventObject { + PyObject_HEAD + struct event ev; + EventBaseObject *eventBase; + PyObject *callback; +} EventObject; + +/* Forward declaration of CPython type object */ +static PyTypeObject Event_Type; + +/* EventObject prototypes */ +static PyObject *Event_New(PyTypeObject *, PyObject *, PyObject *); +static int Event_Init(EventObject *, PyObject *, PyObject *); + +/* Singleton default event base */ +static EventBaseObject *defaultEventBase; + +/* Reference to the logging callback */ +static PyObject *logCallback; + +/* Error Objects */ +PyObject *EventErrorObject; + +/* Typechecker */ +int EventBase_Check(PyObject *o) { + return ((o->ob_type) == &EventBase_Type); +} + +/* Construct a new EventBaseObject */ +static PyObject *EventBase_New(PyTypeObject *type, PyObject *args, + PyObject *kwds) +{ + EventBaseObject *self = NULL; + assert(type != NULL && type->tp_alloc != NULL); + self = (EventBaseObject *)type->tp_alloc(type, 0); + if (self != NULL) { + self->ev_base = event_init(); + if (self->ev_base == NULL) { + return NULL; + } + } + return (PyObject *)self; +} + +/* EventBaseObject initializer */ +static int EventBase_Init(EventBaseObject *self, PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"numPriorities", NULL}; + int numPriorities = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:event", kwlist, + &numPriorities)) + return -1; + + if (!numPriorities) + numPriorities = DEFAULT_NUM_PRIORITIES; + + if ( (event_base_priority_init(self->ev_base, numPriorities)) < 0) { + return -1; + } + return 0; +} + +/* EventBaseObject destructor */ +static void EventBase_Dealloc(EventBaseObject *obj) { + obj->ob_type->tp_free((PyObject *)obj); +} + +/* EventBaseObject methods */ +PyDoc_STRVAR(EventBase_LoopDoc, +"loop(self, [flags=0])\n\ +\n\ +Perform one iteration of the event loop. Valid flags arg EVLOOP_NONBLOCK \n\ +and EVLOOP_ONCE."); +static PyObject *EventBase_Loop(EventBaseObject *self, PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"flags", NULL}; + int flags = 0; + int rv = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:loop", kwlist, &flags)) + return NULL; + + rv = event_base_loop(self->ev_base, flags); + return PyInt_FromLong(rv); +} +PyDoc_STRVAR(EventBase_LoopExitDoc, +"loopExit(self, seconds=0)\n\ +\n\ +Cause the event loop to exit after seconds."); +static PyObject *EventBase_LoopExit(EventBaseObject *self, PyObject *args, + PyObject *kwargs) { + static char * kwlist[] = {"seconds", NULL}; + struct timeval tv; + int rv = 0; + double exitAfterSecs = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "d:loopExit", + kwlist, &exitAfterSecs)) + return NULL; + + tv.tv_sec = (long) exitAfterSecs; + tv.tv_usec = (exitAfterSecs - (long) exitAfterSecs) * 1000000; + rv = event_base_loopexit(self->ev_base, &tv); + return PyInt_FromLong(rv); +} + +PyDoc_STRVAR(EventBase_DispatchDoc, +"dispatch(self)\n\ +\n\ +Run the main dispatch loop associated with this event base. This function\n\ +only terminates when no events remain, or the loop is terminated via an \n\ +explicit call to EventBase.loopExit() or via a signal."); +static PyObject *EventBase_Dispatch(EventBaseObject *self, PyObject *args, + PyObject *kwargs) { + + int rv = event_base_dispatch(self->ev_base); + return PyInt_FromLong(rv); + +} + +PyDoc_STRVAR(EventBase_CreateEventDoc, +"createEvent(self, fd, events, callback)\n\ +\n\ +Create a new Event object for the given file descriptor that will call\n\ + with a 3-tuple of (fd, events, eventObject) when the event\n\ +fires. The first argument, fd, can be either an integer file descriptor\n\ +or a 'file-like' object with a fileno() method."); +static EventObject *EventBase_CreateEvent(EventBaseObject *self, + PyObject *args, PyObject *kwargs) +{ + EventObject *newEvent = NULL; + + newEvent = (EventObject *)Event_New(&Event_Type,NULL,NULL); + + if (Event_Init(newEvent, args, kwargs) < 0) + return NULL; + + if (PyObject_CallMethod((PyObject *)newEvent, + "setEventBase", "O", self) == NULL) + return NULL; + return newEvent; +} + +PyDoc_STRVAR(EventBase_CreateTimerDoc, +"createTimer(self, callback) -> new timer Event\n\ +\n\ +Create a new timer object that will call . The timeout is not\n\ +specified here, but rather via the Event.addToLoop([timeout]) method"); +static EventObject *EventBase_CreateTimer(EventBaseObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"callback", NULL}; + EventObject *newTimer = NULL; + PyObject *callback = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:createTimer", + kwlist, &callback)) + return NULL; + + newTimer = (EventObject *)PyObject_CallMethod((PyObject *)self, + "createEvent", + "OiO", Py_None, EV_TIMEOUT, + callback); + return newTimer; +} + +PyDoc_STRVAR(EventBase_CreateSignalHandlerDoc, +"createSignalHandler(self, signum, callback) -> new signal handler Event\n\ +\n\ +Create a new signal handler object that will call when the signal\n\ +is received. Signal handlers are by default persistent - you must manually\n\ +remove them with removeFromLoop()."); +static EventObject *EventBase_CreateSignalHandler(EventBaseObject *self, + PyObject *args, + PyObject *kwargs) { + static char *kwlist[] = {"signal", "callback", NULL}; + EventObject *newSigHandler = NULL; + PyObject *callback = NULL; + int sig = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iO:createSignalHandler", + kwlist, &sig, &callback)) + return NULL; + + newSigHandler = (EventObject *)PyObject_CallMethod((PyObject *)self, + "createEvent", + "iiO", + sig, + EV_SIGNAL|EV_PERSIST, + callback); + return newSigHandler; +} + + +static PyGetSetDef EventBase_Properties[] = { + {NULL}, +}; + +static PyMemberDef EventBase_Members[] = { + {NULL}, +}; + + +static PyMethodDef EventBase_Methods[] = { + {"loop", (PyCFunction)EventBase_Loop, + METH_VARARGS|METH_KEYWORDS, EventBase_LoopDoc}, + {"loopExit", (PyCFunction)EventBase_LoopExit, + METH_VARARGS|METH_KEYWORDS, EventBase_LoopExitDoc}, + {"createEvent", (PyCFunction)EventBase_CreateEvent, + METH_VARARGS|METH_KEYWORDS, EventBase_CreateEventDoc}, + {"createSignalHandler", (PyCFunction)EventBase_CreateSignalHandler, + METH_VARARGS|METH_KEYWORDS, EventBase_CreateSignalHandlerDoc}, + {"createTimer", (PyCFunction)EventBase_CreateTimer, + METH_VARARGS|METH_KEYWORDS, EventBase_CreateTimerDoc}, + {"dispatch", (PyCFunction)EventBase_Dispatch, + METH_NOARGS, EventBase_DispatchDoc}, + {NULL}, +}; + +static PyTypeObject EventBase_Type = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "event.EventBase", /*tp_name*/ + sizeof(EventBaseObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)EventBase_Dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + PyObject_GenericGetAttr, /*tp_getattro*/ + PyObject_GenericSetAttr, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + EventBase_Methods, /*tp_methods*/ + EventBase_Members, /*tp_members*/ + EventBase_Properties, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)EventBase_Init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + EventBase_New, /*tp_new*/ + PyObject_Del, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + + + +/* Typechecker */ +int Event_Check(PyObject *o) { + return ((o->ob_type) == &Event_Type); +} + +/* Construct a new EventObject */ +static PyObject *Event_New(PyTypeObject *type, PyObject *args, + PyObject *kwargs) +{ + EventObject *self = NULL; + assert(type != NULL && type->tp_alloc != NULL); + self = (EventObject *)type->tp_alloc(type, 0); + self->eventBase = NULL; + return (PyObject *)self; +} + +/* Callback thunk. */ +static void __libevent_ev_callback(int fd, short events, void *arg) { + EventObject *ev = arg; + PyObject *result; + PyObject *tuple = PyTuple_New(3); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(fd)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(events)); + PyTuple_SET_ITEM(tuple, 2, (PyObject *) ev); + Py_INCREF((PyObject *) ev); + result = PyObject_Call(ev->callback, tuple, NULL); + Py_DECREF((PyObject *) ev); + //Py_DECREF(tuple); + if (result) { + Py_DECREF(result); + } + else { + /* + * The callback raised an exception. This usually isnt a problem because + * the callback's caller is in Python-land. Here, we don't have many + * good options. For now, we just print the exception. The commented + * out code below is supposed to asynchronously raise an exception in + * the main thread, but that doesn't work if libevent is blocked on + * an I/O call like select() or kevent(). We could terminate the + * event loop from here, but that seems a little drastic. Somehow, + * we should move the callback invocation to Python. I think. + */ + /* + PyThreadState *ts = PyThreadState_Get(); + int r = PyThreadState_SetAsyncExc(ts->thread_id, EventErrorObject); + printf("%d\n", r); + */ + PyErr_WriteUnraisable(ev->callback); + + } +} + + +/* EventObject initializer */ +static int Event_Init(EventObject *self, PyObject *args, PyObject *kwargs) { + int fd = -1; + PyObject *fdObj = NULL; + int events = 0; + PyObject *callback = NULL; + static char *kwlist[] = {"fd", "events", "callback", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OiO:event", kwlist, + &fdObj, &events, &callback)) + return -1; + + if (!PyCallable_Check(callback)) { + PyErr_SetString(EventErrorObject,"callback argument must be callable"); + return -1; + } + + if (fdObj != Py_None) { + if ( (fd = PyObject_AsFileDescriptor(fdObj)) == -1 ) { + return -1; + } + } + event_set(&self->ev, fd, events, __libevent_ev_callback, self); + if (! event_initialized(&self->ev) ) + return -1; + + Py_INCREF(callback); + self->callback = callback; + return 0; +} + +PyDoc_STRVAR(Event_SetPriorityDoc, +"setPriority(self, priority)\n\ +\n\ +Set the priority for this event."); +static PyObject *Event_SetPriority(EventObject *self, PyObject *args, + PyObject *kwargs) +{ + static char *kwlist[] = {"priority", NULL}; + int priority = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs , "|i:setPriority", + kwlist, &priority)) + return NULL; + if (event_priority_set(&self->ev, priority) < 0) { + PyErr_SetString(EventErrorObject, + "error setting event priority - event is either already active or priorities are not enabled"); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(Event_AddToLoopDoc, +"addToLoop(self, timeout=-1)\n\ +\n\ +Add this event to the event loop, with a timeout of seconds.\n\ +A timeout value of -1 seconds causes the event to remain in the loop \n\ +until it fires or is manually removed with removeFromLoop()."); +static PyObject *Event_AddToLoop(EventObject *self, PyObject *args, + PyObject *kwargs) { + double timeout = -1.0; + struct timeval tv; + static char *kwlist[] = {"timeout", NULL}; + int rv; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|d:addToLoop", kwlist, + &timeout)) + return NULL; + + if (timeout >= 0.0) { + tv.tv_sec = (long) timeout; + tv.tv_usec = (timeout - (long) timeout) * 1000000; + rv = event_add(&((EventObject *) self)->ev, &tv); + } + else { + rv = event_add(&((EventObject *) self)->ev, NULL); + } + if (rv != 0) { + PyErr_SetFromErrno(EventErrorObject); + return NULL; + } + Py_INCREF(self); + Py_INCREF(Py_None); + return Py_None; +} +PyDoc_STRVAR(Event_RemoveFromLoopDoc, +"removeFromLoop(self)\n\ +\n\ +Remove the event from the event loop."); +static PyObject *Event_RemoveFromLoop(EventObject *self, PyObject *args, + PyObject *kwargs) { + + if (event_del(&self->ev) < 0) { + PyErr_SetFromErrno(EventErrorObject); + return NULL; + } + Py_DECREF(self); + Py_INCREF(Py_None); + return Py_None; +} + +static PyObject *Event_SetEventBase(EventObject *self, PyObject *args, + PyObject *kwargs) { + static char *kwlist[] = {"eventBase", NULL}; + PyObject *eventBase; + int rv = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &eventBase)) + return NULL; + + if (!EventBase_Check(eventBase)) { + PyErr_SetString(EventErrorObject, "argument is not an EventBase object"); + return NULL; + } + rv = event_base_set(((EventBaseObject *)eventBase)->ev_base, &self->ev); + if (rv < 0) { + PyErr_SetString(EventErrorObject, "unable to set event base"); + return NULL; + } + if (self->eventBase != NULL) { + Py_XDECREF(self->eventBase); + } + Py_INCREF(eventBase); + self->eventBase = (EventBaseObject *)eventBase; + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(Event_PendingDoc, +"pending(self)\n\ +\n\ +Returns the event flags set for this event OR'd together."); +static PyObject *Event_Pending(EventObject *self, PyObject *args, + PyObject *kwargs) { + int flags; + flags = event_pending(&((EventObject *) self)->ev, + EV_TIMEOUT | EV_READ | EV_WRITE | EV_SIGNAL, NULL); + return PyInt_FromLong(flags); +} + +PyDoc_STRVAR(Event_GetTimeoutDoc, +"getTimeout(self)\n\ +\n\ +Returns the expiration time of this event."); +static PyObject *Event_GetTimeout(EventObject *self, PyObject *args, + PyObject *kwargs) { + double d; + struct timeval tv; + + tv.tv_sec = -1; + event_pending(&((EventObject *) self)->ev, 0, &tv); + + if (tv.tv_sec > -1) { + d = tv.tv_sec + (tv.tv_usec / 1000000.0); + return PyFloat_FromDouble(d); + } + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(Event_FilenoDoc, +"fileno(self)\n\ +\n\ +Return the integer file descriptor number associated with this event.\n\ +Not especially meaningful for signal or timer events."); +static PyObject *Event_Fileno(EventObject *self, PyObject *args, + PyObject *kwargs) { + return PyInt_FromLong(self->ev.ev_fd); +} + + +/* EventObject destructor */ +static void Event_Dealloc(EventObject *obj) { + Py_XDECREF(obj->eventBase); + Py_XDECREF(obj->callback); + obj->ob_type->tp_free((PyObject *)obj); +} + +static PyObject *Event_Repr(EventObject *self) { + char buf[512]; + PyOS_snprintf(buf, sizeof(buf), + "", + (long) self->ev.ev_fd, + (int) self->ev.ev_events); + return PyString_FromString(buf); +} + +#define OFF(x) offsetof(EventObject, x) +static PyMemberDef Event_Members[] = { + {"eventBase", T_OBJECT, OFF(eventBase), + RO, "The EventBase for this event object"}, + {"callback", T_OBJECT, OFF(callback), + RO, "The callback for this event object"}, + {"events", T_SHORT, OFF(ev.ev_events), + RO, "Events registered for this event object"}, + {"numCalls", T_SHORT, OFF(ev.ev_ncalls), + RO, "Number of times this event has been called"}, + {"priority", T_INT, OFF(ev.ev_pri), + RO, "Event priority"}, + {"flags", T_INT, OFF(ev.ev_flags), + RO, "Event flags (internal)"}, + {NULL} +}; +#undef OFF + +static PyGetSetDef Event_Properties[] = { + {NULL}, +}; + +static PyMethodDef Event_Methods[] = { + {"addToLoop", (PyCFunction)Event_AddToLoop, + METH_VARARGS|METH_KEYWORDS, Event_AddToLoopDoc}, + {"removeFromLoop", (PyCFunction)Event_RemoveFromLoop, + METH_NOARGS, Event_RemoveFromLoopDoc}, + {"fileno", (PyCFunction)Event_Fileno, + METH_NOARGS, Event_FilenoDoc}, + {"setPriority", (PyCFunction)Event_SetPriority, + METH_VARARGS|METH_KEYWORDS, Event_SetPriorityDoc}, + {"setEventBase", (PyCFunction)Event_SetEventBase, + METH_VARARGS|METH_KEYWORDS}, + {"pending", (PyCFunction)Event_Pending, + METH_NOARGS, Event_PendingDoc}, + {"getTimeout", (PyCFunction)Event_GetTimeout, + METH_NOARGS, Event_GetTimeoutDoc}, + {NULL}, +}; + +static PyTypeObject Event_Type = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "event.Event", /*tp_name*/ + sizeof(EventObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)Event_Dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (reprfunc)Event_Repr, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + PyObject_GenericGetAttr, /*tp_getattro*/ + PyObject_GenericSetAttr, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + Event_Methods, /*tp_methods*/ + Event_Members, /*tp_members*/ + Event_Properties, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)Event_Init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + Event_New, /*tp_new*/ + PyObject_Del, /*tp_free*/ + 0, /*tp_is_gc*/ +}; + + + +static PyObject *EventModule_setLogCallback(PyObject *self, PyObject *args, + PyObject *kwargs) { + static char *kwlist[] = {"callback", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args,kwargs,"O:setLogCallback", kwlist, + &logCallback)) + return NULL; + + if (!PyCallable_Check(logCallback)) { + PyErr_SetString(EventErrorObject, "log callback is not a callable"); + logCallback = NULL; + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static PyMethodDef EventModule_Functions[] = { + {"setLogCallback", (PyCFunction)EventModule_setLogCallback, + METH_VARARGS|METH_KEYWORDS}, + {NULL}, +}; + +#define ADDCONST(mod, name, const) PyModule_AddIntConstant(mod, name, const) +DL_EXPORT(void) initevent(void) +{ + PyObject *m, *d; + + m = Py_InitModule("event", EventModule_Functions); + d = PyModule_GetDict(m); + + if (EventErrorObject == NULL) { + EventErrorObject = PyErr_NewException("libevent.EventError", + NULL, NULL); + if (EventErrorObject == NULL) + return; + } + Py_INCREF(EventErrorObject); + PyModule_AddObject(m, "EventError", EventErrorObject); + + if (PyType_Ready(&EventBase_Type) < 0) { + return; + } + PyModule_AddObject(m, "EventBase", (PyObject *)&EventBase_Type); + + if (PyType_Ready(&Event_Type) < 0) + return; + PyModule_AddObject(m, "Event", (PyObject *)&Event_Type); + + defaultEventBase = (EventBaseObject *)EventBase_New(&EventBase_Type, + NULL, NULL); + + if (defaultEventBase == NULL) { + PyErr_SetString(EventErrorObject, + "error: couldn't create default event base"); + return; + } + if (EventBase_Init(defaultEventBase, PyTuple_New(0), NULL) < 0) { + PyErr_SetString(EventErrorObject, + "error: couldn't initialize default event base"); + return; + } + PyModule_AddObject(m, "DefaultEventBase", (PyObject *)defaultEventBase); + + /* Add constants to the module */ + ADDCONST(m, "EV_READ", EV_READ); + ADDCONST(m, "EV_WRITE", EV_WRITE); + ADDCONST(m, "EV_TIMEOUT", EV_TIMEOUT); + ADDCONST(m, "EV_SIGNAL", EV_SIGNAL); + ADDCONST(m, "EV_PERSIST", EV_PERSIST); + ADDCONST(m, "EVLOOP_ONCE", EVLOOP_ONCE); + ADDCONST(m, "EVLOOP_NONBLOCK", EVLOOP_NONBLOCK); + PyModule_AddObject(m, "LIBEVENT_VERSION", + PyString_FromString(event_get_version())); + PyModule_AddObject(m, "LIBEVENT_METHOD", + PyString_FromString(event_get_method())); +} diff --git a/libevent/tests/TestAll.py b/libevent/tests/TestAll.py new file mode 100644 index 0000000..d005f94 --- /dev/null +++ b/libevent/tests/TestAll.py @@ -0,0 +1,12 @@ +""" Runs all unit tests for the libevent package. """ +# Copyright (c) 2006 Andy Gross. See LICENSE.txt for details. + +import sys +import unittest + +from TestEvent import * +from TestEventBase import * +from TestPackage import * + +if __name__=='__main__': + unittest.main() diff --git a/libevent/tests/TestEvent.py b/libevent/tests/TestEvent.py new file mode 100644 index 0000000..018d3c8 --- /dev/null +++ b/libevent/tests/TestEvent.py @@ -0,0 +1,125 @@ +import unittest +import tempfile +import sys +import os +import time +import signal +import socket +import libevent + +def passThroughEventCallback(fd, events, eventObj): + return fd, events, eventObj + +def makeEvent(fd=0, events=libevent.EV_WRITE): + return libevent.createEvent(fd, events, passThroughEventCallback) + +class EventConstructionTests(unittest.TestCase): + def testValidConstructionWithIntegerFd(self): + event = makeEvent() + + def testEventsGetDefaultEventBase(self): + event = makeEvent() + self.assertEqual(event.eventBase, libevent.DefaultEventBase) + + def testSettingCustomEventBase(self): + event = makeEvent() + newEventBase = libevent.EventBase() + event.setEventBase(newEventBase) + self.assertEqual(event.eventBase, newEventBase) + + def testInvalidConstructionNonCallableCallback(self): + self.assertRaises(libevent.EventError, libevent.Event, sys.stdout, + libevent.EV_WRITE, "i'm not a callable, thats fer shure") + + def testValidObjectStructure(self): + event = makeEvent(sys.stdout) + self.assertEqual(event.fileno(), sys.stdout.fileno()) + self.assertEqual(event.callback, passThroughEventCallback) + self.assertEqual(event.events & libevent.EV_WRITE, libevent.EV_WRITE) + self.assertEqual(event.numCalls, 0) + + def testValidConstructionWithFileLikeObject(self): + fp = tempfile.TemporaryFile() + event = libevent.Event(fp, libevent.EV_WRITE, passThroughEventCallback) + + def testCreateTimer(self): + timer = libevent.createTimer(passThroughEventCallback) + + def testTimerFlags(self): + timer = libevent.createTimer(passThroughEventCallback) + timer.addToLoop(1) + self.assertEqual(timer.pending() & libevent.EV_TIMEOUT, True) + timer.removeFromLoop() + self.assertEqual(timer.pending() & libevent.EV_TIMEOUT, False) + + +class EventPriorityTests(unittest.TestCase): + def testSettingPriority(self): + e = makeEvent() + e.setPriority(2) + self.assertEqual(e.priority, 2) + + def testDefaultPriorityIsMiddle(self): + e = makeEvent() + self.assertEqual(e.priority, 1) + + def testSettingCustomPriorityCount(self): + eventBase = libevent.EventBase(numPriorities=420) + e = eventBase.createEvent(fd=0, events=libevent.EV_READ, callback=passThroughEventCallback) + self.assertEqual(e.priority, 210) + + def testSettingPriorityAfterLoopAdd(self): + e = makeEvent() + e.addToLoop() + e.setPriority(1) + +class EventLoopSimpleTests(unittest.TestCase): + def testSimpleSocketCallback(self): + def serverCallback(fd, events, eventObj): + s = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) + client, addr = s.accept() + client.send("foo") + eventObj.removeFromLoop() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setblocking(False) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("127.0.0.1", 50505)) + s.listen(5) + serverEvent = libevent.createEvent(fd=s, events=libevent.EV_READ, callback=serverCallback) + serverEvent.addToLoop() + c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + c.setblocking(False) + c.connect_ex(("127.0.0.1", 50505)) + + libevent.dispatch() + + c.setblocking(True) + self.assertEqual(c.recv(3), "foo") + c.close() + s.close() + + def testSimpleTimerCallback(self): + t = int(time.time()) + cb = lambda fd, events, obj: self.assertEqual(int(time.time())-t, 2) + timer = libevent.createTimer(cb) + timer.addToLoop(timeout=2) + libevent.loop(libevent.EVLOOP_ONCE) + + def testLoopExit(self): + cb = lambda fd, events, obj: libevent.loopExit(0) + timer = libevent.createTimer(cb) + timer.addToLoop(timeout=2) + libevent.dispatch() + + def testSignalHandler(self): + signalHandlerCallback = lambda signum, events, obj: obj.removeFromLoop() + signalHandler = libevent.createSignalHandler(signal.SIGUSR1, signalHandlerCallback) + signalHandler.addToLoop() + signalSenderCallback = lambda fd, events, obj: os.kill(os.getpid(), signal.SIGUSR1) + timer = libevent.createTimer(signalSenderCallback) + timer.addToLoop(1) + libevent.dispatch() + # if we get here, it worked - suboptimal way to test this + +if __name__=='__main__': + unittest.main() diff --git a/libevent/tests/TestEventBase.py b/libevent/tests/TestEventBase.py new file mode 100644 index 0000000..f8fb8a6 --- /dev/null +++ b/libevent/tests/TestEventBase.py @@ -0,0 +1,20 @@ +import unittest +import libevent + +__all__ = ["EventBaseTests"] + +class EventBaseTests(unittest.TestCase): + def testEventBaseValidConstructionNoArgs(self): + eventBase = libevent.EventBase() + + def testEventBaseValidConstructionOneArg(self): + eventBase = libevent.EventBase(3) + + def testEventBaseValidConstructionKwargs(self): + eventBase = libevent.EventBase(numPriorities=3) + + def testEventBaseInvalidConstruction(self): + self.assertRaises(TypeError, libevent.EventBase, stupid=1) + +if __name__=='__main__': + unittest.main() diff --git a/libevent/tests/TestPackage.py b/libevent/tests/TestPackage.py new file mode 100644 index 0000000..c579656 --- /dev/null +++ b/libevent/tests/TestPackage.py @@ -0,0 +1,10 @@ +import unittest +import libevent + +__all__ = ["PackageTests"] + +class PackageTests(unittest.TestCase): + pass + +if __name__=='__main__': + unittest.main() diff --git a/libevent/tests/__init__.py b/libevent/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libevent_python.egg-info/PKG-INFO b/libevent_python.egg-info/PKG-INFO new file mode 100644 index 0000000..6d0daac --- /dev/null +++ b/libevent_python.egg-info/PKG-INFO @@ -0,0 +1,18 @@ +Metadata-Version: 1.0 +Name: libevent-python +Version: 0.1a8 +Summary: A CPython extension module wrapping the libevent library +Home-page: http://python-hpio.net/trac/wiki/LibEventPython +Author: Andy Gross +Author-email: andy@andygross.org +License: BSD +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 3 - Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Networking +Classifier: Topic :: Internet diff --git a/libevent_python.egg-info/SOURCES.txt b/libevent_python.egg-info/SOURCES.txt new file mode 100644 index 0000000..44ac956 --- /dev/null +++ b/libevent_python.egg-info/SOURCES.txt @@ -0,0 +1,18 @@ +INSTALL.txt +LICENSE.txt +README.txt +TODO.txt +ez_setup.py +setup.py +examples/echo_server.py +libevent/__init__.py +libevent/eventmodule.c +libevent/tests/TestAll.py +libevent/tests/TestEvent.py +libevent/tests/TestEventBase.py +libevent/tests/TestPackage.py +libevent/tests/__init__.py +libevent_python.egg-info/PKG-INFO +libevent_python.egg-info/SOURCES.txt +libevent_python.egg-info/not-zip-safe +libevent_python.egg-info/top_level.txt diff --git a/libevent_python.egg-info/not-zip-safe b/libevent_python.egg-info/not-zip-safe new file mode 100644 index 0000000..e69de29 diff --git a/libevent_python.egg-info/top_level.txt b/libevent_python.egg-info/top_level.txt new file mode 100644 index 0000000..dd6fade --- /dev/null +++ b/libevent_python.egg-info/top_level.txt @@ -0,0 +1 @@ +libevent diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c27b115 --- /dev/null +++ b/setup.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# Copyright (c) 2006 Andy Gross +# Copyright (c) 2006 Nick Mathewson +# See LICENSE.txt for details. + +import os, sys, ez_setup +ez_setup.use_setuptools() + +from setuptools import setup, Extension, find_packages + +extensions = [ + Extension("libevent.event", + ["libevent/eventmodule.c"], + include_dirs=["/usr/local/include"], + library_dirs=["/usr/local/lib"], + libraries=["event"]), +] + +setup( + name="libevent-python", + version="0.1a8", + description="A CPython extension module wrapping the libevent library", + author="Andy Gross", + author_email="andy@andygross.org", + url="http://python-hpio.net/trac/wiki/LibEventPython", + license="BSD", + packages=find_packages(), + package_data={'': ['*.txt', 'ez_setup.py', 'examples/*']}, + ext_modules = extensions, + zip_safe = False, + test_suite = "libevent.tests.TestAll", + classifiers = [f.strip() for f in """ + Development Status :: 3 - Alpha + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Topic :: Software Development :: Libraries :: Python Modules + Topic :: System :: Networking + Topic :: Internet""".splitlines() if f.strip()], +) +