Repository: bokeh/bokeh

Issue:
Can't import bokeh when resources aren't available
Even `bokeh.min.js` is required (fails with `IOError`).



Discussion:
@mattpap What is the use case for "fixing" this? Even if a particular plot is configured to use resources from CDN and not `server/static/js`, local resources should always be available in a properly set up install or build. Missing local resources means your install is broken, an error seems appropriate. Maybe a different exception?

Alternatively, as a compromise:

```
try:
    INLINE = Resources(mode="inline")
except IOError:
    warnings.warn("Local resources appear to be missing, INLINE not available")
```

Mostly development, but I can imagine production system that is used only to generate JSON and thus no static files are required. Also, I rarely actually need those files when running bokeh, because I rarely use inline mode. bokeh is a library and thus we should allow it to work without such dependencies. If `Resources` was made lazy, then you would get error when you actually needed `INLINE`, not at initialization time.

> Alternatively, as a compromise:

That wouldn't import in examples.

Why not?

So, a make INLINE a `ResourceProxy` that only creates a given resource on first `__getattr__` and subsequently forwards all access to it? Or actually wrap the module in a class to override its `__getattr__`? 

> Why not?

No `INLINE` name in the case of missing resources, unless you set inline to `None`, but that still would be unhelpful. Yes, I would like to run examples in the absence of resources.

> So, a make INLINE a ResourceProxy that only creates a given resource on first (...)

Whatever works. In Scala I would just say `lazy val INLINE = ...` and forgot about this issue.


Resolving patch:
diff --git a/bokeh/resources.py b/bokeh/resources.py
--- a/bokeh/resources.py
+++ b/bokeh/resources.py
@@ -15,6 +15,8 @@
 import logging
 logger = logging.getLogger(__name__)
 
+import six
+
 from . import __version__, settings
 
 def _server_static_dir():
@@ -139,11 +141,11 @@ def __init__(self, mode='inline', version=None, root_dir=None,
         if root_url and not root_url.endswith("/"):
             logger.warning("root_url should end with a /, adding one")
             root_url = root_url + "/"
-                                 
+
         self._root_url = root_url
 
         if mode not in ['inline', 'cdn', 'server', 'server-dev', 'relative', 'relative-dev', 'absolute', 'absolute-dev']:
-            raise ValueError("wrong value for 'mode' parameter, expected 'inline', 'cdn', 'server', 'server-dev', 'relative(-dev)' or 'absolute(-dev)', got %r" % self.mode)
+            raise ValueError("wrong value for 'mode' parameter, expected 'inline', 'cdn', 'server(-dev)', 'relative(-dev)' or 'absolute(-dev)', got %r" % self.mode)
 
         if self.root_dir and not mode.startswith("relative"):
             raise ValueError("setting 'root_dir' makes sense only when 'mode' is set to 'relative'")
@@ -162,15 +164,15 @@ def __init__(self, mode='inline', version=None, root_dir=None,
         css_paths = self._css_paths(dev=self.dev, minified=self.minified)
         base_url = _static_path("js")
 
-        self.js_raw = []
-        self.css_raw = []
+        self._js_raw = []
+        self._css_raw = []
         self.js_files = []
         self.css_files = []
         self.messages = []
 
         if self.mode == "inline":
-            self.js_raw = _inline(js_paths)
-            self.css_raw = _inline(css_paths)
+            self._js_raw = lambda: _inline(js_paths)
+            self._css_raw = lambda: _inline(css_paths)
         elif self.mode == "relative":
             root_dir = self.root_dir or self._default_root_dir
             self.js_files = [ relpath(p, root_dir) for p in js_paths ]
@@ -192,7 +194,19 @@ def __init__(self, mode='inline', version=None, root_dir=None,
 
         if self.dev:
             require = 'require.config({ baseUrl: "%s" });' % base_url
-            self.js_raw.append(require)
+            self._js_raw.append(require)
+
+    @property
+    def js_raw(self):
+        if six.callable(self._js_raw):
+            self._js_raw = self._js_raw()
+        return self._js_raw
+
+    @property
+    def css_raw(self):
+        if six.callable(self._css_raw):
+            self._css_raw = self._css_raw()
+        return self._css_raw
 
     @property
     def root_url(self):
@@ -234,9 +248,3 @@ def _autoload_path(self, elementid):
 CDN = Resources(mode="cdn")
 
 INLINE = Resources(mode="inline")
-
-
-
-
-
-


Test patch: