ddeutil-workflow 0.0.54__tar.gz → 0.0.55__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 (68) hide show
  1. {ddeutil_workflow-0.0.54/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.55}/PKG-INFO +5 -7
  2. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/README.md +4 -6
  3. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/pyproject.toml +0 -2
  4. ddeutil_workflow-0.0.55/src/ddeutil/workflow/__about__.py +1 -0
  5. ddeutil_workflow-0.0.54/src/ddeutil/workflow/api/api.py → ddeutil_workflow-0.0.55/src/ddeutil/workflow/api/__init__.py +1 -1
  6. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/routes/job.py +22 -21
  7. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/routes/schedules.py +0 -2
  8. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/routes/workflows.py +3 -4
  9. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/job.py +17 -13
  10. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/result.py +1 -0
  11. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/scheduler.py +1 -3
  12. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/stages.py +169 -116
  13. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/workflow.py +18 -33
  14. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55/src/ddeutil_workflow.egg-info}/PKG-INFO +5 -7
  15. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil_workflow.egg-info/SOURCES.txt +0 -1
  16. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_job_exec.py +13 -13
  17. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_job_exec_strategy.py +7 -7
  18. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_release_queue.py +6 -2
  19. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow_exec.py +4 -4
  20. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow_exec_job.py +1 -1
  21. ddeutil_workflow-0.0.54/src/ddeutil/workflow/__about__.py +0 -1
  22. ddeutil_workflow-0.0.54/src/ddeutil/workflow/api/__init__.py +0 -1
  23. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/LICENSE +0 -0
  24. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/setup.cfg +0 -0
  25. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/__cron.py +0 -0
  26. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/__init__.py +0 -0
  27. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/__main__.py +0 -0
  28. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/__types.py +0 -0
  29. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/logs.py +0 -0
  30. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
  31. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/routes/logs.py +0 -0
  32. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/api/utils.py +0 -0
  33. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/conf.py +0 -0
  34. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/cron.py +0 -0
  35. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/exceptions.py +0 -0
  36. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/logs.py +0 -0
  37. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/params.py +0 -0
  38. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/reusables.py +0 -0
  39. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil/workflow/utils.py +0 -0
  40. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  41. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil_workflow.egg-info/requires.txt +0 -0
  42. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  43. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test__cron.py +0 -0
  44. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test__regex.py +0 -0
  45. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_conf.py +0 -0
  46. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_cron_on.py +0 -0
  47. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_job.py +0 -0
  48. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_logs_audit.py +0 -0
  49. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_logs_trace.py +0 -0
  50. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_params.py +0 -0
  51. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_release.py +0 -0
  52. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_result.py +0 -0
  53. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_reusables_call_tag.py +0 -0
  54. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_reusables_template.py +0 -0
  55. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_reusables_template_filter.py +0 -0
  56. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_schedule.py +0 -0
  57. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_schedule_pending.py +0 -0
  58. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_schedule_tasks.py +0 -0
  59. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_schedule_workflow.py +0 -0
  60. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_scheduler_control.py +0 -0
  61. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_stage.py +0 -0
  62. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_stage_handler_exec.py +0 -0
  63. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_strategy.py +0 -0
  64. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_utils.py +0 -0
  65. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow.py +0 -0
  66. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow_exec_poke.py +0 -0
  67. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow_exec_release.py +0 -0
  68. {ddeutil_workflow-0.0.54 → ddeutil_workflow-0.0.55}/tests/test_workflow_task.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.54
3
+ Version: 0.0.55
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -121,12 +121,10 @@ flowchart LR
121
121
 
122
122
  > [!WARNING]
123
123
  > _**Disclaimer**_: I inspire the dynamic YAML statement from the [**GitHub Action**](https://github.com/features/actions),
124
- > and all configs pattern from several data orchestration framework tools from
125
- > my data engineering experience. :grimacing:
126
-
127
- > [!NOTE]
128
- > Other workflow orchestration tools that I interest and pick them to be inspiration
129
- > some for this package:
124
+ > and my experience of data framework configs pattern. :grimacing:
125
+ >
126
+ > Other workflow orchestration services that I interest and pick them to be
127
+ > this project inspiration:
130
128
  >
131
129
  > - [Google **Workflows**](https://cloud.google.com/workflows)
132
130
  > - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
@@ -76,12 +76,10 @@ flowchart LR
76
76
 
77
77
  > [!WARNING]
78
78
  > _**Disclaimer**_: I inspire the dynamic YAML statement from the [**GitHub Action**](https://github.com/features/actions),
79
- > and all configs pattern from several data orchestration framework tools from
80
- > my data engineering experience. :grimacing:
81
-
82
- > [!NOTE]
83
- > Other workflow orchestration tools that I interest and pick them to be inspiration
84
- > some for this package:
79
+ > and my experience of data framework configs pattern. :grimacing:
80
+ >
81
+ > Other workflow orchestration services that I interest and pick them to be
82
+ > this project inspiration:
85
83
  >
86
84
  > - [Google **Workflows**](https://cloud.google.com/workflows)
87
85
  > - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
@@ -79,9 +79,7 @@ source = ["src.ddeutil.workflow"]
79
79
  omit = [
80
80
  "src/ddeutil/workflow/__about__.py",
81
81
  "src/ddeutil/workflow/__cron.py",
82
- "src/ddeutil/workflow/context.py",
83
82
  "src/ddeutil/workflow/api/__init__.py",
84
- "src/ddeutil/workflow/api/api.py",
85
83
  "src/ddeutil/workflow/api/log.py",
86
84
  "src/ddeutil/workflow/api/repeat.py",
87
85
  "src/ddeutil/workflow/api/routes/__init__.py",
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.55"
@@ -90,7 +90,7 @@ app.add_middleware(
90
90
  )
91
91
 
92
92
 
93
- @app.get("/")
93
+ @app.get(path="/", response_class=UJSONResponse)
94
94
  async def health():
95
95
  """Index view that not return any template without json status."""
96
96
  return {"message": "Workflow already start up with healthy status."}
@@ -9,7 +9,7 @@ from typing import Any, Optional
9
9
 
10
10
  from fastapi import APIRouter
11
11
  from fastapi.responses import UJSONResponse
12
- from pydantic import BaseModel
12
+ from pydantic import BaseModel, Field
13
13
 
14
14
  from ...__types import DictData
15
15
  from ...exceptions import JobException
@@ -18,33 +18,37 @@ from ...logs import get_logger
18
18
  from ...result import Result
19
19
 
20
20
  logger = get_logger("uvicorn.error")
21
+ job_route = APIRouter(prefix="/job", tags=["job"])
21
22
 
22
23
 
23
- job_route = APIRouter(
24
- prefix="/job",
25
- tags=["job"],
26
- default_response_class=UJSONResponse,
27
- )
24
+ class ResultCreate(BaseModel):
25
+ """Create Result model for receive running IDs to create the Result
26
+ dataclass.
27
+ """
28
28
 
29
-
30
- class ResultPost(BaseModel):
31
- context: DictData
32
- run_id: str
33
- parent_run_id: Optional[str] = None
29
+ run_id: str = Field(description="A running ID.")
30
+ parent_run_id: Optional[str] = Field(
31
+ default=None, description="A parent running ID."
32
+ )
34
33
 
35
34
 
36
- @job_route.post(path="/execute/")
35
+ @job_route.post(path="/execute/", response_class=UJSONResponse)
37
36
  async def job_execute(
38
- result: ResultPost,
37
+ result: ResultCreate,
39
38
  job: Job,
40
39
  params: dict[str, Any],
40
+ extras: Optional[dict[str, Any]] = None,
41
41
  ):
42
- """Execute job via RestAPI."""
42
+ """Execute job via RestAPI with execute route path."""
43
43
  rs: Result = Result(
44
- context=result.context,
45
44
  run_id=result.run_id,
46
45
  parent_run_id=result.parent_run_id,
46
+ extras=extras or {},
47
47
  )
48
+
49
+ if extras:
50
+ job.extras = extras
51
+
48
52
  context: DictData = {}
49
53
  try:
50
54
  job.set_outputs(
@@ -59,14 +63,11 @@ async def job_execute(
59
63
  rs.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
60
64
 
61
65
  return {
62
- "message": "Start execute job via API.",
63
- "result": {
64
- "run_id": rs.run_id,
65
- "parent_run_id": rs.parent_run_id,
66
- },
66
+ "message": "Execute job via RestAPI.",
67
+ "result": {"run_id": rs.run_id, "parent_run_id": rs.parent_run_id},
67
68
  "job": job.model_dump(
68
69
  by_alias=True,
69
- exclude_none=True,
70
+ exclude_none=False,
70
71
  exclude_unset=True,
71
72
  exclude_defaults=True,
72
73
  ),
@@ -17,7 +17,6 @@ from ...logs import get_logger
17
17
  from ...scheduler import Schedule
18
18
 
19
19
  logger = get_logger("uvicorn.error")
20
-
21
20
  schedule_route = APIRouter(
22
21
  prefix="/schedules",
23
22
  tags=["schedules"],
@@ -108,7 +107,6 @@ async def add_deploy_scheduler(request: Request, name: str):
108
107
  schedule.tasks(
109
108
  start_date_waiting,
110
109
  queue=request.state.workflow_queue,
111
- extras={},
112
110
  ),
113
111
  )
114
112
  return {
@@ -21,7 +21,6 @@ from ...result import Result
21
21
  from ...workflow import Workflow
22
22
 
23
23
  logger = get_logger("uvicorn.error")
24
-
25
24
  workflow_route = APIRouter(
26
25
  prefix="/workflows",
27
26
  tags=["workflows"],
@@ -55,7 +54,7 @@ async def get_workflow_by_name(name: str) -> DictData:
55
54
  ) from None
56
55
  return workflow.model_dump(
57
56
  by_alias=True,
58
- exclude_none=True,
57
+ exclude_none=False,
59
58
  exclude_unset=True,
60
59
  exclude_defaults=True,
61
60
  )
@@ -98,7 +97,7 @@ async def get_workflow_audits(name: str):
98
97
  "audits": [
99
98
  audit.model_dump(
100
99
  by_alias=True,
101
- exclude_none=True,
100
+ exclude_none=False,
102
101
  exclude_unset=True,
103
102
  exclude_defaults=True,
104
103
  )
@@ -132,7 +131,7 @@ async def get_workflow_release_audit(name: str, release: str):
132
131
  "message": f"Getting workflow {name!r} audit in release {release}",
133
132
  "audit": audit.model_dump(
134
133
  by_alias=True,
135
- exclude_none=True,
134
+ exclude_none=False,
136
135
  exclude_unset=True,
137
136
  exclude_defaults=True,
138
137
  ),
@@ -540,6 +540,11 @@ class Job(BaseModel):
540
540
  }
541
541
  }
542
542
 
543
+ The keys that will set to the received context is `strategies`,
544
+ `errors`, and `skipped` keys. The `errors` and `skipped` keys will
545
+ extract from the result context if it exists. If it does not found, it
546
+ will not set on the received context.
547
+
543
548
  :raise JobException: If the job's ID does not set and the setting
544
549
  default job ID flag does not set.
545
550
 
@@ -599,7 +604,7 @@ class Job(BaseModel):
599
604
 
600
605
  :param params: (DictData) A parameter data.
601
606
  :param run_id: (str) A job running ID.
602
- :param parent_run_id: (str) A parent workflow running ID.
607
+ :param parent_run_id: (str) A parent running ID.
603
608
  :param event: (Event) An Event manager instance that use to cancel this
604
609
  execution if it forces stopped by parent execution.
605
610
 
@@ -667,15 +672,15 @@ def local_execute_strategy(
667
672
  `set_outputs` method for reconstruct result context data.
668
673
 
669
674
  :param job: (Job) A job model that want to execute.
670
- :param strategy: A strategy metrix value that use on this execution.
671
- This value will pass to the `matrix` key for templating.
675
+ :param strategy: (DictData) A strategy metrix value. This value will pass
676
+ to the `matrix` key for templating in context data.
672
677
  :param params: (DictData) A parameter data.
673
678
  :param result: (Result) A Result instance for return context and status.
674
679
  :param event: (Event) An Event manager instance that use to cancel this
675
680
  execution if it forces stopped by parent execution.
676
681
 
677
- :raise JobException: If it has any error from `StageException` or
678
- `UtilException`.
682
+ :raise JobException: If stage execution raise any error as `StageException`
683
+ or `UtilException`.
679
684
 
680
685
  :rtype: Result
681
686
  """
@@ -683,17 +688,16 @@ def local_execute_strategy(
683
688
  run_id=gen_id(job.id or "not-set", unique=True),
684
689
  extras=job.extras,
685
690
  )
686
-
687
- strategy_id: str = gen_id(strategy)
688
- context: DictData = copy.deepcopy(params)
689
- context.update({"matrix": strategy, "stages": {}})
690
-
691
691
  if strategy:
692
+ strategy_id: str = gen_id(strategy)
692
693
  result.trace.info(f"[JOB]: Start Strategy: {strategy_id!r}")
693
694
  result.trace.info(f"[JOB]: ... matrix: {strategy!r}")
694
695
  else:
695
- result.trace.info("[JOB]: Start Strategy: EMPTY")
696
+ strategy_id: str = "EMPTY"
697
+ result.trace.info("[JOB]: Start Strategy: 'EMPTY'")
696
698
 
699
+ context: DictData = copy.deepcopy(params)
700
+ context.update({"matrix": strategy, "stages": {}})
697
701
  for stage in job.stages:
698
702
 
699
703
  if job.extras:
@@ -707,7 +711,7 @@ def local_execute_strategy(
707
711
  if event and event.is_set():
708
712
  error_msg: str = (
709
713
  "Job strategy was canceled from event that had set before "
710
- "strategy execution."
714
+ "job strategy execution."
711
715
  )
712
716
  return result.catch(
713
717
  status=CANCEL,
@@ -820,7 +824,7 @@ def local_execute(
820
824
  context={
821
825
  "errors": JobException(
822
826
  "Job was canceled from event that had set before "
823
- "local execution."
827
+ "local job execution."
824
828
  ).to_dict()
825
829
  },
826
830
  )
@@ -98,6 +98,7 @@ class Result:
98
98
  return cls(
99
99
  run_id=(run_id or gen_id(id_logic or "", unique=True)),
100
100
  parent_run_id=parent_run_id,
101
+ ts=get_dt_now(dynamic("tz", extras=extras)),
101
102
  extras=(extras or {}),
102
103
  )
103
104
  elif parent_run_id:
@@ -535,9 +535,7 @@ def schedule_task(
535
535
  current_release: datetime = current_date.replace(
536
536
  second=0, microsecond=0
537
537
  )
538
- if (
539
- first_date := q.first_queue.date
540
- ) > current_release: # pragma: no cov
538
+ if (first_date := q.queue[0].date) > current_release: # pragma: no cov
541
539
  result.trace.debug(
542
540
  f"[WORKFLOW]: Skip schedule "
543
541
  f"{first_date:%Y-%m-%d %H:%M:%S} for : {task.alias!r}"