refreshable.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. from dataclasses import dataclass
  2. from typing import Any, Awaitable, Callable, Dict, List, Tuple, Union
  3. from typing_extensions import Self
  4. from .. import background_tasks, globals
  5. from ..dependencies import register_component
  6. from ..element import Element
  7. from ..helpers import KWONLY_SLOTS, is_coroutine_function
  8. register_component('refreshable', __file__, 'refreshable.js')
  9. @dataclass(**KWONLY_SLOTS)
  10. class RefreshableTarget:
  11. container: Element
  12. instance: Any
  13. args: Tuple[Any, ...]
  14. kwargs: Dict[str, Any]
  15. def run(self, func: Callable[..., Any]) -> Union[None, Awaitable]:
  16. if is_coroutine_function(func):
  17. async def wait_for_result() -> None:
  18. with self.container:
  19. if self.instance is None:
  20. await func(*self.args, **self.kwargs)
  21. else:
  22. await func(self.instance, *self.args, **self.kwargs)
  23. return wait_for_result()
  24. else:
  25. with self.container:
  26. if self.instance is None:
  27. func(*self.args, **self.kwargs)
  28. else:
  29. func(self.instance, *self.args, **self.kwargs)
  30. return None # required by mypy
  31. class refreshable:
  32. def __init__(self, func: Callable[..., Any]) -> None:
  33. """Refreshable UI functions
  34. The `@ui.refreshable` decorator allows you to create functions that have a `refresh` method.
  35. This method will automatically delete all elements created by the function and recreate them.
  36. """
  37. self.func = func
  38. self.instance = None
  39. self.targets: List[RefreshableTarget] = []
  40. def __get__(self, instance, _) -> Self:
  41. self.instance = instance
  42. return self
  43. def __call__(self, *args: Any, **kwargs: Any) -> Union[None, Awaitable]:
  44. self.prune()
  45. target = RefreshableTarget(container=Element('refreshable'), instance=self.instance, args=args, kwargs=kwargs)
  46. self.targets.append(target)
  47. return target.run(self.func)
  48. def refresh(self, *args: Any, **kwargs: Any) -> None:
  49. self.prune()
  50. for target in self.targets:
  51. if target.instance != self.instance:
  52. continue
  53. target.container.clear()
  54. target.args = args or target.args
  55. target.kwargs.update(kwargs)
  56. result = target.run(self.func)
  57. if is_coroutine_function(self.func):
  58. assert result is not None
  59. if globals.loop and globals.loop.is_running():
  60. background_tasks.create(result)
  61. else:
  62. globals.app.on_startup(result)
  63. def prune(self) -> None:
  64. self.targets = [target for target in self.targets if target.container.client.id in globals.clients]