Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to Skip Param<Ref|Function|Method> updates #6396

Merged
merged 3 commits into from
Mar 4, 2024
Merged
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
36 changes: 31 additions & 5 deletions panel/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@

import param

try:
from param import Skip
except Exception:
class Skip(Exception):
"""
Exception that allows skipping an update for function-level updates.
"""
from param.parameterized import (
classlist, discard_events, eval_function_with_deps, get_method_owner,
iscoroutinefunction, resolve_ref, resolve_value,
Undefined, classlist, discard_events, eval_function_with_deps,
get_method_owner, iscoroutinefunction, resolve_ref, resolve_value,
)
from param.reactive import rx

Expand Down Expand Up @@ -829,9 +836,20 @@ async def _eval_async(self, awaitable):
self._inner_layout.append(new_obj)
self._pane = self._inner_layout[-1]
else:
self._update_inner(new_obj)
try:
self._update_inner(new_obj)
except Skip:
pass
else:
self._update_inner(await awaitable)
try:
new = await awaitable
if new is Skip or new is Undefined:
raise Skip
self._update_inner(new)
except Skip:
self.param.log(
param.DEBUG, 'Skip event was raised, skipping update.'
)
except Exception as e:
if not curdoc or (has_context and curdoc.session_context):
raise e
Expand All @@ -850,7 +868,15 @@ def _replace_pane(self, *args, force=False):
if self.object is None:
new_object = Spacer()
else:
new_object = self.eval(self.object)
try:
new_object = self.eval(self.object)
if new_object is Skip and new_object is Undefined:
raise Skip
except Skip:
self.param.log(
param.DEBUG, 'Skip event was raised, skipping update.'
)
return
if inspect.isawaitable(new_object) or isinstance(new_object, types.AsyncGeneratorType):
param.parameterized.async_executor(partial(self._eval_async, new_object))
return
Expand Down
59 changes: 52 additions & 7 deletions panel/tests/test_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import pytest

from bokeh.models import (
AutocompleteInput as BkAutocompleteInput, Button, Checkbox as BkCheckbox,
Column as BkColumn, Div, MultiSelect, RangeSlider as BkRangeSlider,
Row as BkRow, Select, Slider, Tabs as BkTabs, TextInput,
TextInput as BkTextInput, Toggle,
AutocompleteInput as BkAutocompleteInput, Button as BkButton,
Checkbox as BkCheckbox, Column as BkColumn, Div, MultiSelect,
RangeSlider as BkRangeSlider, Row as BkRow, Select, Slider, Tabs as BkTabs,
TextInput, TextInput as BkTextInput, Toggle,
)
from packaging.version import Version

Expand All @@ -22,11 +22,11 @@
HTML, Bokeh, Markdown, Matplotlib, PaneBase, Str, panel,
)
from panel.param import (
JSONInit, Param, ParamFunction, ParamMethod,
JSONInit, Param, ParamFunction, ParamMethod, Skip,
)
from panel.tests.util import mpl_available, mpl_figure
from panel.widgets import (
AutocompleteInput, Checkbox, DatePicker, DatetimeInput,
AutocompleteInput, Button, Checkbox, DatePicker, DatetimeInput,
EditableFloatSlider, EditableRangeSlider, LiteralInput, NumberInput,
RangeSlider,
)
Expand Down Expand Up @@ -392,7 +392,7 @@ class Test(param.Parameterized):
model = test_pane.get_root(document, comm=comm)

button = model.children[1]
assert isinstance(button, Button)
assert isinstance(button, BkButton)

# Check that the action is actually executed
pn_button = test_pane.layout[1]
Expand Down Expand Up @@ -1920,3 +1920,48 @@ async def function(value):
assert root.children[0].text == '&lt;p&gt;5&lt;/p&gt;\n'
await asyncio.sleep(0.1)
assert root.children[0].text == '&lt;p&gt;6&lt;/p&gt;\n'


def test_skip_param(document, comm):
checkbox = Checkbox(value=False)
button = Button()

def layout(value, click):
if not click:
raise Skip()
return Markdown(f"{value}")

layout = ParamFunction(bind(layout, checkbox, button))

root = layout.get_root(document, comm)

div = root.children[0]
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'
checkbox.value = True
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'
button.param.trigger('value')
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'

@pytest.mark.asyncio
async def test_async_skip_param(document, comm):
checkbox = Checkbox(value=False)
button = Button()

async def layout(value, click):
if not click:
raise Skip()
return Markdown(f"{value}")

layout = ParamFunction(bind(layout, checkbox, button))

root = layout.get_root(document, comm)

div = root.children[0]
await asyncio.sleep(0.01)
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'
checkbox.value = True
await asyncio.sleep(0.01)
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'
button.param.trigger('value')
await asyncio.sleep(0.01)
assert div.text == '&lt;pre&gt; &lt;/pre&gt;'
Loading