11import os
22import sys
3+ from concurrent .futures import ThreadPoolExecutor , as_completed
34from typing import Tuple , Optional , List
45
56import nbformat
@@ -16,6 +17,7 @@ def should_skip(notebook_path: str, skip_list: List[str]) -> bool:
1617def run_notebook (notebook_path : str , root : str ) -> Tuple [bool , Optional [str ]]:
1718 """Execute a single notebook."""
1819 try :
20+ print (f"🔧 running: { notebook_path } " )
1921 with open (notebook_path , encoding = "utf-8" ) as f :
2022 nb = nbformat .read (f , as_version = 4 )
2123
@@ -28,38 +30,53 @@ def run_notebook(notebook_path: str, root: str) -> Tuple[bool, Optional[str]]:
2830 return False , str (e )
2931
3032
31- def run_all_notebooks (path : str = "." , skip_list : List [str ] = None ) -> None :
33+ def run_all_notebooks (path : str = "." , skip_list : List [str ]= None , max_workers : int = 4 ) -> None :
3234 abs_path = os .path .abspath (path )
3335 print (f"🔍 Scanning for notebooks in: { abs_path } \n " )
3436
3537 skip_list = skip_list or []
3638
37- notebook_found : int = 0
38- success_notebooks : List [str ] = []
39- failed_notebooks : List [Tuple [str , str ]] = []
40-
39+ notebook_paths : List [str ] = []
4140 for root , _ , files in os .walk (abs_path ):
4241 for file in files :
4342 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 } " )
43+ full_path = os .path .join (root , file )
44+ if should_skip (full_path , skip_list ):
45+ print (f"⏭️ Skipped: { full_path } " )
4846 continue
47+ notebook_paths .append (full_path )
4948
50- notebook_found += 1
51- print (f"▶️ Running: { notebook_path } " )
52- success , error = run_notebook (notebook_path , root )
49+ if not notebook_paths :
50+ print ("❌ No notebooks were found. Check the folder path or repo contents." )
51+ sys .exit (1 )
52+
53+ print (f"▶️ Running { len (notebook_paths )} notebooks using { max_workers } workers...\n " )
5354
55+ success_notebooks : List [str ] = []
56+ failed_notebooks : List [Tuple [str , str ]] = []
57+
58+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
59+ futures = {
60+ executor .submit (run_notebook , path , os .path .dirname (path )): path
61+ for path in notebook_paths
62+ }
63+
64+ for future in as_completed (futures ):
65+ notebook_path = futures [future ]
66+ try :
67+ success , error = future .result ()
5468 if success :
55- print (f"✅ Success: { notebook_path } \n " )
69+ print (f"✅ Success: { notebook_path } " )
5670 success_notebooks .append (notebook_path )
5771 else :
5872 print (f"❌ Failed: { notebook_path } \n Error: { error } \n " )
5973 failed_notebooks .append ((notebook_path , error ))
74+ except Exception as e :
75+ print (f"❌ Exception during execution of { notebook_path } \n Error: { e } \n " )
76+ failed_notebooks .append ((notebook_path , str (e )))
6077
6178 # 📋 Summary
62- print ("🧾 Notebook Execution Summary" )
79+ print ("\n 🧾 Notebook Execution Summary" )
6380 print (f"✅ { len (success_notebooks )} succeeded" )
6481 print (f"❌ { len (failed_notebooks )} failed\n " )
6582
@@ -70,10 +87,6 @@ def run_all_notebooks(path: str = ".", skip_list: List[str] = None) -> None:
7087 print (f" - { nb } \n ↳ { last_line } " )
7188 sys .exit (1 )
7289
73- if notebook_found == 0 :
74- print ("❌ No notebooks were found. Check the folder path or repo contents." )
75- sys .exit (1 )
76-
7790 print ("🏁 All notebooks completed successfully." )
7891
7992
0 commit comments