Repository: bokeh/bokeh

Issue:
Can't override settings using environmental variables
Let's say I would like to run and debug the output of `examples/models/file/data_tables.py`. This example uses low-level APIs and sets resources explicitly to `INLINE`. To make debugging possible, I usually switch to `server-dev` resources, however this doesn't work anymore:
```sh
$ BOKEH_RESOURCES=server-dev python data_tables.py
Traceback (most recent call last):
  File "data_tables.py", line 73, in <module>
    f.write(file_html(doc, INLINE, "Data Tables"))
  File "/home/mateusz/repo/bokeh/bokeh/embed/standalone.py", line 306, in file_html
    bundle = bundle_for_objs_and_resources([doc], resources)
  File "/home/mateusz/repo/bokeh/bokeh/embed/bundle.py", line 164, in bundle_for_objs_and_resources
    js_files.extend(js_resources.js_files)
  File "/home/mateusz/repo/bokeh/bokeh/resources.py", line 429, in js_files
    files, _ = self._resolve("js")
  File "/home/mateusz/repo/bokeh/bokeh/resources.py", line 348, in _resolve
    raw = [self._inline(path) for path in paths]
  File "/home/mateusz/repo/bokeh/bokeh/resources.py", line 348, in <listcomp>
    raw = [self._inline(path) for path in paths]
  File "/home/mateusz/repo/bokeh/bokeh/resources.py", line 366, in _inline
    with open(path, "rb") as f:
FileNotFoundError: [Errno 2] No such file or directory: '/home/mateusz/repo/bokeh/bokeh/server/static/js/bokeh.js'
```
I have empty `static/` directory on purpose, which just makes the problem more visible. The problem applies to other areas of bokeh as well, e.g. integration testing.


Discussion:

Resolving patch:
diff --git a/bokeh/embed/bundle.py b/bokeh/embed/bundle.py
--- a/bokeh/embed/bundle.py
+++ b/bokeh/embed/bundle.py
@@ -26,7 +26,8 @@
 from ..core.templates import CSS_RESOURCES, JS_RESOURCES
 from ..document.document import Document
 from ..model import Model
-from ..resources import BaseResources
+from ..resources import BaseResources, Resources
+from ..settings import settings
 from ..util.compiler import bundle_models
 
 #-----------------------------------------------------------------------------
@@ -127,6 +128,11 @@ def bundle_for_objs_and_resources(objs, resources):
         Bundle
 
     '''
+    # Any env vars will overide a local default passed in
+    resources = settings.resources(default=resources)
+    if isinstance(resources, str):
+        resources = Resources(mode=resources)
+
     if resources is None or isinstance(resources, BaseResources):
         js_resources = css_resources = resources
     elif isinstance(resources, tuple) and len(resources) == 2 and all(r is None or isinstance(r, BaseResources) for r in resources):


Test patch:
diff --git a/tests/unit/bokeh/embed/test_bundle.py b/tests/unit/bokeh/embed/test_bundle.py
--- a/tests/unit/bokeh/embed/test_bundle.py
+++ b/tests/unit/bokeh/embed/test_bundle.py
@@ -14,8 +14,12 @@
 # Imports
 #-----------------------------------------------------------------------------
 
+# Standard library imports
+import os
+
 # Bokeh imports
 from bokeh.document import Document
+from bokeh.resources import INLINE
 
 # Module under test
 import bokeh.embed.bundle as beb # isort:skip
@@ -59,7 +63,30 @@ def test_widget():
 #-----------------------------------------------------------------------------
 
 class Test_bundle_for_objs_and_resources(object):
-    pass
+
+    def test_env_vars_precedence(self):
+        b = beb.bundle_for_objs_and_resources([], INLINE)
+        assert all('localhost' not in x for x in b.js_files)
+
+        # this is a cheap test that a BokehJS file is inline
+        assert any(len(x) > 5000 for x in b.js_raw)
+
+        os.environ['BOKEH_RESOURCES'] = "server-dev"
+        b = beb.bundle_for_objs_and_resources([], INLINE)
+        del os.environ['BOKEH_RESOURCES']
+        assert any('localhost' in x for x in b.js_files)
+
+        # this is a cheap test that a BokehJS file is NOT inline
+        assert all(len(x) < 5000 for x in b.js_raw)
+
+        os.environ['BOKEH_RESOURCES'] = "cdn"
+        b = beb.bundle_for_objs_and_resources([], INLINE)
+        del os.environ['BOKEH_RESOURCES']
+        assert any('cdn' in x for x in b.js_files)
+
+        # this is a cheap test that a BokehJS file is NOT inline
+        assert all(len(x) < 5000 for x in b.js_raw)
+
 
 #-----------------------------------------------------------------------------
 # Private API