From 847fd6e69e783fff5203576dceb871eea1862f0e Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Mon, 1 May 2023 10:01:50 +0100 Subject: [PATCH] Add support for private watch methods This change allows for private watch methods. By convention they start with an underscore. If a reactive or var has a private watch method, it will be used in preference to a public watch method. With this change it becomes easier to have a private reactive/var whose watcher is also private. For example: _counter = var(0) """This is a private counter, it won't appear in the docs." ... def _watch__counter(self) -> None: """Watch _counter, but don't appear in the docs either." ... See #2382. --- CHANGELOG.md | 5 +++++ src/textual/reactive.py | 4 +++- tests/test_reactive.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a39253ca0..76862bd58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased + +### Added + +- Watch methods can now optionally be private https://github.com/Textualize/textual/issues/2382 ## [0.22.3] - 2023-04-29 diff --git a/src/textual/reactive.py b/src/textual/reactive.py index aa848bb73..7fb8b3ba2 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -241,7 +241,9 @@ class Reactive(Generic[ReactiveType]): events.Callback(callback=partial(await_watcher, watch_result)) ) - watch_function = getattr(obj, f"watch_{name}", None) + watch_function = getattr( + obj, f"_watch_{name}", getattr(obj, f"watch_{name}", None) + ) if callable(watch_function): invoke_watcher(watch_function, old_value, value) diff --git a/tests/test_reactive.py b/tests/test_reactive.py index 9c824645e..74f944d48 100644 --- a/tests/test_reactive.py +++ b/tests/test_reactive.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import pytest @@ -386,3 +388,25 @@ async def test_watch_compute(): assert app.show_ac is False assert watch_called == [True, True, False, False, True, True, False, False] + + +async def test_private_watch() -> None: + """A private watch method should win over a public watch method.""" + + calls: dict[str, bool] = {"private": False, "public": False} + + class PrivateWatchTest(App): + counter = var(0, init=False) + + def watch_counter(self) -> None: + calls["public"] = True + + def _watch_counter(self) -> None: + calls["private"] = True + + async with PrivateWatchTest().run_test() as pilot: + assert calls["private"] is False + assert calls["public"] is False + pilot.app.counter += 1 + assert calls["private"] is True + assert calls["public"] is False