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.
This commit is contained in:
Dave Pearson
2023-05-01 10:01:50 +01:00
parent 8fac2c7d2a
commit 847fd6e69e
3 changed files with 32 additions and 1 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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