3
3
A utility to watch and stream outputs of local ipykernel events.
4
4
"""
5
5
6
- import asyncio
7
- from jupyter_client .asynchronous import AsyncKernelClient
6
+ import logging
7
+ from jupyter_client .blocking . client import BlockingKernelClient
8
8
from watchdog .observers import Observer
9
9
from watchdog .events import FileSystemEventHandler
10
10
import glob
11
11
import os
12
- import janus
12
+ import re
13
+ import threading
14
+ from queue import Queue
15
+
16
+ ansi_escape = re .compile (r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])" )
17
+ logger = logging .getLogger (__name__ )
13
18
14
19
15
20
class NewFileHandler (FileSystemEventHandler ):
@@ -19,8 +24,8 @@ def __init__(self, queue):
19
24
def on_created (self , event ):
20
25
if event .is_directory or not event .src_path .endswith (".json" ):
21
26
return
22
- print (f"New kernel detected: { event .src_path } " )
23
- self .queue .sync_q . put (event .src_path )
27
+ logger . debug (f"New kernel detected: { event .src_path } " )
28
+ self .queue .put (event .src_path )
24
29
25
30
26
31
def process_msg (msg ):
@@ -30,52 +35,86 @@ def process_msg(msg):
30
35
if "data" in msg ["content" ]
31
36
else msg ["content" ].get ("text" , "" )
32
37
)
33
- print (f"Output: { output } " )
38
+ clean_text = ansi_escape .sub ("" , output )
39
+ logger .info (clean_text .rstrip ("\n " ))
34
40
else :
35
- print (f"Unhandled message type: { msg ['msg_type' ]} " )
36
- print (msg )
41
+ logger . debug (f"Unhandled message type: { msg ['msg_type' ]} " )
42
+ logger . debug (msg )
37
43
38
44
39
- async def watch_kernel (connection_file ):
40
- kc = AsyncKernelClient (connection_file = connection_file )
45
+ def watch_kernel (connection_file , stop_event ):
46
+ kc = BlockingKernelClient (connection_file = connection_file )
41
47
kc .load_connection_file ()
42
48
kc .start_channels ()
43
49
44
- while True :
45
- msg = await kc .iopub_channel .get_msg ()
46
- process_msg (msg )
50
+ while not stop_event .is_set ():
51
+ try :
52
+ msg = kc .get_iopub_msg (timeout = 1 )
53
+ if msg :
54
+ process_msg (msg )
55
+ except Exception as _ :
56
+ continue
47
57
48
58
49
- async def watch_queue (queue , watched_files ):
50
- while True :
51
- new_file = await queue . async_q .get ()
59
+ def watch_queue (queue , watched_files , stop_event ):
60
+ while not stop_event . is_set () :
61
+ new_file = queue .get ()
52
62
if new_file not in watched_files :
53
- print (f"Processing new kernel: { new_file } " )
63
+ logger . debug (f"Processing new kernel: { new_file } " )
54
64
watched_files .add (new_file )
55
- asyncio . create_task ( watch_kernel (new_file ) )
65
+ threading . Thread ( target = watch_kernel , args = (new_file , stop_event )). start ( )
56
66
57
67
58
- async def main ():
68
+ def start_watches ():
69
+ stop_event = threading .Event ()
70
+ threads = []
59
71
paths_to_watch = [
60
- f"{ os .path .expanduser ('~' )} /Library/Jupyter/runtime/" , # only works on mac OS
61
- "/private/var/folders/9n/1rd9yjf913s10bzn5w9mdf_m0000gn/T/" , # I'm certain this is not portable as is
72
+ f"{ os .path .expanduser ('~' )} /Library/Jupyter/runtime/" ,
73
+ "/private/var/folders/9n/1rd9yjf913s10bzn5w9mdf_m0000gn/T/" ,
62
74
"/tmp/" ,
63
75
]
64
76
65
77
existing_files = {
66
78
f for path in paths_to_watch for f in glob .glob (os .path .join (path , "*.json" ))
67
79
}
68
- print (f"Watching { len (existing_files )} existing files" )
69
-
70
- queue = janus .Queue ()
71
-
72
- tasks = [watch_kernel (config_file ) for config_file in existing_files ]
80
+ logger .info (f"Watching { len (existing_files )} existing files" )
81
+
82
+ queue = Queue ()
83
+ watched_files = set (existing_files )
84
+ for config_file in existing_files :
85
+ watch_thread = threading .Thread (
86
+ target = watch_kernel ,
87
+ args = (
88
+ config_file ,
89
+ stop_event ,
90
+ ),
91
+ )
92
+ watch_thread .start ()
93
+ threads .append (watch_thread )
73
94
74
95
observer = Observer ()
75
96
for path in paths_to_watch :
76
97
observer .schedule (NewFileHandler (queue ), path , recursive = True )
77
98
78
- observer_task = asyncio .get_event_loop ().run_in_executor (None , observer .start )
79
- queue_watcher = watch_queue (queue , existing_files )
99
+ observer .start ()
100
+
101
+ # Start the watch_queue function in a separate thread
102
+ watch_queue_thread = threading .Thread (
103
+ target = watch_queue ,
104
+ args = (
105
+ queue ,
106
+ watched_files ,
107
+ stop_event ,
108
+ ),
109
+ )
110
+ watch_queue_thread .start ()
111
+ threads .append (watch_queue_thread )
112
+ return threads , stop_event
113
+
114
+
115
+ def main ():
116
+ start_watches ()
117
+
80
118
81
- await asyncio .gather (* tasks , observer_task , queue_watcher )
119
+ if __name__ == "__main__" :
120
+ main ()
0 commit comments