|
@@ -11,10 +11,11 @@
|
|
|
|
|
|
import inspect
|
|
|
import typing as t
|
|
|
+from abc import abstractmethod
|
|
|
from contextlib import nullcontext
|
|
|
from operator import attrgetter
|
|
|
from pathlib import Path
|
|
|
-from types import FrameType
|
|
|
+from types import FrameType, SimpleNamespace
|
|
|
|
|
|
from flask import has_app_context
|
|
|
|
|
@@ -25,7 +26,7 @@ if t.TYPE_CHECKING:
|
|
|
from .gui import Gui
|
|
|
|
|
|
|
|
|
-class State:
|
|
|
+class State(SimpleNamespace):
|
|
|
"""Accessor to the bound variables from callbacks.
|
|
|
|
|
|
`State` is used when you need to access the value of variables
|
|
@@ -73,6 +74,87 @@ class State:
|
|
|
```
|
|
|
"""
|
|
|
|
|
|
+ def __init__(self) -> None:
|
|
|
+ self._gui: "Gui"
|
|
|
+
|
|
|
+ @abstractmethod
|
|
|
+ def get_gui(self) -> "Gui":
|
|
|
+ """Return the Gui instance for this state object.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Gui: The Gui instance for this state object.
|
|
|
+ """
|
|
|
+ raise NotImplementedError
|
|
|
+
|
|
|
+ def assign(self, name: str, value: t.Any) -> t.Any:
|
|
|
+ """Assign a value to a state variable.
|
|
|
+
|
|
|
+ This should be used only from within a lambda function used
|
|
|
+ as a callback in a visual element.
|
|
|
+
|
|
|
+ Arguments:
|
|
|
+ name (str): The variable name to assign to.
|
|
|
+ value (Any): The new variable value.
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Any: The previous value of the variable.
|
|
|
+ """
|
|
|
+ val = attrgetter(name)(self)
|
|
|
+ _attrsetter(self, name, value)
|
|
|
+ return val
|
|
|
+
|
|
|
+ def refresh(self, name: str):
|
|
|
+ """Refresh a state variable.
|
|
|
+
|
|
|
+ This allows to re-sync the user interface with a variable value.
|
|
|
+
|
|
|
+ Arguments:
|
|
|
+ name (str): The variable name to refresh.
|
|
|
+ """
|
|
|
+ val = attrgetter(name)(self)
|
|
|
+ _attrsetter(self, name, val)
|
|
|
+
|
|
|
+ def _set_context(self, gui: "Gui") -> t.ContextManager[None]:
|
|
|
+ return nullcontext()
|
|
|
+
|
|
|
+ def broadcast(self, name: str, value: t.Any):
|
|
|
+ """Update a variable on all clients.
|
|
|
+
|
|
|
+ All connected clients will receive an update of the variable called *name* with the
|
|
|
+ provided value, even if it is not shared.
|
|
|
+
|
|
|
+ Arguments:
|
|
|
+ name (str): The variable name to update.
|
|
|
+ value (Any): The new variable value.
|
|
|
+ """
|
|
|
+ with self._set_context(self._gui):
|
|
|
+ encoded_name = self._gui._bind_var(name)
|
|
|
+ self._gui._broadcast_all_clients(encoded_name, value)
|
|
|
+
|
|
|
+ def __enter__(self):
|
|
|
+ self._gui.__enter__()
|
|
|
+ return self
|
|
|
+
|
|
|
+ def __exit__(self, exc_type, exc_value, traceback):
|
|
|
+ return self._gui.__exit__(exc_type, exc_value, traceback)
|
|
|
+
|
|
|
+ def set_favicon(self, favicon_path: t.Union[str, Path]):
|
|
|
+ """Change the favicon for the client of this state.
|
|
|
+
|
|
|
+ This function dynamically changes the favicon (the icon associated with the application's
|
|
|
+ pages) of Taipy GUI pages for the specific client of this state.
|
|
|
+
|
|
|
+ Note that the *favicon* parameter to `(Gui.)run()^` can also be used to change
|
|
|
+ the favicon when the application starts.
|
|
|
+
|
|
|
+ Arguments:
|
|
|
+ favicon_path: The path to the image file to use.<br/>
|
|
|
+ This can be expressed as a path name or a URL (relative or not).
|
|
|
+ """
|
|
|
+ self._gui.set_favicon(favicon_path, self)
|
|
|
+
|
|
|
+
|
|
|
+class _GuiState(State):
|
|
|
__gui_attr = "_gui"
|
|
|
__attrs = (
|
|
|
__gui_attr,
|
|
@@ -100,68 +182,66 @@ class State:
|
|
|
__excluded_attrs = __attrs + __methods + __placeholder_attrs
|
|
|
|
|
|
def __init__(self, gui: "Gui", var_list: t.Iterable[str], context_list: t.Iterable[str]) -> None:
|
|
|
- super().__setattr__(State.__attrs[1], list(State.__filter_var_list(var_list, State.__excluded_attrs)))
|
|
|
- super().__setattr__(State.__attrs[2], list(context_list))
|
|
|
- super().__setattr__(State.__attrs[0], gui)
|
|
|
-
|
|
|
- def get_gui(self) -> "Gui":
|
|
|
- """Return the Gui instance for this state object.
|
|
|
-
|
|
|
- Returns:
|
|
|
- Gui: The Gui instance for this state object.
|
|
|
- """
|
|
|
- return super().__getattribute__(State.__gui_attr)
|
|
|
+ super().__setattr__(
|
|
|
+ _GuiState.__attrs[1], list(_GuiState.__filter_var_list(var_list, _GuiState.__excluded_attrs))
|
|
|
+ )
|
|
|
+ super().__setattr__(_GuiState.__attrs[2], list(context_list))
|
|
|
+ super().__setattr__(_GuiState.__attrs[0], gui)
|
|
|
+ super().__init__()
|
|
|
|
|
|
@staticmethod
|
|
|
def __filter_var_list(var_list: t.Iterable[str], excluded_attrs: t.Iterable[str]) -> t.Iterable[str]:
|
|
|
return filter(lambda n: n not in excluded_attrs, var_list)
|
|
|
|
|
|
+ def get_gui(self) -> "Gui":
|
|
|
+ return super().__getattribute__(_GuiState.__gui_attr)
|
|
|
+
|
|
|
def __getattribute__(self, name: str) -> t.Any:
|
|
|
if name == "__class__":
|
|
|
- return State
|
|
|
- if name in State.__methods:
|
|
|
+ return _GuiState
|
|
|
+ if name in _GuiState.__methods:
|
|
|
return super().__getattribute__(name)
|
|
|
gui: "Gui" = self.get_gui()
|
|
|
- if name == State.__gui_attr:
|
|
|
+ if name == _GuiState.__gui_attr:
|
|
|
return gui
|
|
|
- if name in State.__excluded_attrs:
|
|
|
+ if name in _GuiState.__excluded_attrs:
|
|
|
raise AttributeError(f"Variable '{name}' is protected and is not accessible.")
|
|
|
if gui._is_in_brdcst_callback() and (
|
|
|
name not in gui._get_shared_variables() and not gui._bindings()._is_single_client()
|
|
|
):
|
|
|
raise AttributeError(f"Variable '{name}' is not available to be accessed in shared callback.")
|
|
|
- if not name.startswith("__") and name not in super().__getattribute__(State.__attrs[1]):
|
|
|
+ if not name.startswith("__") and name not in super().__getattribute__(_GuiState.__attrs[1]):
|
|
|
raise AttributeError(f"Variable '{name}' is not defined.")
|
|
|
with self._notebook_context(gui), self._set_context(gui):
|
|
|
encoded_name = gui._bind_var(name)
|
|
|
return getattr(gui._bindings(), encoded_name)
|
|
|
|
|
|
def __setattr__(self, name: str, value: t.Any) -> None:
|
|
|
- gui: "Gui" = super().__getattribute__(State.__gui_attr)
|
|
|
+ gui: "Gui" = super().__getattribute__(_GuiState.__gui_attr)
|
|
|
if gui._is_in_brdcst_callback() and (
|
|
|
name not in gui._get_shared_variables() and not gui._bindings()._is_single_client()
|
|
|
):
|
|
|
raise AttributeError(f"Variable '{name}' is not available to be accessed in shared callback.")
|
|
|
- if not name.startswith("__") and name not in super().__getattribute__(State.__attrs[1]):
|
|
|
+ if not name.startswith("__") and name not in super().__getattribute__(_GuiState.__attrs[1]):
|
|
|
raise AttributeError(f"Variable '{name}' is not accessible.")
|
|
|
with self._notebook_context(gui), self._set_context(gui):
|
|
|
encoded_name = gui._bind_var(name)
|
|
|
setattr(gui._bindings(), encoded_name, value)
|
|
|
|
|
|
def __getitem__(self, key: str):
|
|
|
- context = key if key in super().__getattribute__(State.__attrs[2]) else None
|
|
|
+ context = key if key in super().__getattribute__(_GuiState.__attrs[2]) else None
|
|
|
if context is None:
|
|
|
- gui: "Gui" = super().__getattribute__(State.__gui_attr)
|
|
|
+ gui: "Gui" = super().__getattribute__(_GuiState.__gui_attr)
|
|
|
page_ctx = gui._get_page_context(key)
|
|
|
context = page_ctx if page_ctx is not None else None
|
|
|
if context is None:
|
|
|
raise RuntimeError(f"Can't resolve context '{key}' from state object")
|
|
|
- self._set_placeholder(State.__placeholder_attrs[1], context)
|
|
|
+ self._set_placeholder(_GuiState.__placeholder_attrs[1], context)
|
|
|
return self
|
|
|
|
|
|
def _set_context(self, gui: "Gui") -> t.ContextManager[None]:
|
|
|
- if (pl_ctx := self._get_placeholder(State.__placeholder_attrs[1])) is not None:
|
|
|
- self._set_placeholder(State.__placeholder_attrs[1], None)
|
|
|
+ if (pl_ctx := self._get_placeholder(_GuiState.__placeholder_attrs[1])) is not None:
|
|
|
+ self._set_placeholder(_GuiState.__placeholder_attrs[1], None)
|
|
|
if pl_ctx != gui._get_locals_context():
|
|
|
return gui._set_locals_context(pl_ctx)
|
|
|
if len(inspect.stack()) > 1:
|
|
@@ -176,7 +256,7 @@ class State:
|
|
|
return gui.get_flask_app().app_context() if not has_app_context() and _is_in_notebook() else nullcontext()
|
|
|
|
|
|
def _get_placeholder(self, name: str):
|
|
|
- if name in State.__placeholder_attrs:
|
|
|
+ if name in _GuiState.__placeholder_attrs:
|
|
|
try:
|
|
|
return super().__getattribute__(name)
|
|
|
except AttributeError:
|
|
@@ -184,81 +264,16 @@ class State:
|
|
|
return None
|
|
|
|
|
|
def _set_placeholder(self, name: str, value: t.Any):
|
|
|
- if name in State.__placeholder_attrs:
|
|
|
+ if name in _GuiState.__placeholder_attrs:
|
|
|
super().__setattr__(name, value)
|
|
|
|
|
|
def _get_placeholder_attrs(self):
|
|
|
- return State.__placeholder_attrs
|
|
|
+ return _GuiState.__placeholder_attrs
|
|
|
|
|
|
def _add_attribute(self, name: str, default_value: t.Optional[t.Any] = None) -> bool:
|
|
|
- attrs: t.List[str] = super().__getattribute__(State.__attrs[1])
|
|
|
+ attrs: t.List[str] = super().__getattribute__(_GuiState.__attrs[1])
|
|
|
if name not in attrs:
|
|
|
attrs.append(name)
|
|
|
- gui = super().__getattribute__(State.__gui_attr)
|
|
|
+ gui = super().__getattribute__(_GuiState.__gui_attr)
|
|
|
return gui._bind_var_val(name, default_value)
|
|
|
return False
|
|
|
-
|
|
|
- def assign(self, name: str, value: t.Any) -> t.Any:
|
|
|
- """Assign a value to a state variable.
|
|
|
-
|
|
|
- This should be used only from within a lambda function used
|
|
|
- as a callback in a visual element.
|
|
|
-
|
|
|
- Arguments:
|
|
|
- name (str): The variable name to assign to.
|
|
|
- value (Any): The new variable value.
|
|
|
-
|
|
|
- Returns:
|
|
|
- Any: The previous value of the variable.
|
|
|
- """
|
|
|
- val = attrgetter(name)(self)
|
|
|
- _attrsetter(self, name, value)
|
|
|
- return val
|
|
|
-
|
|
|
- def refresh(self, name: str):
|
|
|
- """Refresh a state variable.
|
|
|
-
|
|
|
- This allows to re-sync the user interface with a variable value.
|
|
|
-
|
|
|
- Arguments:
|
|
|
- name (str): The variable name to refresh.
|
|
|
- """
|
|
|
- val = attrgetter(name)(self)
|
|
|
- _attrsetter(self, name, val)
|
|
|
-
|
|
|
- def broadcast(self, name: str, value: t.Any):
|
|
|
- """Update a variable on all clients.
|
|
|
-
|
|
|
- All connected clients will receive an update of the variable called *name* with the
|
|
|
- provided value, even if it is not shared.
|
|
|
-
|
|
|
- Arguments:
|
|
|
- name (str): The variable name to update.
|
|
|
- value (Any): The new variable value.
|
|
|
- """
|
|
|
- gui: "Gui" = super().__getattribute__(State.__gui_attr)
|
|
|
- with self._set_context(gui):
|
|
|
- encoded_name = gui._bind_var(name)
|
|
|
- gui._broadcast_all_clients(encoded_name, value)
|
|
|
-
|
|
|
- def __enter__(self):
|
|
|
- super().__getattribute__(State.__attrs[0]).__enter__()
|
|
|
- return self
|
|
|
-
|
|
|
- def __exit__(self, exc_type, exc_value, traceback):
|
|
|
- return super().__getattribute__(State.__attrs[0]).__exit__(exc_type, exc_value, traceback)
|
|
|
-
|
|
|
- def set_favicon(self, favicon_path: t.Union[str, Path]):
|
|
|
- """Change the favicon for the client of this state.
|
|
|
-
|
|
|
- This function dynamically changes the favicon (the icon associated with the application's
|
|
|
- pages) of Taipy GUI pages for the specific client of this state.
|
|
|
-
|
|
|
- Note that the *favicon* parameter to `(Gui.)run()^` can also be used to change
|
|
|
- the favicon when the application starts.
|
|
|
-
|
|
|
- Arguments:
|
|
|
- favicon_path: The path to the image file to use.<br/>
|
|
|
- This can be expressed as a path name or a URL (relative or not).
|
|
|
- """
|
|
|
- super().__getattribute__(State.__gui_attr).set_favicon(favicon_path, self)
|