Skip to content

Releases: promplate/pyth-on-line

HMR v0.7.6

15 Nov 21:27

Choose a tag to compare

This release brings a new reactivity_loss_strategy option for better control over dependency loss in computations and fixes an edge case when you would see a RuntimeWarning when using cache_across_reloads.

New Feature: reactivity_loss_strategy option for Computations

Computations (like Effect or Derived) now support a configurable strategy when they lose all dependencies. Since v0.7.5, a warning is issued, but now you can set it to "ignore" or "restore" for different behaviors.

Warning

A computation without dependencies usually indicates a code mistake.

By default, a warning is issued when a computation completes without collecting any dependencies.
This often happens when signal access is behind non-reactive conditions or caching.
You can set this to "restore" to automatically preserve previous dependencies as a temporary workaround.
The correct practice is to replace those conditions with reactive ones (e.g. Signal) or use Derived for caching.

Consider "ignore" only when extending this library and manually assigning dependencies. Use with caution.

For example, to ignore warnings in cached functions:

@derived
def f():
    ...  # do not read any reactive value

f.reactivity_loss_strategy = "ignore"  # to suppress warnings

# maybe you manually assign some signals to it
f.dependencies.add(...)
....subscribers.add(f)

# or maybe you manually invalidate it somewhere
f.invalidate()

Other Changes

  • Enhanced test suite with new cases for reactivity strategies.
  • Internal refactoring in context management and module loading.
  • Updated Pytest configuration for better async handling.
  • Add missing Context.derived shorthand property.

Full Changelog: hmr/v0.7.5...hmr/v0.7.6

HMR v0.7.5: Polish & Rigor

04 Nov 10:14

Choose a tag to compare

tl;dr: If v0.7.0 was the flashy feature drop, v0.7.5 is the "actually usable" release. Reactive collections don't leak memory, errors have clean tracebacks, and you get warnings when your effects are doing nothing. Also Python 3.14 support, because why not.

Tip

1. New API: Signal.update() for Untracked Mutations

Sometimes you know the value hasn't changed semantically (e.g., mutating a list in-place but the identity is the same). signal.update(lambda x: x +1) lets you mutate without triggering subscribers. Useful when you want to batch changes and only notify once.

For Signal[int] variables like s, the expression s.update(lambda x: x+1) is functionally equivalent to s.set(s.get(track=False) + 1). However, s.update can also be used as a decorator, which provides clearer intent for value mutations:

@s.update
def _(x: int):
    ...  # perform some calculation
    return x + 1

Note

2. Warning when Computations Lose Dependencies

New in this release: if an Effect or Derived has zero dependencies (or suddenly loses all of them), you'll get a RuntimeWarning. This catches accidental no-ops or the footgun where caching inside an effect makes it non-reactive. The warning includes a proper traceback and skips stdlib/hmr internals so you can actually debug it.

For example:

from reactivity import effect
effect(lambda: None)  # does nothing
path/to/main.py:2: RuntimeWarning: Computation <reactivity.primitives.Effect object at 0x...> has no dependencies and will never be auto-triggered.
  effect(lambda: None)  # does nothing

3. Internal: _stop_event Consolidation

Refactored SyncReloader and AsyncReloader to share the same _stop_event logic in BaseReloader. Turns out watchfiles just polls it anyway, so no need for a full asyncio.Event—a simple object with .is_set() and .set() does the job.


Other changes since v0.7:

Five patch releases later, v0.7 is finally stable enough that I can stop obsessing over edge cases. Here's what changed since the v0.7.0:

  • v0.7.0.1 - v0.7.0.2 Turns out making list slicing reactive is harder than it looks. Fixed a bunch of issues where tracking len() or slicing with negative indices would either miss updates or trigger too many. Also plugged a memory leak where derived values created from slices weren't getting GC'd properly—now they self-destruct when nobody's watching. FS audithook now checks HMR_CONTEXT.leaf.current_computations instead of a global current_computations. This was causing file reads to not be tracked in the correct reactive context—a subtle but nasty bug.

  • v0.7.1 CPython 3.14 Support & skip_annotations. Added skip_annotations parameter to the AST transformer because Python 3.14 defers annotation evaluation by default [PEP 649] Also respects from __future__ import annotations now. If you're still on 3.13, it'll auto-detect whether to skip annotations based on your flags. Boring but necessary.

  • v0.7.2 - v0.7.4 Error handling and reactivity tweaks, path filters for FS tracking...

Full Changelog: hmr/v0.7.4...hmr/v0.7.5

HMR v0.7: Only Python Can Do ✨

19 Sep 22:05

Choose a tag to compare

This is a major release with 3 colossal features and a bunch of significant improvements.

Tip

Our docs site now has an llms.txt endpoint and an MCP server. Check the end of this post for more info.

1. Introducing Async Reactivity

You can now create async primitives from async functions just like sync ones. This is a game-changer for triggering side effects concurrently with state changes. Unlike in JavaScript frameworks like Svelte, where async reactivity is limited to within a microtask, hmr supports tracking reactivity after awaits. This is a minimal example:

from anyio import run, sleep
from reactivity import async_effect, signal

s = signal(0)

@run
async def _():
    @async_effect
    async def _():
        await sleep(0.01)  # for now, only python can track reactivity after awaits
        print(f"{ s.get() = }")

    for i in range(10):
        await sleep(0.1)
        s.set(i)

    await sleep(0.1)  # wait for the last triggered effect to run

And you will see "s.get() = 0" through "s.get() = 9" printed out with a delay of 0.1 seconds between each.

We support both asyncio and trio. And you can easily use structured concurrency with the task_factory parameter. Docs are coming soon.

2. The reactive Primitive

Vue's reactive and Svelte's $state receives container-like objects and makes their properties reactive. Before this release we only support dicts, but now you can use any collection type. list, set or even any custom class with properties reactive. Yeah, you can use pydantic models or dataclasses. You can use it as a decorator for your TypedDict! Here is an example with dataclass:

from dataclasses import dataclass
from reactivity import effect, reactive

@reactive
@dataclass
class Point:
    x: int
    y: int

    @property
    def magnitude(self) -> float:
        return (self.x**2 + self.y**2) ** 0.5

p = Point(1, 2)

with effect(lambda: print(p.magnitude)):
    p.x = 3
    p.y = 4

Reactive lists make your slice access reactive:

from reactivity import effect, reactive

lst = reactive([1, 2])

with effect(lambda: print(lst[-1])), effect(lambda: print(lst[:3])):
    lst.append(3)  # print 3
    lst.append(3)
    lst.append(3)  # never print twice

    lst.insert(0, 1000)  # [1000, 1, 2]
    lst.insert(0, 1000)  # [1000, 1000, 1]
    lst.insert(0, 1000)  # [1000, 1000, 1000]
    lst.insert(0, 1000)  # no print! well done!

We invoke the indices you update very carefully, which suffers me a lot, but finally it just works.

3. Now the whole FS is reactive during hmr

The above two features are related to the reactivity engine, but this one is related to hmr CLI. Before v0.7, only python files are reactive. Now every file you read (in the watched directories) is reactive. For example, if you want to show the parsed result of your pyproject.toml, you can write this in your main.py:

from json import dumps
from tomllib import loads

print("\033c", end="")  # clear the terminal
print(dumps(loads(open("pyproject.toml").read()), indent=2))

Run hmr main.py, and every time you change pyproject.toml, the parsed result will be printed again instantly. How cool is that!

Other Improvements

It's 5:30 am and I am too tired to write more. Please check the changelog below. Maybe I will add more later.


We have a docs site now! We even have an MCP Server to access the docs

Go check them out! I spent a lot of time on the docs site too :)

Docs for human is still a WIP. I will finish it within this week!


Full Changelog: hmr/v0.6.4.4...hmr/v0.7.0