2525)
2626from .integrations import Integration
2727from .logging import configure_logging , setup_logger
28- from .loop import Loop , LoopEvent , LoopManager , WorkflowBlock , WorkflowManager
28+ from .loop import Loop , LoopEvent , LoopManager , Workflow , WorkflowBlock , WorkflowManager
2929from .state .state import StateManager , create_state_manager
3030from .types import BaseConfig , LoopStatus
3131from .utils import get_func_import_path , import_func_from_path , infer_application_path
3232
3333logger = setup_logger ()
3434
3535
36+ def _resolve_event_key (event : str | Enum | type [LoopEvent ] | None ) -> str | None :
37+ """Convert event type/enum/string to string key."""
38+ if not event :
39+ return None
40+ if isinstance (event , type ) and issubclass (event , LoopEvent ):
41+ return event .type
42+ if hasattr (event , "value" ):
43+ return event .value # type: ignore
44+ return event # type: ignore
45+
46+
3647class FastLoop (FastAPI ):
3748 def __init__ (
3849 self ,
@@ -206,14 +217,7 @@ def _decorator(
206217 )
207218 integration .register (self , name )
208219
209- start_event_key = None
210- if start_event :
211- if isinstance (start_event , type ) and issubclass (start_event , LoopEvent ):
212- start_event_key = start_event .type
213- elif hasattr (start_event , "value" ):
214- start_event_key = start_event .value # type: ignore
215- else :
216- start_event_key = start_event
220+ start_event_key = _resolve_event_key (start_event )
217221
218222 if name not in self ._loop_metadata :
219223 self ._loop_metadata [name ] = {
@@ -456,62 +460,45 @@ def workflow(
456460 on_stop : Callable [..., Any ] | None = None ,
457461 on_block_complete : Callable [..., Any ] | None = None ,
458462 on_error : Callable [..., Any ] | None = None ,
459- ) -> Callable [[Callable [..., Any ]], Callable [..., Any ]]:
460- """
461- Decorator to define a durable workflow.
462-
463- A workflow processes a list of blocks sequentially. State is persisted
464- after each block transition, so workflows survive service restarts.
465-
466- Args:
467- name: URL path for the workflow (e.g., "onboarding" → POST /onboarding)
468- start_event: Required event type to start the workflow
469- on_start: Called when workflow starts
470- on_stop: Called when workflow stops (complete or error)
471- on_block_complete: Called after each block completes
472- on_error: Called when a block raises an exception
473-
474- The decorated function receives:
475- ctx: LoopContext with block_index, block_count, previous_payload
476- blocks: List of all WorkflowBlock objects
477- current_block: The block being executed
478-
479- Control flow in the function:
480- ctx.next(payload) → advance to next block
481- ctx.repeat() → re-execute current block
482- normal return → workflow completes
483-
484- HTTP routes created:
485- POST /{name} → start workflow
486- GET /{name}/{id} → get workflow status
487- POST /{name}/{id}/event → send event to workflow
488- POST /{name}/{id}/stop → stop workflow
489- """
490-
491- def _decorator (func : Callable [..., Any ]) -> Callable [..., Any ]:
492- # Resolve start_event to a string key
493- start_event_key = None
494- if start_event :
495- if isinstance (start_event , type ) and issubclass (start_event , LoopEvent ):
496- start_event_key = start_event .type
497- elif hasattr (start_event , "value" ):
498- start_event_key = start_event .value
499- else :
500- start_event_key = start_event
463+ ) -> Callable [
464+ [Callable [..., Any ] | type [Workflow ]], Callable [..., Any ] | type [Workflow ]
465+ ]:
466+ def _decorator (
467+ func_or_class : Callable [..., Any ] | type [Workflow ],
468+ ) -> Callable [..., Any ] | type [Workflow ]:
469+ is_class_based = isinstance (func_or_class , type ) and issubclass (
470+ func_or_class , Workflow
471+ )
472+
473+ if is_class_based :
474+ workflow_instance : Workflow = func_or_class ()
475+ func = workflow_instance .execute
476+ workflow_on_start = workflow_instance .on_start
477+ workflow_on_stop = workflow_instance .on_stop
478+ workflow_on_block_complete = workflow_instance .on_block_complete
479+ workflow_on_error = workflow_instance .on_error
480+ else :
481+ workflow_instance = None # type: ignore
482+ func = func_or_class # type: ignore
483+ workflow_on_start = on_start
484+ workflow_on_stop = on_stop
485+ workflow_on_block_complete = on_block_complete
486+ workflow_on_error = on_error
487+
488+ start_event_key = _resolve_event_key (start_event )
501489
502490 if name in self ._workflow_metadata :
503491 raise LoopAlreadyDefinedError (f"Workflow { name } already registered" )
504492
505493 self ._workflow_metadata [name ] = {
506494 "func" : func ,
507- "on_start" : on_start ,
508- "on_stop" : on_stop ,
509- "on_block_complete" : on_block_complete ,
510- "on_error" : on_error ,
495+ "on_start" : workflow_on_start ,
496+ "on_stop" : workflow_on_stop ,
497+ "on_block_complete" : workflow_on_block_complete ,
498+ "on_error" : workflow_on_error ,
499+ "workflow_instance" : workflow_instance ,
511500 }
512501
513- # --- HTTP Handlers ---
514-
515502 class WorkflowStartRequest (BaseModel ):
516503 type : str
517504 blocks : list [WorkflowBlock ]
@@ -557,10 +544,10 @@ async def _start_handler(request: WorkflowStartRequest):
557544 func ,
558545 context ,
559546 workflow ,
560- on_start = on_start ,
561- on_stop = on_stop ,
562- on_block_complete = on_block_complete ,
563- on_error = on_error ,
547+ on_start = workflow_on_start ,
548+ on_stop = workflow_on_stop ,
549+ on_block_complete = workflow_on_block_complete ,
550+ on_error = workflow_on_error ,
564551 )
565552 return (
566553 await self .state_manager .get_workflow (workflow .workflow_id )
@@ -621,7 +608,6 @@ async def _event_handler(request: dict[str, Any]):
621608 await self .state_manager .push_event (workflow_id , event )
622609 return workflow .to_dict ()
623610
624- # --- Register routes ---
625611 self .add_api_route (f"/{ name } " , _start_handler , methods = ["POST" ])
626612 self .add_api_route (
627613 f"/{ name } /{{workflow_id}}" , _get_handler , methods = ["GET" ]
@@ -633,7 +619,7 @@ async def _event_handler(request: dict[str, Any]):
633619 f"/{ name } /{{workflow_id}}/stop" , _stop_handler , methods = ["POST" ]
634620 )
635621
636- return func
622+ return func_or_class
637623
638624 return _decorator
639625
0 commit comments