Skip to content
This repository has been archived by the owner on Nov 28, 2019. It is now read-only.

Added registry of 'watchers', allowing functions to be executed when … #45

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
151 changes: 151 additions & 0 deletions doc/WidgetLinking.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import param\n",
"import paramnb"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# we'd add get_soft_range() to relevant parameters in param\n",
"class ObjectSelector2(param.ObjectSelector):\n",
" __slots__ = ['softbounds']\n",
" def __init__(self,default=None,objects=None,instantiate=False,\n",
" compute_default_fn=None,check_on_set=None,allow_None=None,softbounds=None,**params): \n",
" self.softbounds=softbounds if softbounds is not None else objects \n",
" super(ObjectSelector2,self).__init__(\n",
" default=default,objects=objects,instantiate=instantiate,\n",
" compute_default_fn=compute_default_fn,check_on_set=check_on_set,allow_None=allow_None,**params)\n",
" \n",
" def get_soft_range(self):\n",
" return param.named_objs(self.softbounds)\n",
"\n",
"param.ObjectSelector2 = ObjectSelector2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class SomeManagement(param.Parameterized):\n",
" employee = param.ObjectSelector2(default='Person Z',objects=['Person X','Person Y','Person Z'])\n",
" project = param.ObjectSelector2(default='A',objects=['A','B','C','D','E','F','G','H'])\n",
" days = param.Integer(default=1,softbounds=(0,10),precedence=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"m = SomeManagement()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"capabilities = { \n",
" 'Person X' : ('A','B','C','D'),\n",
" 'Person Y' : ('A','B','E','F'),\n",
" 'Person Z' : ('G','H'),\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"speed = {\n",
" 'Person X' : 10,\n",
" 'Person Y' : 20,\n",
" 'Person Z' : 30,\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def update_days(parameterized):\n",
" parameterized.params('days').softbounds = (0,speed[parameterized.employee])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def update_projects(parameterized):\n",
" parameterized.params('project').softbounds = capabilities[parameterized.employee]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"paramnb.Widgets(m, watchers={'employee':(update_projects,update_days)})"
]
}
],
"metadata": {
"anaconda-cloud": {},
"hide_input": false,
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.0"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
50 changes: 46 additions & 4 deletions paramnb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ class Widgets(param.ParameterizedFunction):
If true, will continuously update the next_n and/or callback,
if any, as a slider widget is dragged.""")

watchers = param.Dict(default={}, doc="""
Registry of functions to call whenever a parameter's widget value
is changed.

Functions will be passed the parameterized object.

Dictionary of lists :(
""")

def __call__(self, parameterized, **params):
self.p = param.ParamOverrides(self, params)
if self.p.initializer:
Expand Down Expand Up @@ -163,6 +172,7 @@ def __call__(self, parameterized, **params):

if self.p.on_init:
self.execute()
self._execute_watchers()


def _update_trait(self, p_name, p_value, widget=None):
Expand Down Expand Up @@ -232,10 +242,13 @@ def change_event(event):
# Style widget to denote error state
apply_error_style(w, error)

if not error and not self.p.button:
self.execute({p_name: new_values})
else:
self._changed[p_name] = new_values
if not error:
self._execute_watchers({p_name: new_values})
if not self.p.button:
self.execute({p_name: new_values})
else:
self._changed[p_name] = new_values


if hasattr(p_obj, 'callbacks'):
p_obj.callbacks[id(self.parameterized)] = functools.partial(self._update_trait, p_name)
Expand Down Expand Up @@ -277,6 +290,35 @@ def widget(self, param_name):
return self._widgets[param_name]


def _execute_watchers(self, changed=None):
param_values = dict(self.parameterized.get_param_values())
del param_values['name']

if changed is None:
changed = param_values # assume all values have changed
else:
param_values.update(changed)

for param in changed:
for fn in self.p.watchers.get(param,[]):
fn(self.parameterized)

for param in param_values:
p_obj = self.parameterized.params(param)

# TODO: as suggested by Philipp, should factor out
# converting parameter attributes to ipywidget attributes
# e.g. have a registry of functions ;) or more likely some
# kind of update method that's used during creation and
# here.
if hasattr(p_obj, 'get_soft_range'):
self._widgets[param].options = p_obj.get_soft_range()
elif hasattr(p_obj, 'get_soft_bounds'):
min,max=p_obj.get_soft_bounds()
self._widgets[param].min = min
self._widgets[param].max = max


def execute(self, changed={}):
run_next_cells(self.p.next_n)
if self.p.callback is not None:
Expand Down