1818 UnrecognisedEventError ,
1919 ValidationError ,
2020)
21+ from plugboard .schemas .state import Status
2122from plugboard .state import StateBackend
2223from plugboard .utils import DI , ClassRegistry , ExportMixin , is_on_ray_worker
2324
@@ -73,6 +74,8 @@ def __init__(
7374 namespace = self .name ,
7475 component = self ,
7576 )
77+ self .status = Status .CREATED
78+ self ._is_running = False
7679 self ._field_inputs : dict [str , _t .Any ] = {}
7780 self ._field_inputs_ready : bool = False
7881
@@ -88,6 +91,12 @@ def __init_subclass__(cls, *args: _t.Any, **kwargs: _t.Any) -> None:
8891 # Configure IO last in case it fails in case of components with dynamic io args
8992 cls ._configure_io ()
9093
94+ async def _set_status (self , status : Status , publish : bool = True ) -> None :
95+ """Sets the status of the component and optionaly publishes it to the state backend."""
96+ self .status = status
97+ if publish and self ._state and self ._state_is_connected :
98+ await self ._state .upsert_component (self )
99+
91100 @classmethod
92101 def _configure_io (cls ) -> None :
93102 # Get all parent classes that are Component subclasses
@@ -203,8 +212,7 @@ def _handle_init_wrapper(self) -> _t.Callable:
203212 async def _wrapper () -> None :
204213 with self ._job_id_ctx ():
205214 await self ._init ()
206- if self ._state is not None and self ._state_is_connected :
207- await self ._state .upsert_component (self )
215+ await self ._set_status (Status .INIT )
208216
209217 return _wrapper
210218
@@ -233,14 +241,21 @@ def _handle_step_wrapper(self) -> _t.Callable:
233241 @wraps (self .step )
234242 async def _wrapper () -> None :
235243 with self ._job_id_ctx ():
244+ await self ._set_status (Status .RUNNING , publish = not self ._is_running )
236245 await self .io .read ()
237246 await self ._handle_events ()
238247 self ._bind_inputs ()
239248 if self ._can_step :
240- await self ._step ()
249+ try :
250+ await self ._step ()
251+ except Exception as e :
252+ await self ._set_status (Status .FAILED )
253+ self ._logger .exception ("Component step failed" )
254+ raise e
241255 self ._bind_outputs ()
242256 await self .io .write ()
243257 self ._field_inputs_ready = False
258+ await self ._set_status (Status .WAITING , publish = not self ._is_running )
244259
245260 return _wrapper
246261
@@ -304,14 +319,21 @@ async def _stop_event_handler(self, event: StopEvent) -> None:
304319 await self .io .close ()
305320 except IOStreamClosedError :
306321 pass
322+ await self ._set_status (Status .STOPPED )
307323
308324 async def run (self ) -> None :
309325 """Executes component logic for all steps to completion."""
310- while True :
311- try :
312- await self .step ()
313- except IOStreamClosedError :
314- break
326+ self ._is_running = True
327+ await self ._set_status (Status .RUNNING )
328+ try :
329+ while True :
330+ try :
331+ await self .step ()
332+ except IOStreamClosedError :
333+ break
334+ await self ._set_status (Status .COMPLETED )
335+ finally :
336+ self ._is_running = False
315337
316338 async def destroy (self ) -> None :
317339 """Performs tear-down actions for `Component`."""
@@ -327,6 +349,7 @@ def dict(self) -> dict[str, _t.Any]: # noqa: D102
327349 return {
328350 "id" : self .id ,
329351 "name" : self .name ,
352+ "status" : self .status ,
330353 ** field_data ,
331354 "exports" : {name : getattr (self , name , None ) for name in self .exports or []},
332355 }
0 commit comments