This commit is contained in:
Will McGugan
2023-01-13 10:44:13 +00:00
parent 027635b978
commit 8f0f0d8c12
2 changed files with 65 additions and 31 deletions

View File

@@ -111,6 +111,24 @@ class Reactive(Generic[ReactiveType]):
""" """
return cls(default, layout=False, repaint=False, init=True) return cls(default, layout=False, repaint=False, init=True)
def _initialize_reactive(self, obj: Reactable, name: str) -> None:
internal_name = f"_reactive_{name}"
if hasattr(obj, internal_name):
# Attribute already has a value
return
if self._is_compute:
default = getattr(obj, f"compute_{name}")()
else:
default_or_callable = self._default
default = (
default_or_callable()
if callable(default_or_callable)
else default_or_callable
)
setattr(obj, internal_name, default)
if self._init:
self._check_watchers(obj, name, default)
@classmethod @classmethod
def _initialize_object(cls, obj: Reactable) -> None: def _initialize_object(cls, obj: Reactable) -> None:
"""Set defaults and call any watchers / computes for the first time. """Set defaults and call any watchers / computes for the first time.
@@ -118,24 +136,31 @@ class Reactive(Generic[ReactiveType]):
Args: Args:
obj (Reactable): An object with Reactive descriptors obj (Reactable): An object with Reactive descriptors
""" """
return reactives = getattr(obj, "__reactives", {})
if not getattr(obj, "__reactive_initialized", False): for name, reactive in reactives.items():
startswith = str.startswith reactive._initialize_reactive(obj, name)
watchers = []
for key in obj.__class__.__dict__:
if startswith(key, "_default_"):
name = key[9:]
internal_name = f"_reactive_{name}"
# Check defaults
if internal_name not in obj.__dict__:
# Attribute has no value yet
default = getattr(obj, key)
default_value = default() if callable(default) else default
# Set the default vale (calls `__set__`)
obj.__dict__[internal_name] = default_value
watchers.append((name, default_value))
setattr(obj, "__reactive_initialized", True) # startswith = str.startswith
# watchers = []
# reactives = getattr(obj, "__reactives", [])
# print(reactives)
# for name in reactives.keys():
# internal_name = f"_reactive_{name}"
# # Check defaults
# if internal_name not in obj.__dict__:
# # Attribute has no value yet
# for k in obj.__dict__:
# if k.startswith("_default"):
# print(k)
# default = getattr(obj, f"_default_{name}")
# default_value = default() if callable(default) else default
# # Set the default vale (calls `__set__`)
# obj.__dict__[internal_name] = None
# setattr(obj, name, default_value)
# # watchers.append((name, default_value))
@classmethod @classmethod
def _reset_object(cls, obj: object) -> None: def _reset_object(cls, obj: object) -> None:
@@ -148,6 +173,9 @@ class Reactive(Generic[ReactiveType]):
getattr(obj, "__computes", []).clear() getattr(obj, "__computes", []).clear()
def __set_name__(self, owner: Type[MessageTarget], name: str) -> None: def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
reactives = getattr(owner, "__reactives", {})
reactives[name] = self
setattr(owner, "__reactives", reactives)
# Check for compute method # Check for compute method
if hasattr(owner, f"compute_{name}"): if hasattr(owner, f"compute_{name}"):
@@ -170,32 +198,38 @@ class Reactive(Generic[ReactiveType]):
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType: def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType:
_rich_traceback_omit = True _rich_traceback_omit = True
self._initialize_reactive(obj, self.name)
value: _NotSet | ReactiveType value: _NotSet | ReactiveType
if self._is_compute: if self._is_compute:
value = getattr(obj, f"compute_{self.name}")() value = getattr(obj, f"compute_{self.name}")()
else: else:
value = getattr(obj, self.internal_name, _NOT_SET) value = getattr(obj, self.internal_name, _NOT_SET)
if isinstance(value, _NotSet): if not self._no_compute:
# No value present, we need to set the default self._compute(obj)
init_name = f"_default_{self.name}"
default = getattr(obj, init_name)
default_value = default() if callable(default) else default
# Set and return the value
setattr(obj, self.internal_name, default_value)
if self._init: # if isinstance(value, _NotSet):
print("CHECK WATCHERS") # # No value present, we need to set the default
self._check_watchers(obj, self.name, default_value) # init_name = f"_default_{self.name}"
# default = getattr(obj, init_name)
# default_value = default() if callable(default) else default
# # Set and return the value
# setattr(obj, self.internal_name, default_value)
if not self._no_compute: # if self._init:
self._compute(obj) # self._check_watchers(obj, self.name, default_value)
value = getattr(obj, self.internal_name)
# if not self._no_compute:
# self._compute(obj)
# value = getattr(obj, self.internal_name)
return value return value
def __set__(self, obj: Reactable, value: ReactiveType) -> None: def __set__(self, obj: Reactable, value: ReactiveType) -> None:
_rich_traceback_omit = True _rich_traceback_omit = True
# Reactive._initialize_object(obj) # Reactive._initialize_object(obj)
self._initialize_reactive(obj, self.name)
name = self.name name = self.name
current_value = getattr(obj, name) current_value = getattr(obj, name)
# Check for validate function # Check for validate function

View File

@@ -159,7 +159,7 @@ async def test_reactive_with_callable_default():
Textual will call it in order to retrieve the default value.""" Textual will call it in order to retrieve the default value."""
called_with_app = None called_with_app = None
def set_called(app: App) -> int: def set_called() -> int:
nonlocal called_with_app nonlocal called_with_app
called_with_app = app called_with_app = app
return OLD_VALUE return OLD_VALUE