ddeutil-workflow 0.0.56__tar.gz → 0.0.57__tar.gz

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.
Files changed (70) hide show
  1. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/PKG-INFO +41 -35
  2. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/README.md +38 -32
  3. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/pyproject.toml +4 -3
  4. ddeutil_workflow-0.0.57/src/ddeutil/workflow/__about__.py +1 -0
  5. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/__cron.py +26 -12
  6. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/__types.py +1 -0
  7. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/conf.py +20 -8
  8. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/event.py +1 -0
  9. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/exceptions.py +33 -12
  10. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/job.py +81 -57
  11. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/logs.py +13 -5
  12. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/result.py +9 -4
  13. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/scheduler.py +6 -2
  14. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/stages.py +370 -147
  15. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/utils.py +37 -6
  16. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/workflow.py +205 -230
  17. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/PKG-INFO +41 -35
  18. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/SOURCES.txt +2 -2
  19. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/requires.txt +2 -2
  20. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test__cron.py +28 -16
  21. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_conf.py +81 -10
  22. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_event.py +25 -2
  23. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_job.py +20 -21
  24. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_job_exec.py +93 -68
  25. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_job_exec_strategy.py +9 -20
  26. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_params.py +1 -4
  27. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_release.py +7 -20
  28. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_release_queue.py +1 -1
  29. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_result.py +16 -28
  30. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_stage_handler_exec.py +220 -182
  31. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_utils.py +28 -1
  32. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_workflow.py +12 -2
  33. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_workflow_exec.py +158 -137
  34. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_workflow_exec_job.py +10 -28
  35. ddeutil_workflow-0.0.57/tests/test_workflow_poke.py +168 -0
  36. ddeutil_workflow-0.0.57/tests/test_workflow_release.py +153 -0
  37. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_workflow_task.py +34 -13
  38. ddeutil_workflow-0.0.56/src/ddeutil/workflow/__about__.py +0 -1
  39. ddeutil_workflow-0.0.56/tests/test_workflow_exec_poke.py +0 -171
  40. ddeutil_workflow-0.0.56/tests/test_workflow_exec_release.py +0 -125
  41. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/LICENSE +0 -0
  42. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/setup.cfg +0 -0
  43. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/__init__.py +0 -0
  44. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/__main__.py +0 -0
  45. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/__init__.py +0 -0
  46. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/logs.py +0 -0
  47. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
  48. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/routes/job.py +0 -0
  49. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/routes/logs.py +0 -0
  50. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
  51. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
  52. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/api/utils.py +0 -0
  53. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/params.py +0 -0
  54. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil/workflow/reusables.py +0 -0
  55. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  56. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/entry_points.txt +0 -0
  57. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  58. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test__regex.py +0 -0
  59. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_logs_audit.py +0 -0
  60. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_logs_trace.py +0 -0
  61. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_reusables_call_tag.py +0 -0
  62. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_reusables_template.py +0 -0
  63. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_reusables_template_filter.py +0 -0
  64. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_schedule.py +0 -0
  65. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_schedule_pending.py +0 -0
  66. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_schedule_tasks.py +0 -0
  67. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_schedule_workflow.py +0 -0
  68. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_scheduler_control.py +0 -0
  69. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_stage.py +0 -0
  70. {ddeutil_workflow-0.0.56 → ddeutil_workflow-0.0.57}/tests/test_strategy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.56
3
+ Version: 0.0.57
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Requires-Python: >=3.9.13
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: ddeutil[checksum]>=0.4.7
26
- Requires-Dist: ddeutil-io[toml,yaml]>=0.2.11
25
+ Requires-Dist: ddeutil[checksum]>=0.4.8
26
+ Requires-Dist: ddeutil-io[toml,yaml]>=0.2.12
27
27
  Requires-Dist: pydantic==2.11.1
28
28
  Requires-Dist: python-dotenv==1.1.0
29
29
  Requires-Dist: schedule<2.0.0,==1.2.2
@@ -250,42 +250,48 @@ from ddeutil.workflow import Workflow, Result
250
250
 
251
251
  workflow: Workflow = Workflow.from_conf('run-py-local')
252
252
  result: Result = workflow.execute(
253
- params={"source-extract": "USD-THB", "asat-dt": "2024-01-01"}
253
+ params={"source-extract": "USD-THB", "run-date": "2024-01-01"}
254
254
  )
255
255
  ```
256
256
 
257
- So, this package provide the `Schedule` template for this action, and you can dynamic
258
- pass the parameters for changing align with that running time by the `release` prefix.
259
-
260
- ```yaml
261
- schedule-run-local-wf:
262
-
263
- # Validate model that use to parsing exists for template file
264
- type: Schedule
265
- workflows:
266
-
267
- # Map existing workflow that want to deploy with scheduler application.
268
- # It allows you to pass release parameter that dynamic change depend on the
269
- # current context of this scheduler application releasing that time.
270
- - name: run-py-local
271
- params:
272
- source-extract: "USD-THB"
273
- asat-dt: "${{ release.logical_date }}"
274
- ```
275
-
276
- The main method of the `Schedule` model that use to running is `pending`. If you
277
- do not pass the `stop` date on this method, it will use config with `WORKFLOW_APP_STOP_BOUNDARY_DELTA`
278
- key for generate this stop date.
279
-
280
- ```python
281
- from ddeutil.workflow import Schedule
257
+ > [!NOTE]
258
+ > So, this package provide the `Schedule` template for this action, and you can
259
+ > pass the parameters dynamically for changing align with that running time by
260
+ > the `release` prefix.
261
+ >
262
+ > ```yaml
263
+ > schedule-run-local-wf:
264
+ >
265
+ > # Validate model that use to parsing exists for template file
266
+ > type: Schedule
267
+ > workflows:
268
+ >
269
+ > # Map existing workflow that want to deploy with scheduler application.
270
+ > # It allows you to pass release parameter that dynamic change depend on the
271
+ > # current context of this scheduler application releasing that time.
272
+ > - name: run-py-local
273
+ > params:
274
+ > source-extract: "USD-THB"
275
+ > run-date: "${{ release.logical_date }}"
276
+ > ```
277
+ >
278
+ > The main method of the `Schedule` model that use to running is `pending`. If you
279
+ > do not pass the `stop` date on this method, it will use config with
280
+ > `WORKFLOW_APP_STOP_BOUNDARY_DELTA` key for generate this stop date.
281
+ >
282
+ > ```python
283
+ > from ddeutil.workflow import Schedule
284
+ >
285
+ > (
286
+ > Schedule
287
+ > .from_conf("schedule-run-local-wf")
288
+ > .pending(stop=None)
289
+ > )
290
+ > ```
282
291
 
283
- (
284
- Schedule
285
- .from_conf("schedule-run-local-wf")
286
- .pending(stop=None)
287
- )
288
- ```
292
+ > [!WARNING]
293
+ > The scheduler feature is the expensive feature of this project. You should
294
+ > avoid to use it and find a scheduler tool instead.
289
295
 
290
296
  ## :cookie: Configuration
291
297
 
@@ -205,42 +205,48 @@ from ddeutil.workflow import Workflow, Result
205
205
 
206
206
  workflow: Workflow = Workflow.from_conf('run-py-local')
207
207
  result: Result = workflow.execute(
208
- params={"source-extract": "USD-THB", "asat-dt": "2024-01-01"}
208
+ params={"source-extract": "USD-THB", "run-date": "2024-01-01"}
209
209
  )
210
210
  ```
211
211
 
212
- So, this package provide the `Schedule` template for this action, and you can dynamic
213
- pass the parameters for changing align with that running time by the `release` prefix.
214
-
215
- ```yaml
216
- schedule-run-local-wf:
217
-
218
- # Validate model that use to parsing exists for template file
219
- type: Schedule
220
- workflows:
221
-
222
- # Map existing workflow that want to deploy with scheduler application.
223
- # It allows you to pass release parameter that dynamic change depend on the
224
- # current context of this scheduler application releasing that time.
225
- - name: run-py-local
226
- params:
227
- source-extract: "USD-THB"
228
- asat-dt: "${{ release.logical_date }}"
229
- ```
230
-
231
- The main method of the `Schedule` model that use to running is `pending`. If you
232
- do not pass the `stop` date on this method, it will use config with `WORKFLOW_APP_STOP_BOUNDARY_DELTA`
233
- key for generate this stop date.
234
-
235
- ```python
236
- from ddeutil.workflow import Schedule
212
+ > [!NOTE]
213
+ > So, this package provide the `Schedule` template for this action, and you can
214
+ > pass the parameters dynamically for changing align with that running time by
215
+ > the `release` prefix.
216
+ >
217
+ > ```yaml
218
+ > schedule-run-local-wf:
219
+ >
220
+ > # Validate model that use to parsing exists for template file
221
+ > type: Schedule
222
+ > workflows:
223
+ >
224
+ > # Map existing workflow that want to deploy with scheduler application.
225
+ > # It allows you to pass release parameter that dynamic change depend on the
226
+ > # current context of this scheduler application releasing that time.
227
+ > - name: run-py-local
228
+ > params:
229
+ > source-extract: "USD-THB"
230
+ > run-date: "${{ release.logical_date }}"
231
+ > ```
232
+ >
233
+ > The main method of the `Schedule` model that use to running is `pending`. If you
234
+ > do not pass the `stop` date on this method, it will use config with
235
+ > `WORKFLOW_APP_STOP_BOUNDARY_DELTA` key for generate this stop date.
236
+ >
237
+ > ```python
238
+ > from ddeutil.workflow import Schedule
239
+ >
240
+ > (
241
+ > Schedule
242
+ > .from_conf("schedule-run-local-wf")
243
+ > .pending(stop=None)
244
+ > )
245
+ > ```
237
246
 
238
- (
239
- Schedule
240
- .from_conf("schedule-run-local-wf")
241
- .pending(stop=None)
242
- )
243
- ```
247
+ > [!WARNING]
248
+ > The scheduler feature is the expensive feature of this project. You should
249
+ > avoid to use it and find a scheduler tool instead.
244
250
 
245
251
  ## :cookie: Configuration
246
252
 
@@ -25,8 +25,8 @@ classifiers = [
25
25
  ]
26
26
  requires-python = ">=3.9.13"
27
27
  dependencies = [
28
- "ddeutil[checksum]>=0.4.7",
29
- "ddeutil-io[yaml,toml]>=0.2.11",
28
+ "ddeutil[checksum]>=0.4.8",
29
+ "ddeutil-io[yaml,toml]>=0.2.12",
30
30
  "pydantic==2.11.1",
31
31
  "python-dotenv==1.1.0",
32
32
  "schedule==1.2.2,<2.0.0",
@@ -82,8 +82,9 @@ source = ["src.ddeutil.workflow"]
82
82
  omit = [
83
83
  "src/ddeutil/workflow/__about__.py",
84
84
  "src/ddeutil/workflow/__cron.py",
85
+ "src/ddeutil/workflow/__main__.py",
85
86
  "src/ddeutil/workflow/api/__init__.py",
86
- "src/ddeutil/workflow/api/log.py",
87
+ "src/ddeutil/workflow/api/logs.py",
87
88
  "src/ddeutil/workflow/api/utils.py",
88
89
  "src/ddeutil/workflow/api/routes/__init__.py",
89
90
  "src/ddeutil/workflow/api/routes/job.py",
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.57"
@@ -502,10 +502,10 @@ class CronPart:
502
502
  except IndexError:
503
503
  next_value: int = -1
504
504
  if value != (next_value - 1):
505
- # NOTE: ``next_value`` is not the subsequent number
505
+ # NOTE: `next_value` is not the subsequent number
506
506
  if start_number is None:
507
507
  # NOTE:
508
- # The last number of the list ``self.values`` is not in a
508
+ # The last number of the list `self.values` is not in a
509
509
  # range.
510
510
  multi_dim_values.append(value)
511
511
  else:
@@ -703,11 +703,14 @@ class CronJob:
703
703
  *,
704
704
  tz: str | None = None,
705
705
  ) -> CronRunner:
706
- """Returns the schedule datetime runner with this cronjob. It would run
707
- ``next``, ``prev``, or ``reset`` to generate running date that you want.
706
+ """Returns CronRunner instance that be datetime runner with this
707
+ cronjob. It can use `next`, `prev`, or `reset` methods to generate
708
+ running date.
708
709
 
709
- :param date: An initial date that want to mark as the start point.
710
- :param tz: A string timezone that want to change on runner.
710
+ :param date: (datetime) An initial date that want to mark as the start
711
+ point. (Default is use the current datetime)
712
+ :param tz: (str) A string timezone that want to change on runner.
713
+ (Default is None)
711
714
 
712
715
  :rtype: CronRunner
713
716
  """
@@ -743,6 +746,10 @@ class CronJobYear(CronJob):
743
746
  class CronRunner:
744
747
  """Create an instance of Date Runner object for datetime generate with
745
748
  cron schedule object value.
749
+
750
+ :param cron: (CronJob | CronJobYear)
751
+ :param date: (datetime)
752
+ :param tz: (str)
746
753
  """
747
754
 
748
755
  shift_limit: ClassVar[int] = 25
@@ -761,11 +768,17 @@ class CronRunner:
761
768
  cron: CronJob | CronJobYear,
762
769
  date: datetime | None = None,
763
770
  *,
764
- tz: str | None = None,
771
+ tz: str | ZoneInfo | None = None,
765
772
  ) -> None:
766
- # NOTE: Prepare timezone if this value does not set, it will use UTC.
767
- self.tz: ZoneInfo = ZoneInfo("UTC")
773
+ self.tz: ZoneInfo | None = None
768
774
  if tz:
775
+ if isinstance(tz, ZoneInfo):
776
+ self.tz = tz
777
+ elif not isinstance(tz, str):
778
+ raise TypeError(
779
+ "Invalid type of `tz` parameter, it should be str or "
780
+ "ZoneInfo instance."
781
+ )
769
782
  try:
770
783
  self.tz = ZoneInfo(tz)
771
784
  except ZoneInfoNotFoundError as err:
@@ -777,9 +790,10 @@ class CronRunner:
777
790
  raise ValueError(
778
791
  "Input schedule start time is not a valid datetime object."
779
792
  )
780
- if tz is None:
781
- self.tz = date.tzinfo
782
- self.date: datetime = date.astimezone(self.tz)
793
+ if tz is not None:
794
+ self.date: datetime = date.astimezone(self.tz)
795
+ else:
796
+ self.date: datetime = date
783
797
  else:
784
798
  self.date: datetime = datetime.now(tz=self.tz)
785
799
 
@@ -20,6 +20,7 @@ from typing import Any, Optional, TypedDict, Union
20
20
 
21
21
  from typing_extensions import Self
22
22
 
23
+ StrOrInt = Union[str, int]
23
24
  TupleStr = tuple[str, ...]
24
25
  DictData = dict[str, Any]
25
26
  DictStr = dict[str, str]
@@ -200,7 +200,10 @@ class APIConfig:
200
200
  return str2bool(env("API_ENABLE_ROUTE_SCHEDULE", "true"))
201
201
 
202
202
 
203
- class BaseLoad(ABC):
203
+ class BaseLoad(ABC): # pragma: no cov
204
+ """Base Load object is the abstraction object for any Load object that
205
+ should to inherit from this base class.
206
+ """
204
207
 
205
208
  @classmethod
206
209
  @abstractmethod
@@ -335,8 +338,13 @@ class FileLoad(BaseLoad):
335
338
  """
336
339
  excluded: list[str] = excluded or []
337
340
  path: Path = dynamic("conf_path", f=path, extras=extras)
341
+ paths: Optional[list[Path]] = paths or (extras or {}).get("conf_paths")
338
342
  if not paths:
339
343
  paths: list[Path] = [path]
344
+ elif not isinstance(paths, list):
345
+ raise TypeError(
346
+ f"Multi-config paths does not support for type: {type(paths)}"
347
+ )
340
348
  else:
341
349
  paths.append(path)
342
350
 
@@ -431,17 +439,21 @@ def dynamic(
431
439
  """Dynamic get config if extra value was passed at run-time.
432
440
 
433
441
  :param key: (str) A config key that get from Config object.
434
- :param f: An inner config function scope.
442
+ :param f: (T) An inner config function scope.
435
443
  :param extras: An extra values that pass at run-time.
444
+
445
+ :rtype: T
436
446
  """
437
- rsx: Optional[T] = extras[key] if extras and key in extras else None
438
- rs: Optional[T] = getattr(config, key, None) if f is None else f
439
- if rsx is not None and not isinstance(rsx, type(rs)):
447
+ extra: Optional[T] = (extras or {}).get(key, None)
448
+ conf: Optional[T] = getattr(config, key, None) if f is None else f
449
+ if extra is None:
450
+ return conf
451
+ if not isinstance(extra, type(conf)):
440
452
  raise TypeError(
441
- f"Type of config {key!r} from extras: {rsx!r} does not valid "
442
- f"as config {type(rs)}."
453
+ f"Type of config {key!r} from extras: {extra!r} does not valid "
454
+ f"as config {type(conf)}."
443
455
  )
444
- return rsx if rsx is not None else rs
456
+ return extra
445
457
 
446
458
 
447
459
  class Loader(Protocol): # pragma: no cov
@@ -117,6 +117,7 @@ class On(BaseModel):
117
117
  passing["cronjob"] = interval2crontab(
118
118
  **{v: value[v] for v in value if v in ("interval", "day", "time")}
119
119
  )
120
+ print(passing)
120
121
  return cls(extras=extras | passing.pop("extras", {}), **passing)
121
122
 
122
123
  @classmethod
@@ -9,16 +9,16 @@ annotate for handle error only.
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
- from typing import TypedDict
12
+ from typing import Literal, TypedDict, overload
13
13
 
14
- ErrorData = TypedDict(
15
- "ErrorData",
16
- {
17
- "class": Exception,
18
- "name": str,
19
- "message": str,
20
- },
21
- )
14
+
15
+ class ErrorData(TypedDict):
16
+ """Error data type dict for typing necessary keys of return of to_dict func
17
+ and method.
18
+ """
19
+
20
+ name: str
21
+ message: str
22
22
 
23
23
 
24
24
  def to_dict(exception: Exception) -> ErrorData: # pragma: no cov
@@ -29,20 +29,41 @@ def to_dict(exception: Exception) -> ErrorData: # pragma: no cov
29
29
  :rtype: ErrorData
30
30
  """
31
31
  return {
32
- "class": exception,
33
32
  "name": exception.__class__.__name__,
34
33
  "message": str(exception),
35
34
  }
36
35
 
37
36
 
38
37
  class BaseWorkflowException(Exception):
38
+ """Base Workflow exception class will implement the `refs` argument for
39
+ making an error context to the result context.
40
+ """
41
+
42
+ def __init__(self, message: str, *, refs: str | None = None):
43
+ super().__init__(message)
44
+ self.refs: str | None = refs
45
+
46
+ @overload
47
+ def to_dict(
48
+ self, with_refs: Literal[True] = ...
49
+ ) -> dict[str, ErrorData]: ... # pragma: no cov
50
+
51
+ @overload
52
+ def to_dict(
53
+ self, with_refs: Literal[False] = ...
54
+ ) -> ErrorData: ... # pragma: no cov
39
55
 
40
- def to_dict(self) -> ErrorData:
56
+ def to_dict(
57
+ self, with_refs: bool = False
58
+ ) -> ErrorData | dict[str, ErrorData]:
41
59
  """Return ErrorData data from the current exception object.
42
60
 
43
61
  :rtype: ErrorData
44
62
  """
45
- return to_dict(self)
63
+ data: ErrorData = to_dict(self)
64
+ if with_refs and (self.refs is not None and self.refs != "EMPTY"):
65
+ return {self.refs: data}
66
+ return data
46
67
 
47
68
 
48
69
  class UtilException(BaseWorkflowException): ...