1
1
import os
2
2
import sys
3
+ from concurrent .futures import ThreadPoolExecutor , as_completed
3
4
from typing import Tuple , Optional , List
4
5
5
6
import nbformat
6
7
from nbconvert .preprocessors import ExecutePreprocessor
7
8
8
9
9
10
SINGLE_NOTEBOOK_TIMEOUT = 1200
11
+ CONCURRENT_WORKERS = 4
10
12
11
13
12
14
def should_skip (notebook_path : str , skip_list : List [str ]) -> bool :
@@ -16,6 +18,7 @@ def should_skip(notebook_path: str, skip_list: List[str]) -> bool:
16
18
def run_notebook (notebook_path : str , root : str ) -> Tuple [bool , Optional [str ]]:
17
19
"""Execute a single notebook."""
18
20
try :
21
+ print (f"🔧 running: { notebook_path } " )
19
22
with open (notebook_path , encoding = "utf-8" ) as f :
20
23
nb = nbformat .read (f , as_version = 4 )
21
24
@@ -28,38 +31,57 @@ def run_notebook(notebook_path: str, root: str) -> Tuple[bool, Optional[str]]:
28
31
return False , str (e )
29
32
30
33
31
- def run_all_notebooks (path : str = "." , skip_list : List [str ] = None ) -> None :
34
+ def run_all_notebooks (
35
+ path : str = "." ,
36
+ skip_list : List [str ]= None ,
37
+ max_workers : int = CONCURRENT_WORKERS ,
38
+ ) -> None :
32
39
abs_path = os .path .abspath (path )
33
40
print (f"🔍 Scanning for notebooks in: { abs_path } \n " )
34
41
35
42
skip_list = skip_list or []
36
43
37
- notebook_found : int = 0
38
- success_notebooks : List [str ] = []
39
- failed_notebooks : List [Tuple [str , str ]] = []
40
-
44
+ notebook_paths : List [str ] = []
41
45
for root , _ , files in os .walk (abs_path ):
42
46
for file in files :
43
47
if file .endswith (".ipynb" ) and not file .startswith ("." ):
44
- notebook_path = os .path .join (root , file )
45
-
46
- if should_skip (notebook_path , skip_list ):
47
- print (f"⏭️ Skipped: { notebook_path } " )
48
+ full_path = os .path .join (root , file )
49
+ if should_skip (full_path , skip_list ):
50
+ print (f"⏭️ Skipped: { full_path } " )
48
51
continue
52
+ notebook_paths .append (full_path )
49
53
50
- notebook_found += 1
51
- print (f"▶️ Running: { notebook_path } " )
52
- success , error = run_notebook (notebook_path , root )
54
+ if not notebook_paths :
55
+ print ("❌ No notebooks were found. Check the folder path or repo contents." )
56
+ sys .exit (1 )
57
+
58
+ print (f"▶️ Running { len (notebook_paths )} notebooks using { max_workers } workers...\n " )
53
59
60
+ success_notebooks : List [str ] = []
61
+ failed_notebooks : List [Tuple [str , str ]] = []
62
+
63
+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
64
+ futures = {
65
+ executor .submit (run_notebook , path , os .path .dirname (path )): path
66
+ for path in notebook_paths
67
+ }
68
+
69
+ for future in as_completed (futures ):
70
+ notebook_path = futures [future ]
71
+ try :
72
+ success , error = future .result ()
54
73
if success :
55
- print (f"✅ Success: { notebook_path } \n " )
74
+ print (f"✅ Success: { notebook_path } " )
56
75
success_notebooks .append (notebook_path )
57
76
else :
58
77
print (f"❌ Failed: { notebook_path } \n Error: { error } \n " )
59
78
failed_notebooks .append ((notebook_path , error ))
79
+ except Exception as e :
80
+ print (f"❌ Exception during execution of { notebook_path } \n Error: { e } \n " )
81
+ failed_notebooks .append ((notebook_path , str (e )))
60
82
61
83
# 📋 Summary
62
- print ("🧾 Notebook Execution Summary" )
84
+ print ("\n 🧾 Notebook Execution Summary" )
63
85
print (f"✅ { len (success_notebooks )} succeeded" )
64
86
print (f"❌ { len (failed_notebooks )} failed\n " )
65
87
@@ -70,10 +92,6 @@ def run_all_notebooks(path: str = ".", skip_list: List[str] = None) -> None:
70
92
print (f" - { nb } \n ↳ { last_line } " )
71
93
sys .exit (1 )
72
94
73
- if notebook_found == 0 :
74
- print ("❌ No notebooks were found. Check the folder path or repo contents." )
75
- sys .exit (1 )
76
-
77
95
print ("🏁 All notebooks completed successfully." )
78
96
79
97
0 commit comments