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

1#!/usr/bin/env python 

2"""CLI for pycse. 

3 

4In theory this should probably use docker-py, but this worked first. It is only 

5lightly tested. 

6 

7There is not a build in way to update the image. You have to manage this with 

8docker commands. 

9 

10To get the latest image: 

11 

12> docker pull jkitchin/pycse:latest 

13 

14 

15Sometimes you have to manually delete an old container if it doesn't close 

16properly. This should do it: 

17 

18> docker rm -f pycse 

19 

20The Docker Desktop app is also helpful for this kind of stuff. 

21 

22TODO: make this a click app, with cli arguments to update the image? e.g. 

23 

24> pycse --update 

25> pycse path/to/working-dir 

26 

27""" 

28 

29import os 

30import uuid 

31import numpy as np 

32import requests 

33import time 

34import webbrowser 

35import subprocess 

36import shutil 

37import sys 

38 

39 

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/") 

45 

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) 

54 

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.") 

63 

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] 

68 

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() 

75 

76 url = f"http://localhost:{PORT}/lab?token={JUPYTER_TOKEN}" 

77 webbrowser.open(url) 

78 sys.exit() 

79 

80 # Start a new container. 

81 PWD = os.getcwd() 

82 PORT = np.random.randint(8000, 9000) 

83 

84 JUPYTER_TOKEN = str(uuid.uuid4()) 

85 os.environ["JUPYTER_TOKEN"] = JUPYTER_TOKEN 

86 

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 ) 

92 

93 print("Starting Jupyter lab. Type C-c to quit.") 

94 subprocess.Popen(cmd.split()) 

95 

96 time.sleep(2) 

97 

98 url = f"http://localhost:{PORT}/lab?token={JUPYTER_TOKEN}" 

99 

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) 

106 

107 webbrowser.open(url) 

108 

109 subprocess.run(["docker", "attach", "pycse"]) 

110 

111 

112if __name__ == "__main__": 

113 pycse()