prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  BUG: if selenium is installed but not properly configured, Altair cannot be imported
  Fix is to use a more robust lazy import of selenium.
  
  The main issue is that ``import altair`` ends up trying to import selenium. It would be better if selenium weren't imported until it is actually needed. Same for other optional imports.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `altair/utils/headless.py`
  Content:
  ```
  1 """
  2 Utilities that use selenium + chrome headless to save figures
  3 """
  4 
  5 import contextlib
  6 import os
  7 import tempfile
  8 
  9 try:
  10     import selenium.webdriver
  11 except ImportError:
  12     selenium = None
  13 
  14 
  15 @contextlib.contextmanager
  16 def temporary_filename(**kwargs):
  17     """Create and clean-up a temporary file
  18 
  19     Arguments are the same as those passed to tempfile.mkstemp
  20 
  21     We could use tempfile.NamedTemporaryFile here, but that causes issues on
  22     windows (see https://bugs.python.org/issue14243).
  23     """
  24     filedescriptor, filename = tempfile.mkstemp(**kwargs)
  25     os.close(filedescriptor)
  26 
  27     try:
  28         yield filename
  29     finally:
  30         if os.path.exists(filename):
  31             os.remove(filename)
  32 
  33 
  34 HTML_TEMPLATE = """
  35 <!DOCTYPE html>
  36 <html>
  37 <head>
  38   <title>Embedding Vega-Lite</title>
  39   <script src="https://cdn.jsdelivr.net/npm/vega@{vega_version}"></script>
  40   <script src="https://cdn.jsdelivr.net/npm/vega-lite@{vegalite_version}"></script>
  41   <script src="https://cdn.jsdelivr.net/npm/vega-embed@{vegaembed_version}"></script>
  42 </head>
  43 <body>
  44   <div id="vis"></div>
  45 </body>
  46 </html>
  47 """
  48 
  49 EXTRACT_CODE = {
  50 'png': """
  51         var spec = arguments[0];
  52         var mode = arguments[1];
  53         var scaleFactor = arguments[2];
  54         var done = arguments[3];
  55 
  56         if(mode === 'vega-lite'){
  57           // compile vega-lite to vega
  58           const compiled = vl.compile(spec);
  59           spec = compiled.spec;
  60         }
  61 
  62         new vega.View(vega.parse(spec), {
  63               loader: vega.loader(),
  64               logLevel: vega.Warn,
  65               renderer: 'none',
  66             })
  67             .initialize()
  68             .toCanvas(scaleFactor)
  69             .then(function(canvas){return canvas.toDataURL('image/png');})
  70             .then(done)
  71             .catch(function(err) { console.error(err); });
  72         """,
  73 'svg': """
  74         var spec = arguments[0];
  75         var mode = arguments[1];
  76         var scaleFactor = arguments[2];
  77         var done = arguments[3];
  78 
  79         if(mode === 'vega-lite'){
  80           // compile vega-lite to vega
  81           const compiled = vl.compile(spec);
  82           spec = compiled.spec;
  83         }
  84 
  85         new vega.View(vega.parse(spec), {
  86               loader: vega.loader(),
  87               logLevel: vega.Warn,
  88               renderer: 'none',
  89             })
  90             .initialize()
  91             .toSVG(scaleFactor)
  92             .then(done)
  93             .catch(function(err) { console.error(err); });
  94         """,
  95 'vega': """
  96         var spec = arguments[0];
  97         var mode = arguments[1];
  98         var done = arguments[3];
  99 
  100         if(mode === 'vega-lite'){
  101           // compile vega-lite to vega
  102           const compiled = vl.compile(spec);
  103           spec = compiled.spec;
  104         }
  105 
  106         done(spec);
  107         """}
  108 
  109 
  110 def compile_spec(spec, format, mode,
  111                  vega_version, vegaembed_version, vegalite_version,
  112                  scale_factor=1, driver_timeout=20, webdriver='chrome'):
  113   
  114     # TODO: detect & use local Jupyter caches of JS packages?
  115 
  116     if format not in ['png', 'svg', 'vega']:
  117         raise NotImplementedError("format must be 'svg', 'png' or 'vega'")
  118 
  119     if mode not in ['vega', 'vega-lite']:
  120         raise ValueError("mode must be either 'vega' or 'vega-lite'")
  121 
  122     if vega_version is None:
  123         raise ValueError("must specify vega_version")
  124 
  125     if vegaembed_version is None:
  126         raise ValueError("must specify vegaembed_version")
  127 
  128     if mode == 'vega-lite' and vegalite_version is None:
  129         raise ValueError("must specify vega-lite version")
  130 
  131     if selenium is None:
  132         raise ImportError("selenium package is required "
  133                           "for saving chart as {0}".format(format))
  134     if webdriver == 'chrome':
  135         webdriver_class = selenium.webdriver.Chrome
  136         webdriver_options_class = selenium.webdriver.chrome.options.Options
  137     elif webdriver == 'firefox':
  138         webdriver_class = selenium.webdriver.Firefox
  139         webdriver_options_class = selenium.webdriver.firefox.options.Options
  140     else:
  141         raise ValueError("webdriver must be 'chrome' or 'firefox'")
  142 
  143     html = HTML_TEMPLATE.format(vega_version=vega_version,
  144                                 vegalite_version=vegalite_version,
  145                                 vegaembed_version=vegaembed_version)
  146 
  147     webdriver_options = webdriver_options_class()
  148     webdriver_options.add_argument("--headless")
  149 
  150     if issubclass(webdriver_class, selenium.webdriver.Chrome):
  151         # for linux/osx root user, need to add --no-sandbox option.
  152         # since geteuid doesn't exist on windows, we don't check it
  153         if hasattr(os, 'geteuid') and (os.geteuid() == 0):
  154             webdriver_options.add_argument('--no-sandbox')
  155 
  156     driver = webdriver_class(options=webdriver_options)
  157 
  158     try:
  159         driver.set_page_load_timeout(driver_timeout)
  160 
  161         with temporary_filename(suffix='.html') as htmlfile:
  162             with open(htmlfile, 'w') as f:
  163                 f.write(html)
  164             driver.get("file://" + htmlfile)
  165             online = driver.execute_script("return navigator.onLine")
  166             if not online:
  167                 raise ValueError("Internet connection required for saving "
  168                                  "chart as {0}".format(format))
  169             return driver.execute_async_script(EXTRACT_CODE[format],
  170                                                spec, mode, scale_factor)
  171     finally:
  172         driver.close()
  173
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/altair/utils/headless.py b/altair/utils/headless.py
  --- a/altair/utils/headless.py
  +++ b/altair/utils/headless.py
  @@ -6,11 +6,6 @@
   import os
   import tempfile
   
  -try:
  -    import selenium.webdriver
  -except ImportError:
  -    selenium = None
  -
   
   @contextlib.contextmanager
   def temporary_filename(**kwargs):
  @@ -110,9 +105,15 @@
   def compile_spec(spec, format, mode,
                    vega_version, vegaembed_version, vegalite_version,
                    scale_factor=1, driver_timeout=20, webdriver='chrome'):
  -  
       # TODO: detect & use local Jupyter caches of JS packages?
   
  +    # selenium is an optional dependency, so import it here
  +    try:
  +        import selenium.webdriver
  +    except ImportError:
  +        raise ImportError("selenium package is required "
  +                          "for saving chart as {0}".format(format))
  +
       if format not in ['png', 'svg', 'vega']:
           raise NotImplementedError("format must be 'svg', 'png' or 'vega'")
   
  @@ -128,9 +129,6 @@
       if mode == 'vega-lite' and vegalite_version is None:
           raise ValueError("must specify vega-lite version")
   
  -    if selenium is None:
  -        raise ImportError("selenium package is required "
  -                          "for saving chart as {0}".format(format))
       if webdriver == 'chrome':
           webdriver_class = selenium.webdriver.Chrome
           webdriver_options_class = selenium.webdriver.chrome.options.Options
  
verification_info: {"golden_diff": "diff --git a/altair/utils/headless.py b/altair/utils/headless.py\n--- a/altair/utils/headless.py\n+++ b/altair/utils/headless.py\n@@ -6,11 +6,6 @@\n import os\n import tempfile\n \n-try:\n-    import selenium.webdriver\n-except ImportError:\n-    selenium = None\n-\n \n @contextlib.contextmanager\n def temporary_filename(**kwargs):\n@@ -110,9 +105,15 @@\n def compile_spec(spec, format, mode,\n                  vega_version, vegaembed_version, vegalite_version,\n                  scale_factor=1, driver_timeout=20, webdriver='chrome'):\n-  \n     # TODO: detect & use local Jupyter caches of JS packages?\n \n+    # selenium is an optional dependency, so import it here\n+    try:\n+        import selenium.webdriver\n+    except ImportError:\n+        raise ImportError(\"selenium package is required \"\n+                          \"for saving chart as {0}\".format(format))\n+\n     if format not in ['png', 'svg', 'vega']:\n         raise NotImplementedError(\"format must be 'svg', 'png' or 'vega'\")\n \n@@ -128,9 +129,6 @@\n     if mode == 'vega-lite' and vegalite_version is None:\n         raise ValueError(\"must specify vega-lite version\")\n \n-    if selenium is None:\n-        raise ImportError(\"selenium package is required \"\n-                          \"for saving chart as {0}\".format(format))\n     if webdriver == 'chrome':\n         webdriver_class = selenium.webdriver.Chrome\n         webdriver_options_class = selenium.webdriver.chrome.options.Options\n", "issue": "BUG: if selenium is installed but not properly configured, Altair cannot be imported\nFix is to use a more robust lazy import of selenium.\r\n\r\nThe main issue is that ``import altair`` ends up trying to import selenium. It would be better if selenium weren't imported until it is actually needed. Same for other optional imports.\n", "before_files": [{"content": "\"\"\"\nUtilities that use selenium + chrome headless to save figures\n\"\"\"\n\nimport contextlib\nimport os\nimport tempfile\n\ntry:\n    import selenium.webdriver\nexcept ImportError:\n    selenium = None\n\n\n@contextlib.contextmanager\ndef temporary_filename(**kwargs):\n    \"\"\"Create and clean-up a temporary file\n\n    Arguments are the same as those passed to tempfile.mkstemp\n\n    We could use tempfile.NamedTemporaryFile here, but that causes issues on\n    windows (see https://bugs.python.org/issue14243).\n    \"\"\"\n    filedescriptor, filename = tempfile.mkstemp(**kwargs)\n    os.close(filedescriptor)\n\n    try:\n        yield filename\n    finally:\n        if os.path.exists(filename):\n            os.remove(filename)\n\n\nHTML_TEMPLATE = \"\"\"\n<!DOCTYPE html>\n<html>\n<head>\n  <title>Embedding Vega-Lite</title>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega@{vega_version}\"></script>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega-lite@{vegalite_version}\"></script>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega-embed@{vegaembed_version}\"></script>\n</head>\n<body>\n  <div id=\"vis\"></div>\n</body>\n</html>\n\"\"\"\n\nEXTRACT_CODE = {\n'png': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var scaleFactor = arguments[2];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        new vega.View(vega.parse(spec), {\n              loader: vega.loader(),\n              logLevel: vega.Warn,\n              renderer: 'none',\n            })\n            .initialize()\n            .toCanvas(scaleFactor)\n            .then(function(canvas){return canvas.toDataURL('image/png');})\n            .then(done)\n            .catch(function(err) { console.error(err); });\n        \"\"\",\n'svg': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var scaleFactor = arguments[2];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        new vega.View(vega.parse(spec), {\n              loader: vega.loader(),\n              logLevel: vega.Warn,\n              renderer: 'none',\n            })\n            .initialize()\n            .toSVG(scaleFactor)\n            .then(done)\n            .catch(function(err) { console.error(err); });\n        \"\"\",\n'vega': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        done(spec);\n        \"\"\"}\n\n\ndef compile_spec(spec, format, mode,\n                 vega_version, vegaembed_version, vegalite_version,\n                 scale_factor=1, driver_timeout=20, webdriver='chrome'):\n  \n    # TODO: detect & use local Jupyter caches of JS packages?\n\n    if format not in ['png', 'svg', 'vega']:\n        raise NotImplementedError(\"format must be 'svg', 'png' or 'vega'\")\n\n    if mode not in ['vega', 'vega-lite']:\n        raise ValueError(\"mode must be either 'vega' or 'vega-lite'\")\n\n    if vega_version is None:\n        raise ValueError(\"must specify vega_version\")\n\n    if vegaembed_version is None:\n        raise ValueError(\"must specify vegaembed_version\")\n\n    if mode == 'vega-lite' and vegalite_version is None:\n        raise ValueError(\"must specify vega-lite version\")\n\n    if selenium is None:\n        raise ImportError(\"selenium package is required \"\n                          \"for saving chart as {0}\".format(format))\n    if webdriver == 'chrome':\n        webdriver_class = selenium.webdriver.Chrome\n        webdriver_options_class = selenium.webdriver.chrome.options.Options\n    elif webdriver == 'firefox':\n        webdriver_class = selenium.webdriver.Firefox\n        webdriver_options_class = selenium.webdriver.firefox.options.Options\n    else:\n        raise ValueError(\"webdriver must be 'chrome' or 'firefox'\")\n\n    html = HTML_TEMPLATE.format(vega_version=vega_version,\n                                vegalite_version=vegalite_version,\n                                vegaembed_version=vegaembed_version)\n\n    webdriver_options = webdriver_options_class()\n    webdriver_options.add_argument(\"--headless\")\n\n    if issubclass(webdriver_class, selenium.webdriver.Chrome):\n        # for linux/osx root user, need to add --no-sandbox option.\n        # since geteuid doesn't exist on windows, we don't check it\n        if hasattr(os, 'geteuid') and (os.geteuid() == 0):\n            webdriver_options.add_argument('--no-sandbox')\n\n    driver = webdriver_class(options=webdriver_options)\n\n    try:\n        driver.set_page_load_timeout(driver_timeout)\n\n        with temporary_filename(suffix='.html') as htmlfile:\n            with open(htmlfile, 'w') as f:\n                f.write(html)\n            driver.get(\"file://\" + htmlfile)\n            online = driver.execute_script(\"return navigator.onLine\")\n            if not online:\n                raise ValueError(\"Internet connection required for saving \"\n                                 \"chart as {0}\".format(format))\n            return driver.execute_async_script(EXTRACT_CODE[format],\n                                               spec, mode, scale_factor)\n    finally:\n        driver.close()\n", "path": "altair/utils/headless.py"}], "after_files": [{"content": "\"\"\"\nUtilities that use selenium + chrome headless to save figures\n\"\"\"\n\nimport contextlib\nimport os\nimport tempfile\n\n\n@contextlib.contextmanager\ndef temporary_filename(**kwargs):\n    \"\"\"Create and clean-up a temporary file\n\n    Arguments are the same as those passed to tempfile.mkstemp\n\n    We could use tempfile.NamedTemporaryFile here, but that causes issues on\n    windows (see https://bugs.python.org/issue14243).\n    \"\"\"\n    filedescriptor, filename = tempfile.mkstemp(**kwargs)\n    os.close(filedescriptor)\n\n    try:\n        yield filename\n    finally:\n        if os.path.exists(filename):\n            os.remove(filename)\n\n\nHTML_TEMPLATE = \"\"\"\n<!DOCTYPE html>\n<html>\n<head>\n  <title>Embedding Vega-Lite</title>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega@{vega_version}\"></script>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega-lite@{vegalite_version}\"></script>\n  <script src=\"https://cdn.jsdelivr.net/npm/vega-embed@{vegaembed_version}\"></script>\n</head>\n<body>\n  <div id=\"vis\"></div>\n</body>\n</html>\n\"\"\"\n\nEXTRACT_CODE = {\n'png': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var scaleFactor = arguments[2];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        new vega.View(vega.parse(spec), {\n              loader: vega.loader(),\n              logLevel: vega.Warn,\n              renderer: 'none',\n            })\n            .initialize()\n            .toCanvas(scaleFactor)\n            .then(function(canvas){return canvas.toDataURL('image/png');})\n            .then(done)\n            .catch(function(err) { console.error(err); });\n        \"\"\",\n'svg': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var scaleFactor = arguments[2];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        new vega.View(vega.parse(spec), {\n              loader: vega.loader(),\n              logLevel: vega.Warn,\n              renderer: 'none',\n            })\n            .initialize()\n            .toSVG(scaleFactor)\n            .then(done)\n            .catch(function(err) { console.error(err); });\n        \"\"\",\n'vega': \"\"\"\n        var spec = arguments[0];\n        var mode = arguments[1];\n        var done = arguments[3];\n\n        if(mode === 'vega-lite'){\n          // compile vega-lite to vega\n          const compiled = vl.compile(spec);\n          spec = compiled.spec;\n        }\n\n        done(spec);\n        \"\"\"}\n\n\ndef compile_spec(spec, format, mode,\n                 vega_version, vegaembed_version, vegalite_version,\n                 scale_factor=1, driver_timeout=20, webdriver='chrome'):\n    # TODO: detect & use local Jupyter caches of JS packages?\n\n    # selenium is an optional dependency, so import it here\n    try:\n        import selenium.webdriver\n    except ImportError:\n        raise ImportError(\"selenium package is required \"\n                          \"for saving chart as {0}\".format(format))\n\n    if format not in ['png', 'svg', 'vega']:\n        raise NotImplementedError(\"format must be 'svg', 'png' or 'vega'\")\n\n    if mode not in ['vega', 'vega-lite']:\n        raise ValueError(\"mode must be either 'vega' or 'vega-lite'\")\n\n    if vega_version is None:\n        raise ValueError(\"must specify vega_version\")\n\n    if vegaembed_version is None:\n        raise ValueError(\"must specify vegaembed_version\")\n\n    if mode == 'vega-lite' and vegalite_version is None:\n        raise ValueError(\"must specify vega-lite version\")\n\n    if webdriver == 'chrome':\n        webdriver_class = selenium.webdriver.Chrome\n        webdriver_options_class = selenium.webdriver.chrome.options.Options\n    elif webdriver == 'firefox':\n        webdriver_class = selenium.webdriver.Firefox\n        webdriver_options_class = selenium.webdriver.firefox.options.Options\n    else:\n        raise ValueError(\"webdriver must be 'chrome' or 'firefox'\")\n\n    html = HTML_TEMPLATE.format(vega_version=vega_version,\n                                vegalite_version=vegalite_version,\n                                vegaembed_version=vegaembed_version)\n\n    webdriver_options = webdriver_options_class()\n    webdriver_options.add_argument(\"--headless\")\n\n    if issubclass(webdriver_class, selenium.webdriver.Chrome):\n        # for linux/osx root user, need to add --no-sandbox option.\n        # since geteuid doesn't exist on windows, we don't check it\n        if hasattr(os, 'geteuid') and (os.geteuid() == 0):\n            webdriver_options.add_argument('--no-sandbox')\n\n    driver = webdriver_class(options=webdriver_options)\n\n    try:\n        driver.set_page_load_timeout(driver_timeout)\n\n        with temporary_filename(suffix='.html') as htmlfile:\n            with open(htmlfile, 'w') as f:\n                f.write(html)\n            driver.get(\"file://\" + htmlfile)\n            online = driver.execute_script(\"return navigator.onLine\")\n            if not online:\n                raise ValueError(\"Internet connection required for saving \"\n                                 \"chart as {0}\".format(format))\n            return driver.execute_async_script(EXTRACT_CODE[format],\n                                               spec, mode, scale_factor)\n    finally:\n        driver.close()\n", "path": "altair/utils/headless.py"}]}