-
Notifications
You must be signed in to change notification settings - Fork 117
Description
Category
UI Components
Scope
Minor Enhancement
Problem
Perhaps this is a niche use case, but consider a simple example of using Shiny modules from the documentation. If one wants to render conditional output, it is of course possible to define arguments to do so, e.g.,
@module.ui
def row_ui(is_special: bool = False):
label = "Enter special text" if is_special else "Enter text"
return ui.layout_columns(
ui.card(ui.input_text("text_in", label)),
ui.card(ui.output_text("text_out")),
)
...
app_ui = ui.page_fluid(
row_ui("row_1"),
row_ui("row_2"),
[row_ui(x, x.endswith(("3", "5"))) for x in extra_ids]
)(Full app)
However, if this conditional behaviour is dependent on the module ID (as is the case in the example above), the implementation becomes repetitive and verbose. Even more so when the behaviour is complex and needs to be specified inside the module functions, e.g.,
@module.ui
def row_ui(module_id: str):
if foo(module_id):
# complex things like calling another module
if bar(module_id):
# more complex logic
return ui.layout_columns(
ui.card(ui.input_text("text_in", "Enter text")),
ui.card(ui.output_text("text_out")),
)
...
app_ui = ui.page_fluid(
row_ui("row_1", "row_1"),
row_ui("row_2", "row_2"),
[row_ui(x, x) for x in extra_ids]
)It would be much cleaner to access the namespace ID from within the module functions.
Solution
Currently, module decorators do not pass the IDs. For example, here is the module UI function decorator:
def ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
with namespace_context(id):
return fn(*args, **kwargs)
return wrapperIt is fairly trivial to update it to pass along the raw (unresolved) ID:
def ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
with namespace_context(id):
- return fn(*args, **kwargs)
+ return fn(id, *args, **kwargs)
return wrapperThis would naturally require users to always define the first argument, which is not desirable, so the ID could be passed only if expected by module function signature:
+ from inspect import signature
def ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
+ # Assuming the argument should be called `module_id`
+ if 'module_id' in signature(fn).parameters:
+ kwargs['module_id'] = id
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
with namespace_context(id):
- return fn(*args, **kwargs)
+ return fn(id, *args, **kwargs)
return wrapperAlternatives (Optional)
No response
Example (Optional)
@module.ui
def row_ui(module_id: str):
if foo(module_id):
# complex things like calling another module
if bar(module_id):
# more complex logic
return ui.layout_columns(
ui.card(ui.input_text("text_in", "Enter text")),
ui.card(ui.output_text("text_out")),
)
...
app_ui = ui.page_fluid(
row_ui("row_1"),
row_ui("row_2"),
[row_ui(x) for x in extra_ids]
)Impact (Optional)
No response
Contribution? (Optional)
Yes, I can implement (or help).