Skip to content

Commit b7489b7

Browse files
committed
updated no_notebook mode
1 parent 39969cc commit b7489b7

File tree

3 files changed

+92
-35
lines changed

3 files changed

+92
-35
lines changed

vpython/_notebook_helpers.py

+12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@
44

55
def __is_spyder():
66
return any('SPYDER' in name for name in os.environ)
7+
8+
def __is_idle():
9+
return 'idlelib' in sys.modules
10+
11+
def __is_PyCharm():
12+
return "PYCHARM_HOSTED" in os.environ
713

14+
def __is_vscode():
15+
return 'TERM_PROGRAM' in os.environ.keys() and os.environ['TERM_PROGRAM'] == 'vscode'
16+
17+
def __is_spyder_or_similar_IDE():
18+
return __is_idle() or __is_spyder() or __is_PyCharm()
819

920
def _spyder_run_setting_is_correct():
1021
try:
@@ -59,3 +70,4 @@ def __checkisnotebook():
5970
# IMPORTANT NOTE: this is evaluated ONCE the first time this is imported.
6071
_isnotebook = __checkisnotebook()
6172
_in_spyder = __is_spyder()
73+
_in_spyder_or_similar_IDE = __is_spyder_or_similar_IDE()

vpython/no_notebook.py

+79-34
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .vpython import GlowWidget, baseObj, vector, canvas, _browsertype
2-
from ._notebook_helpers import _in_spyder, _undo_vpython_import_in_spyder
2+
from ._notebook_helpers import _in_spyder, _undo_vpython_import_in_spyder, _in_spyder_or_similar_IDE
33

44
from http.server import BaseHTTPRequestHandler, HTTPServer
55
import os
@@ -14,17 +14,18 @@
1414
import copy
1515
import socket
1616
import multiprocessing
17-
17+
import time
1818

1919
import signal
2020
from urllib.parse import unquote
2121

2222
from .rate_control import rate
2323

24+
makeDaemonic = (platform.system() == "Windows")
2425

2526
# Redefine `Thread.run` to not show a traceback for Spyder when stopping
2627
# the server by raising a KeyboardInterrupt or SystemExit.
27-
if _in_spyder:
28+
if _in_spyder_or_similar_IDE:
2829
def install_thread_stopped_message():
2930
"""
3031
Workaround to prevent showing a traceback when VPython server stops.
@@ -38,7 +39,8 @@ def run(*args, **kwargs):
3839
try:
3940
run_old(*args, **kwargs)
4041
except (KeyboardInterrupt, SystemExit):
41-
print("VPython server stopped.")
42+
pass
43+
# ("VPython server stopped.")
4244
except:
4345
raise
4446
threading.Thread.run = run
@@ -49,23 +51,32 @@ def run(*args, **kwargs):
4951

5052
# Check for Ctrl+C. SIGINT will also be sent by our code if WServer is closed.
5153
def signal_handler(signal, frame):
54+
#print("in signal handler, calling stop server")
5255
stop_server()
5356

54-
5557
signal.signal(signal.SIGINT, signal_handler)
5658

5759
# Requests from client to http server can be the following:
5860
# get glowcomm.html, library .js files, images, or font files
5961

6062

61-
def find_free_port(port):
63+
def find_free_port(port=0):
6264
s = socket.socket()
6365
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
64-
s.bind(('', port))
66+
#if hasattr(socket, 'SO_REUSEPORT'): # This may be required on systems that support it. Needs testing.
67+
# s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
68+
try :
69+
s.bind(('', port)) # bind to a port
70+
except:
71+
raise
6572
return s.getsockname()[1]
6673

67-
__HTTP_PORT = find_free_port(4200)
68-
__SOCKET_PORT = find_free_port(4201)
74+
if "VPYTHON_HTTP_PORT" in os.environ:
75+
__HTTP_PORT = int(os.environ["VPYTHON_HTTP_PORT"])
76+
else:
77+
__HTTP_PORT = find_free_port()
78+
79+
__SOCKET_PORT = find_free_port()
6980

7081
try:
7182
if platform.python_implementation() == 'PyPy':
@@ -74,17 +85,17 @@ def find_free_port(port):
7485
except:
7586
pass
7687

77-
# try: # machinery for reusing ports (2023/12/09 always use 4200 and 4201)
78-
# fd = open('free_ports')
79-
# __HTTP_PORT = int(fd.readline())
80-
# __SOCKET_PORT = int(fd.readline())
88+
# try: # machinery for reusing ports
89+
# fd = open('free_ports')
90+
# __HTTP_PORT = int(fd.readline())
91+
# __SOCKET_PORT = int(fd.readline())
8192
# except:
82-
# __HTTP_PORT = find_free_port()
83-
# __SOCKET_PORT = find_free_port()
84-
# fd = open('free_ports', 'w') # this writes to user program's directory
85-
# fd.write(str(__HTTP_PORT))
86-
# fd.write('\n')
87-
# fd.write(str(__SOCKET_PORT))
93+
# __HTTP_PORT = find_free_port()
94+
# __SOCKET_PORT = find_free_port()
95+
# fd = open('free_ports', 'w') # this writes to user program's directory
96+
# fd.write(str(__HTTP_PORT))
97+
# fd.write('\n')
98+
# fd.write(str(__SOCKET_PORT))
8899

89100
# Make it possible for glowcomm.html to find out what the websocket port is:
90101
js = __file__.replace(
@@ -211,8 +222,18 @@ async def onMessage(self, data, isBinary):
211222
# message format used by notebook
212223
msg = {'content': {'data': [m]}}
213224
loop = asyncio.get_event_loop()
214-
await loop.run_in_executor(None, GW.handle_msg, msg)
215-
225+
try:
226+
await loop.run_in_executor(None, GW.handle_msg, msg)
227+
except:
228+
#
229+
# this will throw a runtime exception after the main Thread
230+
# has stopped, but we don't really case since the main thread
231+
# is no longer there to do anything anyway.
232+
if threading.main_thread().is_alive():
233+
raise
234+
else:
235+
pass
236+
216237
def onClose(self, wasClean, code, reason):
217238
"""Called when browser tab is closed."""
218239
global websocketserving
@@ -229,14 +250,14 @@ def onClose(self, wasClean, code, reason):
229250
# need it here because in spyder the script may have stopped on its
230251
# own ( because it has no infinite loop in it ) so the only signal
231252
# that the tab has been closed comes via the websocket.
232-
if _in_spyder:
253+
if _in_spyder_or_similar_IDE:
233254
_undo_vpython_import_in_spyder()
234255

235256
# We want to exit, but the main thread is running.
236257
# Only the main thread can properly call sys.exit, so have a signal
237258
# handler call it on the main thread's behalf.
238259
if platform.system() == 'Windows':
239-
if threading.main_thread().is_alive() and not _in_spyder:
260+
if threading.main_thread().is_alive() and not _in_spyder_or_similar_IDE:
240261
# On windows, if we get here then this signal won't be caught
241262
# by our signal handler. Just call it ourselves.
242263
os.kill(os.getpid(), signal.CTRL_C_EVENT)
@@ -247,19 +268,24 @@ def onClose(self, wasClean, code, reason):
247268

248269

249270
try:
271+
no_launch = os.environ.get("VPYTHON_NO_LAUNCH_BROWSER", False)
272+
if no_launch=="0":
273+
no_launch=False
250274
if platform.python_implementation() == 'PyPy':
251275
server_address = ('', 0) # let HTTPServer choose a free port
252276
__server = HTTPServer(server_address, serveHTTP)
253277
port = __server.server_port # get the chosen port
254278
# Change the global variable to store the actual port used
255279
__HTTP_PORT = port
256-
_webbrowser.open('http://localhost:{}'.format(port)
280+
if not no_launch:
281+
_webbrowser.open('http://localhost:{}'.format(port)
257282
) # or webbrowser.open_new_tab()
258283
else:
259284
__server = HTTPServer(('', __HTTP_PORT), serveHTTP)
260285
# or webbrowser.open_new_tab()
261-
if _browsertype == 'default': # uses default browser
262-
_webbrowser.open('http://localhost:{}'.format(__HTTP_PORT))
286+
if not no_launch:
287+
if _browsertype == 'default': # uses default browser
288+
_webbrowser.open('http://localhost:{}'.format(__HTTP_PORT))
263289

264290
except:
265291
pass
@@ -293,7 +319,7 @@ def start_Qapp(port):
293319
__m = multiprocessing.Process(target=start_Qapp, args=(__HTTP_PORT,))
294320
__m.start()
295321

296-
__w = threading.Thread(target=__server.serve_forever)
322+
__w = threading.Thread(target=__server.serve_forever, daemon=makeDaemonic)
297323
__w.start()
298324

299325

@@ -326,14 +352,16 @@ def start_websocket_server():
326352
# Put the websocket server in a separate thread running its own event loop.
327353
# That works even if some other program (e.g. spyder) already running an
328354
# async event loop.
329-
__t = threading.Thread(target=start_websocket_server)
355+
__t = threading.Thread(target=start_websocket_server, daemon=makeDaemonic)
330356
__t.start()
331357

332358

333359
def stop_server():
334360
"""Shuts down all threads and exits cleanly."""
361+
#print("in stop server")
335362
global __server
336363
__server.shutdown()
364+
337365
event_loop = txaio.config.loop
338366
event_loop.stop()
339367
# We've told the event loop to stop, but it won't shut down until we poke
@@ -343,28 +371,45 @@ def stop_server():
343371
# If we are in spyder, undo our import. This gets done in the websocket
344372
# server onClose above if the browser tab is closed but is not done
345373
# if the user stops the kernel instead.
346-
if _in_spyder:
374+
if _in_spyder_or_similar_IDE:
347375
_undo_vpython_import_in_spyder()
348376

349377
# We don't want Ctrl-C to try to sys.exit inside spyder, i.e.
350378
# in an ipython console with a separate python kernel running.
351-
if _in_spyder:
379+
if _in_spyder_or_similar_IDE:
352380
raise KeyboardInterrupt
353381

354382
if threading.main_thread().is_alive():
383+
#print("main is alive...")
355384
sys.exit(0)
356385
else:
357-
pass
386+
#
387+
# check to see if the event loop is still going, if so join it.
388+
#
389+
#print("main is dead..")
390+
if __t.is_alive():
391+
#print("__t is alive still")
392+
if threading.get_ident() != __t.ident:
393+
#print("but it's not my thread, so I'll join...")
394+
__t.join()
395+
else:
396+
#print("__t is alive, but that's my thread! So skip it.")
397+
pass
398+
else:
399+
if makeDaemonic:
400+
sys.exit(0)
401+
358402
# If the main thread has already stopped, the python interpreter
359403
# is likely just running .join on the two remaining threads (in
360404
# python/threading.py:_shutdown). Since we just stopped those threads,
361405
# we'll now exit.
362-
363-
406+
364407
GW = GlowWidget()
365408

366409
while not (httpserving and websocketserving): # try to make sure setup is complete
367-
rate(60)
410+
time.sleep(0.1)
411+
368412

369413
# Dummy variable to import
370414
_ = None
415+

0 commit comments

Comments
 (0)