Repository: vispy/vispy

Issue:
automatically launch vispy in webgl mode if in notebook
This could be a nice usability improvement, to have vispy automatically pickup the webgl back end if its running in a notebook.

AFAIK, this is possible to detect since ipython provides a config object that can be used to query status.

If there's a consensus on this, I'll try and write a pull request for this 



Discussion:
That would be nice. You can use this:

``` python
def in_ipython():
    try:
        __IPYTHON__
    except NameError:
        return False
    else:
        return True
```

I don't think that's enough: that only tells you if you're in `ipython` or not, not if you're in the notebook. For that you'd need to load the cfg object that ipython provides I guess

-1: I often use desktop backends in the notebook, to have multiple popups
also the webgl backend is too experimental at this point to be enabled by default
finally, the kernel doesn't know whether you are in a notebook, and you'd need some ugly hacks to detect that

i'd rather have an explicit `enable_notebook()` function, like in matplotlib, mpld3, and all other plotting libraries that work in the notebook

@rossant we basically have that already with `use_app('ipynb_webgl')`, right?

yes, i imagine we could save a few characters and improve tab completion discovery with such a function
and we might want to enable other stuff in the notebook later

@Eric89GXL yes, but that's not very nice to use, right? API wise :) It's too much internal detail (what is an "app"? why do I need to invoke "webgl"? stuff like that)

There should be one (preferably obvious) way of doing things as much as
possible. In this case maybe it's not obvious. At the same time I'd rather
not increase the number of ways. So my problem with this is, say someone
wants to use the Pyglet or SDL backend, what function should they use?
Should we make separate functions for those?

Maybe we can rename a function, or its arguments, or move it to a different
namespace to make things clearer. Can you come up with an API that would be
more intuitive in light of these issues?

`enable_notebook()` could enable not only the app backend but also things like JavaScript modules etc, so it would not be equivalent to just `use_app`.

The notebook backend is quite different from all the other backends (desktop) so I don't see a problem with that.

I agree that functionally it operates differently (remote communication etc.), but we have actually constructed it to be as equivalent as possible to other backends in terms of the experience for end users. FWIW I see this as an advantage. Sure the backend does more complex processing, but all we have to do to use it is `use_app('ipynb_webgl')` to use it. If and when we needed more backend-specific options, they could go in the `use_app` function, especially since other backends might also need extensions at some point. Actually such options would probably go in the Canvas constructor, but we can cross that bridge later...

To put it slightly differently, consider what would happen if we added some other backend that we think of as being "different" from our desktop ones. Maybe we find a way to embed our plots in a matplotlib axes instance, do we use `enable_matplotlib()`? We have talked about using GLFW as our main backend -- what if `qt` adds some cool remote features that we want to incorporate, do we add an `enable_qt_remote()` function? I don't find this as simple as the current solution, even if the current naming is bad. I think of all of these as being particular instances of instantiating an application to use, even if what they do under the hood is quite different.

On one hand I agree with Eric, but I also recognise that users are going to forget (and thus having to look up) the "ipynb_webgl" name (at least I do). For desktop use, people actually do not need it in general, because Vispy is smart enough to select a good default app, this is not so much the case for the notebook (because the IPython folks deliberately made it hard to detect whether you're running in a notebook). Therefore, in this case I think practicality beats purity, and I would be in favour of a `enable_notebook()` function, that simply calls `use_app()`.

what if we changed the name to `use_backend` or `enable_backend()`, then the name would be given in the docstring? or maybe we should just change the name of the backend to `notebook` (we can keep an alias to the old one if we want) so it would just be `enable_backend('notebook')`? That to me is almost the same and prevents future namespace pollution.

Have you thought of creating an IPython extension instead? One that automatically switches backends and tunes whatever else might be necessary. It would work something like [holoviews](https://ioam.github.io/holoviews/) and should be familiar to any IPython user (e.g., `%load_ext vispy.ipython`). (Note: I am basing my understanding of the holoviews extension on the one line that appears at the beginning of the linked page, but that seems reasonable.)

+1 for that solution, it seems like the nicest way to integrate. For now I guess all it would do is call `use_app`. Any takers for a PR? @bollu if you have time?

@Eric89GXL I'm not sure as to what the plumbing needed for that is. I'll definitely take a look and outline what you need for this though. 

@QuLogic do we need a extension or a magic command?

Like, matplotlib is just `%matplotlib` [like so](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-matplotlib), which is considered a "magic function", that is given access to `iPython's` context when it is called.

However, an extension is way more extensive and gives close to complete control over the iPython shell. [docs for extension](http://ipython.org/ipython-doc/dev/config/extensions/#ipython-extensions).

I'm guessing we would want it to be an extension for maximum future flexibility, but I still felt that this could be argued upon. 

I'm not sure you can create a magic command without installing an extension in the first place, but I didn't look too deeply into that side of it.

@QuLogic if you load a module, it works. So I'm assuming somewhere in the pipeline of vispy, we check if we're running inside ipython, and then load the custom magic command module. [the docs are here](https://ipython.org/ipython-doc/dev/config/custommagics.html)

I think the extension is the way to go

Le dimanche 7 juin 2015, Siddharth notifications@github.com a écrit :

> @QuLogic https://github.com/QuLogic if you load a module, it works. So
> I'm assuming somewhere in the pipeline of vispy, we check if we're running
> inside ipython, and then load the custom magic command module. the docs
> are here https://ipython.org/ipython-doc/dev/config/custommagics.html
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/vispy/vispy/issues/977#issuecomment-109654568.

@rossant very well. I'll try and get a pull request (WIP) that's a stub in an hour or so. We can move the discussion there, agreed?


Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -134,5 +134,6 @@ def package_tree(pkgroot):
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3.3',
         'Programming Language :: Python :: 3.4',
+        'Framework :: IPython'
     ],
 )
diff --git a/vispy/ipython/__init__.py b/vispy/ipython/__init__.py
new file mode 100644
--- /dev/null
+++ b/vispy/ipython/__init__.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""
+This module provides the binding between Vispy and IPython in the form of
+an IPython extension
+"""
+
+# load the two functions that IPython uses to instantiate an extension
+# that way, the user only needs to run %load_ext vispy.ipython rather that
+# %load_ext vispy.ipython.ipython
+from .ipython import load_ipython_extension, unload_ipython_extension  # noqa
diff --git a/vispy/ipython/ipython.py b/vispy/ipython/ipython.py
new file mode 100644
--- /dev/null
+++ b/vispy/ipython/ipython.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""Entry point for vispy's IPython bindings"""
+
+from distutils.version import LooseVersion
+
+
+def load_ipython_extension(ipython):
+    """ Entry point of the IPython extension
+
+    Parameters
+    ----------
+
+    IPython : IPython interpreter
+        An instance of the IPython interpreter that is handed
+        over to the extension
+    """
+    import IPython
+
+    # don't continue if IPython version is < 3.0
+    ipy_version = LooseVersion(IPython.__version__)
+    if ipy_version < LooseVersion("3.0.0"):
+        ipython.write_err("Your IPython version is older than "
+                          "version 3.0.0, the minimum for Vispy's"
+                          "IPython backend. Please upgrade your IPython"
+                          "version.")
+        return
+
+    _load_webgl_backend(ipython)
+
+
+def _load_webgl_backend(ipython):
+    """ Load the webgl backend for the IPython notebook"""
+
+    from vispy import app
+    app_instance = app.use_app("ipynb_webgl")
+
+    if app_instance.backend_name == "ipynb_webgl":
+        ipython.write("Vispy IPython module has loaded successfully")
+    else:
+        # TODO: Improve this error message
+        ipython.write_err("Unable to load webgl backend of Vispy")
+
+
+def unload_ipython_extension(ipython):
+    """ Unload the IPython extension
+    """
+    pass


Test patch:
diff --git a/vispy/app/tests/test_ipython.py b/vispy/app/tests/test_ipython.py
new file mode 100644
--- /dev/null
+++ b/vispy/app/tests/test_ipython.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+"""Tests for Vispy's IPython bindings"""
+
+# from vispy import IPython
+from numpy.testing import assert_equal, assert_raises
+from vispy.testing import requires_ipython
+
+
+@requires_ipython(2.0)
+def test_webgl_loading():
+    """Test if the vispy.ipython extension loads the webGL backend
+    on IPython 3.0 and greater.
+
+    Test if it fails to load the webGL backend for IPython versions
+    less that 3.0"""
+
+    import IPython
+    from distutils.version import LooseVersion
+    from IPython.testing.globalipapp import get_ipython
+
+    ipy = get_ipython()
+    ipy.run_cell("from vispy import app")
+
+    if LooseVersion(IPython.__version__) >= LooseVersion("3.0.0"):
+        ipy.run_cell("%load_ext vispy.ipython")
+        ipy.run_cell("backend_name = app.use_app().backend_name")
+        # make sure that the webgl backend got loaded
+        assert_equal(ipy.user_ns["backend_name"], "ipynb_webgl")
+    else:
+        ipy.run_cell("%load_ext vispy.ipython")
+        ipy.run_cell("backend_name = app.use_app().backend_name")
+
+        # the above call should have failed, and thus the key
+        # backend_name should not exist in the namespace
+
+        def invalid_backend_access(ipy):
+            ipy.user_ns["backend_name"]
+
+        assert_raises(KeyError, invalid_backend_access, ipy)
diff --git a/vispy/testing/__init__.py b/vispy/testing/__init__.py
--- a/vispy/testing/__init__.py
+++ b/vispy/testing/__init__.py
@@ -40,7 +40,8 @@
 to determine what automatic tests are run.
 """
 
-from ._testing import (SkipTest, requires_application, requires_img_lib,  # noqa
+from ._testing import (SkipTest, requires_application, requires_ipython,  # noqa
+                      requires_img_lib,  # noqa
                       has_backend, requires_pyopengl,  # noqa
                       requires_scipy, has_matplotlib,  # noqa
                       save_testing_image, TestingCanvas, has_pyopengl,  # noqa
diff --git a/vispy/testing/_testing.py b/vispy/testing/_testing.py
--- a/vispy/testing/_testing.py
+++ b/vispy/testing/_testing.py
@@ -242,6 +242,34 @@ def requires_img_lib():
     return np.testing.dec.skipif(not has_img_lib, 'imageio or PIL required')
 
 
+def has_ipython(version='3.0'):
+    """function that checks the presence of IPython"""
+
+    # typecast version to a string, in case an integer is given
+    version = str(version)
+
+    try:
+        import IPython  # noqa
+    except Exception:
+        return False, "IPython library not found"
+    else:
+        if LooseVersion(IPython.__version__) >= LooseVersion(version):
+            return True, "IPython present"
+        else:
+            message = (
+                "current IPython version: (%s) is "
+                "older than expected version: (%s)") % \
+                (IPython.__version__, version)
+
+            return False, message
+
+
+def requires_ipython(version='3.0'):
+    ipython_present, message = has_ipython(version)
+
+    return np.testing.dec.skipif(not ipython_present, message)
+
+
 def has_matplotlib(version='1.2'):
     """Determine if mpl is a usable version"""
     try: