Skip to content

Commit

Permalink
Fall back on webbrowser if xdg-open is not installed on Linux (#701)
Browse files Browse the repository at this point in the history
* Fall back on webbrowser if xdg-open is not installed on Linux

* python27 friendly

* reformat

* linter
  • Loading branch information
monchier committed Nov 19, 2019
1 parent 34ccf6d commit 8166563
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 19 deletions.
7 changes: 7 additions & 0 deletions lib/streamlit/env_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ def is_repl():
return True

return False


def is_executable_in_path(name):
"""Check if executable is in OS path."""
from distutils.spawn import find_executable

return find_executable(name) is not None
2 changes: 1 addition & 1 deletion lib/streamlit/net_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_external_ip():
"Did not auto detect external IP.\n"
"Please go to %s for debugging hints.",
# fmt: on
util.HELP_DOC,
util.HELP_DOC
)
else:
_external_ip = response.strip()
Expand Down
49 changes: 32 additions & 17 deletions lib/streamlit/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,46 @@ def open_browser(url):
"""

if env_util.IS_WINDOWS:
# Treat Windows separately because:
# 1. /dev/null doesn't exist.
# 2. subprocess.Popen(['start', url]) doesn't actually pop up the
# browser even though 'start url' works from the command prompt.
# Fun!
import webbrowser

webbrowser.open(url)
return

# Treat Windows separately because:
# 1. /dev/null doesn't exist.
# 2. subprocess.Popen(['start', url]) doesn't actually pop up the
# browser even though 'start url' works from the command prompt.
# Fun!
# Also, use webbrowser if we are on Linux and xdg-open is not installed.
#
# We don't use the webbrowser module on Linux and Mac because some browsers
# (ahem... Chrome) always print "Opening in existing browser session" to
# the terminal, which is spammy and annoying. So instead we start the
# browser ourselves and send all its output to /dev/null.

if env_util.IS_WINDOWS:
_open_browser_with_webbrowser(url)
return
if env_util.IS_LINUX_OR_BSD:
cmd = ["xdg-open", url]
elif env_util.IS_DARWIN:
cmd = ["open", url]
else:
raise Error('Cannot open browser in platform "%s"' % system)
if env_util.is_executable_in_path("xdg-open"):
_open_browser_with_command("xdg-open", url)
return
_open_browser_with_webbrowser(url)
return
if env_util.IS_DARWIN:
_open_browser_with_command("open", url)
return

import platform

raise Error('Cannot open browser in platform "%s"' % platform.system())


def _open_browser_with_webbrowser(url):
import webbrowser

webbrowser.open(url)


def _open_browser_with_command(command, url):
cmd_line = [command, url]
with open(os.devnull, "w") as devnull:
subprocess.Popen(cmd, stdout=devnull, stderr=subprocess.STDOUT)
subprocess.Popen(cmd_line, stdout=devnull, stderr=subprocess.STDOUT)


# TODO: Move this into errors.py? Replace with StreamlitAPIException?
Expand Down
4 changes: 3 additions & 1 deletion lib/tests/streamlit/file_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ def test_streamlit_write_exception(self):
self.assertEqual(str(e.value), error_msg)

def test_get_project_streamlit_file_path(self):
expected = os.path.join(os.getcwd(), file_util.CONFIG_FOLDER_NAME, "some/random/file")
expected = os.path.join(
os.getcwd(), file_util.CONFIG_FOLDER_NAME, "some/random/file"
)

self.assertEqual(
expected, file_util.get_project_streamlit_file_path("some/random/file")
Expand Down
34 changes: 34 additions & 0 deletions lib/tests/streamlit/util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

import random
import unittest
from mock import patch
from parameterized import parameterized


from streamlit import util

Expand All @@ -28,3 +31,34 @@ def test_memoization(self):
yes_memoized_func = util.memoize(non_memoized_func)
self.assertNotEqual(non_memoized_func(), non_memoized_func())
self.assertEqual(yes_memoized_func(), yes_memoized_func())

@parameterized.expand(
[("Linux", False, True), ("Windows", True, False), ("Darwin", False, True)]
)
def test_open_browser(self, os_type, webbrowser_expect, popen_expect):
"""Test web browser opening scenarios."""
from streamlit import env_util

env_util.IS_WINDOWS = os_type == "Windows"
env_util.IS_DARWIN = os_type == "Darwin"
env_util.IS_LINUX_OR_BSD = os_type == "Linux"

with patch("streamlit.env_util.is_executable_in_path", return_value=True):
with patch("webbrowser.open") as webbrowser_open:
with patch("subprocess.Popen") as subprocess_popen:
util.open_browser("http://some-url")
self.assertEqual(webbrowser_expect, webbrowser_open.called)
self.assertEqual(popen_expect, subprocess_popen.called)

def test_open_browser_linux_no_xdg(self):
"""Test opening the browser on Linux with no xdg installed"""
from streamlit import env_util

env_util.IS_LINUX_OR_BSD = True

with patch("streamlit.env_util.is_executable_in_path", return_value=False):
with patch("webbrowser.open") as webbrowser_open:
with patch("subprocess.Popen") as subprocess_popen:
util.open_browser("http://some-url")
self.assertEqual(True, webbrowser_open.called)
self.assertEqual(False, subprocess_popen.called)

0 comments on commit 8166563

Please sign in to comment.