ddeutil-workflow 0.0.42__py3-none-any.whl → 0.0.44__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.
@@ -1 +1 @@
1
- __version__: str = "0.0.42"
1
+ __version__: str = "0.0.44"
@@ -4,7 +4,7 @@
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
6
  from .__cron import CronJob, CronRunner
7
- from .__types import Re
7
+ from .__types import DictData, DictStr, Matrix, Re, TupleStr
8
8
  from .conf import (
9
9
  Config,
10
10
  Loader,
@@ -47,6 +47,10 @@ from .params import (
47
47
  StrParam,
48
48
  )
49
49
  from .result import (
50
+ FAILED,
51
+ SKIP,
52
+ SUCCESS,
53
+ WAIT,
50
54
  Result,
51
55
  Status,
52
56
  )
@@ -9,10 +9,20 @@ annotate for handle error only.
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
- from typing import Any
12
+ from typing import TypedDict
13
13
 
14
+ ErrorData = TypedDict(
15
+ "ErrorData",
16
+ {
17
+ "class": Exception,
18
+ "name": str,
19
+ "message": str,
20
+ },
21
+ )
14
22
 
15
- def to_dict(exception: Exception) -> dict[str, Any]: # pragma: no cov
23
+
24
+ def to_dict(exception: Exception) -> ErrorData: # pragma: no cov
25
+ """Create dict data from exception instance."""
16
26
  return {
17
27
  "class": exception,
18
28
  "name": exception.__class__.__name__,
@@ -22,7 +32,7 @@ def to_dict(exception: Exception) -> dict[str, Any]: # pragma: no cov
22
32
 
23
33
  class BaseWorkflowException(Exception):
24
34
 
25
- def to_dict(self) -> dict[str, Any]:
35
+ def to_dict(self) -> ErrorData:
26
36
  return to_dict(self)
27
37
 
28
38
 
ddeutil/workflow/job.py CHANGED
@@ -38,8 +38,9 @@ from .exceptions import (
38
38
  JobException,
39
39
  StageException,
40
40
  UtilException,
41
+ to_dict,
41
42
  )
42
- from .result import Result, Status
43
+ from .result import FAILED, SKIP, SUCCESS, WAIT, Result, Status
43
44
  from .reusables import has_template, param2template
44
45
  from .stages import Stage
45
46
  from .utils import cross_product, filter_func, gen_id
@@ -51,7 +52,6 @@ __all__: TupleStr = (
51
52
  "Strategy",
52
53
  "Job",
53
54
  "TriggerRules",
54
- "TriggerState",
55
55
  "RunsOn",
56
56
  "RunsOnLocal",
57
57
  "RunsOnSelfHosted",
@@ -206,16 +206,6 @@ class TriggerRules(str, Enum):
206
206
  none_skipped: str = "none_skipped"
207
207
 
208
208
 
209
- class TriggerState(str, Enum):
210
- waiting: str = "waiting"
211
- passed: str = "passed"
212
- skipped: str = "skipped"
213
- failed: str = "failed"
214
-
215
- def is_waiting(self):
216
- return self.value == "waiting"
217
-
218
-
219
209
  class RunsOnType(str, Enum):
220
210
  """Runs-On enum object."""
221
211
 
@@ -407,30 +397,32 @@ class Job(BaseModel):
407
397
  def check_needs(
408
398
  self,
409
399
  jobs: dict[str, Any],
410
- ) -> TriggerState: # pragma: no cov
411
- """Return True if job's need exists in an input list of job's ID.
400
+ ) -> Status: # pragma: no cov
401
+ """Return Status enum for checking job's need trigger logic in an
402
+ input list of job's ID.
412
403
 
413
404
  :param jobs: A mapping of job ID and result context.
414
405
 
415
406
  :raise NotImplementedError: If the job trigger rule out of scope.
416
407
 
417
- :rtype: TriggerState
408
+ :rtype: Status
418
409
  """
419
410
  if not self.needs:
420
- return TriggerState.passed
411
+ return SUCCESS
421
412
 
422
- def make_return(result: bool) -> TriggerState:
423
- return TriggerState.passed if result else TriggerState.failed
413
+ def make_return(result: bool) -> Status:
414
+ return SUCCESS if result else FAILED
424
415
 
425
416
  need_exist: dict[str, Any] = {
426
417
  need: jobs[need] for need in self.needs if need in jobs
427
418
  }
419
+
428
420
  if len(need_exist) != len(self.needs):
429
- return TriggerState.waiting
421
+ return WAIT
430
422
  elif all("skipped" in need_exist[job] for job in need_exist):
431
- return TriggerState.skipped
423
+ return SKIP
432
424
  elif self.trigger_rule == TriggerRules.all_done:
433
- return TriggerState.passed
425
+ return SUCCESS
434
426
  elif self.trigger_rule == TriggerRules.all_success:
435
427
  rs = all(
436
428
  k not in need_exist[job]
@@ -640,19 +632,6 @@ def local_execute_strategy(
640
632
  result: Result = Result(run_id=gen_id(job.id or "not-set", unique=True))
641
633
 
642
634
  strategy_id: str = gen_id(strategy)
643
-
644
- # PARAGRAPH:
645
- #
646
- # Create strategy execution context and update a matrix and copied
647
- # of params. So, the context value will have structure like;
648
- #
649
- # {
650
- # "params": { ... }, <== Current input params
651
- # "jobs": { ... }, <== Current input params
652
- # "matrix": { ... } <== Current strategy value
653
- # "stages": { ... } <== Catching stage outputs
654
- # }
655
- #
656
635
  context: DictData = copy.deepcopy(params)
657
636
  context.update({"matrix": strategy, "stages": {}})
658
637
 
@@ -660,7 +639,6 @@ def local_execute_strategy(
660
639
  result.trace.info(f"[JOB]: Execute Strategy ID: {strategy_id}")
661
640
  result.trace.info(f"[JOB]: ... Matrix: {strategy_id}")
662
641
 
663
- # IMPORTANT: The stage execution only run sequentially one-by-one.
664
642
  for stage in job.stages:
665
643
 
666
644
  if stage.is_skipped(params=context):
@@ -674,7 +652,7 @@ def local_execute_strategy(
674
652
  "strategy execution."
675
653
  )
676
654
  return result.catch(
677
- status=Status.FAILED,
655
+ status=FAILED,
678
656
  context={
679
657
  strategy_id: {
680
658
  "matrix": strategy,
@@ -684,34 +662,30 @@ def local_execute_strategy(
684
662
  },
685
663
  )
686
664
 
687
- # PARAGRAPH:
688
- #
689
- # This step will add the stage result to `stages` key in that
690
- # stage id. It will have structure like;
691
- #
692
- # {
693
- # "params": { ... },
694
- # "jobs": { ... },
695
- # "matrix": { ... },
696
- # "stages": { { "stage-id-01": { "outputs": { ... } } }, ... }
697
- # }
698
- #
699
- # IMPORTANT:
700
- # This execution change all stage running IDs to the current job
701
- # running ID, but it still trac log to the same parent running ID
702
- # (with passing `run_id` and `parent_run_id` to the stage
703
- # execution arguments).
704
- #
705
665
  try:
706
- stage.set_outputs(
707
- stage.handler_execute(
708
- params=context,
709
- run_id=result.run_id,
710
- parent_run_id=result.parent_run_id,
711
- event=event,
712
- ).context,
713
- to=context,
666
+ rs: Result = stage.handler_execute(
667
+ params=context,
668
+ run_id=result.run_id,
669
+ parent_run_id=result.parent_run_id,
670
+ event=event,
714
671
  )
672
+ stage.set_outputs(rs.context, to=context)
673
+ if rs.status == FAILED:
674
+ error_msg: str = (
675
+ f"Job strategy was break because it has a stage, "
676
+ f"{stage.iden}, failed without raise error."
677
+ )
678
+ return result.catch(
679
+ status=FAILED,
680
+ context={
681
+ strategy_id: {
682
+ "matrix": strategy,
683
+ "stages": context.pop("stages", {}),
684
+ "errors": JobException(error_msg).to_dict(),
685
+ },
686
+ },
687
+ )
688
+
715
689
  except (StageException, UtilException) as err:
716
690
  result.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
717
691
  do_raise: bool = dynamic(
@@ -724,7 +698,7 @@ def local_execute_strategy(
724
698
  ) from None
725
699
 
726
700
  return result.catch(
727
- status=Status.FAILED,
701
+ status=FAILED,
728
702
  context={
729
703
  strategy_id: {
730
704
  "matrix": strategy,
@@ -735,7 +709,7 @@ def local_execute_strategy(
735
709
  )
736
710
 
737
711
  return result.catch(
738
- status=Status.SUCCESS,
712
+ status=SUCCESS,
739
713
  context={
740
714
  strategy_id: {
741
715
  "matrix": strategy,
@@ -756,8 +730,8 @@ def local_execute(
756
730
  raise_error: bool | None = None,
757
731
  ) -> Result:
758
732
  """Local job execution with passing dynamic parameters from the workflow
759
- execution. It will generate matrix values at the first step and run
760
- multithread on this metrics to the `stages` field of this job.
733
+ execution or itself execution. It will generate matrix values at the first
734
+ step and run multithread on this metrics to the `stages` field of this job.
761
735
 
762
736
  This method does not raise any JobException if it runs with
763
737
  multi-threading strategy.
@@ -790,7 +764,7 @@ def local_execute(
790
764
 
791
765
  if event and event.is_set(): # pragma: no cov
792
766
  return result.catch(
793
- status=Status.FAILED,
767
+ status=FAILED,
794
768
  context={
795
769
  "errors": JobException(
796
770
  "Job strategy was canceled from event that had set "
@@ -808,7 +782,7 @@ def local_execute(
808
782
  raise_error=raise_error,
809
783
  )
810
784
 
811
- return result.catch(status=Status.SUCCESS)
785
+ return result.catch(status=result.status)
812
786
 
813
787
  fail_fast_flag: bool = job.strategy.fail_fast
814
788
  ls: str = "Fail-Fast" if fail_fast_flag else "All-Completed"
@@ -819,7 +793,7 @@ def local_execute(
819
793
 
820
794
  if event and event.is_set(): # pragma: no cov
821
795
  return result.catch(
822
- status=Status.FAILED,
796
+ status=FAILED,
823
797
  context={
824
798
  "errors": JobException(
825
799
  "Job strategy was canceled from event that had set "
@@ -828,8 +802,6 @@ def local_execute(
828
802
  },
829
803
  )
830
804
 
831
- # IMPORTANT: Start running strategy execution by multithreading because
832
- # it will run by strategy values without waiting previous execution.
833
805
  with ThreadPoolExecutor(
834
806
  max_workers=job.strategy.max_parallel,
835
807
  thread_name_prefix="job_strategy_exec_",
@@ -849,7 +821,7 @@ def local_execute(
849
821
  ]
850
822
 
851
823
  context: DictData = {}
852
- status: Status = Status.SUCCESS
824
+ status: Status = SUCCESS
853
825
 
854
826
  if not fail_fast_flag:
855
827
  done = as_completed(futures, timeout=1800)
@@ -875,7 +847,7 @@ def local_execute(
875
847
  try:
876
848
  future.result()
877
849
  except JobException as err:
878
- status = Status.FAILED
850
+ status = FAILED
879
851
  result.trace.error(
880
852
  f"[JOB]: {ls} Catch:\n\t{err.__class__.__name__}:"
881
853
  f"\n\t{err}"
@@ -895,6 +867,22 @@ def self_hosted_execute(
895
867
  event: Event | None = None,
896
868
  raise_error: bool | None = None,
897
869
  ) -> Result: # pragma: no cov
870
+ """Self-Hosted job execution with passing dynamic parameters from the
871
+ workflow execution or itself execution. It will make request to the
872
+ self-hosted host url.
873
+
874
+ :param job: (Job) A job model that want to execute.
875
+ :param params: (DictData) An input parameters that use on job execution.
876
+ :param run_id: (str) A job running ID for this execution.
877
+ :param parent_run_id: (str) A parent workflow running ID for this release.
878
+ :param result: (Result) A result object for keeping context and status
879
+ data.
880
+ :param event: (Event) An event manager that pass to the PoolThreadExecutor.
881
+ :param raise_error: (bool) A flag that all this method raise error to the
882
+ strategy execution.
883
+
884
+ :rtype: Result
885
+ """
898
886
  result: Result = Result.construct_with_rs_or_id(
899
887
  result,
900
888
  run_id=run_id,
@@ -903,14 +891,31 @@ def self_hosted_execute(
903
891
  )
904
892
 
905
893
  if event and event.is_set():
906
- return result.catch(status=Status.FAILED)
894
+ return result.catch(
895
+ status=FAILED,
896
+ context={
897
+ "errors": JobException(
898
+ "Job self-hosted execution was canceled from event that "
899
+ "had set before start execution."
900
+ ).to_dict()
901
+ },
902
+ )
907
903
 
908
904
  import requests
909
905
 
910
- resp = requests.post(
911
- job.runs_on.args.host,
912
- data={"job": job.model_dump(), "params": params},
913
- )
906
+ try:
907
+ resp = requests.post(
908
+ job.runs_on.args.host,
909
+ headers={"Auth": f"Barer {job.runs_on.args.token}"},
910
+ data={
911
+ "job": job.model_dump(),
912
+ "params": params,
913
+ "result": result.__dict__,
914
+ "raise_error": raise_error,
915
+ },
916
+ )
917
+ except requests.exceptions.RequestException as e:
918
+ return result.catch(status=FAILED, context={"errors": to_dict(e)})
914
919
 
915
920
  if resp.status_code != 200:
916
921
  do_raise: bool = dynamic(
@@ -922,5 +927,5 @@ def self_hosted_execute(
922
927
  f"{job.runs_on.args.host!r}"
923
928
  )
924
929
 
925
- return result.catch(status=Status.FAILED)
926
- return result.catch(status=Status.SUCCESS)
930
+ return result.catch(status=FAILED)
931
+ return result.catch(status=SUCCESS)
@@ -3,7 +3,6 @@
3
3
  # Licensed under the MIT License. See LICENSE in the project root for
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
- # [ ] Use config
7
6
  """This module include all Param Pydantic Models that use for parsing an
8
7
  incoming parameters that was passed to the Workflow and Schedule objects before
9
8
  execution or release methods.
@@ -18,6 +17,7 @@ from abc import ABC, abstractmethod
18
17
  from datetime import date, datetime
19
18
  from typing import Annotated, Any, Literal, Optional, TypeVar, Union
20
19
 
20
+ from ddeutil.core import str2dict, str2list
21
21
  from pydantic import BaseModel, Field
22
22
 
23
23
  from .__types import TupleStr
@@ -31,6 +31,8 @@ __all__: TupleStr = (
31
31
  "IntParam",
32
32
  "Param",
33
33
  "StrParam",
34
+ "ArrayParam",
35
+ "MapParam",
34
36
  )
35
37
 
36
38
  T = TypeVar("T")
@@ -42,7 +44,10 @@ class BaseParam(BaseModel, ABC):
42
44
  """
43
45
 
44
46
  desc: Optional[str] = Field(
45
- default=None, description="A description of parameter providing."
47
+ default=None,
48
+ description=(
49
+ "A description of this parameter provide to the workflow model."
50
+ ),
46
51
  )
47
52
  required: bool = Field(
48
53
  default=True,
@@ -52,6 +57,7 @@ class BaseParam(BaseModel, ABC):
52
57
 
53
58
  @abstractmethod
54
59
  def receive(self, value: Optional[T] = None) -> T:
60
+ """Abstract method receive value to this parameter model."""
55
61
  raise NotImplementedError(
56
62
  "Receive value and validate typing before return valid value."
57
63
  )
@@ -66,13 +72,14 @@ class DefaultParam(BaseParam):
66
72
  default=False,
67
73
  description="A require flag for the default-able parameter value.",
68
74
  )
69
- default: Optional[str] = Field(
75
+ default: Optional[Any] = Field(
70
76
  default=None,
71
77
  description="A default value if parameter does not pass.",
72
78
  )
73
79
 
74
80
  @abstractmethod
75
81
  def receive(self, value: Optional[Any] = None) -> Any:
82
+ """Abstract method receive value to this parameter model."""
76
83
  raise NotImplementedError(
77
84
  "Receive value and validate typing before return valid value."
78
85
  )
@@ -82,7 +89,10 @@ class DateParam(DefaultParam): # pragma: no cov
82
89
  """Date parameter model."""
83
90
 
84
91
  type: Literal["date"] = "date"
85
- default: date = Field(default_factory=get_d_now)
92
+ default: date = Field(
93
+ default_factory=get_d_now,
94
+ description="A default date that make from the current date func.",
95
+ )
86
96
 
87
97
  def receive(self, value: Optional[str | datetime | date] = None) -> date:
88
98
  """Receive value that match with date. If an input value pass with
@@ -116,7 +126,12 @@ class DatetimeParam(DefaultParam):
116
126
  """Datetime parameter model."""
117
127
 
118
128
  type: Literal["datetime"] = "datetime"
119
- default: datetime = Field(default_factory=get_dt_now)
129
+ default: datetime = Field(
130
+ default_factory=get_dt_now,
131
+ description=(
132
+ "A default datetime that make from the current datetime func."
133
+ ),
134
+ )
120
135
 
121
136
  def receive(self, value: str | datetime | date | None = None) -> datetime:
122
137
  """Receive value that match with datetime. If an input value pass with
@@ -167,10 +182,6 @@ class IntParam(DefaultParam):
167
182
  """Integer parameter."""
168
183
 
169
184
  type: Literal["int"] = "int"
170
- default: Optional[int] = Field(
171
- default=None,
172
- description="A default value if parameter does not pass.",
173
- )
174
185
 
175
186
  def receive(self, value: int | None = None) -> int | None:
176
187
  """Receive value that match with int.
@@ -222,36 +233,84 @@ class ChoiceParam(BaseParam):
222
233
  return value
223
234
 
224
235
 
225
- # TODO: Not implement this parameter yet
226
236
  class MapParam(DefaultParam): # pragma: no cov
237
+ """Map parameter."""
227
238
 
228
239
  type: Literal["map"] = "map"
229
- default: dict[Any, Any] = Field(default_factory=dict)
240
+ default: dict[Any, Any] = Field(
241
+ default_factory=dict,
242
+ description="A default dict that make from the dict built-in func.",
243
+ )
244
+
245
+ def receive(
246
+ self,
247
+ value: Optional[Union[dict[Any, Any], str]] = None,
248
+ ) -> dict[Any, Any]:
249
+ """Receive value that match with map type.
230
250
 
231
- def receive(self, value: Optional[dict[Any, Any]] = None) -> dict[Any, Any]:
251
+ :param value: A value that want to validate with map parameter type.
252
+ :rtype: dict[Any, Any]
253
+ """
232
254
  if value is None:
233
255
  return self.default
234
256
 
257
+ if isinstance(value, str):
258
+ try:
259
+ value: dict[Any, Any] = str2dict(value)
260
+ except ValueError as e:
261
+ raise ParamValueException(
262
+ f"Value that want to convert to map does not support for "
263
+ f"type: {type(value)}"
264
+ ) from e
265
+ elif not isinstance(value, dict):
266
+ raise ParamValueException(
267
+ f"Value of map param support only string-dict or dict type, "
268
+ f"not {type(value)}"
269
+ )
270
+ return value
271
+
235
272
 
236
- # TODO: Not implement this parameter yet
237
273
  class ArrayParam(DefaultParam): # pragma: no cov
274
+ """Array parameter."""
238
275
 
239
276
  type: Literal["array"] = "array"
240
- default: list[Any] = Field(default_factory=list)
277
+ default: list[Any] = Field(
278
+ default_factory=list,
279
+ description="A default list that make from the list built-in func.",
280
+ )
241
281
 
242
- def receive(self, value: Optional[list[T]] = None) -> list[T]:
282
+ def receive(
283
+ self, value: Optional[Union[list[T], tuple[T, ...], str]] = None
284
+ ) -> list[T]:
285
+ """Receive value that match with array type.
286
+
287
+ :param value: A value that want to validate with array parameter type.
288
+ :rtype: list[Any]
289
+ """
243
290
  if value is None:
244
291
  return self.default
245
- if not isinstance(value, list):
292
+ if isinstance(value, str):
293
+ try:
294
+ value: list[T] = str2list(value)
295
+ except ValueError as e:
296
+ raise ParamValueException(
297
+ f"Value that want to convert to array does not support for "
298
+ f"type: {type(value)}"
299
+ ) from e
300
+ elif isinstance(value, (tuple, set)):
301
+ return list(value)
302
+ elif not isinstance(value, list):
246
303
  raise ParamValueException(
247
- f"Value that want to convert to array does not support for "
248
- f"type: {type(value)}"
304
+ f"Value of map param support only string-list or list type, "
305
+ f"not {type(value)}"
249
306
  )
250
307
  return value
251
308
 
252
309
 
253
310
  Param = Annotated[
254
311
  Union[
312
+ MapParam,
313
+ ArrayParam,
255
314
  ChoiceParam,
256
315
  DatetimeParam,
257
316
  DateParam,
@@ -3,8 +3,8 @@
3
3
  # Licensed under the MIT License. See LICENSE in the project root for
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
- """This is the Result module. It is the data context transfer objects that use
7
- by all object in this package. This module provide Result dataclass.
6
+ """Result module. It is the data context transfer objects that use by all object
7
+ in this package. This module provide Status enum object and Result dataclass.
8
8
  """
9
9
  from __future__ import annotations
10
10
 
@@ -23,13 +23,19 @@ from .logs import TraceLog, get_dt_tznow, get_trace
23
23
  from .utils import default_gen_id, gen_id
24
24
 
25
25
  __all__: TupleStr = (
26
+ "SUCCESS",
27
+ "FAILED",
28
+ "WAIT",
29
+ "SKIP",
26
30
  "Result",
27
31
  "Status",
28
32
  )
29
33
 
30
34
 
31
35
  class Status(IntEnum):
32
- """Status Int Enum object."""
36
+ """Status Int Enum object that use for tracking execution status to the
37
+ Result dataclass object.
38
+ """
33
39
 
34
40
  SUCCESS: int = 0
35
41
  FAILED: int = 1
@@ -37,8 +43,17 @@ class Status(IntEnum):
37
43
  SKIP: int = 3
38
44
 
39
45
 
46
+ SUCCESS = Status.SUCCESS
47
+ FAILED = Status.FAILED
48
+ WAIT = Status.WAIT
49
+ SKIP = Status.SKIP
50
+
51
+
40
52
  @dataclass(
41
- config=ConfigDict(arbitrary_types_allowed=True, use_enum_values=True)
53
+ config=ConfigDict(
54
+ arbitrary_types_allowed=True,
55
+ use_enum_values=True,
56
+ ),
42
57
  )
43
58
  class Result:
44
59
  """Result Pydantic Model for passing and receiving data context from any
@@ -49,8 +64,9 @@ class Result:
49
64
  and ``_run_id`` fields to comparing with other result instance.
50
65
  """
51
66
 
52
- status: Status = field(default=Status.WAIT)
67
+ status: Status = field(default=WAIT)
53
68
  context: DictData = field(default_factory=dict)
69
+ errors: DictData = field(default_factory=dict)
54
70
  run_id: Optional[str] = field(default_factory=default_gen_id)
55
71
  parent_run_id: Optional[str] = field(default=None, compare=False)
56
72
  ts: datetime = field(default_factory=get_dt_tznow, compare=False)
@@ -64,9 +80,16 @@ class Result:
64
80
  run_id: str | None = None,
65
81
  parent_run_id: str | None = None,
66
82
  id_logic: str | None = None,
67
- ) -> Self: # pragma: no cov
83
+ ) -> Self:
68
84
  """Create the Result object or set parent running id if passing Result
69
85
  object.
86
+
87
+ :param result:
88
+ :param run_id:
89
+ :param parent_run_id:
90
+ :param id_logic:
91
+
92
+ :rtype: Self
70
93
  """
71
94
  if result is None:
72
95
  result: Result = cls(
@@ -101,18 +124,23 @@ class Result:
101
124
  self,
102
125
  status: int | Status,
103
126
  context: DictData | None = None,
127
+ error: DictData | None = None,
104
128
  ) -> Self:
105
129
  """Catch the status and context to this Result object. This method will
106
130
  use between a child execution return a result, and it wants to pass
107
131
  status and context to this object.
108
132
 
109
- :param status:
110
- :param context:
133
+ :param status: A status enum object.
134
+ :param context: A context data that will update to the current context.
135
+ :param error: An error data that will update to the current errors.
136
+
137
+ :rtype: Self
111
138
  """
112
139
  self.__dict__["status"] = (
113
140
  Status(status) if isinstance(status, int) else status
114
141
  )
115
142
  self.__dict__["context"].update(context or {})
143
+ self.__dict__["errors"].update(error or {})
116
144
  return self
117
145
 
118
146
  def alive_time(self) -> float: # pragma: no cov