ddeutil-workflow 0.0.37__tar.gz → 0.0.38__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 (71) hide show
  1. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/PKG-INFO +8 -2
  2. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/README.md +2 -0
  3. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/pyproject.toml +5 -0
  4. ddeutil_workflow-0.0.38/src/ddeutil/workflow/__about__.py +1 -0
  5. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/__init__.py +4 -1
  6. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/routes/job.py +3 -1
  7. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/routes/logs.py +12 -4
  8. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/caller.py +6 -2
  9. ddeutil_workflow-0.0.38/src/ddeutil/workflow/context.py +59 -0
  10. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/exceptions.py +14 -1
  11. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/job.py +100 -121
  12. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/logs.py +6 -1
  13. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/result.py +1 -1
  14. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/stages.py +364 -111
  15. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/utils.py +1 -44
  16. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/workflow.py +137 -72
  17. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil_workflow.egg-info/PKG-INFO +8 -2
  18. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil_workflow.egg-info/SOURCES.txt +2 -0
  19. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil_workflow.egg-info/requires.txt +4 -0
  20. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_call_tag.py +33 -2
  21. ddeutil_workflow-0.0.38/tests/test_context.py +136 -0
  22. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_job.py +4 -7
  23. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_job_exec.py +24 -28
  24. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_job_exec_strategy.py +2 -4
  25. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_job_strategy.py +6 -0
  26. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_result.py +4 -1
  27. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_stage_handler_exec.py +165 -4
  28. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_utils.py +0 -9
  29. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow.py +1 -2
  30. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow_exec.py +38 -27
  31. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow_exec_job.py +0 -1
  32. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow_exec_poke.py +0 -1
  33. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow_exec_release.py +0 -3
  34. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_workflow_task.py +0 -1
  35. ddeutil_workflow-0.0.37/src/ddeutil/workflow/__about__.py +0 -1
  36. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/LICENSE +0 -0
  37. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/setup.cfg +0 -0
  38. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/__cron.py +0 -0
  39. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/__types.py +0 -0
  40. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/__init__.py +0 -0
  41. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/api.py +0 -0
  42. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/log.py +0 -0
  43. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/repeat.py +0 -0
  44. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
  45. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
  46. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
  47. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/audit.py +0 -0
  48. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/conf.py +0 -0
  49. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/cron.py +0 -0
  50. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/params.py +0 -0
  51. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/scheduler.py +0 -0
  52. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil/workflow/templates.py +0 -0
  53. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  54. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  55. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test__cron.py +0 -0
  56. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test__regex.py +0 -0
  57. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_audit.py +0 -0
  58. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_conf.py +0 -0
  59. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_cron_on.py +0 -0
  60. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_logs.py +0 -0
  61. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_params.py +0 -0
  62. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_release.py +0 -0
  63. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_release_queue.py +0 -0
  64. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_schedule.py +0 -0
  65. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_schedule_pending.py +0 -0
  66. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_schedule_tasks.py +0 -0
  67. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_schedule_workflow.py +0 -0
  68. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_scheduler_control.py +0 -0
  69. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_stage.py +0 -0
  70. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_templates.py +0 -0
  71. {ddeutil_workflow-0.0.37 → ddeutil_workflow-0.0.38}/tests/test_templates_filter.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.37
3
+ Version: 0.0.38
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -31,6 +31,10 @@ Provides-Extra: api
31
31
  Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
32
32
  Requires-Dist: httpx; extra == "api"
33
33
  Requires-Dist: ujson; extra == "api"
34
+ Provides-Extra: async
35
+ Requires-Dist: aiofiles; extra == "async"
36
+ Requires-Dist: aiohttp; extra == "async"
37
+ Dynamic: license-file
34
38
 
35
39
  # Workflow Orchestration
36
40
 
@@ -165,6 +169,8 @@ run-py-local:
165
169
  run-date: datetime
166
170
  jobs:
167
171
  getting-api-data:
172
+ runs-on:
173
+ type: local
168
174
  stages:
169
175
  - name: "Retrieve API Data"
170
176
  id: retrieve-api
@@ -131,6 +131,8 @@ run-py-local:
131
131
  run-date: datetime
132
132
  jobs:
133
133
  getting-api-data:
134
+ runs-on:
135
+ type: local
134
136
  stages:
135
137
  - name: "Retrieve API Data"
136
138
  id: retrieve-api
@@ -40,6 +40,10 @@ api = [
40
40
  "httpx",
41
41
  "ujson",
42
42
  ]
43
+ async = [
44
+ "aiofiles",
45
+ "aiohttp",
46
+ ]
43
47
 
44
48
  [project.urls]
45
49
  Homepage = "https://github.com/ddeutils/ddeutil-workflow/"
@@ -67,6 +71,7 @@ source = ["src.ddeutil.workflow"]
67
71
  omit = [
68
72
  "src/ddeutil/workflow/__about__.py",
69
73
  "src/ddeutil/workflow/__cron.py",
74
+ "src/ddeutil/workflow/context.py",
70
75
  "src/ddeutil/workflow/api/__init__.py",
71
76
  "src/ddeutil/workflow/api/api.py",
72
77
  "src/ddeutil/workflow/api/log.py",
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.38"
@@ -39,6 +39,8 @@ from .job import (
39
39
  Job,
40
40
  RunsOn,
41
41
  Strategy,
42
+ local_execute,
43
+ local_execute_strategy,
42
44
  )
43
45
  from .logs import (
44
46
  TraceData,
@@ -69,6 +71,8 @@ from .stages import (
69
71
  BashStage,
70
72
  CallStage,
71
73
  EmptyStage,
74
+ ForEachStage,
75
+ ParallelStage,
72
76
  PyStage,
73
77
  Stage,
74
78
  TriggerStage,
@@ -89,7 +93,6 @@ from .templates import (
89
93
  from .utils import (
90
94
  batch,
91
95
  cross_product,
92
- dash2underscore,
93
96
  delay,
94
97
  filter_func,
95
98
  gen_id,
@@ -45,6 +45,7 @@ async def job_execute(
45
45
  run_id=result.run_id,
46
46
  parent_run_id=result.parent_run_id,
47
47
  )
48
+ context: DictData = {}
48
49
  try:
49
50
  job.set_outputs(
50
51
  job.execute(
@@ -52,7 +53,7 @@ async def job_execute(
52
53
  run_id=rs.run_id,
53
54
  parent_run_id=rs.parent_run_id,
54
55
  ).context,
55
- to=params,
56
+ to=context,
56
57
  )
57
58
  except JobException as err:
58
59
  rs.trace.error(f"[WORKFLOW]: {err.__class__.__name__}: {err}")
@@ -70,4 +71,5 @@ async def job_execute(
70
71
  exclude_defaults=True,
71
72
  ),
72
73
  "params": params,
74
+ "context": context,
73
75
  }
@@ -6,7 +6,7 @@
6
6
  """This route include audit and trace log paths."""
7
7
  from __future__ import annotations
8
8
 
9
- from fastapi import APIRouter
9
+ from fastapi import APIRouter, Path, Query
10
10
  from fastapi import status as st
11
11
  from fastapi.responses import UJSONResponse
12
12
 
@@ -27,12 +27,17 @@ log_route = APIRouter(
27
27
  summary="Read all trace logs.",
28
28
  tags=["trace"],
29
29
  )
30
- async def get_traces():
30
+ async def get_traces(
31
+ offset: int = Query(default=0, gt=0),
32
+ limit: int = Query(default=100, gt=0),
33
+ ):
31
34
  """Return all trace logs from the current trace log path that config with
32
35
  `WORKFLOW_LOG_PATH` environment variable name.
33
36
  """
34
37
  return {
35
- "message": "Getting trace logs",
38
+ "message": (
39
+ f"Getting trace logs with offset: {offset} and limit: {limit}"
40
+ ),
36
41
  "traces": [
37
42
  trace.model_dump(
38
43
  by_alias=True,
@@ -117,7 +122,10 @@ async def get_audit_with_workflow(workflow: str):
117
122
  summary="Read all audit logs with specific workflow name and release date.",
118
123
  tags=["audit"],
119
124
  )
120
- async def get_audit_with_workflow_release(workflow: str, release: str):
125
+ async def get_audit_with_workflow_release(
126
+ workflow: str = Path(...),
127
+ release: str = Path(...),
128
+ ):
121
129
  """Return all audit logs with specific workflow name and release date from
122
130
  the current audit log path that config with `WORKFLOW_AUDIT_PATH`
123
131
  environment variable name.
@@ -26,6 +26,7 @@ T = TypeVar("T")
26
26
  P = ParamSpec("P")
27
27
 
28
28
  logger = logging.getLogger("ddeutil.workflow")
29
+ logging.getLogger("asyncio").setLevel(logging.INFO)
29
30
 
30
31
 
31
32
  class TagFunc(Protocol):
@@ -60,10 +61,13 @@ def tag(
60
61
 
61
62
  @wraps(func)
62
63
  def wrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
63
- # NOTE: Able to do anything before calling the call function.
64
64
  return func(*args, **kwargs)
65
65
 
66
- return wrapped
66
+ @wraps(func)
67
+ async def awrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
68
+ return await func(*args, **kwargs)
69
+
70
+ return awrapped if inspect.iscoroutinefunction(func) else wrapped
67
71
 
68
72
  return func_internal
69
73
 
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, Union
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from .__types import DictData
8
+
9
+
10
+ class ErrorContext(BaseModel): # pragma: no cov
11
+ model_config = ConfigDict(arbitrary_types_allowed=True)
12
+
13
+ obj: Exception = Field(alias="class")
14
+ name: str = Field(description="A name of exception class.")
15
+ message: str = Field(description="A exception message.")
16
+
17
+
18
+ class OutputContext(BaseModel): # pragma: no cov
19
+ outputs: DictData = Field(default_factory=dict)
20
+ errors: Optional[ErrorContext] = Field(default=None)
21
+
22
+ def is_exception(self) -> bool:
23
+ return self.errors is not None
24
+
25
+
26
+ class StageContext(BaseModel): # pragma: no cov
27
+ stages: dict[str, OutputContext]
28
+ errors: Optional[ErrorContext] = Field(default=None)
29
+
30
+ def is_exception(self) -> bool:
31
+ return self.errors is not None
32
+
33
+
34
+ class MatrixContext(StageContext): # pragma: no cov
35
+ matrix: DictData = Field(default_factory=dict)
36
+
37
+
38
+ MatrixStageContext = dict[
39
+ str, Union[MatrixContext, StageContext]
40
+ ] # pragma: no cov
41
+
42
+
43
+ class StrategyContext(BaseModel): # pragma: no cov
44
+ strategies: MatrixStageContext
45
+ errors: Optional[ErrorContext] = Field(default=None)
46
+
47
+ def is_exception(self) -> bool:
48
+ return self.errors is not None
49
+
50
+
51
+ StrategyMatrixContext = Union[
52
+ StrategyContext, MatrixStageContext
53
+ ] # pragma: no cov
54
+
55
+
56
+ class JobContext(BaseModel): # pragma: no cov
57
+ params: DictData = Field(description="A parameterize value")
58
+ jobs: dict[str, StrategyMatrixContext]
59
+ errors: Optional[ErrorContext] = Field(default=None)
@@ -9,8 +9,21 @@ annotate for handle error only.
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
+ from typing import Any
12
13
 
13
- class BaseWorkflowException(Exception): ...
14
+
15
+ def to_dict(exception: Exception) -> dict[str, Any]: # pragma: no cov
16
+ return {
17
+ "class": exception,
18
+ "name": exception.__class__.__name__,
19
+ "message": str(exception),
20
+ }
21
+
22
+
23
+ class BaseWorkflowException(Exception):
24
+
25
+ def to_dict(self) -> dict[str, Any]:
26
+ return to_dict(self)
14
27
 
15
28
 
16
29
  class UtilException(BaseWorkflowException): ...