Skip to content

Commit af8af1b

Browse files
authored
Ensure watcher isn't called on first_set when init is False and value doesn't change (#1367)
* Ensure watcher not called when value doesnt change, even on first set * Update CHANGELOG.md
1 parent 1058c30 commit af8af1b

File tree

3 files changed

+10
-8
lines changed

3 files changed

+10
-8
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1515

1616
- Added `textual.actions.SkipAction` exception which can be raised from an action to allow parents to process bindings.
1717

18+
### Fixed
19+
20+
- Fixed watch method incorrectly running on first set when value hasnt changed and init=False https://github.com/Textualize/textual/pull/1367
21+
1822
## [0.7.0] - 2022-12-17
1923

2024
### Added
@@ -34,6 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3438
- Fixed validator not running on first reactive set https://github.com/Textualize/textual/pull/1359
3539
- Ensure only printable characters are used as key_display https://github.com/Textualize/textual/pull/1361
3640

41+
3742
## [0.6.0] - 2022-12-11
3843

3944
### Added

src/textual/reactive.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,11 @@ def __set__(self, obj: Reactable, value: ReactiveType) -> None:
175175
current_value = getattr(obj, name)
176176
# Check for validate function
177177
validate_function = getattr(obj, f"validate_{name}", None)
178-
# Check if this is the first time setting the value
179-
first_set = getattr(obj, f"__first_set_{self.internal_name}", True)
180178
# Call validate
181179
if callable(validate_function):
182180
value = validate_function(value)
183181
# If the value has changed, or this is the first time setting the value
184-
if current_value != value or first_set or self._always_update:
185-
# Set the first set flag to False
186-
setattr(obj, f"__first_set_{self.internal_name}", False)
182+
if current_value != value or self._always_update:
187183
# Store the internal value
188184
setattr(obj, self.internal_name, value)
189185
# Check all watchers
@@ -200,7 +196,6 @@ def _check_watchers(cls, obj: Reactable, name: str, old_value: Any):
200196
obj (Reactable): The reactable object.
201197
name (str): Attribute name.
202198
old_value (Any): The old (previous) value of the attribute.
203-
first_set (bool, optional): True if this is the first time setting the value. Defaults to False.
204199
"""
205200
_rich_traceback_omit = True
206201
# Get the current value.

tests/test_reactive.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ async def watch_count(self, old_value: int, new_value: int) -> None:
8888
assert app.watcher_new_value == OLD_VALUE # The value wasn't changed
8989

9090

91-
@pytest.mark.xfail(
92-
reason="Reactive watcher is incorrectly always called the first time it is set, even if value is same [issue#1230]")
9391
async def test_watch_init_false_always_update_false():
9492
class WatcherInitFalse(App):
9593
count = reactive(0, init=False)
@@ -102,6 +100,10 @@ def watch_count(self, new_value: int) -> None:
102100
async with app.run_test():
103101
app.count = 0 # Value hasn't changed, and always_update=False, so watch_count shouldn't run
104102
assert app.watcher_call_count == 0
103+
app.count = 0
104+
assert app.watcher_call_count == 0
105+
app.count = 1
106+
assert app.watcher_call_count == 1
105107

106108

107109
async def test_watch_init_true():

0 commit comments

Comments
 (0)