diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..65ff8e8 Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..62043b3 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +ECA - Event Condition Action +================================= + +ECA was developed as an educational tool aimed at a diverse CS student +population with programming experience ranging from "very little" to "very +much": it allows students to quickly develop a prototype real-time Twitter +Dashboard that really looks cool. And it is open to more advanced programming +to challenge students who have more experience. +The basis of ECA is a rule system that reacts to externally generated events. + +The architecture favours simplicity over robustness. Multithreading is used in +favour of more suitable parallelism options such as greenlets to keep +dependencies to a minimum for ease of deployment. + +Documentation can be found in the Wiki of this github site. + +This program is not intended for production use. It may contain security issues +not tolerable outside of a controlled environment. + +ECA requires Python 3.2 or higher. + + + + diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..cfef46f --- /dev/null +++ b/TODO.txt @@ -0,0 +1,21 @@ +[x] Refactor handlers in neca.py to eca/http.py +[x] Add session manager to lazy-create new Contexts +[x] Context switching through session manager +[x] Add SSE handler to allow event-based pushes +[x] Add pubsub for SSE handlers to allow emitting from within ECA context +[x] Rework static file handling to not be the exception to normal operations +[x] Add basic Javascript handling of events +[x] Add a way to configure twitter (event generation) input thread +[ ] Add control to event generation thread through HTTP interface +[x] Document all core modules +[ ] Add sample component of a graph/twitter list +[ ] Harden system against programmer error (i.e. sanity check all rules-module data flow) +[x] Add a delayed event scheduler (for 'fire in 2 seconds' things) +[x] locatie tweets http://library.ewi.utwente.nl/ecadata/batatweets.txt +[x] Clean up samples and rename leading sample to template.py + template dir +[x] Button Block +[ ] Session block +[x] Full speed tweet time_factor +[x] Docs in graph.js +[x] emit() in non-server context should doe something useful +[x] Rework rules set to non-global object diff --git a/demos/advancedcontexts.py b/demos/advancedcontexts.py new file mode 100644 index 0000000..f044a4d --- /dev/null +++ b/demos/advancedcontexts.py @@ -0,0 +1,56 @@ +from eca import * +import random + +# declare two separate rule sets + +consumer = Rules() +producer = Rules() + + +# default init + +@event('init') +def bootstrap(c, e): + # start a few contexts for the generation of stock quotes + # (The simple workload here can easily be done in a single context, but we + # do this in separate contexts to give an example of the feature.) + spawn_context({'symbol':'GOOG', 'start':500.0, 'delay':1.2}, rules=producer, daemon=True) + spawn_context({'symbol':'AAPL', 'start':99.0, 'delay':0.9}, rules=producer, daemon=True) + spawn_context(rules=consumer, daemon=True) + +@event('end-of-input') +def done(c,e): + # terminate if the input is closed + shutdown() + + +# producer rules + +@producer.event('init') +def start_work(c, e): + c.symbol = e.data['symbol'] + c.delay = e.data['delay'] + + fire('sample', { + 'previous': e.data['start'] + }) + +@producer.event('sample') +def work(c, e): + current = e.data['previous'] + random.uniform(-0.5, 0.5) + + fire_global('quote', { + 'symbol': c.symbol, + 'value': current + }) + + fire('sample', { + 'previous': current + }, delay=c.delay) + + +# consumer rules + +@consumer.event('quote') +def show_quote(c, e): + print("Quote for {symbol}: {value}".format(**e.data)) diff --git a/demos/average.py b/demos/average.py new file mode 100644 index 0000000..f41b9b9 --- /dev/null +++ b/demos/average.py @@ -0,0 +1,47 @@ +from eca import * + + +@event('main') +def setup(ctx, e): + """ + Initialise the context with an accumulator value, and inform + the user about the fact that we process input. + """ + print("Enter a number per line and end with EOF:") + print("(EOF is ctrl+d under linux and MacOSes, ctrl+z followed by return under Windows)") + ctx.accumulator = 0 + ctx.count = 0 + + +@event('line') +def line(ctx, e): + """ + Tries to parse the input line as a number and add it to the accumulator. + """ + try: + value = float(e.data) if '.' in e.data else int(e.data) + ctx.accumulator += value + ctx.count += 1 + print("sum = " + str(ctx.accumulator)) + except ValueError: + print("'{}' is not a number.".format(e.data)) + + +@event('end-of-input') +@condition(lambda c,e: c.count > 0) +def done(ctx, e): + """ + Outputs the final average to the user. + """ + print("{} samples with average of {}".format(ctx.count, ctx.accumulator / ctx.count)) + shutdown() + + +@event('end-of-input') +@condition(lambda c,e: c.count == 0) +def no_input(ctx, e): + """ + Invoked of no input is given and input is finished. + """ + print("0 samples. \"Does not compute!\"") + shutdown() diff --git a/demos/chat.py b/demos/chat.py new file mode 100644 index 0000000..d955c1f --- /dev/null +++ b/demos/chat.py @@ -0,0 +1,40 @@ +from eca import * +import datetime +import eca.http + +# add message posting handler +def add_request_handlers(httpd): + httpd.add_route('/api/message', eca.http.GenerateEvent('incoming'), methods=['POST']) + + # use the library content from the template_static dir instead of our own + # this is a bit finicky, since execution now depends on a proper working directory. + httpd.add_content('/lib/', 'template_static/lib') + httpd.add_content('/style/', 'template_static/style') + + +# store name of context +@event('init') +def setup(ctx, e): + ctx.name = e.data['name'] + + +# emit incoming messages to the client +@event('message') +def on_message(ctx, e): + name = e.data['name'] + text = e.data['text'] + time = e.data['time'].strftime('%Y-%m-%d %H:%M:%S') + + emit('message',{ + 'text': "{} @{}: {}".format(name, time, text) + }) + + +# do a global fire for each message from the client +@event('incoming') +def on_incoming(ctx, e): + fire_global('message', { + 'name': ctx.name, + 'text': e.data['text'], + 'time': datetime.datetime.now() + }) diff --git a/demos/chat_static/index.html b/demos/chat_static/index.html new file mode 100644 index 0000000..b97a63e --- /dev/null +++ b/demos/chat_static/index.html @@ -0,0 +1,44 @@ + + +
+This is the dashboard template file. The easiest way to get started is to think up a simple name (let's say we take 'dashboard'). Now copy template.py to {name}.py start a new module (so that's dashboard.py) and copy template_static to {name}_static.
+
Now you can run the new project with:
python neca.py -s {name}.py
+Further documentation on the ECA system can be found at github.com/utwente-db/eca/wiki, and demos can be found in the demos/ directory.
+
In the sample template.py (which comes with the dashboard you're looking at right now), you will find the rules that power this example.
+
Rules are written in Python and work as follows: +
@event("foo")
+def action(context, event):
+ print("Event " + event.name + "!")
+
+The @event part tells the system to fire the action whenever the event 'foo' occurs. The def action(context, event): part defines a new action that takes two arguments: the context and the event. The rest of the code is the action body.
+The graph to the right is continuously filled with data generated by the rules. +
In template.py you can see that an event called 'sample' is fired again and again to create new data points for the graph.
+
These points are then sent to the browser with: +
emit('sample',{
+ 'action': 'add',
+ 'value': sample
+})
+
+This is the dashboard template file. The easiest way to get started is to think up a simple name (let's say we take 'dashboard'). Now copy template.py to {name}.py start a new module (so that's dashboard.py) and copy template_static to {name}_static.
+
Now you can run the new project with:
python neca.py -s {name}.py
+Further documentation on the ECA system can be found at github.com/utwente-db/eca/wiki, and demos can be found in the demos/ directory.
+
In the sample template.py (which comes with the dashboard you're looking at right now), you will find the rules that power this example.
+
Rules are written in Python and work as follows: +
@event("foo")
+def action(context, event):
+ print("Event " + event.name + "!")
+
+The @event part tells the system to fire the action whenever the event 'foo' occurs. The def action(context, event): part defines a new action that takes two arguments: the context and the event. The rest of the code is the action body.
+The graph to the right is continuously filled with data generated by the rules. +
In template.py you can see that an event called 'sample' is fired again and again to create new data points for the graph.
+
These points are then sent to the browser with: +
emit('sample',{
+ 'action': 'add',
+ 'value': sample
+})
+
+