1414from collections .abc import Sequence
1515from contextlib import AsyncExitStack
1616from logging import getLogger
17+ from types import TracebackType
1718from typing import (
1819 Any ,
1920 Callable ,
@@ -56,13 +57,13 @@ class Layout:
5657 """Responsible for "rendering" components. That is, turning them into VDOM."""
5758
5859 __slots__ : tuple [str , ...] = (
59- "root" ,
6060 "_event_handlers" ,
61- "_rendering_queue " ,
61+ "_model_states_by_life_cycle_state_id " ,
6262 "_render_tasks" ,
6363 "_render_tasks_ready" ,
64+ "_rendering_queue" ,
6465 "_root_life_cycle_state_id" ,
65- "_model_states_by_life_cycle_state_id " ,
66+ "root " ,
6667 )
6768
6869 if not hasattr (abc .ABC , "__weakref__" ): # nocov
@@ -80,17 +81,17 @@ async def __aenter__(self) -> Layout:
8081 self ._event_handlers : EventHandlerDict = {}
8182 self ._render_tasks : set [Task [LayoutUpdateMessage ]] = set ()
8283 self ._render_tasks_ready : Semaphore = Semaphore (0 )
83-
8484 self ._rendering_queue : _ThreadSafeQueue [_LifeCycleStateId ] = _ThreadSafeQueue ()
8585 root_model_state = _new_root_model_state (self .root , self ._schedule_render_task )
86-
8786 self ._root_life_cycle_state_id = root_id = root_model_state .life_cycle_state .id
8887 self ._model_states_by_life_cycle_state_id = {root_id : root_model_state }
8988 self ._schedule_render_task (root_id )
9089
9190 return self
9291
93- async def __aexit__ (self , * exc : object ) -> None :
92+ async def __aexit__ (
93+ self , exc_type : type [Exception ], exc_value : Exception , traceback : TracebackType
94+ ) -> None :
9495 root_csid = self ._root_life_cycle_state_id
9596 root_model_state = self ._model_states_by_life_cycle_state_id [root_csid ]
9697
@@ -109,7 +110,7 @@ async def __aexit__(self, *exc: object) -> None:
109110 del self ._root_life_cycle_state_id
110111 del self ._model_states_by_life_cycle_state_id
111112
112- async def deliver (self , event : LayoutEventMessage ) -> None :
113+ async def deliver (self , event : LayoutEventMessage | dict [ str , Any ] ) -> None :
113114 """Dispatch an event to the targeted handler"""
114115 # It is possible for an element in the frontend to produce an event
115116 # associated with a backend model that has been deleted. We only handle
@@ -217,7 +218,7 @@ async def _render_component(
217218 parent .children_by_key [key ] = new_state
218219 # need to add this model to parent's children without mutating parent model
219220 old_parent_model = parent .model .current
220- old_parent_children = old_parent_model [ "children" ]
221+ old_parent_children = old_parent_model . setdefault ( "children" , [])
221222 parent .model .current = {
222223 ** old_parent_model ,
223224 "children" : [
@@ -318,8 +319,11 @@ async def _render_model_children(
318319 new_state : _ModelState ,
319320 raw_children : Any ,
320321 ) -> None :
321- if not isinstance (raw_children , (list , tuple )):
322- raw_children = [raw_children ]
322+ if not isinstance (raw_children , list ):
323+ if isinstance (raw_children , tuple ):
324+ raw_children = list (raw_children )
325+ else :
326+ raw_children = [raw_children ]
323327
324328 if old_state is None :
325329 if raw_children :
@@ -609,7 +613,7 @@ def __init__(
609613 parent : _ModelState | None ,
610614 index : int ,
611615 key : Any ,
612- model : Ref [VdomJson ],
616+ model : Ref [VdomJson | dict [ str , Any ] ],
613617 patch_path : str ,
614618 children_by_key : dict [Key , _ModelState ],
615619 targets_by_event : dict [str , str ],
@@ -656,7 +660,7 @@ def parent(self) -> _ModelState:
656660 return parent
657661
658662 def append_child (self , child : Any ) -> None :
659- self .model .current [ "children" ] .append (child )
663+ self .model .current . setdefault ( "children" , []) .append (child )
660664
661665 def __repr__ (self ) -> str : # nocov
662666 return f"ModelState({ {s : getattr (self , s , None ) for s in self .__slots__ } } )"
0 commit comments