{ "info": { "author": "Alberto Berti", "author_email": "alberto@metapensiero.it", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5" ], "description": ".. -*- coding: utf-8 -*-\r\n.. :Project: metapensiero.reactive -- a unobtrusive and light reactive system\r\n.. :Created: dom 09 ago 2015 12:57:35 CEST\r\n.. :Author: Alberto Berti \r\n.. :License: GNU General Public License version 3 or later\r\n.. :Copyright: Copyright (C) 2015 Alberto Berti\r\n..\r\n\r\n=======================\r\n metapensiero.reactive\r\n=======================\r\n\r\n :author: Alberto Berti\r\n :contact: alberto@metapensiero.it\r\n :license: GNU General Public License version 3 or later\r\n\r\nAn unobtrusive and light reactive system\r\n========================================\r\n\r\nGoal\r\n----\r\n\r\nThis package implements a framework for `functional reactive\r\nprogramming `_\r\nwhich wants to be simple to use and extend without imposing complex\r\nconcepts of streams, channels and so on that are typical of *dataflow\r\nprogramming*.\r\n\r\nTo explain reactive programming just think about a spreadsheet where\r\nyou have a value cell and a formula cell. The latter updates\r\nautomatically just when it's appropriate.\r\n\r\nThis package implement just that. No, wait, not a spreadsheet, but a\r\nway to express that a block of code (a function) that creates a result\r\n(a calculated value) or a *side effect* depends on some other value\r\nso that when the value changes, the block of code is automatically\r\nre-run.\r\n\r\nIt has been inspired by Javascript `meteor's \"tracker\" package`__ but\r\nit diverges from in order to be more pythonic.\r\n\r\n__ https://github.com/meteor/meteor/tree/devel/packages/tracker\r\n\r\nUsage\r\n-----\r\n\r\nLet's see a small example:\r\n\r\n.. code:: python\r\n\r\n cur_temp_fahrenheit = 40\r\n\r\n def cur_temp_celsius(t_fahrenheit):\r\n return (t_fahrenheit - 32) / 1.8\r\n\r\n log = []\r\n\r\n def log_temp_celsius():\r\n log.append(cur_temp_celsius(cur_temp_fahrenheit))\r\n\r\n log_temp_celsius()\r\n\r\n assert log == [4.444444444444445]\r\n\r\nThis is a small piece of code with a function that converts Fahrenheit\r\ndegrees to Celsius and then logs them to a list, but you can think of\r\nit as any kind of side effect.\r\n\r\nNow, we suppose that ``cur_temp_fahrenheit`` changes and we want to\r\nlog it whenever it does so.\r\n\r\nTo do that we need to trasform ``cur_temp_fahrenheit`` into a\r\n*reactive* value and have the *tracker* track the dependencies between\r\nthat value and the *computation* that uses it. This way, when the\r\nvalue is changed, our ``log_temp_celsius()`` can be re-run and it will\r\ndo its work. So we change the code a bit mostly by using a getter and\r\na setter to change the temp variable and add some code when\r\nthis happens and then instruct the *tracker* to run the log\r\nfunction so that it knows what to re-run. Let's see:\r\n\r\n.. code:: python\r\n\r\n from metapensiero import reactive\r\n\r\n tracker = reactive.get_tracker()\r\n dep = tracker.dependency()\r\n\r\n # this is just to handle setting a global var\r\n cur_temp_fahrenheit = [40]\r\n\r\n def get_temp_f():\r\n dep.depend()\r\n return cur_temp_fahrenheit[0]\r\n\r\n def set_temp_f(new):\r\n if new != cur_temp_fahrenheit[0]:\r\n dep.changed()\r\n cur_temp_fahrenheit[0] = new\r\n\r\n def cur_temp_celsius(t_fahrenheit):\r\n return (t_fahrenheit - 32) / 1.8\r\n\r\n log = []\r\n\r\n def log_temp_celsius(handle):\r\n log.append(cur_temp_celsius(get_temp_f()))\r\n\r\n handle = tracker.reactive(log_temp_celsius)\r\n\r\n assert log == [4.444444444444445]\r\n\r\n set_temp_f(50)\r\n\r\n assert log == [4.444444444444445, 10.0]\r\n assert cur_temp_fahrenheit == 50\r\n\r\n handle.stop()\r\n\r\n set_temp_f(60)\r\n\r\n assert log == [4.444444444444445, 10.0]\r\n assert cur_temp_fahrenheit == 60\r\n\r\nAs you can see, when we set the current temperature to a new\r\nvalue, ``log_temp_celsius`` is re-run and a new entry is added to the\r\n``log`` list. we can still use the function(s) without using the\r\ntracker, in which case we will have the default, normal, non-reactive\r\nbehavior. When we use ``tracker.reactive()`` all the defined\r\ndependencies on reactive-aware data sources are tracked by running\r\nthe given function immediately. Next, when the reactive source\r\nchanges, the tracker re-executes the function, thus re-tracking the\r\ndependencies that may be different. ``tracker.reactive()`` returns an\r\nhandle, a ``Computation`` object that can be used to stop the\r\nreactive behavior when it's no more necessary. The same object is\r\ngiven as parameter to the tracked function.\r\n\r\nThe example proposed is indeed silly, but shows you the power of the\r\nframework:\r\n\r\n* code changes are minimal;\r\n\r\n* the new concepts to learn are very few and simple;\r\n\r\n* the reactive functions can be run alone without tracker involvement\r\n and they will run as normal code, without the need to refactor them.\r\n\r\nTracked functions can use ``tracker.reactive()`` themselves, in which\r\ncase the inner trackings will be stopped when the outer is re-run.\r\n\r\nThe code above is a bit ugly due to the usage of the getter and\r\nsetter, how can we avoid that? Here is the same example using the\r\n``Value`` class:\r\n\r\n.. code:: python\r\n\r\n from metapensiero import reactive\r\n\r\n tracker = reactive.get_tracker()\r\n cur_temp_fahrenheit = reactive.Value(40)\r\n\r\n def cur_temp_celsius(t_fahrenheit):\r\n return (t_fahrenheit - 32) / 1.8\r\n\r\n log = []\r\n\r\n def log_temp_celsius(handle):\r\n log.append(cur_temp_celsius(cur_temp_fahrenheit.value))\r\n\r\n handle = tracker.reactive(log_temp_celsius)\r\n\r\n assert log == [4.444444444444445]\r\n\r\n cur_temp_fahrenheit.value = 50\r\n\r\n assert log == [4.444444444444445, 10.0]\r\n\r\n handle.stop()\r\n\r\n cur_temp_fahrenheit.value = 60\r\n assert log == [4.444444444444445, 10.0]\r\n\r\n``Value`` class can be used also be used as a method decorator in a\r\nway similar to the builtin ``property`` decorator but with only a\r\n*getter* function.\r\n\r\nAnother way to use the Value class is just as a value container, by\r\nusing its ``value`` to get or set the value, or just as any other data\r\nmember in a class body.\r\n\r\n.. code:: python\r\n\r\n a = Value()\r\n\r\n a.value = True\r\n assert a.value == True\r\n\r\n class Foo(object):\r\n\r\n bar = Value()\r\n\r\n @Value()\r\n def zoo(self):\r\n # ... calc something useful\r\n\r\n\r\n\r\n foo = Foo()\r\n foo.bar = True\r\n\r\n assert foo.bar == True\r\n\r\n animal = foo.zoo\r\n\r\nWhen used in class' body a ``Value`` saves a triplet of ``(Dependency,\r\nComputation, value)`` per instance so you have to take that into\r\naccount. ``Value`` uses weak references in order to avoid keeping\r\ninstances alive.\r\n\r\nThere is also a constructor to build reactive\r\n`namedlist`__ classes.\r\n\r\n__ https://pypi.python.org/pypi/namedlist\r\n\r\nThe framework is also compatible with ``gevent`` and ``asyncio`` in\r\norder to batch computation's recalculation in another ``Greenlet`` or\r\n``Task``, respectively. As all the *invalidated* calculations are\r\nrecomputed sequentially, it's important to avoid having *suspension\r\npoints* in the reactive code, like calls to ``sleep()`` functions or\r\nthe execution of ``yield from`` and ``await`` statements. If this is\r\nunavoidable, a *manual* suspension context manager is avaliable in\r\ncomputations, named ``suspend()``. Using that, the block of code\r\ninside a *with* statement runs isolated, and tracking is reinstated\r\nafterwards.\r\n\r\nFor all those features, please have a look at code and tests for now.\r\n\r\nTesting\r\n-------\r\n\r\nTo run the tests you should run the following at the package root::\r\n\r\n python setup.py test\r\n\r\nTo test both ``gevent`` with Python 2.7 and ``asyncio`` with Python\r\n3.5, run::\r\n\r\n pip install tox\r\n tox\r\n\r\nBuild status\r\n------------\r\n\r\n.. image:: https://travis-ci.org/azazel75/metapensiero.reactive.svg?branch=master\r\n :target: https://travis-ci.org/azazel75/metapensiero.reactive\r\n\r\n\r\n.. -*- coding: utf-8 -*-\r\n\r\nChanges\r\n-------\r\n\r\n0.1 (2016-02-05)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- Initial effort.\r\n- Added testing with tox and ``gevent`` and ``asyncio``.\r\n- Firt cut of the docs.\r\n\r\n0.2 (2016-02-05)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- small doc fixes.\r\n\r\n0.3 (2016-02-10)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- more tests.\r\n- allow __set__ if generator is not defined.\r\n- refactoring of Value's code.\r\n- Fix behavior if Value's accessed when tracking isn't active.\r\n- Provide a mechanism to halt tracking while computing if system\r\n suspension is needed (``gevent``, ``asyncio``).\r\n- Updates to the doc.\r\n- Code is now tested in a pre-production environment.\r\n\r\n0.4 (2016-02-11)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- fix a small bug in __delete__().\r\n\r\n0.5 (2016-02-11)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- require flush only when there are dependents.\r\n\r\n0.6 (2016-02-13)\r\n~~~~~~~~~~~~~~~~\r\n\r\n- documentation updates.\r\n- add a ``@computation`` decorator.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/azazel75/metapensiero.reactive", "keywords": "reactive functional dataflow asyncio gevent", "license": "GPLv3+", "maintainer": "", "maintainer_email": "", "name": "metapensiero.reactive", "package_url": "https://pypi.org/project/metapensiero.reactive/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/metapensiero.reactive/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/azazel75/metapensiero.reactive" }, "release_url": "https://pypi.org/project/metapensiero.reactive/0.6/", "requires_dist": null, "requires_python": null, "summary": "an unobtrusive and light reactive system", "version": "0.6" }, "last_serial": 1956268, "releases": { "0.5": [ { "comment_text": "", "digests": { "md5": "7e6646bf66f138ea7b2cd70e2ab92cd1", "sha256": "bcff4aec4fd0e89e116635b0ff319fed647d965e7bc6c5850740cfc194c1fd6c" }, "downloads": -1, "filename": "metapensiero.reactive-0.5.tar.gz", "has_sig": false, "md5_digest": "7e6646bf66f138ea7b2cd70e2ab92cd1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11086, "upload_time": "2016-02-11T23:32:08", "url": "https://files.pythonhosted.org/packages/47/f1/ad20b238975215bae166ab963b58330f4901932db7855be380a6d336158d/metapensiero.reactive-0.5.tar.gz" } ], "0.6": [ { "comment_text": "", "digests": { "md5": "033482ca1630abe49a64e27741310533", "sha256": "f989ca5e941ae2b5daff05cba7b68f19b469cacb3c701e1adfe61c7317276d6b" }, "downloads": -1, "filename": "metapensiero.reactive-0.6.tar.gz", "has_sig": false, "md5_digest": "033482ca1630abe49a64e27741310533", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12399, "upload_time": "2016-02-13T14:18:56", "url": "https://files.pythonhosted.org/packages/59/3c/40ec33b35c862882ea496c72cc9e1ea164101e99f251fe3798ef0d7ca2bd/metapensiero.reactive-0.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "033482ca1630abe49a64e27741310533", "sha256": "f989ca5e941ae2b5daff05cba7b68f19b469cacb3c701e1adfe61c7317276d6b" }, "downloads": -1, "filename": "metapensiero.reactive-0.6.tar.gz", "has_sig": false, "md5_digest": "033482ca1630abe49a64e27741310533", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12399, "upload_time": "2016-02-13T14:18:56", "url": "https://files.pythonhosted.org/packages/59/3c/40ec33b35c862882ea496c72cc9e1ea164101e99f251fe3798ef0d7ca2bd/metapensiero.reactive-0.6.tar.gz" } ] }