Skip to content

Commit

Permalink
Factor the open browser logic into utils.
Browse files Browse the repository at this point in the history
This vastly simplifies the implementation of jupyter and rstudio
commands, and avoids repetition
  • Loading branch information
bloodearnest committed Oct 17, 2024
1 parent 3917df7 commit 7e38922
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 110 deletions.
96 changes: 31 additions & 65 deletions opensafely/jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@
import json
import os
import subprocess
import sys
import threading
import time
import webbrowser
from pathlib import Path
from urllib import request

from opensafely import utils

Expand Down Expand Up @@ -50,59 +46,29 @@ def add_arguments(parser):
)


def open_browser(name, port):
try:
metadata = None
metadata_path = "/tmp/.local/share/jupyter/runtime/nbserver-*.json"

# wait for jupyter to be set up
start = time.time()
while metadata is None and time.time() - start < 120.0:
ps = subprocess.run(
["docker", "exec", name, "bash", "-c", f"cat {metadata_path}"],
text=True,
capture_output=True,
)
if ps.returncode == 0:
utils.debug(ps.stdout)
metadata = json.loads(ps.stdout)
else:
time.sleep(1)

if metadata is None:
utils.debug("get_metadata: Could not get metadata")
return

url = f"http://localhost:{port}/?token={metadata['token']}"
utils.debug(f"open_browser: url={url}")

# wait for port to be open
utils.debug("open_browser: waiting for port")
start = time.time()
while time.time() - start < 60.0:
try:
response = request.urlopen(url, timeout=1)
except (request.URLError, OSError):
pass
else:
break

if not response:
utils.debug("open_browser: open_browser: could not get response")
return

# open a webbrowser pointing to the docker container
utils.debug("open_browser: open_browser: opening browser window")
webbrowser.open(url, new=2)

except Exception:
# reformat exception printing to work from thread
import traceback

sys.stderr.write("Error in open browser thread:\r\n")
tb = traceback.format_exc().replace("\n", "\r\n")
sys.stderr.write(tb)
sys.stderr.flush()
def get_metadata(name, port):
metadata = None
metadata_path = "/tmp/.local/share/jupyter/runtime/nbserver-*.json"

# wait for jupyter to be set up
start = time.time()
while metadata is None and time.time() - start < 120.0:
ps = subprocess.run(
["docker", "exec", name, "bash", "-c", f"cat {metadata_path}"],
text=True,
capture_output=True,
)
if ps.returncode == 0:
utils.debug(ps.stdout)
metadata = json.loads(ps.stdout)
else:
time.sleep(1)

if metadata is None:
utils.debug("get_metadata: Could not get metadata")
return None

return metadata


def main(directory, name, port, no_browser, jupyter_args):
Expand Down Expand Up @@ -130,13 +96,6 @@ def main(directory, name, port, no_browser, jupyter_args):
print(f"Running following jupyter cmd in OpenSAFELY docker container {name}...")
print(" ".join(jupyter_cmd))

if not no_browser:
# start thread to open web browser
thread = threading.Thread(target=open_browser, args=(name, port), daemon=True)
thread.name = "browser thread"
utils.debug("starting open_browser thread")
thread.start()

docker_args = [
# we use our port on both sides of the docker port mapping so that
# jupyter's logging uses the correct port from the user's perspective
Expand All @@ -150,10 +109,17 @@ def main(directory, name, port, no_browser, jupyter_args):
"PYTHONPATH=/workspace",
]

if not no_browser:
metadata = get_metadata(name, port)
if metadata:
url = f"http://localhost:{port}/?token={metadata['token']}"
utils.open_browser_in_thread(url)

utils.debug("docker: " + " ".join(docker_args))
ps = utils.run_docker(docker_args, "python", jupyter_cmd, interactive=True)

ps = utils.run_docker(
docker_args, "python", jupyter_cmd, interactive=True, directory=directory
)

# we want to exit with the same code that jupyter did
return ps.returncode
50 changes: 5 additions & 45 deletions opensafely/rstudio.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import os
import sys
import threading
import time
import webbrowser
from pathlib import Path
from sys import platform
from urllib import request

from opensafely import utils

Expand Down Expand Up @@ -33,53 +28,14 @@ def add_arguments(parser):
)


def open_browser(name, port):
try:
url = f"http://localhost:{port}"
utils.debug(f"open_browser: url={url}")

# wait for port to be open
utils.debug("open_browser: waiting for port")
start = time.time()
while time.time() - start < 60.0:
try:
response = request.urlopen(url, timeout=1)
except (request.URLError, OSError):
pass
else:
break

if not response:
utils.debug("open_browser: open_browser: could not get response")
return

# open a webbrowser pointing to the docker container
utils.debug("open_browser: open_browser: opening browser window")
webbrowser.open(url, new=2)

except Exception:
# reformat exception printing to work from thread
import traceback

sys.stderr.write("Error in open browser thread:\r\n")
tb = traceback.format_exc().replace("\n", "\r\n")
sys.stderr.write(tb)
sys.stderr.flush()


def main(directory, name, port):
if name is None:
name = f"os-rstudio-{directory.name}"

if port is None:
port = str(utils.get_free_port())

# if not no_browser:
# start thread to open web browser
thread = threading.Thread(target=open_browser, args=(name, port), daemon=True)
thread.name = "browser thread"
utils.debug("starting open_browser thread")
thread.start()
url = f"http://localhost:{port}"

# Determine if on Linux, if so obtain user id
# And need to know in Windows win32 for text file line endings setting
Expand All @@ -104,8 +60,12 @@ def main(directory, name, port):
f"Opening an RStudio Server session at http://localhost:{port}/ when "
"you are finished working please press Ctrl+C here to end the session"
)

utils.open_browser_in_thread(url)

ps = utils.run_docker(
docker_args, "rstudio", "", interactive=True, user="0:0", directory=directory
)

# we want to exit with the same code that rstudio-server did
return ps.returncode
44 changes: 44 additions & 0 deletions opensafely/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
import socket
import subprocess
import sys
import threading
import time
import webbrowser
from urllib import request

from opensafely._vendor.jobrunner import config

Expand Down Expand Up @@ -159,3 +163,43 @@ def get_free_port():
port = sock.getsockname()[1]
sock.close()
return port


def open_browser(url):
try:
debug(f"open_browser: url={url}")

# wait for port to be open
debug("open_browser: waiting for port")
start = time.time()
while time.time() - start < 60.0:
try:
response = request.urlopen(url, timeout=1)
except (request.URLError, OSError):
pass
else:
break

if not response:
debug("open_browser: open_browser: could not get response")
return

# open a webbrowser pointing to the docker container
debug("open_browser: open_browser: opening browser window")
webbrowser.open(url, new=2)

except Exception:
# reformat exception printing to work from thread
import traceback

sys.stderr.write("Error in open browser thread:\r\n")
tb = traceback.format_exc().replace("\n", "\r\n")
sys.stderr.write(tb)
sys.stderr.flush()


def open_browser_in_thread(url):
thread = threading.Thread(target=open_browser, args=(url,), daemon=True)
thread.name = "browser thread"
debug("starting browser thread")
thread.start()

0 comments on commit 7e38922

Please sign in to comment.