ddeutil-workflow 0.0.85__py3-none-any.whl → 0.0.86__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -122,6 +122,7 @@ from .utils import (
122
122
  extract_id,
123
123
  filter_func,
124
124
  gen_id,
125
+ get_dt_now,
125
126
  make_exec,
126
127
  to_train,
127
128
  )
@@ -133,9 +134,9 @@ DictOrModel = Union[DictData, BaseModel]
133
134
  class BaseStage(BaseModel, ABC):
134
135
  """Abstract base class for all stage implementations.
135
136
 
136
- BaseStage provides the foundation for all stage types in the workflow system.
137
- It defines the common interface and metadata fields that all stages must
138
- implement, ensuring consistent behavior across different stage types.
137
+ BaseStage provides the foundation for all stage types in the workflow
138
+ system. It defines the common interface and metadata fields that all stages
139
+ must implement, ensuring consistent behavior across different stage types.
139
140
 
140
141
  This abstract class handles core stage functionality including:
141
142
  - Stage identification and naming
@@ -143,7 +144,7 @@ class BaseStage(BaseModel, ABC):
143
144
  - Output management and templating
144
145
  - Execution lifecycle management
145
146
 
146
- Custom stages should inherit from this class and implement the abstract
147
+ Custom stages should inherit from this class and implement the abstract
147
148
  `process()` method to define their specific execution behavior.
148
149
 
149
150
  Attributes:
@@ -189,7 +190,7 @@ class BaseStage(BaseModel, ABC):
189
190
  default=None,
190
191
  description=(
191
192
  "A stage condition statement to allow stage executable. This field "
192
- "alise with `if` field."
193
+ "alias with `if` key."
193
194
  ),
194
195
  alias="if",
195
196
  )
@@ -260,6 +261,13 @@ class BaseStage(BaseModel, ABC):
260
261
  """Process abstraction method that action something by sub-model class.
261
262
  This is important method that make this class is able to be the stage.
262
263
 
264
+ For process method, it designs to break process with any status by
265
+ raise it with a specific exception class.
266
+
267
+ - StageError -> FAILED
268
+ - StageSkipError -> SKIP
269
+ - StageCancelError -> CANCEL
270
+
263
271
  Args:
264
272
  params (DictData): A parameter data that want to use in this
265
273
  execution.
@@ -321,13 +329,16 @@ class BaseStage(BaseModel, ABC):
321
329
  parent_run_id, run_id = extract_id(
322
330
  self.iden, run_id=run_id, extras=self.extras
323
331
  )
324
- context: DictData = {"status": WAIT}
332
+ context: DictData = {
333
+ "status": WAIT,
334
+ "info": {"exec_start": get_dt_now()},
335
+ }
325
336
  trace: Trace = get_trace(
326
337
  run_id, parent_run_id=parent_run_id, extras=self.extras
327
338
  )
328
339
  try:
329
340
  _id: str = (
330
- f" with ID: {param2template(self.id, params=params)!r}"
341
+ f" with ID: {self.pass_template(self.id, params=params)!r}"
331
342
  if self.id
332
343
  else ""
333
344
  )
@@ -348,84 +359,73 @@ class BaseStage(BaseModel, ABC):
348
359
 
349
360
  # NOTE: Start call wrapped execution method that will use custom
350
361
  # execution before the real execution from inherit stage model.
351
- result_caught: Result = self._execute(
362
+ result: Result = self._execute(
352
363
  params,
353
- run_id=run_id,
354
364
  context=context,
355
- parent_run_id=parent_run_id,
365
+ trace=trace,
356
366
  event=event,
357
367
  )
358
- if result_caught.status == WAIT: # pragma: no cov
368
+ if result.status == WAIT: # pragma: no cov
359
369
  raise StageError(
360
370
  "Status from execution should not return waiting status."
361
371
  )
362
- return result_caught.make_info(
363
- {"execution_time": time.monotonic() - ts}
364
- )
372
+ return result
365
373
 
366
374
  # NOTE: Catch this error in this line because the execution can raise
367
375
  # this exception class at other location.
368
- except (
369
- StageSkipError,
370
- StageNestedSkipError,
371
- StageNestedError,
372
- StageError,
373
- ) as e: # pragma: no cov
376
+ except StageError as e: # pragma: no cov
374
377
  updated: Optional[DictData] = {"errors": e.to_dict()}
375
378
  if isinstance(e, StageNestedError):
376
- trace.error(f"[STAGE]: Nested: {e}")
379
+ trace.error(f"[STAGE]: ⚠️ Nested: {e}")
377
380
  elif isinstance(e, (StageSkipError, StageNestedSkipError)):
378
- trace.error(f"[STAGE]: ⏭️ Skip: {e}")
381
+ trace.error(f"[STAGE]: ⏭️ Skip: {e}", module="stage")
379
382
  updated = None
380
383
  elif e.allow_traceback:
381
384
  trace.error(
382
- f"[STAGE]: Stage Failed:||🚨 {traceback.format_exc()}||"
385
+ f"[STAGE]: 📢 Stage Failed:||🚨 {traceback.format_exc()}||"
383
386
  )
384
387
  else:
385
388
  trace.error(
386
389
  f"[STAGE]: 🤫 Stage Failed with disable traceback:||{e}"
387
390
  )
388
391
  st: Status = get_status_from_error(e)
389
- return Result(
390
- run_id=run_id,
391
- parent_run_id=parent_run_id,
392
+ return Result.from_trace(trace).catch(
392
393
  status=st,
393
394
  context=catch(context, status=st, updated=updated),
394
- info={"execution_time": time.monotonic() - ts},
395
- extras=self.extras,
396
395
  )
397
396
  except Exception as e:
398
397
  trace.error(
399
- f"[STAGE]: Error Failed:||🚨 {traceback.format_exc()}||"
398
+ f"💥 Error Failed:||🚨 {traceback.format_exc()}||",
399
+ module="stage",
400
400
  )
401
- return Result(
402
- run_id=run_id,
403
- parent_run_id=parent_run_id,
401
+ return Result.from_trace(trace).catch(
404
402
  status=FAILED,
405
403
  context=catch(
406
404
  context, status=FAILED, updated={"errors": to_dict(e)}
407
405
  ),
408
- info={"execution_time": time.monotonic() - ts},
409
- extras=self.extras,
410
406
  )
411
407
  finally:
412
- trace.debug("[STAGE]: End Handler stage execution.")
408
+ context["info"].update(
409
+ {
410
+ "exec_end": get_dt_now(),
411
+ "exec_latency": round(time.monotonic() - ts, 6),
412
+ }
413
+ )
414
+ trace.debug("End Handler stage execution.", module="stage")
413
415
 
414
416
  def _execute(
415
417
  self,
416
418
  params: DictData,
417
- run_id: str,
418
419
  context: DictData,
419
- parent_run_id: Optional[str] = None,
420
+ trace: Trace,
420
421
  event: Optional[Event] = None,
421
422
  ) -> Result:
422
423
  """Wrapped the process method before returning to handler execution.
423
424
 
424
425
  Args:
425
426
  params: A parameter data that want to use in this execution.
426
- run_id (str):
427
427
  context:
428
- parent_run_id:
428
+ trace (Trace):
429
429
  event: An event manager that use to track parent process
430
430
  was not force stopped.
431
431
 
@@ -435,9 +435,9 @@ class BaseStage(BaseModel, ABC):
435
435
  catch(context, status=WAIT)
436
436
  return self.process(
437
437
  params,
438
- run_id=run_id,
438
+ run_id=trace.run_id,
439
439
  context=context,
440
- parent_run_id=parent_run_id,
440
+ parent_run_id=trace.parent_run_id,
441
441
  event=event,
442
442
  )
443
443
 
@@ -454,7 +454,7 @@ class BaseStage(BaseModel, ABC):
454
454
  For example of setting output method, If you receive process output
455
455
  and want to set on the `to` like;
456
456
 
457
- ... (i) output: {'foo': 'bar', 'skipped': True}
457
+ ... (i) output: {'foo': 'bar', 'status': SUCCESS, 'info': {}}
458
458
  ... (ii) to: {'stages': {}}
459
459
 
460
460
  The received context in the `to` argument will be;
@@ -463,7 +463,8 @@ class BaseStage(BaseModel, ABC):
463
463
  'stages': {
464
464
  '<stage-id>': {
465
465
  'outputs': {'foo': 'bar'},
466
- 'skipped': True,
466
+ 'status': SUCCESS,
467
+ 'info': {},
467
468
  }
468
469
  }
469
470
  }
@@ -504,8 +505,13 @@ class BaseStage(BaseModel, ABC):
504
505
  status: dict[str, Status] = (
505
506
  {"status": output.pop("status")} if "status" in output else {}
506
507
  )
508
+ info: DictData = (
509
+ {"info": output.pop("info")} if "info" in output else {}
510
+ )
507
511
  kwargs: DictData = kwargs or {}
508
- to["stages"][_id] = {"outputs": output} | errors | status | kwargs
512
+ to["stages"][_id] = (
513
+ {"outputs": output} | errors | status | info | kwargs
514
+ )
509
515
  return to
510
516
 
511
517
  def get_outputs(self, output: DictData) -> DictData:
@@ -533,15 +539,18 @@ class BaseStage(BaseModel, ABC):
533
539
  """Return true if condition of this stage do not correct. This process
534
540
  use build-in eval function to execute the if-condition.
535
541
 
536
- :param params: (DictData) A parameters that want to pass to condition
537
- template.
542
+ Args:
543
+ params (DictData): A parameters that want to pass to condition
544
+ template.
538
545
 
539
- :raise StageError: When it has any error raise from the eval
540
- condition statement.
541
- :raise StageError: When return type of the eval condition statement
542
- does not return with boolean type.
546
+ Raises:
547
+ StageError: When it has any error raise from the eval
548
+ condition statement.
549
+ StageError: When return type of the eval condition statement
550
+ does not return with boolean type.
543
551
 
544
- :rtype: bool
552
+ Returns:
553
+ bool: True if the condition is valid with the current parameters.
545
554
  """
546
555
  # NOTE: Support for condition value is empty string.
547
556
  if not self.condition:
@@ -569,9 +578,11 @@ class BaseStage(BaseModel, ABC):
569
578
  """Generate stage ID that dynamic use stage's name if it ID does not
570
579
  set.
571
580
 
572
- :param params: (DictData) A parameter or context data.
581
+ Args:
582
+ params (DictData): A parameter or context data.
573
583
 
574
- :rtype: str
584
+ Returns:
585
+ str: An ID that already generated from id or name fields.
575
586
  """
576
587
  return (
577
588
  param2template(self.id, params=params, extras=self.extras)
@@ -779,15 +790,19 @@ class BaseAsyncStage(BaseStage, ABC):
779
790
  Result: The execution result with status and context data.
780
791
  """
781
792
  ts: float = time.monotonic()
782
- parent_run_id: StrOrNone = run_id
783
- run_id: str = gen_id(self.iden, unique=True, extras=self.extras)
784
- context: DictData = {}
793
+ parent_run_id, run_id = extract_id(
794
+ self.iden, run_id=run_id, extras=self.extras
795
+ )
796
+ context: DictData = {
797
+ "status": WAIT,
798
+ "info": {"exec_start": get_dt_now()},
799
+ }
785
800
  trace: Trace = get_trace(
786
801
  run_id, parent_run_id=parent_run_id, extras=self.extras
787
802
  )
788
803
  try:
789
804
  _id: str = (
790
- f" with ID: {param2template(self.id, params=params)!r}"
805
+ f" with ID: {self.pass_template(self.id, params=params)!r}"
791
806
  if self.id
792
807
  else ""
793
808
  )
@@ -808,67 +823,59 @@ class BaseAsyncStage(BaseStage, ABC):
808
823
 
809
824
  # NOTE: Start call wrapped execution method that will use custom
810
825
  # execution before the real execution from inherit stage model.
811
- result_caught: Result = await self._axecute(
826
+ result: Result = await self._axecute(
812
827
  params,
813
828
  run_id=run_id,
814
829
  context=context,
815
830
  parent_run_id=parent_run_id,
816
831
  event=event,
817
832
  )
818
- if result_caught.status == WAIT: # pragma: no cov
833
+ if result.status == WAIT: # pragma: no cov
819
834
  raise StageError(
820
835
  "Status from execution should not return waiting status."
821
836
  )
822
- return result_caught
837
+ return result
823
838
 
824
839
  # NOTE: Catch this error in this line because the execution can raise
825
840
  # this exception class at other location.
826
- except (
827
- StageSkipError,
828
- StageNestedSkipError,
829
- StageNestedError,
830
- StageError,
831
- ) as e: # pragma: no cov
841
+ except StageError as e: # pragma: no cov
842
+ updated: Optional[DictData] = {"errors": e.to_dict()}
832
843
  if isinstance(e, StageNestedError):
833
- await trace.ainfo(f"[STAGE]: Nested: {e}")
844
+ await trace.aerror(f"[STAGE]: ⚠️ Nested: {e}")
834
845
  elif isinstance(e, (StageSkipError, StageNestedSkipError)):
835
- await trace.ainfo(f"[STAGE]: ⏭️ Skip: {e}")
846
+ await trace.aerror(f"[STAGE]: ⏭️ Skip: {e}")
847
+ updated = None
848
+ elif e.allow_traceback:
849
+ await trace.aerror(
850
+ f"[STAGE]: 📢 Stage Failed:||🚨 {traceback.format_exc()}||"
851
+ )
836
852
  else:
837
- await trace.ainfo(
838
- f"[STAGE]: Stage Failed:||🚨 {traceback.format_exc()}||"
853
+ await trace.aerror(
854
+ f"[STAGE]: 🤫 Stage Failed with disable traceback:||{e}"
839
855
  )
840
856
  st: Status = get_status_from_error(e)
841
- return Result(
842
- run_id=run_id,
843
- parent_run_id=parent_run_id,
857
+ return Result.from_trace(trace).catch(
844
858
  status=st,
845
- context=catch(
846
- context,
847
- status=st,
848
- updated=(
849
- None
850
- if isinstance(e, (StageSkipError, StageNestedSkipError))
851
- else {"errors": e.to_dict()}
852
- ),
853
- ),
854
- info={"execution_time": time.monotonic() - ts},
855
- extras=self.extras,
859
+ context=catch(context, status=st, updated=updated),
856
860
  )
857
861
  except Exception as e:
858
862
  await trace.aerror(
859
- f"[STAGE]: Error Failed:||🚨 {traceback.format_exc()}||"
863
+ f"💥 Error Failed:||🚨 {traceback.format_exc()}||",
864
+ module="stage",
860
865
  )
861
- return Result(
862
- run_id=run_id,
863
- parent_run_id=parent_run_id,
866
+ return Result.from_trace(trace).catch(
864
867
  status=FAILED,
865
868
  context=catch(
866
869
  context, status=FAILED, updated={"errors": to_dict(e)}
867
870
  ),
868
- info={"execution_time": time.monotonic() - ts},
869
- extras=self.extras,
870
871
  )
871
872
  finally:
873
+ context["info"].update(
874
+ {
875
+ "exec_end": get_dt_now(),
876
+ "exec_latency": time.monotonic() - ts,
877
+ }
878
+ )
872
879
  trace.debug("[STAGE]: End Handler stage process.")
873
880
 
874
881
  async def _axecute(
@@ -905,6 +912,7 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
905
912
  `StageRetryError`.
906
913
  """
907
914
 
915
+ action_stage: ClassVar[bool] = True
908
916
  retry: int = Field(
909
917
  default=0,
910
918
  ge=0,
@@ -918,9 +926,8 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
918
926
  def _execute(
919
927
  self,
920
928
  params: DictData,
921
- run_id: str,
922
929
  context: DictData,
923
- parent_run_id: Optional[str] = None,
930
+ trace: Trace,
924
931
  event: Optional[Event] = None,
925
932
  ) -> Result:
926
933
  """Wrapped the execute method with retry strategy before returning to
@@ -938,9 +945,6 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
938
945
  current_retry: int = 0
939
946
  exception: Exception
940
947
  catch(context, status=WAIT)
941
- trace: Trace = get_trace(
942
- run_id, parent_run_id=parent_run_id, extras=self.extras
943
- )
944
948
  # NOTE: First execution for not pass to retry step if it passes.
945
949
  try:
946
950
  if (
@@ -949,16 +953,16 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
949
953
  ):
950
954
  return self.dryrun(
951
955
  params | {"retry": current_retry},
952
- run_id=run_id,
956
+ run_id=trace.run_id,
953
957
  context=context,
954
- parent_run_id=parent_run_id,
958
+ parent_run_id=trace.parent_run_id,
955
959
  event=event,
956
960
  )
957
961
  return self.process(
958
962
  params | {"retry": current_retry},
959
- run_id=run_id,
963
+ run_id=trace.run_id,
960
964
  context=context,
961
- parent_run_id=parent_run_id,
965
+ parent_run_id=trace.parent_run_id,
962
966
  event=event,
963
967
  )
964
968
  except (
@@ -970,13 +974,11 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
970
974
  trace.debug("[STAGE]: process raise skip or cancel error.")
971
975
  raise
972
976
  except Exception as e:
977
+ if self.retry == 0:
978
+ raise
979
+
973
980
  current_retry += 1
974
981
  exception = e
975
- finally:
976
- trace.debug("[STAGE]: Failed at the first execution.")
977
-
978
- if self.retry == 0:
979
- raise exception
980
982
 
981
983
  trace.warning(
982
984
  f"[STAGE]: Retry count: {current_retry} ... "
@@ -996,16 +998,16 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
996
998
  ):
997
999
  return self.dryrun(
998
1000
  params | {"retry": current_retry},
999
- run_id=run_id,
1001
+ run_id=trace.run_id,
1000
1002
  context=context,
1001
- parent_run_id=parent_run_id,
1003
+ parent_run_id=trace.parent_run_id,
1002
1004
  event=event,
1003
1005
  )
1004
1006
  return self.process(
1005
1007
  params | {"retry": current_retry},
1006
- run_id=run_id,
1008
+ run_id=trace.run_id,
1007
1009
  context=context,
1008
- parent_run_id=parent_run_id,
1010
+ parent_run_id=trace.parent_run_id,
1009
1011
  event=event,
1010
1012
  )
1011
1013
  except (
@@ -1086,13 +1088,11 @@ class BaseRetryStage(BaseAsyncStage, ABC): # pragma: no cov
1086
1088
  await trace.adebug("[STAGE]: process raise skip or cancel error.")
1087
1089
  raise
1088
1090
  except Exception as e:
1091
+ if self.retry == 0:
1092
+ raise
1093
+
1089
1094
  current_retry += 1
1090
1095
  exception = e
1091
- finally:
1092
- await trace.adebug("[STAGE]: Failed at the first execution.")
1093
-
1094
- if self.retry == 0:
1095
- raise exception
1096
1096
 
1097
1097
  await trace.awarning(
1098
1098
  f"[STAGE]: Retry count: {current_retry} ... "
@@ -1172,6 +1172,7 @@ class EmptyStage(BaseAsyncStage):
1172
1172
  ... })
1173
1173
  """
1174
1174
 
1175
+ action_stage: ClassVar[bool] = True
1175
1176
  echo: StrOrNone = Field(
1176
1177
  default=None,
1177
1178
  description=(
@@ -1205,13 +1206,14 @@ class EmptyStage(BaseAsyncStage):
1205
1206
  without calling logging function.
1206
1207
 
1207
1208
  Args:
1208
- params: A parameter data that want to use in this
1209
+ params (DictData): A parameter data that want to use in this
1209
1210
  execution.
1210
- run_id: A running stage ID.
1211
- context: A context data.
1212
- parent_run_id: A parent running ID. (Default is None)
1213
- event: An event manager that use to track parent process
1214
- was not force stopped.
1211
+ run_id (str): A running stage ID.
1212
+ context (DictData): A context data that was passed from handler
1213
+ method.
1214
+ parent_run_id (str, default None): A parent running ID.
1215
+ event (Event, default None): An event manager that use to track
1216
+ parent process was not force stopped.
1215
1217
 
1216
1218
  Raises:
1217
1219
  StageCancelError: If event was set before start process.
@@ -1223,9 +1225,7 @@ class EmptyStage(BaseAsyncStage):
1223
1225
  run_id, parent_run_id=parent_run_id, extras=self.extras
1224
1226
  )
1225
1227
  message: str = (
1226
- param2template(
1227
- dedent(self.echo.strip("\n")), params, extras=self.extras
1228
- )
1228
+ self.pass_template(dedent(self.echo.strip("\n")), params=params)
1229
1229
  if self.echo
1230
1230
  else "..."
1231
1231
  )
@@ -1238,12 +1238,8 @@ class EmptyStage(BaseAsyncStage):
1238
1238
  if self.sleep > 5:
1239
1239
  trace.info(f"[STAGE]: Sleep ... ({self.sleep} sec)")
1240
1240
  time.sleep(self.sleep)
1241
- return Result(
1242
- run_id=run_id,
1243
- parent_run_id=parent_run_id,
1244
- status=SUCCESS,
1245
- context=catch(context=context, status=SUCCESS),
1246
- extras=self.extras,
1241
+ return Result.from_trace(trace).catch(
1242
+ status=SUCCESS, context=catch(context=context, status=SUCCESS)
1247
1243
  )
1248
1244
 
1249
1245
  async def async_process(
@@ -1259,13 +1255,14 @@ class EmptyStage(BaseAsyncStage):
1259
1255
  stdout.
1260
1256
 
1261
1257
  Args:
1262
- params: A parameter data that want to use in this
1258
+ params (DictData): A parameter data that want to use in this
1263
1259
  execution.
1264
- run_id: A running stage ID.
1265
- context: A context data.
1266
- parent_run_id: A parent running ID. (Default is None)
1267
- event: An event manager that use to track parent process
1268
- was not force stopped.
1260
+ run_id (str): A running stage ID.
1261
+ context (DictData): A context data that was passed from handler
1262
+ method.
1263
+ parent_run_id (str, default None): A parent running ID.
1264
+ event (Event, default None): An event manager that use to track
1265
+ parent process was not force stopped.
1269
1266
 
1270
1267
  Raises:
1271
1268
  StageCancelError: If event was set before start process.
@@ -1277,9 +1274,7 @@ class EmptyStage(BaseAsyncStage):
1277
1274
  run_id, parent_run_id=parent_run_id, extras=self.extras
1278
1275
  )
1279
1276
  message: str = (
1280
- param2template(
1281
- dedent(self.echo.strip("\n")), params, extras=self.extras
1282
- )
1277
+ self.pass_template(dedent(self.echo.strip("\n")), params=params)
1283
1278
  if self.echo
1284
1279
  else "..."
1285
1280
  )
@@ -1292,12 +1287,8 @@ class EmptyStage(BaseAsyncStage):
1292
1287
  if self.sleep > 5:
1293
1288
  await trace.ainfo(f"[STAGE]: Sleep ... ({self.sleep} sec)")
1294
1289
  await asyncio.sleep(self.sleep)
1295
- return Result(
1296
- run_id=run_id,
1297
- parent_run_id=parent_run_id,
1298
- status=SUCCESS,
1299
- context=catch(context=context, status=SUCCESS),
1300
- extras=self.extras,
1290
+ return Result.from_trace(trace).catch(
1291
+ status=SUCCESS, context=catch(context=context, status=SUCCESS)
1301
1292
  )
1302
1293
 
1303
1294
 
@@ -1322,7 +1313,6 @@ class BashStage(BaseRetryStage):
1322
1313
  ... })
1323
1314
  """
1324
1315
 
1325
- action_stage: ClassVar[bool] = True
1326
1316
  bash: str = Field(
1327
1317
  description=(
1328
1318
  "A bash statement that want to execute via Python subprocess."
@@ -1370,10 +1360,11 @@ class BashStage(BaseRetryStage):
1370
1360
  # NOTE: Make this .sh file able to executable.
1371
1361
  make_exec(f"./{f_name}")
1372
1362
 
1373
- yield f_shebang, f_name
1374
-
1375
- # Note: Remove .sh file that use to run bash.
1376
- Path(f"./{f_name}").unlink()
1363
+ try:
1364
+ yield f_shebang, f_name
1365
+ finally:
1366
+ # Note: Remove .sh file that use to run bash.
1367
+ Path(f"./{f_name}").unlink()
1377
1368
 
1378
1369
  @contextlib.contextmanager
1379
1370
  def make_sh_file(
@@ -1408,10 +1399,11 @@ class BashStage(BaseRetryStage):
1408
1399
  # NOTE: Make this .sh file able to executable.
1409
1400
  make_exec(f"./{f_name}")
1410
1401
 
1411
- yield f_shebang, f_name
1412
-
1413
- # Note: Remove .sh file that use to run bash.
1414
- Path(f"./{f_name}").unlink()
1402
+ try:
1403
+ yield f_shebang, f_name
1404
+ finally:
1405
+ # Note: Remove .sh file that use to run bash.
1406
+ Path(f"./{f_name}").unlink()
1415
1407
 
1416
1408
  @staticmethod
1417
1409
  def prepare_std(value: str) -> Optional[str]:
@@ -1592,7 +1584,6 @@ class PyStage(BaseRetryStage):
1592
1584
  ... })
1593
1585
  """
1594
1586
 
1595
- action_stage: ClassVar[bool] = True
1596
1587
  run: str = Field(
1597
1588
  description="A Python string statement that want to run with `exec`.",
1598
1589
  )
@@ -1622,6 +1613,7 @@ class PyStage(BaseRetryStage):
1622
1613
  or (value.startswith("__") and value.endswith("__"))
1623
1614
  or ismodule(values[value])
1624
1615
  or isclass(values[value])
1616
+ or value in ("trace",)
1625
1617
  ):
1626
1618
  continue
1627
1619
 
@@ -1831,7 +1823,6 @@ class CallStage(BaseRetryStage):
1831
1823
  ... })
1832
1824
  """
1833
1825
 
1834
- action_stage: ClassVar[bool] = True
1835
1826
  uses: str = Field(
1836
1827
  description=(
1837
1828
  "A caller function with registry importer syntax that use to load "
@@ -2337,10 +2328,10 @@ class TriggerStage(BaseRetryStage):
2337
2328
  if (msg := result.context.get("errors", {}).get("message"))
2338
2329
  else "."
2339
2330
  )
2340
- err = StageError(
2341
- f"Trigger workflow was failed{err_msg}", allow_traceback=False
2331
+ raise StageError(
2332
+ f"Trigger workflow was failed{err_msg}",
2333
+ allow_traceback=False,
2342
2334
  )
2343
- raise err
2344
2335
  elif result.status == CANCEL:
2345
2336
  raise StageCancelError("Trigger workflow was cancel.")
2346
2337
  elif result.status == SKIP:
@@ -2405,8 +2396,8 @@ class BaseNestedStage(BaseAsyncStage, ABC):
2405
2396
  execute func.
2406
2397
 
2407
2398
  Args:
2408
- context: (DictData) A context data.
2409
- error: (StageError) A stage exception object.
2399
+ context (DictData): A context data.
2400
+ error (StageError): A stage exception object.
2410
2401
  """
2411
2402
  if "errors" in context:
2412
2403
  context["errors"][error.refs] = error.to_dict()
@@ -2425,13 +2416,14 @@ class BaseNestedStage(BaseAsyncStage, ABC):
2425
2416
  """Async process for nested-stage do not implement yet.
2426
2417
 
2427
2418
  Args:
2428
- params: A parameter data that want to use in this
2419
+ params (DictData): A parameter data that want to use in this
2429
2420
  execution.
2430
- run_id: A running stage ID.
2431
- context: A context data.
2432
- parent_run_id: A parent running ID. (Default is None)
2433
- event: An event manager that use to track parent process
2434
- was not force stopped.
2421
+ run_id (str): A running stage ID.
2422
+ context (DictData): A context data that was passed from handler
2423
+ method.
2424
+ parent_run_id (str, default None): A parent running ID.
2425
+ event (Event, default None): An event manager that use to track
2426
+ parent process was not force stopped.
2435
2427
 
2436
2428
  Returns:
2437
2429
  Result: The execution result with status and context data.