11import os
22import sys
3+ from concurrent .futures import ThreadPoolExecutor , as_completed
34from typing import Tuple , Optional , List
45
56import nbformat
67from nbconvert .preprocessors import ExecutePreprocessor
78
89
910SINGLE_NOTEBOOK_TIMEOUT = 1200
11+ CONCURRENT_WORKERS = 4
1012
1113
1214def 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:
1618def run_notebook (notebook_path : str , root : str ) -> Tuple [bool , Optional [str ]]:
1719 """Execute a single notebook."""
1820 try :
21+ print (f"🔧 running: { notebook_path } " )
1922 with open (notebook_path , encoding = "utf-8" ) as f :
2023 nb = nbformat .read (f , as_version = 4 )
2124
@@ -28,38 +31,57 @@ def run_notebook(notebook_path: str, root: str) -> Tuple[bool, Optional[str]]:
2831 return False , str (e )
2932
3033
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 :
3239 abs_path = os .path .abspath (path )
3340 print (f"🔍 Scanning for notebooks in: { abs_path } \n " )
3441
3542 skip_list = skip_list or []
3643
37- notebook_found : int = 0
38- success_notebooks : List [str ] = []
39- failed_notebooks : List [Tuple [str , str ]] = []
40-
44+ notebook_paths : List [str ] = []
4145 for root , _ , files in os .walk (abs_path ):
4246 for file in files :
4347 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 } " )
4851 continue
52+ notebook_paths .append (full_path )
4953
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 " )
5359
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 ()
5473 if success :
55- print (f"✅ Success: { notebook_path } \n " )
74+ print (f"✅ Success: { notebook_path } " )
5675 success_notebooks .append (notebook_path )
5776 else :
5877 print (f"❌ Failed: { notebook_path } \n Error: { error } \n " )
5978 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 )))
6082
6183 # 📋 Summary
62- print ("🧾 Notebook Execution Summary" )
84+ print ("\n 🧾 Notebook Execution Summary" )
6385 print (f"✅ { len (success_notebooks )} succeeded" )
6486 print (f"❌ { len (failed_notebooks )} failed\n " )
6587
@@ -70,10 +92,6 @@ def run_all_notebooks(path: str = ".", skip_list: List[str] = None) -> None:
7092 print (f" - { nb } \n ↳ { last_line } " )
7193 sys .exit (1 )
7294
73- if notebook_found == 0 :
74- print ("❌ No notebooks were found. Check the folder path or repo contents." )
75- sys .exit (1 )
76-
7795 print ("🏁 All notebooks completed successfully." )
7896
7997
0 commit comments