1212# Global registry mapping lowercase names to task wrapper classes
1313TASK_REGISTRY = {}
1414
15+ # Failures encountered during task discovery: module_name -> short error string.
16+ # Surfaced in get_task_class() KeyError so users see import problems right when
17+ # they look up a missing task, rather than having to enable DEBUG logging.
18+ _DISCOVERY_FAILURES : dict [str , str ] = {}
19+
1520
1621def register_task (* names ):
1722 """Class decorator to register a task under one or more names.
@@ -86,7 +91,9 @@ def _discover_task_modules() -> None:
8691 try :
8792 import_module (module_name )
8893 except Exception as e :
89- log .debug (f"Task discovery: skip module '{ module_name } ': { e } " )
94+ err_str = f"{ type (e ).__name__ } : { e } "
95+ _DISCOVERY_FAILURES [module_name ] = err_str
96+ log .debug (f"Task discovery: skip module '{ module_name } ': { err_str } " )
9097 except Exception as e :
9198 log .error (f"Task discovery: error scanning package '{ pkg_name } ': { e } " )
9299
@@ -105,7 +112,13 @@ def get_task_class(name: str) -> type[BaseTaskEnv]:
105112 return TASK_REGISTRY [key ]
106113 except KeyError as exc :
107114 available = ", " .join (sorted (TASK_REGISTRY .keys ())) or "<none>"
108- raise KeyError (f"Unknown task '{ name } ' " ) from exc
115+ msg = f"Unknown task '{ name } '. Registered tasks: { available } "
116+ if _DISCOVERY_FAILURES :
117+ # Surface import errors so users can see if the task they want
118+ # failed to register because its module raised during import.
119+ failures = "\n " .join (f"{ m } : { e } " for m , e in sorted (_DISCOVERY_FAILURES .items ()))
120+ msg += f"\n \n { len (_DISCOVERY_FAILURES )} task module(s) failed to import during discovery:\n { failures } "
121+ raise KeyError (msg ) from exc
109122
110123
111124def list_tasks ():
0 commit comments