Skip to content

Commit

Permalink
browser: New module for reasonably ergonomic web browser interaction
Browse files Browse the repository at this point in the history
Based on `nextstrain view`'s interaction with the standard library's
webbrowser module.  Arranges to launch the browser in a separate thread,
as this will be good enough for many contexts.  `nextstrain view` itself
still arranges to launch the browser in a separate _process_, since
view's main thread/process exec's into a new program shortly after
launching the browser.
  • Loading branch information
tsibley committed Nov 21, 2023
1 parent 1c6a658 commit dbfe5ad
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 16 deletions.
51 changes: 51 additions & 0 deletions nextstrain/cli/browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Web browser interaction.
"""
import webbrowser
from threading import Thread, ThreadError
from os import environ
from .util import warn


# Avoid text-mode browsers
TERM = environ.pop("TERM", None)
try:
BROWSER = webbrowser.get()
except:
BROWSER = None
finally:
if TERM is not None:
environ["TERM"] = TERM


def open_browser(url: str, new_thread: bool = True):
"""
Opens *url* in a web browser.
Opens in a new tab, if possible, and raises the window to the top, if
possible.
Launches the browser from a separate thread by default so waiting on the
browser child process doesn't block the main (or calling) thread. Set
*new_thread* to False to launch from the same thread as the caller (e.g. if
you've already spawned a dedicated thread or process for the browser).
Note that some registered browsers launch in the background themselves, but
not all do, so this feature makes launch behaviour consistent across
browsers.
Prints a warning to stderr if a browser can't be found or can't be
launched, as automatically opening a browser is considered a
nice-but-not-necessary feature.
"""
if not BROWSER:
warn(f"Couldn't open <{url}> in browser: no browser found")
return

try:
if new_thread:
Thread(target = open_browser, args = (url, False), daemon = True).start()
else:
# new = 2 means new tab, if possible
BROWSER.open(url, new = 2, autoraise = True)
except (ThreadError, webbrowser.Error) as err:
warn(f"Couldn't open <{url}> in browser: {err!r}")
18 changes: 2 additions & 16 deletions nextstrain/cli/command/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
from multiprocessing import Process, ProcessError
import re
import requests
import webbrowser
from inspect import cleandoc
from os import environ
from pathlib import Path
Expand All @@ -57,6 +56,7 @@
from typing import Iterable, NamedTuple, Tuple, Union
from .. import runner
from ..argparse import add_extended_help_flags, SUPPRESS, SKIP_AUTO_DEFAULT_IN_HELP
from ..browser import BROWSER, open_browser as __open_browser
from ..runner import docker, ambient, conda, singularity
from ..util import colored, remove_suffix, warn
from ..volume import NamedVolume
Expand All @@ -67,16 +67,6 @@
PORT = environ.get("PORT") or "4000"


# Avoid text-mode browsers
TERM = environ.pop("TERM", None)
try:
BROWSER = webbrowser.get()
except:
BROWSER = None
finally:
if TERM is not None:
environ["TERM"] = TERM

OPEN_DEFAULT = bool(BROWSER)


Expand Down Expand Up @@ -454,8 +444,4 @@ def _open_browser(url: str):
warn(f"Couldn't open <{url}> in browser: Auspice never started listening")
return

try:
# new = 2 means new tab, if possible
BROWSER.open(url, new = 2, autoraise = True)
except webbrowser.Error as err:
warn(f"Couldn't open <{url}> in browser: {err!r}")
__open_browser(url, new_thread = False)

0 comments on commit dbfe5ad

Please sign in to comment.