Merge pull request #1014 from Textualize/reactive-always-update

Reactive `always_update`
This commit is contained in:
Will McGugan
2022-10-31 13:05:35 +00:00
committed by GitHub
3 changed files with 36 additions and 9 deletions

3
.gitignore vendored
View File

@@ -116,3 +116,6 @@ venv.bak/
# Snapshot testing report output directory
tests/snapshot_tests/output
# Sandbox folder - convenient place for us to develop small test apps without leaving the repo
sandbox/

View File

@@ -165,7 +165,11 @@ If you click the buttons in the above example it will show the current count. Wh
## Watch methods
Watch methods are another superpower. Textual will call watch methods when reactive attributes are modified. Watch methods begin with `watch_` followed by the name of the attribute. If the watch method accepts a positional argument, it will be called with the new assigned value. If the watch method accepts *two* positional arguments, it will be called with both the *old* value and the *new* value.
Watch methods are another superpower.
Textual will call watch methods when reactive attributes are modified.
Watch methods begin with `watch_` followed by the name of the attribute.
If the watch method accepts a positional argument, it will be called with the new assigned value.
If the watch method accepts *two* positional arguments, it will be called with both the *old* value and the *new* value.
The following app will display any color you type in to the input. Try it with a valid color in Textual CSS. For example `"darkorchid"` or `"#52de44"`.
@@ -192,6 +196,12 @@ The following app will display any color you type in to the input. Try it with a
The color is parsed in `on_input_submitted` and assigned to `self.color`. Because `color` is reactive, Textual also calls `watch_color` with the old and new values.
### When are watch methods called?
Textual only calls watch methods if the value of a reactive attribute _changes_.
If the newly assigned value is the same as the previous value, the watch method is not called.
You can override this behaviour by passing `always_update=True` to `reactive`.
## Compute methods
Compute methods are the final superpower offered by the `reactive` descriptor. Textual runs compute methods to calculate the value of a reactive attribute. Compute methods begin with `compute_` followed by the name of the reactive value.

View File

@@ -5,7 +5,6 @@ from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Callable, Generic, Type, TypeVar, Union
from weakref import WeakSet
from . import events
from ._callback import count_parameters, invoke
from ._types import MessageTarget
@@ -16,7 +15,6 @@ if TYPE_CHECKING:
Reactable = Union[Widget, App]
ReactiveType = TypeVar("ReactiveType")
@@ -37,7 +35,7 @@ class Reactive(Generic[ReactiveType]):
layout (bool, optional): Perform a layout on change. Defaults to False.
repaint (bool, optional): Perform a repaint on change. Defaults to True.
init (bool, optional): Call watchers on initialize (post mount). Defaults to False.
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
"""
def __init__(
@@ -47,11 +45,13 @@ class Reactive(Generic[ReactiveType]):
layout: bool = False,
repaint: bool = True,
init: bool = False,
always_update: bool = False,
) -> None:
self._default = default
self._layout = layout
self._repaint = repaint
self._init = init
self._always_update = always_update
@classmethod
def init(
@@ -60,6 +60,7 @@ class Reactive(Generic[ReactiveType]):
*,
layout: bool = False,
repaint: bool = True,
always_update: bool = False,
) -> Reactive:
"""A reactive variable that calls watchers and compute on initialize (post mount).
@@ -67,11 +68,17 @@ class Reactive(Generic[ReactiveType]):
default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
layout (bool, optional): Perform a layout on change. Defaults to False.
repaint (bool, optional): Perform a repaint on change. Defaults to True.
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
Returns:
Reactive: A Reactive instance which calls watchers or initialize.
"""
return cls(default, layout=layout, repaint=repaint, init=True)
return cls(
default,
layout=layout,
repaint=repaint,
init=True,
always_update=always_update,
)
@classmethod
def var(
@@ -153,7 +160,7 @@ class Reactive(Generic[ReactiveType]):
if callable(validate_function) and not first_set:
value = validate_function(value)
# If the value has changed, or this is the first time setting the value
if current_value != value or first_set:
if current_value != value or first_set or self._always_update:
# Set the first set flag to False
setattr(obj, f"__first_set_{self.internal_name}", False)
# Store the internal value
@@ -259,7 +266,7 @@ class reactive(Reactive[ReactiveType]):
layout (bool, optional): Perform a layout on change. Defaults to False.
repaint (bool, optional): Perform a repaint on change. Defaults to True.
init (bool, optional): Call watchers on initialize (post mount). Defaults to True.
always_update(bool, optional): Call watchers even when the new value equals the old value. Defaults to False.
"""
def __init__(
@@ -269,8 +276,15 @@ class reactive(Reactive[ReactiveType]):
layout: bool = False,
repaint: bool = True,
init: bool = True,
always_update: bool = False,
) -> None:
super().__init__(default, layout=layout, repaint=repaint, init=init)
super().__init__(
default,
layout=layout,
repaint=repaint,
init=init,
always_update=always_update,
)
class var(Reactive[ReactiveType]):