diff --git a/.travis.yml b/.travis.yml index 23e9f5b..809f309 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,14 +25,14 @@ jobs: env: PYTHON_VERSION="2.7" install: - source ./etc/travis-miniconda.sh - - conda create -n test-environment python=$PYTHON_VERSION + - conda create -q -n test-environment python=$PYTHON_VERSION - source activate test-environment - - conda install param - - conda install -c bokeh "bokeh>=0.12.10" + - conda install -q param + - conda install -q -c bokeh "bokeh>=0.12.10" # dependencies for testing - pip install pytest pytest-nbsmoke - - conda install -c ioam "holoviews>=1.9.0" - - conda install pandas notebook flake8 pyparsing + - conda install -q -c ioam "holoviews>=1.9.0" + - conda install -q pandas notebook flake8 pyparsing - python setup.py develop --no-deps - conda env export - conda list @@ -50,13 +50,13 @@ jobs: # TODO: could (build and) use conda package; to be cleaned up # once auto versioning/package building is finalized - source ./etc/travis-miniconda.sh - - conda create -n test-environment python=$PYTHON_VERSION + - conda create -q -n test-environment python=$PYTHON_VERSION - source activate test-environment - - conda install param - - conda install pandas - - conda install -c bokeh "bokeh>=0.12.10" - - conda install -c ioam "holoviews>=1.9.0" - - conda install -c conda-forge notebook ipython sphinx beautifulsoup4 graphviz selenium phantomjs + - conda install -q param + - conda install -q pandas + - conda install -q -c bokeh "bokeh>=0.12.10" + - conda install -q -c ioam "holoviews>=1.9.0" + - conda install -q -c conda-forge notebook ipython sphinx beautifulsoup4 graphviz selenium phantomjs - pip install nbsite sphinx_ioam_theme - pip install -e . script: @@ -87,8 +87,8 @@ jobs: install: - source ./etc/travis-miniconda.sh # for building and uploading packages - - conda install conda-build=3.0.25 - - conda install anaconda-client + - conda install -q conda-build=3.0.25 + - conda install -q anaconda-client script: - conda build conda.recipe - anaconda -t $CONDA_UPLOAD_TOKEN upload --force -u cball $HOME/miniconda/conda-bld/noarch/parambokeh*.tar.bz2 diff --git a/examples/user_guide/Bokeh_App.ipynb b/examples/user_guide/Bokeh_App.ipynb index 4a20a68..30343c5 100644 --- a/examples/user_guide/Bokeh_App.ipynb +++ b/examples/user_guide/Bokeh_App.ipynb @@ -19,12 +19,11 @@ "\n", "import param\n", "import datetime as dt\n", - "\n", - "def hello(x, **kwargs):\n", - " print(\"Hello %s\" % x)\n", " \n", "class Example(param.Parameterized):\n", " \"\"\"An example Parameterized class\"\"\"\n", + " timestamps = []\n", + " \n", " x = param.Parameter(default=3.14,doc=\"X position\")\n", " y = param.Parameter(default=\"Not editable\",constant=True)\n", " string_value = param.String(default=\"str\",doc=\"A string\")\n", @@ -46,7 +45,8 @@ " int_list = param.ListSelector(default=[3,5], objects=[1,3,5,7,9],precedence=0.5)\n", " single_file = param.FileSelector(path='../../*/*.py*',precedence=0.5)\n", " multiple_files = param.MultiFileSelector(path='../../*/*.py?',precedence=0.5)\n", - " #msg = param.Action(hello, doc=\"\"\"Print a message.\"\"\",precedence=0.7)\n" + " record_timestamp = param.Action(lambda x: x.timestamps.append(dt.datetime.now()), \n", + " doc=\"\"\"Record timestamp.\"\"\",precedence=0.7)\n" ] }, { diff --git a/examples/user_guide/Introduction.ipynb b/examples/user_guide/Introduction.ipynb index 1008c75..8dbb4cf 100644 --- a/examples/user_guide/Introduction.ipynb +++ b/examples/user_guide/Introduction.ipynb @@ -20,9 +20,6 @@ "import param\n", "import datetime as dt\n", "\n", - "def hello(x, **kwargs):\n", - " print(\"Hello %s\" % x)\n", - " \n", "class BaseClass(param.Parameterized):\n", " x = param.Parameter(default=3.14,doc=\"X position\")\n", " y = param.Parameter(default=\"Not editable\",constant=True)\n", @@ -39,6 +36,8 @@ " \n", "class Example(BaseClass):\n", " \"\"\"An example Parameterized class\"\"\"\n", + " timestamps = []\n", + "\n", " boolean = param.Boolean(True, doc=\"A sample Boolean parameter\")\n", " color = param.Color(default='#FFFFFF')\n", " date = param.Date(dt.datetime(2017, 1, 1),\n", @@ -48,7 +47,8 @@ " int_list = param.ListSelector(default=[3,5], objects=[1,3,5,7,9],precedence=0.5)\n", " single_file = param.FileSelector(path='../../*/*.py*',precedence=0.5)\n", " multiple_files = param.MultiFileSelector(path='../../*/*.py?',precedence=0.5)\n", - " #msg = param.Action(hello, doc=\"\"\"Print a message.\"\"\",precedence=0.7)\n", + " record_timestamp = param.Action(lambda x: x.timestamps.append(dt.datetime.now()), \n", + " doc=\"\"\"Record timestamp.\"\"\",precedence=0.7)\n", " \n", "Example.num_int" ] @@ -124,6 +124,22 @@ "Example.num_int" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Example.timestamps records the times you pressed the \"record timestamp\" button." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Example.timestamps" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/parambokeh/__init__.py b/parambokeh/__init__.py index d2c41dc..69a3b63 100644 --- a/parambokeh/__init__.py +++ b/parambokeh/__init__.py @@ -174,6 +174,9 @@ def __call__(self, parameterized, doc=None, plots=[], **params): def on_msg(self, msg): p_name = msg['p_name'] p_obj = self.parameterized.params(p_name) + if isinstance(p_obj, param.Action): + getattr(self.parameterized, p_name)(self.parameterized) + return w = self._widgets[p_name] self._queue.append((w, p_obj, p_name, None, None, msg['value'])) self.change_event() @@ -258,10 +261,6 @@ def _make_widget(self, p_name): value = getattr(self.parameterized, p_name) kw = dict(value=value) - if isinstance(p_obj, param.Action): - def action_cb(button): - getattr(self.parameterized, p_name)(self.parameterized) - kw['value'] = action_cb kw['title'] = p_name @@ -289,12 +288,17 @@ def action_cb(button): if hasattr(p_obj, 'callbacks'): p_obj.callbacks[id(self.parameterized)] = functools.partial(self._update_trait, p_name) - elif isinstance(w, (Button, Toggle)): + elif isinstance(w, Toggle): if self.p.mode in ['server', 'raw']: w.on_change('active', functools.partial(self.on_change, w, p_obj, p_name)) else: js_callback = self._get_customjs('active', p_name) w.js_on_change('active', js_callback) + elif isinstance(w, Button): + if self.p.mode in ['server', 'raw']: + w.on_click(functools.partial(value,self.parameterized)) + else: + w.js_on_click(self._get_customjs('active', p_name)) elif not p_obj.constant: if self.p.mode in ['server', 'raw']: cb = functools.partial(self.on_change, w, p_obj, p_name) diff --git a/parambokeh/comms.py b/parambokeh/comms.py index 085aafa..e1017b1 100644 --- a/parambokeh/comms.py +++ b/parambokeh/comms.py @@ -225,6 +225,10 @@ def _handle_msg(self, msg): with StandardOutput() as stdout: self._on_msg(msg) except Exception as e: + # TODO: isn't this cutting out info needed to understand what's gone wrong? + # Since it's only going to the js console, maybe we could just show everything + # (error = traceback.format_exc() or something like that)? Separately we do need a mechanism + # to report reasonable messages to users, though. frame =traceback.extract_tb(sys.exc_info()[2])[-2] fname,lineno,fn,text = frame error_kwargs = dict(type=type(e).__name__, fn=fn, fname=fname, diff --git a/parambokeh/widgets.py b/parambokeh/widgets.py index 2e799ed..0246d05 100644 --- a/parambokeh/widgets.py +++ b/parambokeh/widgets.py @@ -31,10 +31,8 @@ def ToggleWidget(*args, **kw): def ButtonWidget(*args, **kw): kw['label'] = kw.pop('title') - cb = kw.pop('value') - button = Button(*args, **kw) - button.on_click(cb) - return button + kw.pop('value') # button doesn't have value (value attached as click callback) + return Button(*args, **kw) # TODO: make a composite box/slider widget; slider only appears if # there's a range.