FibraNet-10/0000755000175000017500000000000010504153033012014 5ustar simonsimonFibraNet-10/nanothreads.py0000644000175000017500000002440310504152754014710 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ The nanothreads module simulates concurrency using Python generators to implement cooperative threads. """ __author__ = "simonwittber@gmail.com" #import the best timing function possible (platform dependent) import platform if platform.system() == "Windows": from time import clock as time_func else: from time import time as time_func del platform from collections import deque from itertools import chain as chain_iterators from threading import Thread, Lock import time import eventnet.driver #core functions and classes def throw(e): """ Raise an exception. This function exists, so that lambda functions can raise Exceptions. """ raise e def synchronize(func): """ A decorator which adds threading.Lock functionality around func. """ lock = Lock() def f(*args, **kw): lock.acquire() try: r = func(*args, **kw) finally: lock.release() return r return f class _KillFibra(Exception): pass class NanoEvent(object): pass class UNBLOCK(NanoEvent): """ Yield UNBLOCK() to spawn the next iteration into a seperate, OS level thread. """ pass class CONTINUE(NanoEvent): """ Yield CONTINUE() or None, to allow the next task in the schedule to iterate. """ pass class SLEEP(NanoEvent): """ Yield SLEEP() to make the task pause iterations for a period. """ def __init__(self, seconds): self.seconds = seconds class RESUME_ON_EVENT(NanoEvent): """ Yield RESUME_ON_EVENT(event_name) to pause the task, and resume when event_name is posted. """ def __init__(self, name): self.name = name class Fibra(object): """ Fibra is a Latin word, meaning fiber. Fibra instances are very light cooperatives threads, which are iterated by the Pool. To create a Fibra instance, pass a generator function the the Pool(). register method, which will return a Fibra instance. A Fibra instance can be .pause(d), .resume(d), .kill(ed), and .end(ed). When a Fibra instance ends, it calls all functions which were registered with the Fibra.call_on_exit method. When a Fibra instance is killed, it does not call any exit functions. """ __slots__ = 'task', 'next', 'thread', 'exit_funcs' def __init__(self, task): self.task = task try: self.next = self.task.next except AttributeError, e: raise AttributeError("Cannot install a function as a nanothread, use a generator function.") self.exit_funcs = [] def __repr__(self): return "<%s object at 0x%X >" % (self.__class__.__name__, id(self)) def pause(self): """ Stops the execution of the fibra, until the resume method is called. """ self.next = lambda: None def resume(self): """ Resumes the fibra after it has been paused. """ self.next = self.task.next def kill(self): """ Stop the fibra, and do not call any registered exit functions. """ self.next = lambda: throw(_KillFibra) def end(self): """ Stop the fibra, and call any registered exit functions. """ self.next = lambda: throw(StopIteration) def call_on_exit(self, func, *args, **kw): """ Add a function which is called when the fibra terminates. """ self.exit_funcs.append((func, list(args), dict(kw))) def call_exit_funcs(self): """ Calls exit functions registered for this instance. This method is called automatically by the scheduler when the task finishes. """ for func, arg, kw in self.exit_funcs: func(*arg, **kw) def spawn_thread(self, onThreadFinish): """ Run the next iteration step of the task in a seperate thread. """ def yield_one_step(): try: self.task.next() except Exception, e: self.next = lambda: throw(e) onThreadFinish(self) self.thread = Thread(target=yield_one_step) self.thread.start() class Pool(object): """ A Cooperative/Preemptive thread scheduler, implemented using generators. """ __slots__ = ["pool", "running"] def __init__(self): self.pool = deque() self.running = False @synchronize def install(self, generator): """ Add a new generator (task) to the pool. Returns a fibra instance, which can control the task. """ fibra = Fibra(generator) self.pool.append(fibra) return fibra @synchronize def install_fibra(self, fibra): """ Add a fibra to the pool. Returns the same Fibra. """ self.pool.append(fibra) return fibra def chain(self, *args): """ Chain the execution of multiple generators. """ return self.install(chain_iterators(*args)) def defer(self, func, *args, **kw): """ Defer a function call until the next iteration of the pool. """ def deferred(*args, **kw): func(*args, **kw) yield None return self.install(deferred(*args, **kw)) def defer_for(self, seconds, func, *args, **kw): """ Defer a function call for a number of seconds. """ def deferred(seconds, func, *args, **kw): start = time_func() while (time_func() - start) <= seconds: yield None func(*args, **kw) yield None return self.install(deferred(seconds, func, *args, **kw)) def poll(self): """ Iterate the execution queue. Used for integrating nanothreads with other mainloop constructs. """ try: task = self.pool.popleft() except IndexError: return try: r = task.next() except StopIteration: task.call_exit_funcs() return except _KillFibra: return if isinstance(r, UNBLOCK): task.spawn_thread(self.resume_from_thread) elif isinstance(r, RESUME_ON_EVENT): self.resume_on_event(r.name, task) elif isinstance(r, SLEEP): self.resume_later(r.seconds, task) else: self.pool.append(task) def loop(self): """ Start the scheduler, and keep running until all tasks have finished. """ #The below assignments are simple optimisations, which help avoid multiple attribute lookups per loop. self_pool_popleft = self.pool.popleft self_pool_append = self.pool.append self_pool = self.pool self.running = True while self.running: try: task = self_pool_popleft() except IndexError: continue try: r = task.next() except StopIteration: task.call_exit_funcs() continue except _KillFibra: continue if isinstance(r, UNBLOCK): task.spawn_thread(self.resume_from_thread) elif isinstance(r, RESUME_ON_EVENT): self.resume_on_event(r.name, task) elif isinstance(r, SLEEP): self.resume_later(r.seconds, task) else: self_pool_append(task) def shutdown(self): """ Shutdown the looping scheduler cleanly. """ self.running = False def resume_later(self, seconds, task): def resume_task(): self.pool.append(task) self.defer_for(seconds, resume_task) def resume_on_event(self, event_name, task): @eventnet.driver.subscribe(event_name) def resume_task(): self.pool.append(task) resume_task.release() def resume_from_thread(self, fibra): def reinstall(): fibra.thread.join() self.pool.append(fibra) self.defer(reinstall) # This code generates global names which point to method names of a global # Pool instance. This is so the user can use these names to access a default # global nanothread scheduler. __pool = Pool() pool = __pool.pool for name in dir(__pool): if name[:2] != "__": attr = getattr(__pool, name) if callable(attr): globals()[name] = attr #user functions and classes class TaskQueue(object): def __init__(self): self.tasks = [] self.current_thread = None def _update(self): if len(self.tasks) > 0 and self.current_thread is None: self.current_thread = install(self.tasks.pop(0)) self.current_thread.call_on_exit(self._finish_task) def _finish_task(self): self.current_thread = None self._update() def install(self, task): self.tasks.append(task) self._update() def flush(self): self.tasks[:] = [] if hasattr(self, 'current_thread'): self.current_thread.kill() self.current_thread = None FibraNet-10/LICENSE0000644000175000017500000000204110504152754013027 0ustar simonsimonCopyright (c) 2006 Simon Wittber 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. FibraNet-10/sandbox/0000755000175000017500000000000010504153033013452 5ustar simonsimonFibraNet-10/sandbox/server.py0000644000175000017500000000500110504152754015337 0ustar simonsimonimport pygame import nanothreads import nanotubes import time import weakref import random class PygameListener(nanotubes.Listener): """ A custom listener which tracks connections in a list. Connected sockets are available via the connections attribute, and use the MessageProtocol to communicate. """ def __init__(self, (host, port)): nanotubes.Listener.__init__(self, (host, port), nanotubes.MessageProtocol) self.connections = [] def broadcast(self, name, **kw): """ Broadcast a message to all connected sockets. """ for conn_ref in self.connections: conn_ref().protocol.post(name, **kw) def cleanup(self): """ Remove dead connections. """ connections = [] for conn_ref in self.connections: if conn_ref() is not None: connections.append(conn_ref) self.connections[:] = connections def handle_new(self, conn): self.connections.append(weakref.ref(conn)) def main(): pygame.init() #start a server listening for connections on port 1984 server = PygameListener(('10.1.1.55',1984)) screen = pygame.display.set_mode((320,200)) while True: time.sleep(0.01) #the SocketHandler must be polled regularly. nanotubes.SocketHandler.poll() for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: #broadcast MOUSEMOTION events to all connected clients. server.broadcast(event.type, **event.dict) for conn_ref in server.connections: #get a reference to the connection conn = conn_ref() #if the reference is dead, ignore it if conn is None: continue #recieved events are stored in .protocol.messages for event_type, args in conn.protocol.messages: #if recieved event is pygame.MOUSEMOTION, draw a pixel if event_type == pygame.MOUSEMOTION: screen.set_at(args['pos'], (255,255,255)) #remove messages from the connections queue. conn.protocol.messages[:] = [] pygame.display.flip() #cleanup and remove dead connections. server.cleanup() yield None nanothreads.install(main()) nanothreads.loop() FibraNet-10/sandbox/client.py0000644000175000017500000000235710504152754015322 0ustar simonsimonimport pygame import nanothreads import nanotubes import time def main(): pygame.init() #create a client connection to the server on port 1984 using the MessageProtocol client = nanotubes.Connector(('10.1.1.55',1984), nanotubes.MessageProtocol) screen = pygame.display.set_mode((320,200)) while True: time.sleep(0.01) #itereate the network queue nanotubes.SocketHandler.poll() for event in pygame.event.get(): if event.type == pygame.QUIT: raise SystemExit elif event.type == pygame.MOUSEMOTION: #send all MOUSEMOTION events to the server client.protocol.post(event.type, **event.dict) #any events recieved from the server are stored in client.protocol.messages for event_type, args in client.protocol.messages: if event_type == pygame.MOUSEMOTION: #draw all MOUSEMOTION events as pixels to the screen screen.set_at(args['pos'], (255,255,255)) #clear the incoming network event queue client.protocol.messages[:] = [] pygame.display.flip() yield None nanothreads.install(main()) nanothreads.loop()FibraNet-10/PKG-INFO0000644000175000017500000000132510504153033013112 0ustar simonsimonMetadata-Version: 1.0 Name: FibraNet Version: 10 Summary: A cooperative threading and event driven framework with simple network capabilities. Home-page: http://code.google.com/p/fibranet/ Author: Simon Wittber Author-email: simonwittber@gmail.com License: BSD Description: The FibraNet package provides an event dispatch mechanism, a cooperative scheduler, an asynchronous networking library and a safe, fast serializer for simple Python types. It is designed to simplify applications which need to simulate concurrency, particularly games. New in this Version: =================== gherkin now efficiently encodes homogenous lists and tuples. Platform: Any FibraNet-10/FibraNet.egg-info/0000755000175000017500000000000010504153033015200 5ustar simonsimonFibraNet-10/FibraNet.egg-info/PKG-INFO0000644000175000017500000000132510504153016016277 0ustar simonsimonMetadata-Version: 1.0 Name: FibraNet Version: 10 Summary: A cooperative threading and event driven framework with simple network capabilities. Home-page: http://code.google.com/p/fibranet/ Author: Simon Wittber Author-email: simonwittber@gmail.com License: BSD Description: The FibraNet package provides an event dispatch mechanism, a cooperative scheduler, an asynchronous networking library and a safe, fast serializer for simple Python types. It is designed to simplify applications which need to simulate concurrency, particularly games. New in this Version: =================== gherkin now efficiently encodes homogenous lists and tuples. Platform: Any FibraNet-10/FibraNet.egg-info/dependency_links.txt0000644000175000017500000000000110504153016021247 0ustar simonsimon FibraNet-10/FibraNet.egg-info/SOURCES.txt0000644000175000017500000000051210504153016017063 0ustar simonsimonLICENSE ez_setup.py gherkin.py nanothreads.py nanotubes.py setup.py FibraNet.egg-info/PKG-INFO FibraNet.egg-info/SOURCES.txt FibraNet.egg-info/dependency_links.txt FibraNet.egg-info/top_level.txt eventnet/__init__.py eventnet/_pygame.py eventnet/baseevent.py eventnet/driver.py eventnet/net.py sandbox/client.py sandbox/server.py FibraNet-10/FibraNet.egg-info/top_level.txt0000644000175000017500000000004710504153016017734 0ustar simonsimoneventnet nanothreads gherkin nanotubes FibraNet-10/setup.cfg0000644000175000017500000000007310504153033013635 0ustar simonsimon[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 FibraNet-10/eventnet/0000755000175000017500000000000010504153033013644 5ustar simonsimonFibraNet-10/eventnet/driver.py0000644000175000017500000001130610504152754015523 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ This module contains helpers for using the basevent.Dispatcher. It provides a decorator for subscribing functions and methods, a class for handling mutiple events and a hook function for Dispatcher.post integration. """ import weakref import threading import baseevent import nanothreads SUBSCRIBE_PREFIX = "EVT_" CAPTURE_PREFIX = "CAP_" dispatcher = baseevent.Dispatcher() post_lock = threading.Lock() def _post(*args, **kw): post_lock.acquire() try: return dispatcher.post(*args, **kw) finally: post_lock.release() def post(*args, **kw): nanothreads.defer(_post, *args, **kw) def hook(callback): """ Hook a callback function into the event dispatch machinery. """ first_post = globals()["post"] def hooked_post(name, **kw): callback(name, **kw) return first_post(name, **kw) globals()["post"] = hooked_post class subscribe(object): """ A decorator, which subscribes a callable to an event. """ def __init__(self, _event_identity, **kw): self.name = _event_identity self.kw = kw def __call__(self, func): self.func = func self.id = dispatcher.subscribe(func, self.name, **self.kw) return self def release(self): """ Unsubscribe this function from the event. """ dispatcher.unsubscribe(self.id, self.name) class capture(object): """ A decorator, which binds a callable to an event. """ def __init__(self, _event_identity): self.name = _event_identity def __call__(self, func): self.id = dispatcher.capture(func, self.name) return self def release(self): """ Unbind this function from the event. """ dispatcher.release(self.id, self.name) class Subscriber(object): """ This is a mixin class. It has no __init__ constructor. A Subscriber instance receives multiple events, which are handled by methods defined with a special prefix, 'EVT_'. eg: class Handler(Subscriber): def EVT_SomeEvent(self, event): "This method will be called whenever 'SomeEvent' is posted." pass def EVT_SomeOtherEvent(self, event): "This method will be called whenever 'SomeOtherEvent' is posted." pass h = Handler() #enable event handling, call the .capture method. h.capture() #disable event handling, call the .release method. h.release() """ def capture(self): """ Enable event handling for all EVT_ methods in this instance. """ self.__subscribers = [] for meth in dir(self): if SUBSCRIBE_PREFIX in meth[:len(SUBSCRIBE_PREFIX)]: name = meth[len(SUBSCRIBE_PREFIX):] s = subscribe(name)(getattr(self, meth)) self.__subscribers.append(s) if CAPTURE_PREFIX in meth[:len(CAPTURE_PREFIX)]: name = meth[len(CAPTURE_PREFIX):] s = capture(name)(getattr(self, meth)) self.__subscribers.append(s) def release(self): """ Disable event handling for all EVT_ methods in this instance. """ for subscriber in self.__subscribers: subscriber.release() class Handler(object): """ A handler is different, in that it does not receive dispatched events, but only events which are posted directly to it, via its own post method. """ def post(self, _event_identity, **kw): return tuple([baseevent.call_with_keywords(getattr(self, meth), **kw) for meth in dir(self) if SUBSCRIBE_PREFIX+_event_identity == meth]) FibraNet-10/eventnet/baseevent.py0000644000175000017500000000777110504152754016217 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ A simple event/signal dispatcher. """ import weakref from collections import deque def call_with_keywords(func, *args, **kw): """ Call a function with allowed keywords only. """ return func(*args, **dict([i for i in kw.items() if i[0] in func.func_code.co_varnames])) class Dispatcher(object): """ A simple event dispatcher. """ def __init__(self): self.events = {} self.stacks = {} def subscribe(self, func, name, static=False): """ Subscribe a callable to an event. Keeps a weakref to func. """ if static: print func callable = func.im_func obj = func function = lambda kw: call_with_keywords(callable, obj, **kw) else: if hasattr(func, 'im_self'): obj = weakref.proxy(func.im_self) callable = func.im_func function = lambda kw: call_with_keywords(callable, obj, **kw) function.ref = weakref.ref(func.im_self, lambda x: self.events[name].remove(function)) else: callable = weakref.proxy(func) function = lambda kw: call_with_keywords(callable, **kw) function.ref = weakref.ref(func, lambda x: self.events[name].remove(function)) try: self.events[name].append(function) except KeyError: self.events[name] = [function] except AttributeError: raise RuntimeError("Cannot subscribe to a captured event. %s is currently captured by %s." % (name,self.events[name][0])) return function def capture(self, func, name): """ Bind a callable exclusively to an event. """ try: handlers = self.events[name] except KeyError: handlers = [] self.stacks.setdefault(name, deque()).appendleft(handlers) function = lambda kw: call_with_keywords(func, **kw) self.events[name] = (function,) return function def release(self, func, name): """ Release a captured event. """ if func is not self.events[name][0]: raise RuntimeError("A callable bound to %s was released out of sequence." % name) handlers = self.stacks[name].popleft() self.events[name] = handlers def unsubscribe(self, func, name): """ Unsubscribe a callable from an event. """ try: self.events[name].remove(func) except ValueError: pass def post(self, _event_identity, **kw): """ Post an event, which is dispatched to all callables which are subscribed to that event. """ try: events = self.events[_event_identity] except KeyError: return tuple() else: return tuple((i(kw) for i in events)) FibraNet-10/eventnet/net.py0000644000175000017500000000763610504152754015031 0ustar simonsimonimport struct import cStringIO import gherkin as encoder from twisted.internet import protocol, reactor import driver import warnings warnings.warn("This module has been deprecated in favour of nanotubes.") class StringQueue(object): def __init__(self): self.l_buffer = [] self.s_buffer = "" def write(self, data): self.l_buffer.append(data) def read(self, count=None): if count > len(self.s_buffer) or count==None: self.build_s_buffer() result = self.s_buffer[:count] self.s_buffer = self.s_buffer[count:] return result def build_s_buffer(self): new_string = "".join(self.l_buffer) self.s_buffer = "".join((self.s_buffer, new_string)) self.l_buffer = [] def __len__(self): self.build_s_buffer() return len(self.s_buffer) def message_unpacker(data): while True: if len(data) >= 4: size = struct.unpack("!l", data.read(4))[0] while len(data) < size: yield None message = data.read(size) yield message else: yield None def pack_message(data): size = struct.pack("!l", len(data)) return "".join((size, data)) class MessageProtocol(protocol.Protocol): def __init__(self, *args, **kw): self.buffer = StringQueue() self.message_unpacker = message_unpacker(self.buffer) def connectionMade(self): if self.factory.handleNewConnection: self.factory.handleNewConnection(self) def connectionLost(self, reason): if self.factory.handleCloseConnection: self.factory.handleCloseConnection(self, reason) def post(self, name, **kw): data = pack_message(encoder.dumps((name, kw))) self.transport.write(data) def flush(self): reactor.iterate() def dataReceived(self, data): self.buffer.write(data) for message in self.message_unpacker: if message is not None: name, kw = encoder.loads(message) kw['_conn'] = self kw['conn'] = self kw['reply'] = self.post if hasattr(self, 'event_handler'): self.event_handler.post(name, **kw) else: driver.post(name, **kw) else: break class ClientFactory(protocol.ReconnectingClientFactory): def __init__(self, onConnect,onClose, protocol): self.handleNewConnection = onConnect self.handleCloseConnection = onClose self.maxDelay = 10 self.__protocol = protocol def buildProtocol(self, addr): self.resetDelay() p = self.__protocol() p.factory = self return p class ServerFactory(protocol.ServerFactory): def __init__(self, onConnect,onClose, protocol): self.handleNewConnection = onConnect self.handleCloseConnection = onClose self.__protocol = protocol def buildProtocol(self, addr): p = self.__protocol() p.factory = self return p def listen(port, onConnect, onClose, protocol=MessageProtocol): """ Listen on a port. Calls onConnect (with connection as argument) when a new connection occurs. Calls onClose (with connection and reason as arguments) when a connection is closed. """ reactor.listenTCP(port, ServerFactory(onConnect, onClose, protocol)) def connect(address, port, onConnect, onClose, protocol=MessageProtocol): """ Connects to an address and port. Calls onConnect (with connection as argument) when a new connection occurs. Calls onClose (with connection and reason as arguments) when the connection is closed. """ reactor.connectTCP(address, port, ClientFactory(onConnect, onClose, protocol)) poll = reactor.iterate def poll_iterator(): """ An iterator which polls the network stuff as it is iterated. """ while True: yield poll() FibraNet-10/eventnet/_pygame.py0000644000175000017500000000245710504152754015660 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. import pygame import pygame.locals import driver def pump_events(): event_name = pygame.event.event_name while True: for event in pygame.event.get(): driver.post(event_name(event.type), **event.dict) yield None FibraNet-10/eventnet/__init__.py0000644000175000017500000000546610504152754016001 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ EventNet provides a generic, global dispatcher system for Event driven systems. = Posting Events = To post an event, use: eventnet.driver.post(event_name, **kw) Any registered subscribers will be called with the event as the first argument. An instance of an event has a name attribute, an args dictionary. All keys in the args dictionary are also attributes of the instances. = Handling Events with Functions = To subscribe a function to an event, use the subscriber decorator. If the event has arg1 and arg2 attributes, they will be passed to the function. The function signature need only specify the event attributes it needs. @eventnet.driver.subscribe(event_name) def event_handler(arg1, arg2): print arg1, arg2 A subscribed function has a release method which will stop the function from being called when subscribed event is posted. = Handling Events with Classes = A class which inherits from eventnet.driver.Subscriber can handle multiple events. class Handler(eventnet.driver.Subscriber): def EVT_Event(self, arg1, arg2): print arg1, arg2 def EVT_SomeOtherEvent(self, arg1): print arg1 The above class defined two methods which are prefixed with 'EVT_'. This prefix causes the class to automatically subscribe those methods to with the event name which is taken from the remainder of the method name. Eg: the method EVT_Event would be called whenever eventnet.driver.post('Event', arg1=1, arg2=2, arg3=3) is called. Only the arguments in the method signature are supplied, the method need not handle all potential event attributes. To allow a class instance to start handling events, the capture method must be called. To stop a class instance from handling events, the release method must be called. """ import driver FibraNet-10/setup.py0000644000175000017500000000160710504152754013543 0ustar simonsimonfrom ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages setup( name = "FibraNet", version = "10", packages = find_packages(), py_modules=['nanothreads','gherkin','nanotubes'], description='A cooperative threading and event driven framework with simple network capabilities.', author='Simon Wittber', author_email='simonwittber@gmail.com', license='BSD', platforms=['Any'], url="http://code.google.com/p/fibranet/", long_description=""" The FibraNet package provides an event dispatch mechanism, a cooperative scheduler, an asynchronous networking library and a safe, fast serializer for simple Python types. It is designed to simplify applications which need to simulate concurrency, particularly games. New in this Version: =================== gherkin now efficiently encodes homogenous lists and tuples. """ ) FibraNet-10/ez_setup.py0000644000175000017500000001755410504152754014251 0ustar simonsimon#!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.6a7" 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.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', } 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. ---------------------------------------------------------------------------""", version, download_base, delay ); 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:]) FibraNet-10/nanotubes.py0000644000175000017500000002536210504152754014405 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ The nanotubes module implements asynchronous socket handling, and provides protocol classes which simplify event passing over sockets. """ __author__ = "simonwittber@gmail.com" import socket import select import struct import gherkin as encoder from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN, EINTR, EISCONN #---------------------------------------------------------------------# class StringQueue(object): """ A simple character buffer which is used for queing network bytes. """ def __init__(self): self.l_buffer = [] self.s_buffer = "" def write(self, data): """ Append's data to the buffer. """ self.l_buffer.append(data) def read(self, count=None): """ Returns and removes a number of bytes from the buffer. If count is None, all bytes are returned. """ result = self.peek(count) self.discard(count) return result def discard(self, count): """ Removes a number of bytes from the buffer. """ self.s_buffer = self.s_buffer[count:] def peek(self, count=None): """ Returns a number of bytes from the buffer, but does not remove them. If count is None, all bytes are returned. """ if count > len(self.s_buffer) or count==None: self._build_s_buffer() result = self.s_buffer[:count] return result def _build_s_buffer(self): new_string = "".join(self.l_buffer) self.s_buffer = "".join((self.s_buffer, new_string)) self.l_buffer = [] def __len__(self): self._build_s_buffer() return len(self.s_buffer) #---------------------------------------------------------------------# class SocketHandler(object): """ The SocketHandler class registers all socket connections, and uses the select function to trigger read, write and close calls on Connection objects. """ sockets = [] socket_objects = {} dead_sockets = [] @classmethod def poll(cls): """ The poll method removes dead sockets, and triggers method calls on any Connection objects which are waiting for attention. This method needs to be called often. """ for s in cls.dead_sockets: cls.sockets.remove(s) del cls.socket_objects[s] cls.dead_sockets[:] = [] if cls.sockets: readable,writable,exceptional = select.select(cls.sockets,cls.sockets,cls.sockets,0) for s in readable: cls.socket_objects[s].handle_read() for s in writable: cls.socket_objects[s].handle_write() for s in exceptional: cls.socket_objects[s].handle_exception() @classmethod def nanothread(cls): """ The nanothread method returns a generator which can be installed into the nanothread scheduler which will iterate the poll method. """ while True: cls.poll() yield None @classmethod def remove(cls, s): """ Schedules a socket for deletion. """ cls.dead_sockets.append(s) @classmethod def add_socket(cls, s, s_o): """ Adds a socket and socket_object to the handler. The socket_object will have handler methods called when activity occurs on the socket. """ cls.sockets.append(s) cls.socket_objects[s] = s_o class Connection(object): def handle_close(self): pass def handle_read(self): pass def handle_write(self): pass def handle_exception(self): pass class TCPConnection(Connection): def __init__(self, tcp_socket, protocol): self.protocol = protocol self.tcp_socket = tcp_socket self.tcp_socket.setblocking(0) SocketHandler.add_socket(self.tcp_socket, self) def channel(self, port): """ Opens a UDP channel on a port. """ udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) return UDPConnection(udp_socket, (self.host, port)) def handle_read(self): try: data = self.tcp_socket.recv(4096) except socket.error: self.handle_exception() else: if len(data) == 0: self.handle_close() self.protocol.input_buffer.write(data) self.protocol.handle_read() def handle_write(self): data = self.protocol.output_buffer.peek(4096) if data: try: count = self.tcp_socket.send(data) except socket.error: self.handle_exception() else: self.protocol.output_buffer.discard(count) def handle_exception(self): self.protocol.handle_exception() class UDPConnection(Connection): def __init__(self, udp_socket, (host, port)): self.udp_socket = udp_socket self.udp_socket.setblocking(0) self.host = host self.port = port self.transmissions = [] SocketHandler.add_socket(self.udp_socket, self) def listen(self, receiver): """ Listen on this channel for incoming traffic. receiver is a callback function which takes (address, data) as args. """ self.receiver = receiver self.udp_socket.bind((self.host, self.port)) self.handle_read = self._handle_read def handle_read(self): #Do nothing by default. pass def _handle_read(self): try: data, address = self.udp_socket.recvfrom(4096) except socket.error, e: self.handle_exception() else: self.receiver(address, data) def transmit(self, address, data): """ Transmit data to address on this channel's port. """ self.transmissions.append((address, data)) def handle_write(self): try: address, data = self.transmissions.pop(-1) except IndexError: pass else: try: count = self.udp_socket.sendto(data, (address, self.port)) except socket.error, e: self.handle_exception() def handle_exception(self): print 'UDP Exception.' pass class Protocol(object): def __init__(self): self.input_buffer = StringQueue() self.output_buffer = StringQueue() def handle_close(self): pass def handle_read(self): pass def handle_exception(self): pass class Listener(TCPConnection): """ Opens a socket and starts listening for connections. """ def __init__(self, (host, port), protocol_factory): TCPConnection.__init__(self, socket.socket(socket.AF_INET, socket.SOCK_STREAM), self) self.host = host self.port = port self.protocol_factory = protocol_factory self.tcp_socket.bind((host, port)) self.tcp_socket.listen(5) def handle_new(self, tcp_connection): pass def handle_read(self): new_socket, address = self.tcp_socket.accept() c = TCPConnection(new_socket, self.protocol_factory()) self.handle_new(c) class Connector(TCPConnection): """ Connects to a socket on a remote host. """ def __init__(self, (host, port), protocol_factory): TCPConnection.__init__(self, socket.socket(socket.AF_INET, socket.SOCK_STREAM), protocol_factory()) self.host = host self.port = port self.connect() def connect(self): err = self.tcp_socket.connect_ex((self.host, self.port)) if err in (EINPROGRESS, EALREADY, EWOULDBLOCK): pass else: raise socket.error, err #---------------------------------------------------------------------# class MessageProtocol(Protocol): """ The message protocol fires and forgets events over a connections. Events are (name, dict) pairs. Recieved events are queued up in the messages attribute. """ def __init__(self): Protocol.__init__(self) self.unpacker = self._create_unpacker(self.input_buffer) self.messages = [] def _create_unpacker(self, buffer): """ This generator yields new messages from the incoming buffer. """ while True: if len(buffer) >= 4: size = struct.unpack("!l", buffer.read(4))[0] while len(buffer) < size: yield None message = buffer.read(size) yield message else: yield None def pack_message(self, name, kw): data = encoder.dumps((name, kw)) size = struct.pack("!l", len(data)) return "".join((size, data)) def post(self, name, **kw): """ Post an event over the connection. The name argument is the event name, kw arguments are the event attributes. """ data = self.pack_message(name, kw) self.output_buffer.write(data) def handle_read(self): for message in self.unpacker: if message is not None: self.messages.append(encoder.loads(message)) else: break FibraNet-10/gherkin.py0000644000175000017500000002317710504152754014040 0ustar simonsimon#Copyright (c) 2006 Simon Wittber # #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. """ Gherkin provides safe serialization for simple python types. """ from types import IntType,TupleType,StringType,FloatType,LongType,ListType,DictType,NoneType,BooleanType,UnicodeType from struct import pack, unpack from cStringIO import StringIO SIZEOF_INT = 4 SIZEOF_FLOAT = 8 UNICODE_CODEC = 'utf-8' def memoize(func): cache = {} def check_memo(*args): if args in cache: return cache[args] else: return cache.setdefault(args, func(*args)) return check_memo class Gherkin(object): def __init__(self): self.strings = {} self.header = 'GHE' self.version = 1 self.protocol = { TupleType:"T", ListType:"L", DictType:"D", LongType:"B", IntType:"I", FloatType:"F", StringType:"S", NoneType:"N", BooleanType:"b", UnicodeType:"U", 'HomogenousList':'H', 'HomogenousTuple':'h' } self.int_size = SIZEOF_INT self.float_size = SIZEOF_FLOAT self.encoder = { DictType:self.enc_dict_type, ListType:self.enc_list_type, TupleType:self.enc_list_type, IntType:memoize(self.enc_int_type), FloatType:memoize(self.enc_float_type), LongType:memoize(self.enc_long_type), UnicodeType:memoize(self.enc_unicode_type), StringType:memoize(self.enc_string_type), NoneType:self.enc_none_type, BooleanType:memoize(self.enc_bool_type) } self.decoder = { self.protocol[TupleType]:self.dec_tuple_type, self.protocol[ListType]:self.dec_list_type, self.protocol[DictType]:self.dec_dict_type, self.protocol[LongType]:self.dec_long_type, self.protocol[StringType]:self.dec_string_type, self.protocol[FloatType]:self.dec_float_type, self.protocol[IntType]:self.dec_int_type, self.protocol[NoneType]:self.dec_none_type, self.protocol[BooleanType]:self.dec_bool_type, self.protocol[UnicodeType]:self.dec_unicode_type, self.protocol['HomogenousList']:self.dec_homogenous_list_type, self.protocol['HomogenousTuple']:self.dec_homogenous_tuple_type } def enc_dict_type(self, obj): data = "".join([self.encoder[type(i)](i) for i in obj.items()]) return "%s%s%s" % (self.protocol[DictType], pack("!L", len(data)), data) def enc_list_type(self, obj): if len(set([type(i) for i in obj])) == 1: return self.enc_homogenous_list_type(obj) data = "".join([self.encoder[type(i)](i) for i in obj]) return "%s%s%s" % (self.protocol[type(obj)], pack("!L", len(data)), data) def enc_homogenous_list_type(self, obj): data = "".join([self.encoder[type(i)](i)[1:] for i in obj]) if type(obj) == type([]): prefix = self.protocol['HomogenousList'] else: prefix = self.protocol['HomogenousTuple'] return "%s%s%s%s" % (prefix, self.protocol[type(obj[0])], pack("!L", len(data)), data) def enc_int_type(self, obj): return "%s%s" % (self.protocol[IntType], pack("!i", obj)) def enc_float_type(self, obj): return "%s%s" % (self.protocol[FloatType], pack("!d", obj)) def enc_long_type(self, obj): obj = hex(obj) if obj[0] == "-": pre = "-" obj = obj[3:-1] else: pre = "+" obj = obj[2:-1] return "%s%s%s%s" % (self.protocol[LongType], pre, pack("!L", len(obj)), obj) def enc_unicode_type(self, obj): obj = obj.encode(UNICODE_CODEC) return "%s%s%s" % (self.protocol[UnicodeType], pack("!L", len(obj)), obj) def enc_string_type(self, obj): return "%s%s%s" % (self.protocol[StringType], pack("!L", len(obj)), obj) def enc_none_type(self, obj): return self.protocol[NoneType] def enc_bool_type(self, obj): return self.protocol[BooleanType] + str(int(obj)) def dumps(self, obj): """ Return the string that would be written to a file by dump(value, file). The value must be a supported type. Raise a ValueError exception if value has (or contains an object that has) an unsupported type. """ options = "".join((hex(self.version)[2:],hex(SIZEOF_INT)[2:],hex(SIZEOF_FLOAT)[2:])) assert len(options) == 3 try: data = self.encoder[type(obj)](obj) except KeyError, e: raise ValueError, "Type not supported. (%s)" % e header = "".join((self.header, options)) assert len(header) == 6 return "".join((header, data)) def dump(self, obj, file): """ Write the value on the open file. The value must be a supported type. The file must be an open file object such as sys.stdout or returned by open() or posix.popen(). It must be opened in binary mode ('wb' or 'w+b'). If the value has (or contains an object that has) an unsupported type, a ValueError exception is raised """ return file.write(self.dumps(obj)) def build_sequence(self, data, cast=list): size = unpack('!L', data.read(SIZEOF_INT))[0] items = [] data_tell = data.tell items_append = items.append self_decoder = self.decoder data_read = data.read start_position = data.tell() while (data_tell() - start_position) < size: T = data_read(1) value = self_decoder[T](data) items_append(value) return cast(items) def build_homogenous_sequence(self, data, cast=list): T = data.read(1) size = unpack('!L', data.read(SIZEOF_INT))[0] items = [] data_tell = data.tell items_append = items.append self_decoder = self.decoder data_read = data.read start_position = data.tell() while (data_tell() - start_position) < size: value = self_decoder[T](data) items_append(value) return cast(items) def dec_tuple_type(self, data): return self.build_sequence(data, cast=tuple) def dec_list_type(self, data): return self.build_sequence(data, cast=list) def dec_homogenous_list_type(self, data): return self.build_homogenous_sequence(data, cast=list) def dec_homogenous_tuple_type(self, data): return self.build_homogenous_sequence(data, cast=tuple) def dec_dict_type(self, data): return self.build_sequence(data, cast=dict) def dec_long_type(self, data): pre = data.read(1) size = unpack('!L', data.read(self.int_size))[0] value = long(data.read(size),16) if pre == "-": value = -value return value def dec_string_type(self, data): size = unpack('!L', data.read(self.int_size))[0] value = str(data.read(size)) return value def dec_float_type(self, data): value = unpack('!d', data.read(self.float_size))[0] return value def dec_int_type(self, data): value = unpack('!i', data.read(self.int_size))[0] return value def dec_none_type(self, data): return None def dec_bool_type(self, data): value = int(data.read(1)) return bool(value) def dec_unicode_type(self, data): size = unpack('!L', data.read(self.int_size))[0] value = data.read(size).decode(UNICODE_CODEC) return value def loads(self, data): """ Convert the string to a value. If no valid value is found, raise EOFError, ValueError or TypeError. Extra characters in the string are ignored. """ self.strings = {} buffer = StringIO(data) header = buffer.read(len(self.header)) assert header == self.header assert self.version <= int(buffer.read(1), 10) self.int_size = int(buffer.read(1), 10) self.float_size = int(buffer.read(1), 10) try: value = self.decoder[buffer.read(1)](buffer) except KeyError, e: raise ValueError, "Type prefix not supported. (%s)" % (e) return value def load(self, file): """ Read one value from the open file and return it. If no valid value is read, raise EOFError, ValueError or TypeError. The file must be an open file object opened in binary mode ('rb' or 'r+b'). """ return self.loads(file.read()) __gherk = Gherkin() dumps = __gherk.dumps loads = __gherk.loads dump = __gherk.dump load = __gherk.load