Show HN: Reactive Signals for Python – inspired by Angular's reactivity model (github.com)
Hey everyone, I built reaktiv, a small reactive signals library for Python, inspired by Angular’s reactivity model. It lets you define Signals, Computed Values, and Effects that automatically track dependencies and update efficiently. The main focus is async-first reactivity without external dependencies.
Here is an example code:
``` import asyncio from reaktiv import Signal, ComputeSignal, Effect
async def main(): count = Signal(0) doubled = ComputeSignal(lambda: count.get() * 2)
async def log_count():
print(f"Count: {count.get()}, Doubled: {doubled.get()}")
Effect(log_count).schedule()
count.set(5) # Triggers: "Count: 5, Doubled: 10"
await asyncio.sleep(0) # Allow effects to process
asyncio.run(main())
```
20 comments
[ 3.1 ms ] story [ 25.6 ms ] threadI did look at the repo, but I couldn't easily understand the use case and what exactly it's doing from examples alone.
In frontend frameworks, Signals eliminate the need for manual subscriptions and event handling, making state updates more efficient. I wanted the same benefits for backend systems where changes to shared state should propagate automatically, without polling, callbacks, or race conditions.
For example, if a config value or cached result updates, anything depending on it should react immediately. With Signals, you don’t have to manually notify consumers or keep track of what depends on what. It just works (hopefully).
About `asyncio.sleep(0)`: it’s just there to keep the example concise. Normally, you’d be inside an async function that naturally yields control, like handling a request or waiting for I/O.
Also, I looked at your implementation, and your ComputedSignal implementation is not sound - it can give inconsistent results with respect to the input signals depending on the ordering of reads when you have a ComputedSignal that depends on other ComputedSignal.
Could you share an example of the problem you’re referring to?
The simplest solution to implement is to do two phase propagation, the first phase marks all ComputeSignal as “dirty” so any read of them must re-compute. Then in the second phase you do what you’re already doing which is to walk down, re-computing, and stop at ComputeSignals that didn’t change. There are other approaches like using a logical clock that have different balances between time complexity at write time and time complexity at read time.
Besides the correctness issue, the eager depth first strategy can re-compute the same ComputeSignal multiple times for a single change to a root. This can also be solved with the dirty phase, because once a dirty ComputeSignal becomes clean you don’t need to re-compute it again during that write’s fan-out.
https://github.com/posit-dev/py-shiny/blob/main/shiny/reacti...
https://github.com/posit-dev/py-shiny/blob/main/shiny/reacti...
(I haven't looked at Reaktiv beyond the readme but it's clearly based on the same concepts, albeit it's "only" the reactive primitives and doesn't provide the rest of the stack like the frameworks I mentioned do)
Also, care to compare to RxPY? (https://github.com/ReactiveX/RxPY)
RxPY is a full reactive programming toolkit with observables, operators, and complex stream processing, while Signals are a more lightweight and declarative way to track state dependencies. RxPY is great for handling event streams, but for simple state management and reactive updates, Signals are much easier to reason about.
Not your fault of course. Just thought you'd wanna know.
P.S. - HN staff please add markdown support. I'm sure this gets mentioned 10 times a day, so this is mention 11.
Sure, they could have figured it out. But it's annoying.