import sys, types, traceback from py.magic import greenlet class Mainloop(type): maincoro = None tasks = [] @classmethod def mainloop(self): """Run the main loop. Start by executing the file indicated by sys.argv[1]. Then, for each task that was scheduled as a result of running this file, run them. As new tasks are scheduled and the mainloop is resumed, run them in first in first out order. """ execfile(sys.argv[1]) while self.tasks: task = self.tasks.pop(0) task.switch() @classmethod def schedule(self, coro): """Schedule an Actor's coroutine to run in the mainloop. """ self.tasks.append(coro) @classmethod def resume(self): self.maincoro.switch() class Proto(type): def __new__(cls, name, bases, dct): """Create a new instance, a clone of Proto. Create a unique event queue for this Actor and a coroutine for this actor, and start the Actor's mainloop. """ self = type.__new__(cls, name, bases, dct) self._event_queue = [] self._coro = greenlet(self.mainloop) ## Prime the pump self._coro.switch() return self @classmethod def category(self, category=None): """Give this prototype a category. For example, passing "foo.bar" would put this object in a module foo/bar.py along with any other objects of this category. If no category is passed, the category is not changed. Returns the object's category. """ if category: assert "." not in category, "Only modules, not packages, supported for now" self.__module__ = category return self.__module__ @classmethod def name(self, name=None): """Give this prototype a name. This changes the name of the class that this object is written to when the graph is saved. If no name is passed, the name is not changed. Returns the object's name. """ if name: self.__name__ = name return self.__name__ @classmethod def mainloop(self): """Run an Actor's mainloop. The Actor's coroutine is created in __new__ and mainloop is immediately scheduled. We then immediately switch back to the parent (main) coro. After that, the Actor's coro will only be resumed when there is a suitable event in the _event_queue. The event is executed and the mainloop is resumed. In this way starvation can be avoided. """ greenlet.getcurrent().parent.switch() while True: caller, code, globs, locs, continuation = self._event_queue.pop(0) exec code in globs, locs Mainloop.resume() @classmethod def execute(self): """Execute a method, making sure it is being executed in this Actor's coroutine. If the current coro is not our actor's coro, schedule this Actor in the mainloop, schedule this method to be executed in this Actor, and resume the mainloop. """ frame = sys._getframe() caller = frame.f_back.f_locals.get('self') verb_name = frame.f_code.co_name verb_code = getattr(self, "_code_%s" % (verb_name, )) if caller != self: ## Switch coroutines self._event_queue.append([caller, verb_code, frame.f_back.f_locals, dict( verb_name=verb_name, self=self), greenlet.getcurrent()]) Mainloop.schedule(self._coro) Mainloop.resume() return self @classmethod def create_execute(self, verb): """Create a copy of self.execute that has the name "verb". This is for debugging and introspection support. "execute" uses co_name and makes it available to the executed method body as "verb_name". """ c = self.execute.func_code return classmethod( types.FunctionType( types.CodeType( c.co_argcount, c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names, c.co_varnames, self.__module__, verb, 0, c.co_lnotab), globals())) @classmethod def program(self, verb, code, do=None, io=None, prep=None): """Program a verb on this object. verb: The name of the new verb. code: The code of the new verb. do: The direct object specifier. io: The indirect object specifier. prep: The preposition specifier. """ setattr(self, "_code_%s" % (verb, ), code) setattr(self, verb, self.create_execute(verb)) @classmethod def clone(self, name, **kw): """Clone this object, and give it the name "name". Arguments passed as keyword arguments to this function will become attributes of this clone. """ new = self(name, (self, ), kw) category = self.category() if category == "proto": category = "objects" new.category(category) if not sys.modules.has_key(category): sys.modules[category] = types.ModuleType(category) setattr(sys.modules[category], name, new) return new @classmethod def sync(self): """Save the entire proto object graph to a tree of python modules. """ to_write = Proto.__subclasses__(Proto) seen_packages = dict() while to_write: proto = to_write.pop(0) category = proto.category() if category in seen_packages: mode = 'a' prelude = "\n" else: mode = 'w' prelude = '"""%s.py\n"""\n\nfrom proto import Proto\n\n' % (category, ) seen_packages[category] = True file_handle = open("%s.py" % (category, ), mode) file_handle.write(prelude) base_name = proto.__bases__[0].__name__ base_module = proto.__bases__[0].__module__ if base_module not in ['proto', category]: file_handle.write("from %s import %s\n" % (base_module, base_name)) file_handle.write("class %s(%s):\n" % (proto.name(), base_name)) found = False for name, value in proto.__dict__.items(): if name.startswith('_'): continue found = True if isinstance(value, classmethod): code_string = getattr(proto, "_code_%s" % (name, ), None) if code_string is None: print name, value continue file_handle.write('\t%(name)s = Proto.create_execute("%(name)s")\n' % dict(name=name)) file_handle.write('\t_code_%s = """\n%s\n"""\n' % (name, code_string.strip())) else: file_handle.write("\t%s = %r\n" % (name, value)) if not found: file_handle.write("\tpass\n") file_handle.write("\n") to_write.extend(proto.__subclasses__(proto))