Package: reactives
ReactivesReactiveslets you write reactive code easily by making any of your objects and functionsreactive. A reactive
can betriggered(reactive.react.trigger()), causing all itsreactorsto be called. Areactoris any callable
that takes no arguments, and you add it to a reactive viareactive.react(reactor). When a reactive is triggered,
its reactors and its reactors' reactors are resolved, and each is called once in order.Reactivesuses 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.UsageFor any type to be reactive, it must extendreactives.Reactiveand expose areactives.reactor.ReactorControllerinstance through itsreactinstance attribute.Reactivesprovides the reactive framework, the tools to make your own types reactive, and several fully reactive
types out of the box, batteries included!Custom classesDecorate a class to make its individual instances reactive:fromreactives.instanceimportReactiveInstanceclassApple(ReactiveInstance):passapple=Apple()apple.react(lambda:print('The apple got triggered!'))apple.react.trigger()# >>> "The apple got triggered!"Functions and methodsDecorate a function:fromreactives.functionimportreactive_function@reactive_functiondefapple():passapple.react(lambda:print('The apple got triggered!'))apple.react.trigger()# >>> "The apple got triggered!"Decorate a method on areactive class:fromreactives.instanceimportReactiveInstancefromreactives.instance.methodimportreactive_methodclassApple(ReactiveInstance):@reactive_methoddefapple(self):passapple=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, becauseApple.applewould 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:fromreactives.functionimportreactive_function@reactive_function(on_trigger_call=True)defwarm_caches():"""Warm the application's caches. When triggered (because the cached data has changed), re-warm the caches."""passPropertiesDecorate a property:fromreactives.instanceimportReactiveInstancefromreactives.instance.propertyimportreactive_propertyclassApple(ReactiveInstance):@property@reactive_propertydefapple(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 propertydeleteris present, it may be called automatically when the property is triggered:fromreactives.instanceimportReactiveInstancefromreactives.instance.propertyimportreactive_propertyclassApple(ReactiveInstance):def__init__(self):super().__init__()self._cached_something=None@property@reactive_property(on_trigger_delete=True)defapple(self)->str:ifself._cached_somethingisNone:self._cached_something='I got you something!'returnself._cached_something@apple.deleterdefapple(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!"Propertysetterswork exactly like with any otherproperty:fromreactives.instanceimportReactiveInstancefromreactives.instance.propertyimportreactive_propertyclassApple(ReactiveInstance):def__init__(self):super().__init__()self._something='I got you something!'@property@reactive_propertydefapple(self)->str:returnself._something@apple.setterdefapple(self,something:str):self._something=somethingapple=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.Sequencesreactives.collections.ReactiveSequenceandreactives.collections.ReactiveMutableSequenceare reactive versions of
Python's built-inSequenceandMutableSequence, which are drop-in replacements forlist.fromreactives.collectionsimportReactiveMutableSequencefruits=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 aReactiveSequenceorReactiveMutableSequencemay 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 aReactiveSequenceorReactiveMutableSequence, all you need to do is add your reactor to the sequence.Mappingsreactives.collections.ReactiveMappingandreactives.collections.ReactiveMutableMappingare reactive versions of
Python's built-inMappingandMutableMapping, which are drop-in replacements fordict.fromreactives.collectionsimportReactiveMutableMappingfruits=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 aReactiveMappingorReactiveMutableMappingmay 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 aReactiveMappingorReactiveMutableMapping, all you  need to do is add your reactor to the mapping.AutowiringWe've seen howproperties,sequences, andmappingsautowire 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 usescope. Any reactive can start a scope withreactives.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) withreactives.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.DevelopmentFirst,fork and clonethe repository, and navigate to its root directory.RequirementsBash (you're all good ifwhich bashoutputs a path in your terminal)InstallationIf you havetoxinstalled on your machine,tox --developwill create the necessary
virtual environments and install all development dependencies.Alternatively, in any existing Python environment, run./bin/build-dev.TestingIn any existing Python environment, run./bin/test.Fixing problems automaticallyIn any existing Python environment, run./bin/fix.Contributions 🥳Reactives is Free and Open Source Software. As such you are welcome toreport bugsorsubmit improvements.Copyright & licenseReactives is copyrightBart Feenstraand contributors, and released under theGNU General Public License, Version 3. In short, that meansyou are free to use Reactives, butif you
distribute Reactives yourself, you must do so under the exact same license, provide that license, and make your source
code available.