1+ import os
12import socket
23import subprocess
34import sys
4- from PySide6 .QtCore import QThread , Signal
55import time
6+ import traceback
7+
8+ from PySide6 .QtCore import QThread , Signal
9+ from je_editor import language_wrapper
10+
11+ from pybreeze .utils .logging .logger import pybreeze_logger
612
713
814def find_free_port ():
@@ -13,41 +19,93 @@ def find_free_port():
1319 return port
1420
1521
16- class JupyterServerThread (QThread ):
17- server_ready = Signal (str )
22+ def get_venv_python ():
23+ # 如果在 venv 中
24+ if hasattr (sys , 'real_prefix' ) or (hasattr (sys , 'base_prefix' ) and sys .base_prefix != sys .prefix ):
25+ return sys .executable
26+
27+ # 嘗試從常見位置找 venv
28+ possible_paths = [
29+ os .path .join (os .getcwd (), "venv" , "Scripts" , "python.exe" ),
30+ os .path .join (os .getcwd (), ".venv" , "Scripts" , "python.exe" ),
31+ ]
1832
19- def __init__ (self ):
20- super ().__init__ ()
21- self .process = None
33+ for path in possible_paths :
34+ if os .path .exists (path ):
35+ return path
36+
37+ raise RuntimeError ("找不到 venv 的 python.exe" )
38+
39+
40+ def is_jupyter_installed (python_exe ):
41+ result = subprocess .run (
42+ [python_exe , "-m" , "pip" , "show" , "jupyterlab" ],
43+ stdout = subprocess .PIPE ,
44+ stderr = subprocess .PIPE
45+ )
46+ return result .returncode == 0
47+
48+
49+ class JupyterLauncherThread (QThread ):
50+ server_ready = Signal (str )
51+ status_update = Signal (str )
52+ error_occurred = Signal (str )
2253
2354 def run (self ):
24- port = find_free_port ()
25-
26- cmd = [
27- sys .executable ,
28- "-m" ,
29- "jupyterlab" ,
30- "--no-browser" ,
31- f"--ServerApp.port={ port } " ,
32- "--ServerApp.token=" ,
33- "--ServerApp.password=" ,
34- "--ServerApp.allow_origin=*" ,
35- "--ServerApp.disable_check_xsrf=True" ,
36- ]
37-
38- self .process = subprocess .Popen (cmd )
39-
40- # 輪詢 port,直到可連
41- while True :
42- try :
43- s = socket .create_connection (("localhost" , port ), timeout = 0.5 )
44- s .close ()
45- break
46- except OSError :
47- time .sleep (0.1 )
48-
49- # Server ready,發射 signal
50- self .server_ready .emit (f"http://localhost:{ port } /lab" )
55+ try :
56+ python_exe = get_venv_python ()
57+
58+ if not is_jupyter_installed (python_exe ):
59+ self .status_update .emit (language_wrapper .language_word_dict .get ("jupyterlab_downloading" ))
60+
61+ result = subprocess .run ([
62+ python_exe ,
63+ "-m" ,
64+ "pip" ,
65+ "install" ,
66+ "jupyterlab" ,
67+ "-U"
68+ ], capture_output = True , text = True )
69+
70+ if result .returncode != 0 :
71+ raise RuntimeError (result .stderr )
72+
73+ self .status_update .emit (language_wrapper .language_word_dict .get ("jupyterlab_loading" ))
74+
75+ port = find_free_port ()
76+
77+ self .process = subprocess .Popen ([
78+ python_exe ,
79+ "-m" ,
80+ "jupyterlab" ,
81+ "--no-browser" ,
82+ f"--ServerApp.port={ port } " ,
83+ "--ServerApp.token=" ,
84+ "--ServerApp.password=" ,
85+ "--ServerApp.allow_origin=*" ,
86+ "--ServerApp.disable_check_xsrf=True" ,
87+ ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True )
88+
89+ start_time = time .time ()
90+
91+ while True :
92+ if time .time () - start_time > 30 :
93+ raise TimeoutError ("JupyterLab 啟動超時" )
94+
95+ try :
96+ s = socket .create_connection (("localhost" , port ), timeout = 0.5 )
97+ s .close ()
98+ break
99+ except OSError :
100+ time .sleep (0.2 )
101+
102+ self .server_ready .emit (f"http://localhost:{ port } /lab" )
103+
104+ except Exception :
105+ err = traceback .format_exc ()
106+ print (err )
107+ self .error_occurred .emit (err )
108+ pybreeze_logger .info (err )
51109
52110 def stop (self ):
53111 if self .process :
0 commit comments