Skip to main content

A declarative reactive programming framework.

Project description

Reactives

Test status Code coverage PyPI releases Supported Python versions Recent downloads

Reactives lets you write reactive code easily by making any of your objects and functions reactive. A reactive can be triggered (reactive.react.trigger()), causing all its reactors to be called. A reactor is any callable that takes no arguments, and you add it to a reactive via reactive.react(reactor). When a reactive is triggered, its reactors and its reactors' reactors are resolved, and each is called once in order.

Reactives uses a push-pull approach, meaning change notifications are pushed (reactors are called automatically and won't have to pull for changes), but if a reactor needs to know what exactly changed, it must pull this information itself.

Usage

For any type to be reactive, it must extend reactives.Reactive and expose a reactives.reactor.ReactorController instance through its react instance attribute.

Reactives provides the reactive framework, the tools to make your own types reactive, and several fully reactive types out of the box, batteries included!

Custom classes

Decorate a class to make its individual instances reactive:

from reactives.instance import ReactiveInstance

class Apple(ReactiveInstance):
    pass

apple = Apple()
apple.react(lambda: print('The apple got triggered!'))
apple.react.trigger()
# >>> "The apple got triggered!"

Functions and methods

Decorate a function:

from reactives.function import reactive_function

@reactive_function
def apple():
    pass

apple.react(lambda: print('The apple got triggered!'))
apple.react.trigger()
# >>> "The apple got triggered!"

Decorate a method on a reactive class:

from reactives.instance import ReactiveInstance
from reactives.instance.method import reactive_method

class Apple(ReactiveInstance):
    @reactive_method
    def apple(self):
        pass

apple = Apple()
apple.react['apple'].react(lambda: print('The apple got triggered!'))
apple.react['apple'].react.trigger()
# >>> "The apple got triggered!"

Reactive methods must be accessed through their instance, because Apple.apple would yield the class method.

Both functions and methods can be called automatically when they're triggered. This lets them set up something once, and update that thing when they're triggered:

from reactives.function import reactive_function

@reactive_function(on_trigger_call=True)
def warm_caches():
    """
    Warm the application's caches. When triggered (because the cached data has changed), re-warm the caches.
    """
    pass

Properties

Decorate a property:

from reactives.instance import ReactiveInstance
from reactives.instance.property import reactive_property

class Apple(ReactiveInstance):
    @property
    @reactive_property
    def apple(self) -> str:
        return 'I got you something!'

apple = Apple()
apple.react['apple'].react(lambda: print('The apple got triggered!'))
apple.react['apple'].react.trigger()
# >>> "The apple got triggered!"

If a property deleter is present, it may be called automatically when the property is triggered:

from reactives.instance import ReactiveInstance
from reactives.instance.property import reactive_property

class Apple(ReactiveInstance):
    def __init__(self):
        super().__init__()
        self._cached_something = None

    @property
    @reactive_property(on_trigger_delete=True)
    def apple(self) -> str:
        if self._cached_something is None:
            self._cached_something = 'I got you something!'
        return self._cached_something

    @apple.deleter
    def apple(self)  -> None:
        self._cached_something = 'I got you nothing!'

apple = Apple()
print(apple.apple)
# >>> "I got you something!"
apple.react['apple'].react.trigger()
print(apple.apple)
# >>> "I got you nothing!"

Property setters work exactly like with any other property:

from reactives.instance import ReactiveInstance
from reactives.instance.property import reactive_property

class Apple(ReactiveInstance):
    def __init__(self):
        super().__init__()
        self._something = 'I got you something!'

    @property
    @reactive_property
    def apple(self) -> str:
        return self._something

    @apple.setter
    def apple(self, something: str):
        self._something = something

apple = Apple()
apple.react['apple'].react(lambda: print('The apple got triggered!'))
apple.apple = 'I got you something else!'
# >>> "The apple got triggered!"

Values set through a property may themselves be reactive too. If they are, the property and the value are autowired, which means that the property becomes a reactor to the newly added value. As soon as the value is triggered, so is the property. Therefore, if you want to react to any change to any of the values a property might have, all you need to do is add your reactor to the property.

Sequences

reactives.collections.ReactiveSequence and reactives.collections.ReactiveMutableSequence are reactive versions of Python's built-in Sequence and MutableSequence, which are drop-in replacements for list.

from reactives.collections import ReactiveMutableSequence

fruits = ReactiveMutableSequence(['apple', 'banana'])
fruits.react(lambda: print('Look at all these delicious fruits!'))
fruits.append('orange')
# >>> "Look at all these delicious fruits!"

Values added to a ReactiveSequence or ReactiveMutableSequence may themselves be reactive too. If they are, the sequence and the value are autowired, which means that the sequence becomes a reactor to the newly added value. As soon as the value is triggered, so is the sequence. Therefore, if you want to react to any change to any of the values in a ReactiveSequence or ReactiveMutableSequence, all you need to do is add your reactor to the sequence.

Mappings

reactives.collections.ReactiveMapping and reactives.collections.ReactiveMutableMapping are reactive versions of Python's built-in Mapping and MutableMapping, which are drop-in replacements for dict.

from reactives.collections import ReactiveMutableMapping

fruits = ReactiveMutableMapping(apple=5, banana=2)
fruits.react(lambda: print('Look at all these delicious fruits!'))
fruits['orange'] = 4
# >>> "Look at all these delicious fruits!"

Values added to a ReactiveMapping or ReactiveMutableMapping may themselves be reactive too. If they are, the mapping and the value are autowired, which means that the mapping becomes a reactor to the newly added value. As soon as the value is triggered, so is the mapping. Therefore, if you want to react to any change to any of the values in a ReactiveMapping or ReactiveMutableMapping, all you need to do is add your reactor to the mapping.

Autowiring

We've seen how properties, sequences, and mappings autowire themselves to their values. This is possible because properties, sequences, and mappings know exactly which values move in and out of them. In other cases, we use scope. Any reactive can start a scope with reactives.scope.collect() and collect all reactives that are called or used during that scope window, and autowire itself to them. Conversely, any reactive can register itself with the current scope (if there is one) with reactives.scope.register*(), and allow reactives depending on it to autowire themselves. In fact, this is what properties do internally.

Autowiring means that as a developer, you won't need to worry about connecting the parts of your application most of the time.

Development

First, fork and clone the repository, and navigate to its root directory.

Requirements

  • Bash (you're all good if which bash outputs a path in your terminal)

Installation

If you have tox installed on your machine, tox --develop will create the necessary virtual environments and install all development dependencies.

Alternatively, in any existing Python environment, run ./bin/build-dev.

Testing

In any existing Python environment, run ./bin/test.

Fixing problems automatically

In any existing Python environment, run ./bin/fix.

Contributions 🥳

Reactives is Free and Open Source Software. As such you are welcome to report bugs or submit improvements.

Copyright & license

Reactives is copyright Bart Feenstra and contributors, and released under the GNU General Public License, Version 3. In short, that means you are free to use Reactives, but if you distribute Reactives yourself, you must do so under the exact same license, provide that license, and make your source code available.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reactives-0.5.1.tar.gz (34.0 kB view details)

Uploaded Source

Built Distribution

reactives-0.5.1-py2.py3-none-any.whl (53.1 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file reactives-0.5.1.tar.gz.

File metadata

  • Download URL: reactives-0.5.1.tar.gz
  • Upload date:
  • Size: 34.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.3

File hashes

Hashes for reactives-0.5.1.tar.gz
Algorithm Hash digest
SHA256 4cec3c6948cf410f37240c0e31e7e1162dcee4bec28fc68a4d11aea9fd059ddb
MD5 524b0946b6cbf3566733b4c5a64fbac3
BLAKE2b-256 aed7651bdf0396b81f171a16cfaaf48fba301b5711d18d03b361c72c1597ab9b

See more details on using hashes here.

File details

Details for the file reactives-0.5.1-py2.py3-none-any.whl.

File metadata

  • Download URL: reactives-0.5.1-py2.py3-none-any.whl
  • Upload date:
  • Size: 53.1 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.3

File hashes

Hashes for reactives-0.5.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 4ef77d41d23b629821eca0d57429e9b33e8f485e8270b0e9c0aa0a5b045e12be
MD5 1311e2881e34830ad52c6f91a47ed580
BLAKE2b-256 e4a566ec60b2106d433fde5b02c824913ca82c30bc36bd0319a5f90f0abdec40

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page