1515 Set ,
1616 Tuple ,
1717)
18- from weakref import ReferenceType , ref
18+ from weakref import ref
1919
2020from jsonpatch import apply_patch , make_patch
2121from typing_extensions import TypedDict
@@ -125,7 +125,7 @@ async def _model_state_by_component_id(
125125
126126 def _create_layout_update (self , component : AbstractComponent ) -> LayoutUpdate :
127127 old_state = self ._model_state_by_component_id [id (component )]
128- new_state = old_state .new ()
128+ new_state = old_state .new (None , component )
129129
130130 self ._render_component (old_state , new_state , component )
131131 changes = make_patch (getattr (old_state , "model" , {}), new_state .model ).patch
@@ -269,26 +269,33 @@ def _render_model_children(
269269
270270 old_keys = set (old_state .children_by_key ).difference (raw_typed_children_by_key )
271271 old_child_states = {key : old_state .children_by_key [key ] for key in old_keys }
272- if old_child_states :
272+ if old_keys :
273273 self ._unmount_model_states (list (old_child_states .values ()))
274274
275275 new_children = new_state .model ["children" ] = []
276276 for index , (key , (child_type , child )) in enumerate (
277277 raw_typed_children_by_key .items ()
278278 ):
279279 if child_type is DICT_TYPE :
280- child_state = _ModelState (ref (new_state ), index , key , None )
281- self ._render_model (old_child_states .get (key ), child_state , child )
282- new_children .append (child_state .model )
283- new_state .children_by_key [key ] = child_state
280+ old_child_state = old_state .children_by_key .get (key )
281+ if old_child_state is not None :
282+ new_child_state = old_child_state .new (new_state , None )
283+ else :
284+ new_child_state = _ModelState (new_state , index , key , None )
285+ self ._render_model (old_child_state , new_child_state , child )
286+ new_children .append (new_child_state .model )
287+ new_state .children_by_key [key ] = new_child_state
284288 elif child_type is COMPONENT_TYPE :
285- key = getattr (child , "key" , "" ) or hex (id (child ))
286- life_cycle_hook = LifeCycleHook (child , self )
287- child_state = _ModelState (ref (new_state ), index , key , life_cycle_hook )
288- self ._render_component (old_child_states .get (key ), child_state , child )
289- new_children .append (child_state .model )
290- new_state .children_by_key [key ] = child_state
291- self ._model_state_by_component_id [id (child )] = child_state
289+ old_child_state = old_state .children_by_key .get (key )
290+ if old_child_state is not None :
291+ new_child_state = old_child_state .new (new_state , child )
292+ else :
293+ hook = LifeCycleHook (child , self )
294+ new_child_state = _ModelState (new_state , index , key , hook )
295+ self ._render_component (old_child_state , new_child_state , child )
296+ new_children .append (new_child_state .model )
297+ new_state .children_by_key [key ] = new_child_state
298+ self ._model_state_by_component_id [id (child )] = new_child_state
292299 else :
293300 new_children .append (child )
294301
@@ -299,14 +306,14 @@ def _render_model_children_without_old_state(
299306 for index , child in enumerate (raw_children ):
300307 if isinstance (child , dict ):
301308 key = child .get ("key" ) or hex (id (child ))
302- child_state = _ModelState (ref ( new_state ) , index , key , None )
309+ child_state = _ModelState (new_state , index , key , None )
303310 self ._render_model (None , child_state , child )
304311 new_children .append (child_state .model )
305312 new_state .children_by_key [key ] = child_state
306313 elif isinstance (child , AbstractComponent ):
307314 key = getattr (child , "key" , "" ) or hex (id (child ))
308315 life_cycle_hook = LifeCycleHook (child , self )
309- child_state = _ModelState (ref ( new_state ) , index , key , life_cycle_hook )
316+ child_state = _ModelState (new_state , index , key , life_cycle_hook )
310317 self ._render_component (None , child_state , child )
311318 new_children .append (child_state .model )
312319 new_state .children_by_key [key ] = child_state
@@ -322,6 +329,10 @@ def _unmount_model_states(self, old_states: List[_ModelState]) -> None:
322329 hook = state .life_cycle_hook
323330 hook .component_will_unmount ()
324331 del self ._model_state_by_component_id [id (hook .component )]
332+ import gc
333+
334+ print (state )
335+ print (gc .get_referrers (hook ))
325336 to_unmount .extend (state .children_by_key .values ())
326337
327338 def __repr__ (self ) -> str :
@@ -333,7 +344,7 @@ class _ModelState:
333344 __slots__ = (
334345 "index" ,
335346 "key" ,
336- "parent_ref " ,
347+ "_parent_ref " ,
337348 "life_cycle_hook" ,
338349 "patch_path" ,
339350 "key_path" ,
@@ -347,40 +358,49 @@ class _ModelState:
347358
348359 def __init__ (
349360 self ,
350- parent_ref : Optional [ReferenceType [ _ModelState ] ],
361+ parent : Optional [_ModelState ],
351362 index : int ,
352363 key : str ,
353364 life_cycle_hook : Optional [LifeCycleHook ],
354365 ) -> None :
355366 self .index = index
356367 self .key = key
357368
358- if parent_ref is not None :
359- self .parent_ref = parent_ref
360- # temporarilly hydrate for use below
361- parent = parent_ref ()
369+ if parent is not None :
370+ self ._parent_ref = ref ( parent )
371+ self . key_path = f" { parent . key_path } / { key } "
372+ self . patch_path = f" { parent . patch_path } /children/ { index } "
362373 else :
363- parent = None
374+ self . key_path = self . patch_path = ""
364375
365376 if life_cycle_hook is not None :
366377 self .life_cycle_hook = life_cycle_hook
367378
368- if parent is None :
369- self .key_path = self .patch_path = ""
370- else :
371- self .key_path = f"{ parent .key_path } /{ key } "
372- self .patch_path = f"{ parent .patch_path } /children/{ index } "
373-
374379 self .event_targets : Set [str ] = set ()
375380 self .children_by_key : Dict [str , _ModelState ] = {}
376381
377- def new (self ) -> _ModelState :
378- return _ModelState (
379- getattr (self , "parent_ref" , None ),
380- self .index ,
381- self .key ,
382- getattr (self , "life_cycle_hook" , None ),
383- )
382+ @property
383+ def parent (self ) -> _ModelState :
384+ # An AttributeError here is ok. It's synonymous
385+ # with the existance of 'parent' attribute
386+ p = self ._parent_ref ()
387+ assert p is not None , "detached model state"
388+ return p
389+
390+ def new (
391+ self ,
392+ new_parent : Optional [_ModelState ],
393+ component : Optional [AbstractComponent ],
394+ ) -> _ModelState :
395+ if new_parent is None :
396+ new_parent = getattr (self , "parent" , None )
397+ if hasattr (self , "life_cycle_hook" ):
398+ assert component is not None
399+ life_cycle_hook = self .life_cycle_hook
400+ life_cycle_hook .component = component
401+ else :
402+ life_cycle_hook = None
403+ return _ModelState (new_parent , self .index , self .key , life_cycle_hook )
384404
385405 def iter_children (self , include_self : bool = True ) -> Iterator [_ModelState ]:
386406 to_yield = [self ] if include_self else []
@@ -390,6 +410,28 @@ def iter_children(self, include_self: bool = True) -> Iterator[_ModelState]:
390410 to_yield .extend (node .children_by_key .values ())
391411
392412
413+ class _ComponentQueue :
414+
415+ __slots__ = "_loop" , "_queue" , "_pending"
416+
417+ def __init__ (self ) -> None :
418+ self ._loop = asyncio .get_event_loop ()
419+ self ._queue : "asyncio.Queue[AbstractComponent]" = asyncio .Queue ()
420+ self ._pending : Set [int ] = set ()
421+
422+ def put (self , component : AbstractComponent ) -> None :
423+ component_id = id (component )
424+ if component_id not in self ._pending :
425+ self ._pending .add (component_id )
426+ self ._loop .call_soon_threadsafe (self ._queue .put_nowait , component )
427+ return None
428+
429+ async def get (self ) -> AbstractComponent :
430+ component = await self ._queue .get ()
431+ self ._pending .remove (id (component ))
432+ return component
433+
434+
393435class _ModelEventTarget (TypedDict ):
394436 target : str
395437 preventDefault : bool # noqa
@@ -415,25 +457,3 @@ class _ModelVdomRequired(TypedDict, total=True):
415457
416458class _ModelVdom (_ModelVdomRequired , _ModelVdomOptional ):
417459 """A VDOM dictionary model specifically for use with a :class:`Layout`"""
418-
419-
420- class _ComponentQueue :
421-
422- __slots__ = "_loop" , "_queue" , "_pending"
423-
424- def __init__ (self ) -> None :
425- self ._loop = asyncio .get_event_loop ()
426- self ._queue : "asyncio.Queue[AbstractComponent]" = asyncio .Queue ()
427- self ._pending : Set [int ] = set ()
428-
429- def put (self , component : AbstractComponent ) -> None :
430- component_id = id (component )
431- if component_id not in self ._pending :
432- self ._pending .add (component_id )
433- self ._loop .call_soon_threadsafe (self ._queue .put_nowait , component )
434- return None
435-
436- async def get (self ) -> AbstractComponent :
437- component = await self ._queue .get ()
438- self ._pending .remove (id (component ))
439- return component
0 commit comments