/reactive-py

A Python library to overload the basic Data-Model to convert it to reactive data structures making it possible to fuse reactive programming with ordinary imperative programming.

Primary LanguagePythonMIT LicenseMIT

Reactive-Py

A Python library to overload the basic Data-Model to convert it to reactive data structures making it possible to fuse reactive programming with ordinary imperative programming for:

  • On-Change Callbacks
  • On-Compare Callbacks
  • Pre and Post filters for function calls

Data-structures supported (user-defined):

  • str
  • int
  • float
  • bool
  • list
  • tuples
  • dict
  • functions/methods
  • classes

Importing the module

from reactive import _

On-Change Callbacks

Basic Callbacks (Asynchronous)

def printer(trace, message='Hello', *args, **kwargs):
  print 'Message: ' + str(message)
  print 'Trace: ' + str(trace) 
  print 'Args: ' + str(args)
  print 'Kwargs: ' + str(kwargs)
 
obj = _(1)

obj.onchange(printer, args=(), kwargs={'message': 'Test'})

obj += 1

Output:

Message: Test
Trace: {'after_value': 2, 'before_value': 1, 'after_type': <type 'int'>, 'event': '__iadd__', 'before_type': <type 'int'>}
Args: ()
Kwargs: {}
[Finished in 0.1s]

(By default a callback is Asynchronous

Synchronous Callbacks

import time

def printer(trace, message='Hello', *args, **kwargs):
  time.sleep(2)
  print 'Message: ' + str(message)
 
obj = _("Hello")

obj.onchange(printer, args=(), kwargs={'message': 'Test'}, async=False)

obj += " World"

print "Here!"

Output:

Message: Test
Here!
[Finished in 2.1s]

Chaining Callbacks

def printer(trace, message='Hello', *args, **kwargs):
  print 'Message: ' + str(message)

def hello_world(trace, name='Deepanshu', *args, **kwargs):
  print 'Hello ' + name + '!'
 
obj = _("Hello")

obj.onchange(printer, args=(), kwargs={'message': "Test"})\
  .onchange(hello_world, args=(), kwargs={})

obj += " World"

Output:

Message: Test
Hello Deepanshu!
[Finished in 0.1s]

On-compare Callbacks

def printer(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)
 
obj = _("Hello")

obj.oncompare(printer, args=(), kwargs={})

obj == " World"

Output:

Trace: {'after_type': <type 'str'>, 'before_type': <type 'str'>, 'after_value': ' World', 'comparison_output': False, 'before_value': 'Hello', 'event': '__eq__'}
[Finished in 0.1s]

Function callbacks

Before Function-Call Callback

def foo(bar=None, baz=None):
  print 'bar: ' + str(bar)
  print 'baz: ' + str(baz)

def printer(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)
 
foo_obj = _(foo)

foo_obj.before_call(printer, args=(), kwargs={})

foo_obj(bar=1)

Output:

bar: 1
baz: None
Trace: {'kwargs': {'bar': 1}, 'args': (), 'event': '__call__', 'before_call': True}
[Finished in 0.1s]

After Function-Call Callback

def foo(bar=None, baz=None):
  print 'bar: ' + str(bar)
  print 'baz: ' + str(baz)
  return "Return Value"

def printer(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)
 
foo_obj = _(foo)

foo_obj.after_call(printer, args=(), kwargs={})

foo_obj(bar=1)

Output:

bar: 1
baz: None
Trace: {'kwargs': {'bar': 1}, 'args': (), 'output': 'Return Value', 'event': '__call__', 'after_call': True}
[Finished in 0.1s]

Assigning values to reactive objects

In Python, assignment is a language intrinsic which doesn't have a modification hook. Which means, it can't be overloaded like the other Arithemetic or Logical operators.

A hack around this would be to inspect the stack at every tick and call the respective hooks accordingly. This however, isn't an ideal way to do it and acts as a major bottleneck in program execution.

However, to truly make the python object "reactive", assignment is done in the following way:

def foo(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)

# Create a new reactive <'type' int> object
obj = _(1)

obj.onchange(foo, args=(), kwargs={})

# Assign value 2 to the object while keeping all its hooks (onchange, oncompare, before_call, after_call, etc) if any, intact and trigger a onchange event simultaneously
 
obj._(2)

Output:

Trace: {'after_value': 2, 'before_value': 1, 'after_type': <type 'int'>, 'event': '__assignment__', 'before_type': <type 'int'>}
[Finished in 0.1s]

Working with lists

def foo(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)

# Create a new reactive list
obj = _([1, 2, 3, 4, 5])

obj.onchange(foo, args=(), kwargs={})
 
obj[2] = 4

Output:

Trace: {'index': 2, 'after_type': <type 'int'>, 'before_type': <type 'int'>, 'after_value': 4, 'before_value': 3, 'event': '__setitem__'}
[Finished in 0.1s]

Working with tuples

def foo(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)

# Create a new reactive tuple
obj = _(('a', 'b'))

obj.onchange(foo, args=(), kwargs={})
 
obj += ('c', 'd')

Output:

Trace: {'after_value': ('a', 'b', 'c', 'd'), 'before_value': ('a', 'b'), 'after_type': <type 'tuple'>, 'event': '__iadd__', 'before_type': <type 'tuple'>}
[Finished in 0.1s]

Working with dicts

def foo(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)

# Create a new reactive dict
obj = _({'a': 'b', 'c': 'd'})

obj.onchange(foo, args=(), kwargs={})
 
obj['e'] = 'f'

Output:

Trace: {'index': 'e', 'after_type': <type 'str'>, 'before_type': <type 'NoneType'>, 'after_value': 'f', 'before_value': None, 'event': '__setitem__'}
[Finished in 0.1s]

Working with classes

def foo(trace, *args, **kwargs):
  print 'Trace: ' + str(trace)

class A:
  pass

# Create a new reactive <'type' int> object
obj = _(A())

obj.onchange(foo, args=(), kwargs={})
 
obj.a = 'B'
obj.a = 'C'

Output:

Trace: {'attr': 'a', 'after_type': <type 'str'>, 'before_type': <type 'NoneType'>, 'after_value': 'B', 'before_value': None, 'event': '__setattr__'}
Trace: {'attr': 'a', 'after_type': <type 'str'>, 'before_type': <type 'str'>, 'after_value': 'C', 'before_value': 'B', 'event': '__setattr__'}
[Finished in 0.1s]

Supported Operators

License

The MIT License (MIT)

Copyright (c) 2014 Deepanshu Mehndiratta