You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

102 lines
3.7 KiB

from dataclasses import dataclass
from typing import Any, Awaitable, Callable, Dict, List, Tuple, Union
from typing_extensions import Self
from .. import background_tasks, globals # pylint: disable=redefined-builtin
from ..dataclasses import KWONLY_SLOTS
from ..element import Element
from ..helpers import is_coroutine_function
@dataclass(**KWONLY_SLOTS)
class RefreshableTarget:
container: Element
instance: Any
args: Tuple[Any, ...]
kwargs: Dict[str, Any]
def run(self, func: Callable[..., Any]) -> Union[None, Awaitable]:
# pylint: disable=no-else-return
if is_coroutine_function(func):
async def wait_for_result() -> None:
with self.container:
if self.instance is None:
await func(*self.args, **self.kwargs)
else:
await func(self.instance, *self.args, **self.kwargs)
return wait_for_result()
else:
with self.container:
if self.instance is None:
func(*self.args, **self.kwargs)
else:
func(self.instance, *self.args, **self.kwargs)
return None # required by mypy
class RefreshableContainer(Element, component='refreshable.js'):
pass
class refreshable:
def __init__(self, func: Callable[..., Any]) -> None:
"""Refreshable UI functions
The `@ui.refreshable` decorator allows you to create functions that have a `refresh` method.
This method will automatically delete all elements created by the function and recreate them.
"""
self.func = func
self.instance = None
self.targets: List[RefreshableTarget] = []
def __get__(self, instance, _) -> Self:
self.instance = instance
return self
def __getattribute__(self, __name: str) -> Any:
attribute = object.__getattribute__(self, __name)
if __name == 'refresh':
def refresh(*args: Any, _instance=self.instance, **kwargs: Any) -> None:
self.instance = _instance
attribute(*args, **kwargs)
return refresh
return attribute
def __call__(self, *args: Any, **kwargs: Any) -> Union[None, Awaitable]:
self.prune()
target = RefreshableTarget(container=RefreshableContainer(), instance=self.instance, args=args, kwargs=kwargs)
self.targets.append(target)
return target.run(self.func)
def refresh(self, *args: Any, **kwargs: Any) -> None:
self.prune()
for target in self.targets:
if target.instance != self.instance:
continue
target.container.clear()
target.args = args or target.args
target.kwargs.update(kwargs)
try:
result = target.run(self.func)
except TypeError as e:
if 'got multiple values for argument' in str(e):
function = str(e).split()[0].split('.')[-1]
parameter = str(e).split()[-1]
raise TypeError(f'{parameter} needs to be consistently passed to {function} '
'either as positional or as keyword argument') from e
raise
if is_coroutine_function(self.func):
assert result is not None
if globals.loop and globals.loop.is_running():
background_tasks.create(result)
else:
globals.app.on_startup(result)
def prune(self) -> None:
self.targets = [
target
for target in self.targets
if target.container.client.id in globals.clients and target.container.id in target.container.client.elements
]