Coverage for src/pycse/cli.py: 100.00%
45 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 16:23 -0400
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-23 16:23 -0400
1#!/usr/bin/env python
2"""CLI for pycse.
4In theory this should probably use docker-py, but this worked first. It is only
5lightly tested.
7There is not a build in way to update the image. You have to manage this with
8docker commands.
10To get the latest image:
12> docker pull jkitchin/pycse:latest
15Sometimes you have to manually delete an old container if it doesn't close
16properly. This should do it:
18> docker rm -f pycse
20The Docker Desktop app is also helpful for this kind of stuff.
22TODO: make this a click app, with cli arguments to update the image? e.g.
24> pycse --update
25> pycse path/to/working-dir
27"""
29import os
30import uuid
31import numpy as np
32import requests
33import time
34import webbrowser
35import subprocess
36import shutil
37import sys
40def pycse():
41 """CLI to launch a Docker image with Jupyter lab in the CWD.
42 This assumes you have a working Docker installation."""
43 if shutil.which("docker") is None:
44 raise Exception("docker was not found. Please install it from https://www.docker.com/")
46 # Check setup and get image if needed
47 try:
48 subprocess.run(
49 ["docker", "image", "inspect", "jkitchin/pycse"],
50 capture_output=True,
51 )
52 except:
53 subprocess.run(["docker", "pull", "jkitchin/pycse"], capture_output=True)
55 # Check if the container is already running
56 p = subprocess.run(["docker", "ps", "--format", '"{{.Names}}"'], capture_output=True)
57 if "pycse" in p.stdout.decode("utf-8"):
58 ans = input("There is already a pycse container running.Do you want to kill it? (y/n)")
59 if ans.lower() == "y":
60 subprocess.run("docker rm -f pycse".split())
61 else:
62 print("There can only be one pycse container running at a time.Connecting to it.")
64 # this outputs something like 0.0.0.0:8987
65 p = subprocess.run("docker port pycse 8888".split(), capture_output=True)
66 output = p.stdout.decode("utf-8").strip()
67 PORT = output.split(":")[-1]
69 # We need the token for the running container
70 p = subprocess.run(
71 ["docker", "exec", "pycse", "printenv", "JUPYTER_TOKEN"],
72 capture_output=True,
73 )
74 JUPYTER_TOKEN = p.stdout.decode("utf-8").strip()
76 url = f"http://localhost:{PORT}/lab?token={JUPYTER_TOKEN}"
77 webbrowser.open(url)
78 sys.exit()
80 # Start a new container.
81 PWD = os.getcwd()
82 PORT = np.random.randint(8000, 9000)
84 JUPYTER_TOKEN = str(uuid.uuid4())
85 os.environ["JUPYTER_TOKEN"] = JUPYTER_TOKEN
87 cmd = (
88 f"docker run -d --name pycse -it --rm -p {PORT}:8888 "
89 f"-e JUPYTER_TOKEN -v {PWD}:/home/jovyan/work "
90 "jkitchin/pycse"
91 )
93 print("Starting Jupyter lab. Type C-c to quit.")
94 subprocess.Popen(cmd.split())
96 time.sleep(2)
98 url = f"http://localhost:{PORT}/lab?token={JUPYTER_TOKEN}"
100 # See if we can wait until the url actually returns
101 for i in range(10):
102 if requests.get(url).status_code == 200:
103 break
104 else:
105 time.sleep(1)
107 webbrowser.open(url)
109 subprocess.run(["docker", "attach", "pycse"])
112if __name__ == "__main__":
113 pycse()