git » pymisc » commit 7a80bda

Initial import.

author Alberto Bertogli
2005-07-10 06:35:20 UTC
committer Alberto Bertogli
2005-07-10 06:35:20 UTC

Initial import.

TODO +19 -0
adatasks.py +209 -0
contract.py +136 -0
samples/adatasks/sample1.py +31 -0
samples/adatasks/sample2.py +43 -0
samples/adatasks/sample3.py +21 -0
samples/contract/sample1.py +28 -0

diff --git a/TODO b/TODO
new file mode 100644
index 0000000..fc6a286
--- /dev/null
+++ b/TODO
@@ -0,0 +1,19 @@
+
+common
+------
+
+* license and stuff
+
+
+adatasks
+--------
+
+* documentation
+* optional timeouts
+
+
+contract
+--------
+
+* documentation
+
diff --git a/adatasks.py b/adatasks.py
new file mode 100644
index 0000000..99fbea9
--- /dev/null
+++ b/adatasks.py
@@ -0,0 +1,209 @@
+
+"""
+adatasks - An ada-alike tasks implementation for Python
+Alberto Bertogli (albertogli@telpin.com.ar)
+3/Jul/2005
+"""
+
+import thread, threading
+
+class EntryQueue:
+	"Queue of pending entries to a task."
+
+	def __init__(self, mutex):
+		self.lock = threading.Condition(mutex)
+		self.queue = []
+
+	def inqueue(self, entry):
+		for e, params in self.queue:
+			if e == entry:
+				return True
+		return False
+
+	def isempty(self):
+		if self.queue:
+			return False
+		return True
+
+	def push(self, entry, params):
+		self.queue.append((entry, params))
+
+	def pop(self, entry):
+		for e, params in self.queue:
+			if e == entry:
+				self.queue.remove((e, params))
+				return params
+		raise 'NeverGetHere!'
+
+	def popany(self):
+		if self.queue:
+			return self.queue.pop(0)
+		return None
+
+
+# entry queue dictionary, indexes queues per task
+entrydb = {}
+entrydb_lock = threading.Lock()
+
+def create_queue(name):
+	"Creates a queue for the task with the given name."
+	entrydb_lock.acquire()
+	if not entrydb.has_key(name):
+		entrydb[name] = EntryQueue(entrydb_lock)
+	entrydb_lock.release()
+
+def get_queue(name):
+	"Gets a tasks queue."
+	entrydb_lock.acquire()
+	q = entrydb[name]
+	entrydb_lock.release()
+	return q
+
+
+# list of tasks to launch
+tolaunch = []
+tolaunch_lock = threading.Lock()
+
+# list of running tasks: running[task] = running_count
+running = {}
+running_lock = threading.Condition()
+
+
+class Task:
+	"A task."
+
+	def __init__(self, f):
+		self.f = f
+		self.fname = f.func_name
+		create_queue(self.fname)
+
+	def __run(self, *params):
+		try:
+			self.f(*params)
+		finally:
+			running_lock.acquire()
+			running[self.fname] -= 1
+			running_lock.notifyAll()
+			running_lock.release()
+
+	def __call__(self, *params):
+		running_lock.acquire()
+		if not running.has_key(self.fname):
+			running[self.fname] = 1
+		else:
+			running[self.fname] += 1
+		running_lock.release()
+
+		thread.start_new_thread(self.__run, params)
+
+	def __getattr__(self, entry):
+		if entry in dir(self.f):
+			# return f's attribute if one exists with the given
+			# name
+			return self.f.__getattribute__(entry)
+
+		# write the entry wrapper
+		def wrapper(*params):
+			q = get_queue(self.fname)
+			q.lock.acquire()
+			q.push(entry, params)
+			q.lock.notifyAll()
+			q.lock.release()
+			return
+
+		# return the wrapper
+		return wrapper
+
+	def accept(self, entry):
+		q = get_queue(self.fname)
+		q.lock.acquire()
+		while not q.inqueue(entry):
+			q.lock.wait()
+		ret = q.pop(entry)
+		q.lock.release()
+		return ret
+
+	def select(self, *entries):
+		q = get_queue(self.fname)
+
+		q.lock.acquire()
+		if not entries:
+			r = q.popany()
+			while r == None:
+				q.lock.wait()
+				r = q.popany()
+			q.lock.release()
+			return r
+		else:
+			loop = True
+			while loop:
+				for e in entries:
+					if q.inqueue(e):
+						loop = False
+						break
+				else:
+					q.lock.wait()
+			r = q.pop(e)
+			q.lock.release()
+			return (e, r)
+
+
+def task(f):
+	"Task decorator"
+	task = Task(f)
+	tolaunch_lock.acquire()
+	tolaunch.append(task)
+	tolaunch_lock.release()
+	return task
+
+
+def launch():
+	"Launch pending tasks"
+	global tolaunch
+	tolaunch_lock.acquire()
+	for t in tolaunch:
+		t()
+	tolaunch = []
+	tolaunch_lock.release()
+
+
+def wait_for_task(task):
+	"Wait for the given task to end running"
+	running_lock.acquire()
+	while not (running.has_key(task.fname) and running[task.fname] == 0):
+		running_lock.wait()
+	running_lock.release()
+
+
+def wait_for_all():
+	"Wait for all tasks to end running"
+	running_lock.acquire()
+	while True:
+		for t in running.keys():
+			if running[t] != 0:
+				break
+		else:
+			break
+		running_lock.wait()
+	running_lock.release()
+
+
+
+#
+# Asynchronous functions, a simple and obvious idea which might be useful for
+# some applications. If you want to see formal documentation about this, check
+# this out:
+# http://research.microsoft.com/Users/luca/Papers/Polyphony%20(TOPLAS).pdf
+# (the paper is about much more intresting things, but mentions stuff like
+# this in detail).
+#
+def async(f):
+	"""Asynchronous decorator. It makes function calls asynchronous, they
+	will run in a new independant thread and you can't get their return
+	value or wait for them; if you need any of those things use the ada
+	tasks, this is just too simple."""
+	def newf(*args, **kwargs):
+		thread.start_new_thread(f, args, kwargs)
+	return newf
+
+
diff --git a/contract.py b/contract.py
new file mode 100644
index 0000000..afa20a0
--- /dev/null
+++ b/contract.py
@@ -0,0 +1,136 @@
+
+
+"""
+Design by contract decorators for Python
+Alberto Bertogli (albertogli@telpin.com.ar)
+8/Jul/2005
+-------------------------------------------
+
+This module provides a set of three decorators to implement design by
+contract, using pre and post functions embedded inside the function body,
+like:
+
+@contract
+def f(x):
+	@precond(f)
+	def pre(x):
+		assert x > 0
+
+	@postcond(f)
+	def post(ret, x):
+		assert ret > 0
+
+	return (2 * x) / x
+
+
+The precondition will run as soon as it gets defined, so it's wise to put it
+on top of the function definition.
+The postcondition can be anywhere.
+You can omit either or both, at your discresion.
+
+The precondition function takes the same parameters as the function; the
+postcondition function takes the returned value first, and then all the
+function parameters.
+
+
+Expressing the conditions as independant functions is trivial, and this module
+doesn't implement it. Putting them inside the function have some drawbacks,
+most notably it's harder to reuse them, and it's not really that different
+from open-coding them directly in the function. However, it can be more
+comfortable if you prefer looking at everything inside the same function body.
+"""
+
+
+def contract(f):
+	"Decorator for functions that will support DBC."
+
+	class Wrapper:
+		"This class will wrap around the function"
+		def __init__(self):
+			# function in question
+			self.f = f
+
+			# post-condition function to run
+			self.__atexit = None
+
+			# arguments passed to f()
+			self.args = []
+			self.kwargs = {}
+
+		def put_post(self, postfun):
+			"Register a post-condition function"
+			self.__atexit = postfun
+
+		def run_pre(self, prefun):
+			"Run a pre-condition function"
+			prefun(*self.args, **self.kwargs)
+
+		def __call__(self, *args, **kwargs):
+			"Emulate a call to the function."
+			# save the arguments first
+			self.args = args
+			self.kwargs = kwargs
+
+			# run the function; here the precondition will be ran
+			# by its decorator calling run_pre(), and put_post()
+			# will be called by the postcondition decorator to
+			# register the postcondition function
+			r = self.f(*args, **kwargs)
+
+			# if we have a postcondition, run it
+			if self.__atexit:
+				self.__atexit(r, *args, **kwargs)
+			return r
+	return Wrapper()
+
+
+def precond(pfun):
+	"""Decorator for the precondition function, takes the parent function
+	as the first parameter, and returns the decorator to use."""
+	def mkdeco(prefun):
+		# run the precondition function
+		pfun.run_pre(prefun)
+		return prefun
+	return mkdeco
+
+
+def postcond(pfun):
+	"""Decorator for the postcondition function, takes the parent function
+	as the first parameter, and returns the decorator to use."""
+	def mkdeco(postfun):
+		# register the postcondition function
+		pfun.put_post(postfun)
+		return postfun
+	return mkdeco
+
+
+#
+# Example!
+#
+
+if __name__ == '__main__':
+
+	@contract
+	def f(a):
+		"My beautiful function."
+
+		@precond(f)
+		def pre(a):
+			"Precondition."
+			print 'pre', a
+
+		@postcond(f)
+		def post(ret, a):
+			"Postcondition."
+			print 'post', ret, a
+
+		print 'f', a
+		return 2 * a
+
+
+	print '* run f'
+	r = f(26)
+	print '* f returned', r
+	print '* exit f'
+
+
diff --git a/samples/adatasks/sample1.py b/samples/adatasks/sample1.py
new file mode 100644
index 0000000..c00dbc8
--- /dev/null
+++ b/samples/adatasks/sample1.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+import time
+import adatasks
+
+@adatasks.task
+def mostrador():
+	print 'el mostrador!'
+	while True:
+		entry, params = mostrador.select()
+		print entry, params
+		if entry == 'quit':
+			break
+	print 'chau mostrador!'
+
+
+def main():
+	mostrador()
+	adatasks.launch()
+	mostrador.dale('arrancamos')
+	mostrador.mostra('mostrame')
+	mostrador.mostra()
+	mostrador.mostra('otra mas')
+	mostrador.quit('chau')
+	mostrador.quit('chau')
+	adatasks.wait_for_task(mostrador)
+	#adatasks.wait_for_all()
+
+if __name__ == "__main__":
+	main()
+
diff --git a/samples/adatasks/sample2.py b/samples/adatasks/sample2.py
new file mode 100644
index 0000000..855c28e
--- /dev/null
+++ b/samples/adatasks/sample2.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+import time
+import adatasks
+
+@adatasks.task
+def f():
+	print 'launch f'
+	params = f.accept("entry1")
+	print params[0]
+
+	params = f.accept("entry2")
+	print params
+
+	#raise "err"
+
+	entry, params = f.select("entry3", "entry4")
+	print entry, params
+
+
+
+def main():
+	adatasks.launch()
+	f.entry1('Hello')
+	f.entry2()
+	f.entry3('World')
+
+	f.entry1('Vamos')
+	f.entry2('de')
+	f.entry3('nuevo')
+
+if __name__ == "__main__":
+	main()
+	f()
+
+	print 'wait for f'
+	adatasks.wait_for_task(f)
+
+	print 'wait for all'
+	adatasks.wait_for_all()
+
+	print 'bye'
+
diff --git a/samples/adatasks/sample3.py b/samples/adatasks/sample3.py
new file mode 100644
index 0000000..dc87925
--- /dev/null
+++ b/samples/adatasks/sample3.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+import time
+import adatasks
+
+
+@adatasks.async
+def f(arg1, kwarg = 23, sleep = 0):
+	time.sleep(sleep)
+	print sleep, arg1, kwarg
+
+def main():
+	f(26, sleep = 0.2)
+	f(kwarg = 34, arg1 = 23, sleep = 0.1)
+	f(22, sleep = 0)
+	print 'main!'
+
+
+if __name__ == "__main__":
+	main()
+	time.sleep(2)
diff --git a/samples/contract/sample1.py b/samples/contract/sample1.py
new file mode 100644
index 0000000..80c01e1
--- /dev/null
+++ b/samples/contract/sample1.py
@@ -0,0 +1,28 @@
+
+from contract import *
+
+
+@contract
+def f(a):
+	"My beautiful function."
+
+	@precond(f)
+	def pre(a):
+		"Precondition."
+		print 'pre', a
+
+	@postcond(f)
+	def post(ret, a):
+		"Postcondition."
+		print 'post', ret, a
+
+	print 'f', a
+	return 2 * a
+
+
+print '* run f'
+r = f(26)
+print '* f returned', r
+print '* exit f'
+
+