Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions examples/express/expressify_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from shiny.express import expressify, ui


# @expressify converts a function to work with Express syntax
@expressify
def expressified1(s: str):
f"Expressified function 1: {s}"
ui.br()


expressified1("Hello")

expressified1("world")


ui.br()


# @expressify() also works with parens
@expressify()
def expressified2(s: str):
f"Expressified function 2: {s}"
ui.br()


expressified2("Hello")

expressified2("world")
28 changes: 28 additions & 0 deletions examples/express/hold_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import shiny.ui
from shiny.express import input, render, ui

# `ui.hold() as x` can be used to save `x` for later output
with ui.hold() as hello_card:
with ui.card():
with ui.span():
"This is a"
ui.span(" card", style="color: red;")

hello_card

hello_card

ui.hr()


# `ui.hold()` can be used to just suppress output
with ui.hold():

@render.text()
def txt():
return f"Slider value: {input.n()}"


ui.input_slider("n", "N", 1, 100, 50)

shiny.ui.output_text("txt", inline=True)
17 changes: 17 additions & 0 deletions examples/express/render_express_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from shiny.express import input, render, ui

ui.input_slider("n", "N", 1, 100, 50)


# @render.express is like @render.ui, but with Express syntax
@render.express
def render_express1():
"Slider value:"
input.n()


# @render.express() also works with parens
@render.express()
def render_express2():
"Slider value:"
input.n()
15 changes: 7 additions & 8 deletions shiny/api-examples/render_display/app.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import datetime

from shiny import render, ui
from shiny.express import input, layout
from shiny.express import input, render, ui

with layout.card(id="card"):
with ui.card(id="card"):
ui.input_slider("val", "slider", 0, 100, 50)
"Text outside of render display call"
"Text outside of render express call"
ui.tags.br()
f"Rendered time: {str(datetime.datetime.now())}"

@render.display
def render_display():
"Text inside of render display call"
@render.express
def render_express():
"Text inside of render express call"
ui.tags.br()
"Dynamic slider value: "
input.val()
ui.tags.br()
f"Display's rendered time: {str(datetime.datetime.now())}"
f"Rendered time: {str(datetime.datetime.now())}"
7 changes: 3 additions & 4 deletions shiny/express/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
from . import ui
from ._is_express import is_express_app
from ._output import ( # noqa: F401
suspend_display,
output_args, # pyright: ignore[reportUnusedImport]
suspend_display, # pyright: ignore[reportUnusedImport] - Deprecated
)
from ._run import wrap_express_app
from .display_decorator import display_body
from .expressify_decorator import expressify


__all__ = (
Expand All @@ -21,10 +21,9 @@
"output",
"session",
"is_express_app",
"suspend_display",
"wrap_express_app",
"ui",
"display_body",
"expressify",
)

# Add types to help type checkers
Expand Down
46 changes: 30 additions & 16 deletions shiny/express/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
from typing import Callable, Generator, TypeVar, overload

from .. import ui
from .._deprecated import warn_deprecated
from .._typing_extensions import ParamSpec
from ..render.renderer import RendererBase, RendererBaseT

__all__ = ("suspend_display",)
__all__ = (
"hide",
"suspend_display",
)

P = ParamSpec("P")
R = TypeVar("R")
Expand Down Expand Up @@ -49,29 +53,29 @@ def wrapper(renderer: RendererBaseT) -> RendererBaseT:


@overload
def suspend_display(fn: CallableT) -> CallableT:
def hide(fn: CallableT) -> CallableT:
...


@overload
def suspend_display(fn: RendererBaseT) -> RendererBaseT:
def hide(fn: RendererBaseT) -> RendererBaseT:
...


@overload
def suspend_display() -> AbstractContextManager[None]:
def hide() -> AbstractContextManager[None]:
...


def suspend_display(
def hide(
fn: Callable[P, R] | RendererBaseT | None = None
) -> Callable[P, R] | RendererBaseT | AbstractContextManager[None]:
"""Suppresses the display of UI elements in various ways.
"""Prevent the display of UI elements in various ways.

If used as a context manager (`with suspend_display():`), it suppresses the display
of all UI elements within the context block. (This is useful when you want to
temporarily suppress the display of a large number of UI elements, or when you want
to suppress the display of UI elements that are not directly under your control.)
If used as a context manager (`with hide():`), it prevents the display of all UI
elements within the context block. (This is useful when you want to temporarily
prevent the display of a large number of UI elements, or when you want to prevent
the display of UI elements that are not directly under your control.)

If used as a decorator (without parentheses) on a Shiny rendering function, it
prevents that function from automatically outputting itself at the point of its
Expand All @@ -85,19 +89,19 @@ def suspend_display(
Parameters
----------
fn
The function to decorate. If `None`, returns a context manager that suppresses
the display of UI elements within the context block.
The function to decorate. If `None`, returns a context manager that prevents the
display of UI elements within the context block.

Returns
-------
:
If `fn` is `None`, returns a context manager that suppresses the display of UI
If `fn` is `None`, returns a context manager that prevents the display of UI
elements within the context block. Otherwise, returns a decorated version of
`fn`.
"""

if fn is None:
return suspend_display_ctxmgr()
return hide_ctxmgr()

# Special case for RendererBase; when we decorate those, we just mean "don't
# display yourself"
Expand All @@ -106,11 +110,21 @@ def suspend_display(
fn.auto_output_ui = null_ui
return fn

return suspend_display_ctxmgr()(fn)
return hide_ctxmgr()(fn)


def suspend_display(
fn: Callable[P, R] | RendererBaseT | None = None
) -> Callable[P, R] | RendererBaseT | AbstractContextManager[None]:
warn_deprecated(
"`suspend_display` is deprecated. Please use `hide` instead. "
"It has a new name, but the exact same functionality."
)
return hide(fn) # type: ignore


@contextlib.contextmanager
def suspend_display_ctxmgr() -> Generator[None, None, None]:
def hide_ctxmgr() -> Generator[None, None, None]:
oldhook = sys.displayhook
sys.displayhook = null_displayhook
try:
Expand Down
8 changes: 4 additions & 4 deletions shiny/express/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
from .._app import App
from ..session import Inputs, Outputs, Session
from ._recall_context import RecallContextManager
from .display_decorator._func_displayhook import _display_decorator_function_def
from .display_decorator._node_transformers import (
from .expressify_decorator._func_displayhook import _expressify_decorator_function_def
from .expressify_decorator._node_transformers import (
DisplayFuncsTransformer,
display_decorator_func_name,
expressify_decorator_func_name,
)

__all__ = ("wrap_express_app",)
Expand Down Expand Up @@ -83,7 +83,7 @@ def set_result(x: object):

var_context: dict[str, object] = {
"__file__": file_path,
display_decorator_func_name: _display_decorator_function_def,
expressify_decorator_func_name: _expressify_decorator_function_def,
"input": InputNotImportedShim(),
}

Expand Down
3 changes: 0 additions & 3 deletions shiny/express/display_decorator/__init__.py

This file was deleted.

3 changes: 3 additions & 0 deletions shiny/express/expressify_decorator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._expressify import expressify

__all__ = ("expressify",)
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@
import linecache
import sys
import types
from typing import Any, Callable, Dict, Protocol, TypeVar, cast, runtime_checkable
from typing import (
Any,
Callable,
Dict,
Protocol,
TypeVar,
cast,
overload,
runtime_checkable,
)

from ._func_displayhook import _display_decorator_function_def
from ._func_displayhook import _expressify_decorator_function_def
from ._helpers import find_code_for_func
from ._node_transformers import (
DisplayFuncsTransformer,
FuncBodyDisplayHookTransformer,
TargetFunctionTransformer,
display_decorator_func_name,
expressify_decorator_func_name,
sys_alias,
)

# It's quite expensive to decorate with display_body, and it could be done to
# inner functions where the outer function is called a lot. Use a cache to save
# us from having to do the expensive stuff (parsing, transforming, compiling)
# more than once.
# It's quite expensive to decorate with expressify, and it could be done to inner
# functions where the outer function is called a lot. Use a cache to save us from having
# to do the expensive stuff (parsing, transforming, compiling) more than once.
code_cache: Dict[types.CodeType, types.CodeType] = {}

T = TypeVar("T")
Expand All @@ -45,12 +53,12 @@ def unwrap(fn: TFunc) -> TFunc:
return fn


display_body_attr = "__display_body__"
expressify_attr = "__expressify__"


def display_body_unwrap_inplace() -> Callable[[TFunc], TFunc]:
def expressify_unwrap_inplace() -> Callable[[TFunc], TFunc]:
"""
Like `display_body`, but far more violent. This will attempt to traverse any
Like `expressify`, but far more violent. This will attempt to traverse any
decorators between this one and the function, and then modify the function _in
place_. It will then return the function that was passed in.
"""
Expand All @@ -59,7 +67,7 @@ def decorator(fn: TFunc) -> TFunc:
unwrapped_fn = unwrap(fn)

# Check if we've already done this
if hasattr(unwrapped_fn, display_body_attr):
if hasattr(unwrapped_fn, expressify_attr):
return fn

if unwrapped_fn.__code__ in code_cache:
Expand All @@ -70,13 +78,23 @@ def decorator(fn: TFunc) -> TFunc:
code_cache[unwrapped_fn.__code__] = fcode

unwrapped_fn.__code__ = fcode
setattr(unwrapped_fn, display_body_attr, True)
setattr(unwrapped_fn, expressify_attr, True)
return fn

return decorator


def display_body() -> Callable[[TFunc], TFunc]:
@overload
def expressify(fn: TFunc) -> TFunc:
...


@overload
def expressify() -> Callable[[TFunc], TFunc]:
...


def expressify(fn: TFunc | None = None) -> TFunc | Callable[[TFunc], TFunc]:
def decorator(fn: TFunc) -> TFunc:
if fn.__code__ in code_cache:
fcode = code_cache[fn.__code__]
Expand All @@ -93,7 +111,7 @@ def decorator(fn: TFunc) -> TFunc:
# have a different `sys` alias in scope.
globals={
sys_alias: auto_displayhook,
display_decorator_func_name: _display_decorator_function_def,
expressify_decorator_func_name: _expressify_decorator_function_def,
**fn.__globals__,
},
name=fn.__name__,
Expand All @@ -106,6 +124,9 @@ def decorator(fn: TFunc) -> TFunc:
new_func.__dict__.update(fn.__dict__)
return cast(TFunc, functools.wraps(fn)(new_func))

if fn is not None:
return decorator(fn)

return decorator


Expand Down Expand Up @@ -219,4 +240,4 @@ def comparator(candidate: types.CodeType, target: types.CodeType) -> bool:


__builtins__[sys_alias] = auto_displayhook
__builtins__[display_decorator_func_name] = _display_decorator_function_def
__builtins__[expressify_decorator_func_name] = _expressify_decorator_function_def
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# A decorator used for `def` statements. It makes sure that any `def` statement which
# returns a tag-like object, or one with a `_repr_html` method, will be passed on to
# the current sys.displayhook.
def _display_decorator_function_def(fn: object) -> object:
def _expressify_decorator_function_def(fn: object) -> object:
if isinstance(fn, (Tag, TagList, Tagifiable)) or hasattr(fn, "_repr_html_"):
sys.displayhook(fn)

Expand Down
Loading