Repository: bokeh/bokeh

Issue:
Defining a JS callback using Python for Python 2.x
Since Flexx is currently  only available for Python 3.x, The CustomJS as Python functionality is only available on Python 3.x. Tasks needed to fix this:
- [x] Make Flexx' code work on Python 2.7 (https://github.com/zoofIO/flexx/pull/79)
- [x] Make Flexx' commonast module produce correct code using the ast module of Python 2.7
- [x] Make Flexx' test suite pass on Python 2.7
- [x] Update code in Bokeh that warns on 2.x
- [x] Update Bokeh's docs
- [x] Update pyscript to use JS-specific namespace (https://github.com/zoofIO/flexx/issues/76)
- [x] Update pyscript to use bound methods (avoid `that = this` stuff)
- [x] Flexx new release
- [ ] ~~Create conda package for Flexx~~
- [x] Change how legacy py is supported, to an approach that does not require translation, so we can make a noarch package.
- [x] Flexx release again (v0.3.1)
- [ ] Create noarch conda package for Flexx in the conda channel



Discussion:

Resolving patch:
diff --git a/bokeh/models/callbacks.py b/bokeh/models/callbacks.py
--- a/bokeh/models/callbacks.py
+++ b/bokeh/models/callbacks.py
@@ -5,11 +5,13 @@
 import sys
 from types import FunctionType
 from ..model import Model
+from ..util.dependencies import import_required
 
 from ..core.properties import abstract
 from ..core.properties import Dict, Instance, String, Enum
 from ..core.enums import ScriptingLanguage
 
+
 @abstract
 class Callback(Model):
     """ Base class for interactive callback. ``Callback`` is generally
@@ -35,22 +37,17 @@ def from_py_func(cls, func):
         """
         if not isinstance(func, FunctionType):
             raise ValueError('CustomJS.from_py_func needs function object.')
-        try:
-            from flexx.pyscript import py2js
-        except ImportError:
-            if sys.version_info < (3, ):
-                raise RuntimeError('Using Python functions for CustomJS '
-                                    'is currently not supported on Python 2.x')
-            else:
-                raise RuntimeError('To use Python functions for CustomJS, you need Flexx '
+        pyscript = import_required('flexx.pyscript',
+                                   'To use Python functions for CustomJS, you need Flexx ' +
                                    '("conda install -c bokeh flexx" or "pip install flexx")')
         # Collect default values
         default_values = func.__defaults__  # Python 2.6+
         default_names = func.__code__.co_varnames[:len(default_values)]
         args = dict(zip(default_names, default_values))
+        args.pop('window', None)  # Clear window, so we use the global window object
         # Get JS code, we could rip out the function def, or just
         # call the function. We do the latter.
-        code = py2js(func, 'cb') + 'cb(%s);\n' % ', '.join(default_names)
+        code = pyscript.py2js(func, 'cb') + 'cb(%s);\n' % ', '.join(default_names)
         return cls(code=code, args=args, lang='javascript')
 
     args = Dict(String, Instance(Model), help="""
diff --git a/examples/plotting/file/python_callback.py b/examples/plotting/file/python_callback.py
--- a/examples/plotting/file/python_callback.py
+++ b/examples/plotting/file/python_callback.py
@@ -2,9 +2,7 @@
 which is transpiled to JavaScript using PyScript. Read more on PyScript
 here:
 
-    http://flexx.readthedocs.org/en/latest/pyscript/index.html
-
-NOTE: This feature is currently only supported in Python 3
+    http://flexx.readthedocs.org/en/stable/pyscript
 
 """
 
@@ -23,13 +21,13 @@
 
 plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
 
-def callback(source=source):
+def callback(source=source, window=None):
     data = source.get('data')
     A, B = amp.get('value'), offset.get('value')
     k, phi = freq.get('value'), phase.get('value')
     x, y = data['x'], data['y']
     for i in range(len(x)):
-        y[i] = B + A * Math.sin(k * x[i] + phi)
+        y[i] = B + A * window.Math.sin(k * x[i] + phi)
     source.trigger('change')
 
 # turn our function into a CustomJS object.
diff --git a/sphinx/source/docs/user_guide/source_examples/interaction_callbacks_in_python.py b/sphinx/source/docs/user_guide/source_examples/interaction_callbacks_in_python.py
--- a/sphinx/source/docs/user_guide/source_examples/interaction_callbacks_in_python.py
+++ b/sphinx/source/docs/user_guide/source_examples/interaction_callbacks_in_python.py
@@ -12,12 +12,12 @@
 plot = Figure(plot_width=400, plot_height=400)
 plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
 
-def callback(source=source):
+def callback(source=source, window=None):
     data = source.get('data')
     f = cb_obj.get('value')
     x, y = data['x'], data['y']
     for i in range(len(x)):
-        y[i] = Math.pow(x[i], f)
+        y[i] = window.Math.pow(x[i], f)
     source.trigger('change');
 
 slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", 


Test patch:
diff --git a/bokeh/models/tests/test_callbacks.py b/bokeh/models/tests/test_callbacks.py
--- a/bokeh/models/tests/test_callbacks.py
+++ b/bokeh/models/tests/test_callbacks.py
@@ -1,7 +1,7 @@
 
 import sys
 
-from pytest import raises, mark
+from pytest import raises
 from bokeh.models import CustomJS, Slider
 
 
@@ -21,7 +21,6 @@ def test_js_callback():
         CustomJS(code="foo();", x=slider)
 
 
-@mark.skipif(sys.version_info < (3, ), reason='requires Python 3')
 def test_py_callback():
     
     slider = Slider()
diff --git a/tests/travis/test.yaml b/tests/travis/test.yaml
--- a/tests/travis/test.yaml
+++ b/tests/travis/test.yaml
@@ -8,7 +8,7 @@
 - path: "plotting/file"
   type: file
   skip: ["image.py", "image_rgba.py", "choropleth.py", "iris_blend.py",
-         "scatter10k.py", "clustering.py", "line10k.py", "python_callback.py"]
+         "scatter10k.py", "clustering.py", "line10k.py"]
 - path: "plotting/server"
   type: server
 - path: "plotting/notebook"