Skip to content

Commit a19168b

Browse files
feat(status): add class to handle state status
1 parent 3f12885 commit a19168b

File tree

1 file changed

+79
-67
lines changed

1 file changed

+79
-67
lines changed

trame_server/state.py

Lines changed: 79 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
import logging
33
import weakref
4+
from contextlib import contextmanager
45

56
from .utils import asynchronous, is_dunder, is_private, share
67
from .utils.hot_reload import reload
@@ -15,6 +16,32 @@
1516
TRAME_NON_INIT_VALUE = "__trame__: non_init_value_that_is_not_None"
1617

1718

19+
class StateStatus:
20+
"""
21+
Tracks status flags for a State.
22+
"""
23+
24+
def __init__(self, flushing: bool = False, ready: bool = False):
25+
self.flushing = flushing
26+
self.ready = ready
27+
28+
def mark_ready(self):
29+
self.ready = True
30+
31+
@property
32+
def skip_flushing(self) -> bool:
33+
return self.flushing or not self.ready
34+
35+
@contextmanager
36+
def flushing_context(self):
37+
"""Context manager for flushing state safely."""
38+
self.flushing = True
39+
try:
40+
yield
41+
finally:
42+
self.flushing = False
43+
44+
1845
class StateChangeHandler:
1946
def __init__(self, listeners):
2047
self._all_listeners = listeners
@@ -67,39 +94,30 @@ def __init__(
6794
self._state_listeners = share(
6895
internal, "_state_listeners", StateChangeHandler(self._change_callbacks)
6996
)
70-
self._is_flushing = share(internal, "_is_flushing", {"value": False})
97+
self._status = share(internal, "_status", StateStatus(ready=ready))
7198
self._parent_state = internal
7299
self._children_state = []
73-
self._ready_flag = ready
74100
if internal:
75101
internal._children_state.append(self)
76102

77-
def ready(self) -> None:
78-
"""Mark the state as ready for synchronization."""
79-
if self._ready_flag:
80-
return
81-
82-
self._ready_flag = True
83-
self.flush()
84-
85-
if self._parent_state:
86-
self._parent_state.ready()
87-
88-
for child in self._children_state:
89-
child.ready()
90-
91103
@property
92104
def is_ready(self) -> bool:
93105
"""Return True is the instance is ready for synchronization, False otherwise."""
94-
if self._parent_state:
95-
return self._parent_state.is_ready
96-
return self._ready_flag
106+
return self._status.ready
97107

98108
@property
99109
def translator(self) -> Translator:
100110
"""Return the translator instance used to namespace the variable names."""
101111
return self._translator
102112

113+
def ready(self) -> None:
114+
"""Mark the state as ready for synchronization."""
115+
if self.is_ready:
116+
return
117+
118+
self._status.mark_ready()
119+
self.flush()
120+
103121
def __getitem__(self, key):
104122
key = self._translator.translate_key(key)
105123
return self._pending_update.get(key, self._pushed_state.get(key))
@@ -268,64 +286,58 @@ def modified_keys(self):
268286
# for child server we may need to run the translator on them
269287
return self._modified_keys
270288

289+
def _flush_pending_keys(self) -> set[str]:
290+
_keys = set(self._pending_update.keys())
291+
292+
# update modified keys for current update batch
293+
self._modified_keys.clear()
294+
self._modified_keys |= _keys
295+
296+
# Do the flush
297+
if self._push_state_fn:
298+
self._push_state_fn(self._pending_update)
299+
self._pushed_state.update(self._pending_update)
300+
self._pending_update.clear()
301+
302+
# Execute state listeners
303+
self._state_listeners.add_all(_keys)
304+
for fn, translator in self._state_listeners:
305+
if isinstance(fn, weakref.WeakMethod):
306+
callback = fn()
307+
if callback is None:
308+
continue
309+
else:
310+
callback = fn
311+
312+
if self._hot_reload:
313+
if not inspect.iscoroutinefunction(callback):
314+
callback = reload(callback)
315+
316+
reverse_translated_state = translator.reverse_translate_dict(
317+
self._pushed_state
318+
)
319+
coroutine = callback(**reverse_translated_state)
320+
if inspect.isawaitable(coroutine):
321+
asynchronous.create_task(coroutine)
322+
323+
self._state_listeners.clear()
324+
return _keys
325+
271326
def flush(self):
272327
"""
273328
Force pushing modified state and execute any @state.change listener
274329
if the variable value is different (by value AND reference) from its
275330
previous value or if `dirty` has been flagged on the variable and it has
276331
not been unflagged since.
277332
"""
278-
if not self.is_ready:
279-
return None
280-
281-
if self._is_flushing["value"]:
333+
if self._status.skip_flushing:
282334
return None
283335

284-
self._is_flushing["value"] = True
285336
keys = set()
286-
if len(self._pending_update):
287-
_keys = set(self._pending_update.keys())
288-
289-
while len(_keys):
290-
keys |= _keys
291-
292-
# update modified keys for current update batch
293-
self._modified_keys.clear()
294-
self._modified_keys |= _keys
295-
296-
# Do the flush
297-
if self._push_state_fn:
298-
self._push_state_fn(self._pending_update)
299-
self._pushed_state.update(self._pending_update)
300-
self._pending_update.clear()
301-
302-
# Execute state listeners
303-
self._state_listeners.add_all(_keys)
304-
for fn, translator in self._state_listeners:
305-
if isinstance(fn, weakref.WeakMethod):
306-
callback = fn()
307-
if callback is None:
308-
continue
309-
else:
310-
callback = fn
311-
312-
if self._hot_reload:
313-
if not inspect.iscoroutinefunction(callback):
314-
callback = reload(callback)
315-
316-
reverse_translated_state = translator.reverse_translate_dict(
317-
self._pushed_state
318-
)
319-
coroutine = callback(**reverse_translated_state)
320-
if inspect.isawaitable(coroutine):
321-
asynchronous.create_task(coroutine)
322-
323-
self._state_listeners.clear()
324-
325-
# Check if state change from state listeners
326-
_keys = set(self._pending_update.keys())
327-
328-
self._is_flushing["value"] = False
337+
with self._status.flushing_context():
338+
while bool(self._pending_update):
339+
keys |= self._flush_pending_keys()
340+
329341
return keys
330342

331343
@property

0 commit comments

Comments
 (0)