Skip to content

Commit 055da77

Browse files
committed
make Layout context management async
1 parent fc8ff68 commit 055da77

File tree

5 files changed

+73
-76
lines changed

5 files changed

+73
-76
lines changed

docs/source/about/changelog.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ See :ref:`Custom JavaScript Components` for details on the new interface.
444444
- Make docs section margins larger - :issue:`450`
445445
- Search broken in docs - :issue:`443`
446446
- Move src/idom/client out of Python package - :issue:`429`
447-
- Use composition instead of classes with Layout and LifeCycleHook - :issue:`412`
447+
- Use composition instead of classes async with Layout and LifeCycleHook - :issue:`412`
448448
- Remove Python language extension - :issue:`282`
449449
- Add keys to models so React doesn't complain of child arrays requiring them -
450450
:issue:`255`

src/idom/core/layout.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ class LayoutEvent(NamedTuple):
6161
"""A list of event data passed to the event handler."""
6262

6363

64-
_Self = TypeVar("_Self", bound="Layout")
65-
66-
6764
class Layout:
6865
"""Responsible for "rendering" components. That is, turning them into VDOM."""
6966

@@ -84,7 +81,7 @@ def __init__(self, root: "ComponentType") -> None:
8481
raise TypeError(f"Expected a ComponentType, not {type(root)!r}.")
8582
self.root = root
8683

87-
def __enter__(self: _Self) -> _Self:
84+
async def __aenter__(self) -> Layout:
8885
# create attributes here to avoid access before entering context manager
8986
self._event_handlers: EventHandlerDict = {}
9087

@@ -98,7 +95,7 @@ def __enter__(self: _Self) -> _Self:
9895

9996
return self
10097

101-
def __exit__(self, *exc: Any) -> None:
98+
async def __aexit__(self, *exc: Any) -> None:
10299
root_csid = self._root_life_cycle_state_id
103100
root_model_state = self._model_states_by_life_cycle_state_id[root_csid]
104101
self._unmount_model_states([root_model_state])

src/idom/core/serve.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async def serve_json_patch(
3939
recv: RecvCoroutine,
4040
) -> None:
4141
"""Run a dispatch loop for a single view instance"""
42-
with layout:
42+
async with layout:
4343
try:
4444
async with create_task_group() as task_group:
4545
task_group.start_soon(_single_outgoing_loop, layout, send)

tests/test_core/test_hooks.py

+35-35
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def SimpleComponentWithHook():
2222
with pytest.raises(RuntimeError, match="No life cycle hook is active"):
2323
await SimpleComponentWithHook().render()
2424

25-
with idom.Layout(SimpleComponentWithHook()) as layout:
25+
async with idom.Layout(SimpleComponentWithHook()) as layout:
2626
await layout.render()
2727

2828

@@ -35,7 +35,7 @@ def SimpleStatefulComponent():
3535

3636
sse = SimpleStatefulComponent()
3737

38-
with idom.Layout(sse) as layout:
38+
async with idom.Layout(sse) as layout:
3939
patch_1 = await render_json_patch(layout)
4040
assert patch_1.path == ""
4141
assert_same_items(
@@ -75,7 +75,7 @@ def SimpleStatefulComponent():
7575

7676
sse = SimpleStatefulComponent()
7777

78-
with idom.Layout(sse) as layout:
78+
async with idom.Layout(sse) as layout:
7979
await layout.render()
8080
await layout.render()
8181
await layout.render()
@@ -108,7 +108,7 @@ def Inner():
108108
state, set_inner_state.current = idom.use_state(make_default)
109109
return idom.html.div(state)
110110

111-
with idom.Layout(Outer()) as layout:
111+
async with idom.Layout(Outer()) as layout:
112112
await layout.render()
113113

114114
assert constructor_call_count.current == 1
@@ -141,7 +141,7 @@ def Counter():
141141
count.current, set_count.current = idom.hooks.use_state(0)
142142
return idom.html.div(count.current)
143143

144-
with idom.Layout(Counter()) as layout:
144+
async with idom.Layout(Counter()) as layout:
145145
await layout.render()
146146

147147
for i in range(4):
@@ -308,7 +308,7 @@ def CheckNoEffectYet():
308308
effect_triggers_after_final_render.current = not effect_triggered.current
309309
return idom.html.div()
310310

311-
with idom.Layout(OuterComponent()) as layout:
311+
async with idom.Layout(OuterComponent()) as layout:
312312
await layout.render()
313313

314314
assert effect_triggered.current
@@ -336,7 +336,7 @@ def cleanup():
336336

337337
return idom.html.div()
338338

339-
with idom.Layout(ComponentWithEffect()) as layout:
339+
async with idom.Layout(ComponentWithEffect()) as layout:
340340
await layout.render()
341341

342342
assert not cleanup_triggered.current
@@ -375,7 +375,7 @@ def cleanup():
375375

376376
return idom.html.div()
377377

378-
with idom.Layout(OuterComponent()) as layout:
378+
async with idom.Layout(OuterComponent()) as layout:
379379
await layout.render()
380380

381381
assert not cleanup_triggered.current
@@ -406,7 +406,7 @@ def effect():
406406

407407
return idom.html.div()
408408

409-
with idom.Layout(ComponentWithMemoizedEffect()) as layout:
409+
async with idom.Layout(ComponentWithMemoizedEffect()) as layout:
410410
await layout.render()
411411

412412
assert effect_run_count.current == 1
@@ -449,7 +449,7 @@ def cleanup():
449449

450450
return idom.html.div()
451451

452-
with idom.Layout(ComponentWithEffect()) as layout:
452+
async with idom.Layout(ComponentWithEffect()) as layout:
453453
await layout.render()
454454

455455
assert cleanup_trigger_count.current == 0
@@ -476,7 +476,7 @@ async def effect():
476476

477477
return idom.html.div()
478478

479-
with idom.Layout(ComponentWithAsyncEffect()) as layout:
479+
async with idom.Layout(ComponentWithAsyncEffect()) as layout:
480480
await layout.render()
481481
await asyncio.wait_for(effect_ran.wait(), 1)
482482

@@ -496,7 +496,7 @@ async def effect():
496496

497497
return idom.html.div()
498498

499-
with idom.Layout(ComponentWithAsyncEffect()) as layout:
499+
async with idom.Layout(ComponentWithAsyncEffect()) as layout:
500500
await layout.render()
501501

502502
component_hook.latest.schedule_render()
@@ -527,7 +527,7 @@ async def effect():
527527

528528
return idom.html.div()
529529

530-
with idom.Layout(ComponentWithLongWaitingEffect()) as layout:
530+
async with idom.Layout(ComponentWithLongWaitingEffect()) as layout:
531531
await layout.render()
532532

533533
await effect_ran.wait()
@@ -554,7 +554,7 @@ def bad_effect():
554554
return idom.html.div()
555555

556556
with assert_idom_logged(match_message=r"Layout post-render effect .* failed"):
557-
with idom.Layout(ComponentWithEffect()) as layout:
557+
async with idom.Layout(ComponentWithEffect()) as layout:
558558
await layout.render() # no error
559559

560560

@@ -575,7 +575,7 @@ def bad_cleanup():
575575
return idom.html.div()
576576

577577
with assert_idom_logged(match_error=r"Layout post-render effect .* failed"):
578-
with idom.Layout(ComponentWithEffect()) as layout:
578+
async with idom.Layout(ComponentWithEffect()) as layout:
579579
await layout.render()
580580
component_hook.latest.schedule_render()
581581
await layout.render() # no error
@@ -604,7 +604,7 @@ def bad_cleanup():
604604
match_message=r"Pre-unmount effect .*? failed",
605605
error_type=ValueError,
606606
):
607-
with idom.Layout(OuterComponent()) as layout:
607+
async with idom.Layout(OuterComponent()) as layout:
608608
await layout.render()
609609
set_key.current("second")
610610
await layout.render() # no error
@@ -629,7 +629,7 @@ def Counter(initial_count):
629629
)
630630
return idom.html.div()
631631

632-
with idom.Layout(Counter(0)) as layout:
632+
async with idom.Layout(Counter(0)) as layout:
633633
await layout.render()
634634

635635
assert saved_count.current == 0
@@ -659,7 +659,7 @@ def ComponentWithUseReduce():
659659
saved_dispatchers.append(idom.hooks.use_reducer(reducer, 0)[1])
660660
return idom.html.div()
661661

662-
with idom.Layout(ComponentWithUseReduce()) as layout:
662+
async with idom.Layout(ComponentWithUseReduce()) as layout:
663663
for _ in range(3):
664664
await layout.render()
665665
saved_dispatchers[-1]("increment")
@@ -679,7 +679,7 @@ def ComponentWithRef():
679679
used_callbacks.append(idom.hooks.use_callback(lambda: None))
680680
return idom.html.div()
681681

682-
with idom.Layout(ComponentWithRef()) as layout:
682+
async with idom.Layout(ComponentWithRef()) as layout:
683683
await layout.render()
684684
component_hook.latest.schedule_render()
685685
await layout.render()
@@ -705,7 +705,7 @@ def cb():
705705
used_callbacks.append(cb)
706706
return idom.html.div()
707707

708-
with idom.Layout(ComponentWithRef()) as layout:
708+
async with idom.Layout(ComponentWithRef()) as layout:
709709
await layout.render()
710710
set_state_hook.current(1)
711711
await layout.render()
@@ -733,7 +733,7 @@ def ComponentWithMemo():
733733
used_values.append(value)
734734
return idom.html.div()
735735

736-
with idom.Layout(ComponentWithMemo()) as layout:
736+
async with idom.Layout(ComponentWithMemo()) as layout:
737737
await layout.render()
738738
set_state_hook.current(1)
739739
await layout.render()
@@ -758,7 +758,7 @@ def ComponentWithMemo():
758758
used_values.append(value)
759759
return idom.html.div()
760760

761-
with idom.Layout(ComponentWithMemo()) as layout:
761+
async with idom.Layout(ComponentWithMemo()) as layout:
762762
await layout.render()
763763
component_hook.latest.schedule_render()
764764
await layout.render()
@@ -785,7 +785,7 @@ def ComponentWithMemo():
785785
used_values.append(value)
786786
return idom.html.div()
787787

788-
with idom.Layout(ComponentWithMemo()) as layout:
788+
async with idom.Layout(ComponentWithMemo()) as layout:
789789
await layout.render()
790790
component_hook.latest.schedule_render()
791791
deps_used_in_memo.current = None
@@ -810,7 +810,7 @@ def ComponentWithMemo():
810810
used_values.append(value)
811811
return idom.html.div()
812812

813-
with idom.Layout(ComponentWithMemo()) as layout:
813+
async with idom.Layout(ComponentWithMemo()) as layout:
814814
await layout.render()
815815
component_hook.latest.schedule_render()
816816
await layout.render()
@@ -830,7 +830,7 @@ def ComponentWithRef():
830830
used_refs.append(idom.hooks.use_ref(1))
831831
return idom.html.div()
832832

833-
with idom.Layout(ComponentWithRef()) as layout:
833+
async with idom.Layout(ComponentWithRef()) as layout:
834834
await layout.render()
835835
component_hook.latest.schedule_render()
836836
await layout.render()
@@ -865,7 +865,7 @@ def some_effect_that_uses_count():
865865

866866
return idom.html.div()
867867

868-
with idom.Layout(CounterWithEffect()) as layout:
868+
async with idom.Layout(CounterWithEffect()) as layout:
869869
await layout.render()
870870
await did_effect.wait()
871871
did_effect.clear()
@@ -893,7 +893,7 @@ def some_memo_func_that_uses_count():
893893

894894
return idom.html.div()
895895

896-
with idom.Layout(CounterWithEffect()) as layout:
896+
async with idom.Layout(CounterWithEffect()) as layout:
897897
await layout.render()
898898
await did_memo.wait()
899899
did_memo.clear()
@@ -918,7 +918,7 @@ def ComponentUsesContext():
918918
value.current = idom.use_context(Context)
919919
return html.div()
920920

921-
with idom.Layout(ComponentProvidesContext()) as layout:
921+
async with idom.Layout(ComponentProvidesContext()) as layout:
922922
await layout.render()
923923
assert value.current == "something"
924924

@@ -927,7 +927,7 @@ def ComponentUsesContext():
927927
value.current = idom.use_context(Context)
928928
return html.div()
929929

930-
with idom.Layout(ComponentUsesContext()) as layout:
930+
async with idom.Layout(ComponentUsesContext()) as layout:
931931
await layout.render()
932932
assert value.current == "something"
933933

@@ -958,7 +958,7 @@ def ComponentInContext():
958958
render_count.current += 1
959959
return html.div()
960960

961-
with idom.Layout(ComponentProvidesContext()) as layout:
961+
async with idom.Layout(ComponentProvidesContext()) as layout:
962962
await layout.render()
963963
assert render_count.current == 1
964964

@@ -995,7 +995,7 @@ def MemoizedComponentUsesContext():
995995
render_count.current += 1
996996
return html.div()
997997

998-
with idom.Layout(ComponentProvidesContext()) as layout:
998+
async with idom.Layout(ComponentProvidesContext()) as layout:
999999
await layout.render()
10001000
assert render_count.current == 1
10011001
assert value.current == 0
@@ -1041,7 +1041,7 @@ def Inner():
10411041
inner_render_count.current += 1
10421042
return html.div()
10431043

1044-
with idom.Layout(Root()) as layout:
1044+
async with idom.Layout(Root()) as layout:
10451045
await layout.render()
10461046
assert outer_render_count.current == 1
10471047
assert inner_render_count.current == 1
@@ -1097,7 +1097,7 @@ def Right():
10971097
right_used_value.current = idom.use_context(RightContext)
10981098
return idom.html.div()
10991099

1100-
with idom.Layout(Root()) as layout:
1100+
async with idom.Layout(Root()) as layout:
11011101
await layout.render()
11021102
assert left_render_count.current == 1
11031103
assert right_render_count.current == 1
@@ -1142,7 +1142,7 @@ def bad_effect():
11421142
error_type=ValueError,
11431143
match_error="The error message",
11441144
):
1145-
with idom.Layout(ComponentWithEffect()) as layout:
1145+
async with idom.Layout(ComponentWithEffect()) as layout:
11461146
await layout.render()
11471147
component_hook.latest.schedule_render()
11481148
await layout.render() # no error
@@ -1159,7 +1159,7 @@ def SetStateDuringRender():
11591159
set_state(state + 1)
11601160
return html.div(state)
11611161

1162-
with Layout(SetStateDuringRender()) as layout:
1162+
async with Layout(SetStateDuringRender()) as layout:
11631163
await layout.render()
11641164
assert render_count.current == 1
11651165
await layout.render()

0 commit comments

Comments
 (0)