import json from threading import Thread from typing import Any, Callable from typing_extensions import Self from webview.util import escape_string try: from enum import StrEnum # Python 3.11 and above except ImportError: from enum import Enum class StrEnum(str, Enum): pass class StateEventType(StrEnum): CHANGE = 'change' DELETE = 'delete' class State(dict): _serializable = False def __init__(self, window): self.__event_handlers = [] self.__window = window def __update_js(self, key, value): special_key = '__pywebviewHaltUpdate__' + key script = f"window.pywebview.state.{special_key} = JSON.parse('{escape_string(json.dumps(value))}')" self.__window.run_js(script) def __notify_handlers(self, type, key, value=None): def notify_handlers(): for handler in self.__event_handlers: if callable(handler): if type == StateEventType.CHANGE: handler(type, key, value) elif type == StateEventType.DELETE: handler(type, key, value) t = Thread(target=notify_handlers) t.start() def __setattr__(self, key, value, should_update_js=True): def update_and_notify(): self.__update_js(key, value) self.__notify_handlers(StateEventType.CHANGE, key, value) if key.startswith('_State__'): super().__setattr__(key, value) return if key not in self or self[key] != value: self[key] = value if not should_update_js: self.__notify_handlers(StateEventType.CHANGE, key, value) return if self.__window.events.loaded.is_set(): update_and_notify() else: self.__window.events.loaded += update_and_notify def __getattr__(self, key): if key in self: return self[key] raise AttributeError(f"'{type(self).__key__}' object has no attribute '{key}'") def __delattr__(self, key): if key.startswith('__pywebviewHaltUpdate__'): key = key.replace('__pywebviewHaltUpdate__', '') halt_update = True else: halt_update = False if key in self: old_value = self[key] del self[key] if not halt_update: special_key = '__pywebviewHaltUpdate__' + key self.__window.run_js(f'delete window.pywebview.state.{special_key}') self.__notify_handlers(StateEventType.DELETE, key, old_value) else: raise AttributeError(f"'{type(self).__key__}' object has no attribute '{key}'") def __add__(self, item: Callable[..., Any]) -> Self: self.__event_handlers.append(item) return self def __sub__(self, item: Callable[..., Any]) -> Self: self.__event_handlers.remove(item) return self def __iadd__(self, item: Callable[..., Any]) -> Self: self.__event_handlers.append(item) return self def __isub__(self, item: Callable[..., Any]) -> Self: self.__event_handlers.remove(item) return self