From 435296528a699b759cc544bd60607396de3a4433 Mon Sep 17 00:00:00 2001 From: Winston Chang Date: Wed, 10 Jan 2024 17:31:02 -0600 Subject: [PATCH] Cause RecallContextManagers to run when used without `with` --- shiny/express/_recall_context.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/shiny/express/_recall_context.py b/shiny/express/_recall_context.py index e491f733c..3a63d3dc2 100644 --- a/shiny/express/_recall_context.py +++ b/shiny/express/_recall_context.py @@ -29,11 +29,14 @@ def __init__( kwargs = {} self.args: list[object] = list(args) self.kwargs: dict[str, object] = dict(kwargs) + # Let htmltools.wrap_displayhook_handler decide what to do with objects before + # we append them. + self.wrapped_append = wrap_displayhook_handler(self.args.append) def __enter__(self) -> None: self._prev_displayhook = sys.displayhook # Collect each of the "printed" values in the args list. - sys.displayhook = wrap_displayhook_handler(self.args.append) + sys.displayhook = self.displayhook def __exit__( self, @@ -47,6 +50,23 @@ def __exit__( sys.displayhook(res) return False + def displayhook(self, x: object) -> None: + if isinstance(x, RecallContextManager): + # This displayhook first checks if x (the child) is a RecallContextManager, + # in which case it uses `with x` to trigger x.__enter__() and x.__exit__(). + # When x.__exit__() is called, it will invoke x.fn() and then pass the + # result to this object's (the parent) self.displayhook(), which is this + # same function, but instead of passing in a RecallContextManager, it will + # pass in the actual object. + # + # In short, this is a way of invoking a re-entrant call to the current + # function, but instead of passing in a RecallContextManager, it passes in + # the result from the RecallContextManager. + with x: + pass + else: + self.wrapped_append(x) + def tagify(self) -> Tag | TagList | MetadataNode | str: res = self.fn(*self.args, **self.kwargs)