Repository: bokeh/bokeh

Issue:
FuncTickFormatter.from_py_func() example ValueError
I am trying the FuncTickFormatter.from_py_func() example and I get the error
`ValueError: Function 'func' may only contain keyword arguments.`

A MWE that produces this error is:

```
from bokeh.models import FuncTickFormatter
from bokeh.plotting import figure, show
import math

def ticker(tick):
    return math.floor(tick) + " + " + (tick % 1).toFixed(2)

p = figure(plot_width=400, plot_height=300)

p.yaxis.formatter = FuncTickFormatter.from_py_func(ticker)

p.circle([2, 4, 6], [1, 3, 5])

show(p)
```

I am running Bokeh version 0.12.3 and have flexx installed.



Discussion:
I'm not sure whether this'll work with `from_py_func` - have you seen an example of it working somewhere. ping @canavandl.

This is actually based on the example given in the Bokeh user guide http://bokeh.pydata.org/en/latest/docs/user_guide/styling.html#functickformatter

I should add that the error still occurs when the "math" for the returned tick value in the ticker function isn't so complicated. For example `return tick + 1` results in the same error.

Wow! I actually went to that example, but missed the `from_py_func` underneath.....tired eyes.

Those particular docs examples aren't spitting out plots so it seems like they're broken.

Thanks for the report.


Resolving patch:
diff --git a/bokeh/models/formatters.py b/bokeh/models/formatters.py
--- a/bokeh/models/formatters.py
+++ b/bokeh/models/formatters.py
@@ -215,11 +215,23 @@ class FuncTickFormatter(TickFormatter):
     @classmethod
     def from_py_func(cls, func):
         """ Create a FuncTickFormatter instance from a Python function. The
-        function is translated to Javascript using PyScript.
+        function is translated to JavaScript using PyScript. The variable
+        ``tick`` will contain the unformatted tick value and can be expected to
+        be present in the function namespace at render time.
+
+        Example:
+
+        .. code-block:: python
+
+            code = '''
+                def ticker():
+                    return "{:.0f} + {:.2f}".format(tick, tick % 1)
+            '''
+
+        The python function must have no positional arguments. It's
+        possible to pass Bokeh models (e.g. a ColumnDataSource) as keyword
+        arguments to the function.
 
-        .. warning::
-            The function can have only a single positional argument and return
-            a single value.
         """
         if not isinstance(func, FunctionType):
             raise ValueError('CustomJS.from_py_func needs function object.')
@@ -241,12 +253,25 @@ def from_py_func(cls, func):
 
         # Wrap the code attr in a function named `formatter` and call it
         # with arguments that match the `args` attr
-        code = pyscript.py2js(func, 'formatter') + 'formatter(%s);\n' % ', '.join(default_names)
+        code = pyscript.py2js(func, 'formatter') + 'return formatter(%s);\n' % ', '.join(default_names)
 
         return cls(code=code, args=func_kwargs)
 
     @classmethod
     def from_coffeescript(cls, code, args={}):
+        """ Create a FuncTickFormatter instance from a CoffeeScript snippet.
+        The function body is translated to JavaScript using node. The variable
+        ``tick`` will contain the unformatted tick value and can be expected to
+        be present in the code snippet namespace at render time.
+
+        Example:
+
+        .. code-block:: coffeescript
+
+            code = '''
+                return Math.floor(tick) + " + " + (tick % 1).toFixed(2)
+            '''
+        """
         compiled = nodejs_compile(code, lang="coffeescript", file="???")
         if "error" in compiled:
             raise CompilationError(compiled.error)
@@ -260,24 +285,17 @@ def from_coffeescript(cls, code, args={}):
     """)
 
     code = String(default="", help="""
-    An anonymous JavaScript function expression to reformat a
-    single tick to the desired format.
+    A snippet of JavaScript code that reformats a single tick to the desired
+    format. The variable ``tick`` will contain the unformatted tick value and
+    can be expected to be present in the code snippet namespace at render time.
 
     Example:
 
-        .. code-block:: python
+        .. code-block:: javascript
 
             code = '''
-            function (tick) {
-                // Convert from minutes to seconds and add units str
-                return tick * 60 + " seconds"
-            };
+                return Math.floor(tick) + " + " + (tick % 1).toFixed(2)
             '''
-
-    .. warning::
-        The function can have only a single positional argument and return
-        a single value.
-
     """)
 
 def DEFAULT_DATETIME_FORMATS():
diff --git a/sphinx/source/docs/user_guide/source_examples/styling_func_tick_formatter.py b/sphinx/source/docs/user_guide/source_examples/styling_func_tick_formatter.py
--- a/sphinx/source/docs/user_guide/source_examples/styling_func_tick_formatter.py
+++ b/sphinx/source/docs/user_guide/source_examples/styling_func_tick_formatter.py
@@ -7,9 +7,7 @@
 p.circle([0, 2, 4, 6, 8, 10], [6, 2, 4, 10, 8, 0], size=30)
 
 p.yaxis.formatter = FuncTickFormatter(code="""
-    function (tick) {
-        return Math.floor(tick) + " + " + (tick % 1).toFixed(2)
-    };
+    return Math.floor(tick) + " + " + (tick % 1).toFixed(2)
 """)
 
 show(p)


Test patch:
diff --git a/bokeh/models/tests/test_formatters.py b/bokeh/models/tests/test_formatters.py
--- a/bokeh/models/tests/test_formatters.py
+++ b/bokeh/models/tests/test_formatters.py
@@ -1,3 +1,5 @@
+from textwrap import dedent
+
 import pytest
 
 from bokeh.models import FuncTickFormatter, Slider
@@ -14,7 +16,7 @@ def convert_to_minutes():
 
     function_wrapper = formatter.code.replace(js_code, '')
 
-    assert function_wrapper == "formatter();\n"
+    assert function_wrapper == "return formatter();\n"
 
 def test_functickformatter_from_py_func_with_args():
 
@@ -28,7 +30,7 @@ def convert_to_minutes(x=slider):
 
     function_wrapper = formatter.code.replace(js_code, '')
 
-    assert function_wrapper == "formatter(x);\n"
+    assert function_wrapper == "return formatter(x);\n"
     assert formatter.args['x'] is slider
 
 def test_functickformatter_bad_pyfunc_formats():
@@ -48,33 +50,33 @@ def has_non_Model_keyword_argument(x=10):
         FuncTickFormatter.from_py_func(has_non_Model_keyword_argument)
 
 def test_functickformatter_from_coffeescript_no_arg():
-    coffee_code = """\
-square = (x) -> x * x
-return square(tick)
-"""
+    coffee_code = dedent("""
+        square = (x) -> x * x
+        return square(tick)
+        """)
 
     formatter = FuncTickFormatter.from_coffeescript(code=coffee_code)
 
-    assert formatter.code == """\
-"use strict";
-var square;
-square = function (x) {
-    return x * x;
-};
-return square(tick);
-"""
+    assert formatter.code == dedent("""\
+        "use strict";
+        var square;
+        square = function (x) {
+            return x * x;
+        };
+        return square(tick);
+        """)
     assert formatter.args == {}
 
 def test_functickformatter_from_coffeescript_with_args():
-    coffee_code = """\
-return slider.get("value") // 2 + tick
-"""
+    coffee_code = dedent("""
+         return slider.get("value") // 2 + tick
+         """)
 
     slider = Slider()
     formatter = FuncTickFormatter.from_coffeescript(code=coffee_code, args={"slider": slider})
 
-    assert formatter.code == """\
-"use strict";
-return Math.floor(slider.get("value") / 2) + tick;
-"""
+    assert formatter.code == dedent("""\
+        "use strict";
+        return Math.floor(slider.get("value") / 2) + tick;
+        """)
     assert formatter.args == {"slider": slider}