Package: metapensiero.reactive
author:Alberto Berticontact:alberto@metapensiero.itlicense:GNU General Public License version 3 or laterAn unobtrusive and light reactive systemGoalThis package implements a framework forfunctional reactive
programmingwhich wants to be simple to use and extend without imposing complex
concepts of streams, channels and so on that are typical ofdataflow
programming.To explain reactive programming just think about a spreadsheet where
you have a value cell and a formula cell. The latter updates
automatically just when it’s  appropriate.This package implement just that. No, wait, not a spreadsheet, but a
way to express that a block of code (a function) that creates a result
(a calculated value) or aside effectdepends on some other value
so that when the value changes, the block of code is automatically
re-run.It has been inspired by Javascriptmeteor’s “tracker” packagebut
it diverges from in order to be more pythonic.UsageLet’s see a small example:cur_temp_fahrenheit=40defcur_temp_celsius(t_fahrenheit):return(t_fahrenheit-32)/1.8log=[]deflog_temp_celsius():log.append(cur_temp_celsius(cur_temp_fahrenheit))log_temp_celsius()assertlog==[4.444444444444445]This is a small piece of code with a function that converts Fahrenheit
degrees to Celsius and then logs them to a list, but you can think of
it as any kind of side effect.Now, we suppose thatcur_temp_fahrenheitchanges and we want to
log it whenever it does so.To do that we need to trasformcur_temp_fahrenheitinto areactivevalue and have thetrackertrack the dependencies between
that value and thecomputationthat uses it. This way, when the
value is changed, ourlog_temp_celsius()can be re-run and it will
do its work. So we change the code a bit mostly by using a getter and
a setter to change the temp variable and add some code  when
this happens and then instruct thetrackerto run the log
function so that it knows what to re-run. Let’s see:frommetapensieroimportreactivetracker=reactive.get_tracker()dep=tracker.dependency()# this is just to handle setting a global varcur_temp_fahrenheit=[40]defget_temp_f():dep.depend()returncur_temp_fahrenheit[0]defset_temp_f(new):ifnew!=cur_temp_fahrenheit[0]:dep.changed()cur_temp_fahrenheit[0]=newdefcur_temp_celsius(t_fahrenheit):return(t_fahrenheit-32)/1.8log=[]deflog_temp_celsius(handle):log.append(cur_temp_celsius(get_temp_f()))handle=tracker.reactive(log_temp_celsius)assertlog==[4.444444444444445]set_temp_f(50)assertlog==[4.444444444444445,10.0]assertcur_temp_fahrenheit==50handle.stop()set_temp_f(60)assertlog==[4.444444444444445,10.0]assertcur_temp_fahrenheit==60As you can see, when we set the current temperature to a new
value,log_temp_celsiusis re-run and a new entry is added to theloglist. we can still use the function(s) without using the
tracker, in which case we will have the default, normal, non-reactive
behavior. When we usetracker.reactive()all the defined
dependencies on reactive-aware data sources are tracked by running
the given function immediately. Next, when the reactive source
changes, the tracker re-executes the function, thus re-tracking the
dependencies that may be different.tracker.reactive()returns an
handle, aComputationobject that can be used to stop the
reactive behavior when it’s no more necessary. The same object is
given as parameter to the tracked function.The example proposed is indeed silly, but shows you the power of the
framework:code changes are minimal;the new concepts to learn are very few and simple;the reactive functions can be run alone without tracker involvement
and they will run as normal code, without the need to refactor them.Tracked functions can usetracker.reactive()themselves, in which
case the inner trackings will be stopped when the outer is re-run.The code above is a bit ugly due to the usage of the getter and
setter, how can we avoid that? Here is the same example using theValueclass:frommetapensieroimportreactivetracker=reactive.get_tracker()cur_temp_fahrenheit=reactive.Value(40)defcur_temp_celsius(t_fahrenheit):return(t_fahrenheit-32)/1.8log=[]deflog_temp_celsius(handle):log.append(cur_temp_celsius(cur_temp_fahrenheit.value))handle=tracker.reactive(log_temp_celsius)assertlog==[4.444444444444445]cur_temp_fahrenheit.value=50assertlog==[4.444444444444445,10.0]handle.stop()cur_temp_fahrenheit.value=60assertlog==[4.444444444444445,10.0]Valueclass can be used also be used as a method decorator in a
way similar to the builtinpropertydecorator but with only agetterfunction.Another way to use the Value class is just as a value container, by
using itsvalueto get or set the value, or just as any other data
member in a class body.a=Value()a.value=Trueasserta.value==TrueclassFoo(object):bar=Value()@Value()defzoo(self):# ... calc something usefulfoo=Foo()foo.bar=Trueassertfoo.bar==Trueanimal=foo.zooWhen used in class’ body aValuesaves a triplet of(Dependency, Computation, value)per instance so you have to take that into
account.Valueuses weak references in order to avoid keeping
instances alive.There is also a constructor to build reactivenamedlistclasses.The framework is also compatible withgeventandasyncioin
order to batch computation’s recalculation in anotherGreenletorTask, respectively. As all theinvalidatedcalculations are
recomputed sequentially, it’s important to avoid havingsuspension
pointsin the reactive code, like calls tosleep()functions or
the execution ofyield fromandawaitstatements. If this is
unavoidable, amanualsuspension context manager is avaliable in
computations, namedsuspend(). Using that, the block of code
inside awithstatement runs isolated, and tracking is reinstated
afterwards.For all those features, please have a look at code and tests for now.TestingTo run the tests you should run the following at the package root:python setup.py testTo test bothgeventwith Python 2.7 andasynciowith Python
3.5, run:pip install tox
toxBuild statusChanges0.1 (2016-02-05)Initial effort.Added testing with tox andgeventandasyncio.Firt cut of the docs.0.2 (2016-02-05)small doc fixes.0.3 (2016-02-10)more tests.allow __set__ if generator is not defined.refactoring of Value’s code.Fix behavior if Value’s accessed when tracking isn’t active.Provide a mechanism to halt tracking while computing if system
suspension is needed (gevent,asyncio).Updates to the doc.Code is now tested in a pre-production environment.0.4 (2016-02-11)fix a small bug in __delete__().0.5 (2016-02-11)require flush only when there are dependents.0.6 (2016-02-13)documentation updates.add a@computationdecorator.