ddeutil-workflow 0.0.65__tar.gz → 0.0.66__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 (58) hide show
  1. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/PKG-INFO +2 -2
  2. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/pyproject.toml +3 -4
  3. ddeutil_workflow-0.0.66/src/ddeutil/workflow/__about__.py +1 -0
  4. ddeutil_workflow-0.0.66/src/ddeutil/workflow/__main__.py +4 -0
  5. ddeutil_workflow-0.0.66/src/ddeutil/workflow/cli.py +66 -0
  6. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/conf.py +1 -5
  7. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/errors.py +0 -3
  8. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/job.py +6 -10
  9. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/logs.py +1 -1
  10. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/result.py +1 -3
  11. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/stages.py +9 -0
  12. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/workflow.py +25 -11
  13. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/PKG-INFO +2 -2
  14. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/SOURCES.txt +1 -0
  15. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/requires.txt +1 -1
  16. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_conf.py +0 -6
  17. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_job_exec_strategy.py +4 -2
  18. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_workflow_release.py +52 -0
  19. ddeutil_workflow-0.0.65/src/ddeutil/workflow/__about__.py +0 -1
  20. ddeutil_workflow-0.0.65/src/ddeutil/workflow/__main__.py +0 -30
  21. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/LICENSE +0 -0
  22. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/README.md +0 -0
  23. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/setup.cfg +0 -0
  24. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/__cron.py +0 -0
  25. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/__init__.py +0 -0
  26. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/__types.py +0 -0
  27. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/__init__.py +0 -0
  28. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/logs.py +0 -0
  29. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
  30. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/routes/job.py +0 -0
  31. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/routes/logs.py +0 -0
  32. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
  33. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/event.py +0 -0
  34. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/params.py +0 -0
  35. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/reusables.py +0 -0
  36. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil/workflow/utils.py +0 -0
  37. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  38. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/entry_points.txt +0 -0
  39. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  40. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test__cron.py +0 -0
  41. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test__regex.py +0 -0
  42. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_errors.py +0 -0
  43. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_event.py +0 -0
  44. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_job.py +0 -0
  45. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_job_exec.py +0 -0
  46. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_logs_audit.py +0 -0
  47. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_logs_trace.py +0 -0
  48. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_params.py +0 -0
  49. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_result.py +0 -0
  50. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_reusables_call_tag.py +0 -0
  51. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_reusables_func_model.py +0 -0
  52. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_reusables_template.py +0 -0
  53. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_reusables_template_filter.py +0 -0
  54. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_strategy.py +0 -0
  55. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_utils.py +0 -0
  56. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_workflow.py +0 -0
  57. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_workflow_exec.py +0 -0
  58. {ddeutil_workflow-0.0.65 → ddeutil_workflow-0.0.66}/tests/test_workflow_exec_job.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.65
3
+ Version: 0.0.66
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -27,7 +27,7 @@ Requires-Dist: ddeutil-io[toml,yaml]>=0.2.14
27
27
  Requires-Dist: pydantic==2.11.4
28
28
  Requires-Dist: pydantic-extra-types==2.10.4
29
29
  Requires-Dist: python-dotenv==1.1.0
30
- Requires-Dist: schedule<2.0.0,==1.2.2
30
+ Requires-Dist: typer==0.15.4
31
31
  Provides-Extra: all
32
32
  Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "all"
33
33
  Requires-Dist: uvicorn; extra == "all"
@@ -30,7 +30,8 @@ dependencies = [
30
30
  "pydantic==2.11.4",
31
31
  "pydantic-extra-types==2.10.4",
32
32
  "python-dotenv==1.1.0",
33
- "schedule==1.2.2,<2.0.0",
33
+ # "schedule==1.2.2,<2.0.0",
34
+ "typer==0.15.4",
34
35
  ]
35
36
  dynamic = ["version"]
36
37
 
@@ -90,15 +91,13 @@ omit = [
90
91
  "src/ddeutil/workflow/__about__.py",
91
92
  "src/ddeutil/workflow/__cron.py",
92
93
  "src/ddeutil/workflow/__main__.py",
94
+ "src/ddeutil/workflow/cli.py",
93
95
  "src/ddeutil/workflow/api/__init__.py",
94
96
  "src/ddeutil/workflow/api/logs.py",
95
- "src/ddeutil/workflow/api/utils.py",
96
97
  "src/ddeutil/workflow/api/routes/__init__.py",
97
98
  "src/ddeutil/workflow/api/routes/job.py",
98
99
  "src/ddeutil/workflow/api/routes/logs.py",
99
- "src/ddeutil/workflow/api/routes/schedules.py",
100
100
  "src/ddeutil/workflow/api/routes/workflows.py",
101
- "app.py",
102
101
  ]
103
102
 
104
103
  [tool.coverage.report]
@@ -0,0 +1 @@
1
+ __version__: str = "0.0.66"
@@ -0,0 +1,4 @@
1
+ from .cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()
@@ -0,0 +1,66 @@
1
+ import json
2
+ from typing import Annotated, Any
3
+
4
+ import typer
5
+ import uvicorn
6
+
7
+ from .__about__ import __version__
8
+ from .api import app as fastapp
9
+ from .api.logs import LOGGING_CONFIG
10
+
11
+ app = typer.Typer(
12
+ pretty_exceptions_enable=True,
13
+ )
14
+
15
+
16
+ @app.callback()
17
+ def callback():
18
+ """
19
+ Awesome Portal Gun
20
+ """
21
+ typer.echo("Start call from callback function")
22
+
23
+
24
+ @app.command()
25
+ def version():
26
+ """Get the ddeutil-workflow package version."""
27
+ typer.echo(__version__)
28
+
29
+
30
+ @app.command()
31
+ def job(
32
+ params: Annotated[str, typer.Option(help="A job execute parameters")],
33
+ ):
34
+ """Job execution on the local.
35
+
36
+ Example:
37
+ ... workflow-cli job --params "{\"test\": 1}"
38
+ """
39
+ try:
40
+ params_dict: dict[str, Any] = json.loads(params)
41
+ except json.JSONDecodeError as e:
42
+ raise ValueError(f"params does not support format: {params!r}.") from e
43
+ typer.echo(f"Job params: {params_dict}")
44
+
45
+
46
+ @app.command()
47
+ def api(
48
+ host: Annotated[str, typer.Option(help="A host url.")] = "0.0.0.0",
49
+ port: Annotated[int, typer.Option(help="A port url.")] = 80,
50
+ debug: Annotated[bool, typer.Option(help="A debug mode flag")] = True,
51
+ ):
52
+ """
53
+ Provision API application from the FastAPI.
54
+ """
55
+
56
+ uvicorn.run(
57
+ fastapp,
58
+ host=host,
59
+ port=port,
60
+ log_config=uvicorn.config.LOGGING_CONFIG | LOGGING_CONFIG,
61
+ log_level=("DEBUG" if debug else "INFO"),
62
+ )
63
+
64
+
65
+ if __name__ == "__main__":
66
+ app()
@@ -18,7 +18,7 @@ from zoneinfo import ZoneInfo
18
18
  from ddeutil.core import str2bool
19
19
  from ddeutil.io import YamlFlResolve, search_env_replace
20
20
  from ddeutil.io.paths import glob_files, is_ignored, read_ignore
21
- from pydantic import SecretStr, TypeAdapter
21
+ from pydantic import SecretStr
22
22
 
23
23
  from .__types import DictData
24
24
 
@@ -470,7 +470,3 @@ class CallerSecret(SecretStr): # pragma: no cov
470
470
  :rtype: str
471
471
  """
472
472
  return pass_env(super().get_secret_value())
473
-
474
-
475
- # NOTE: Define the caller secret type for use it directly in the caller func.
476
- CallerSecretType = TypeAdapter(CallerSecret)
@@ -114,9 +114,6 @@ class WorkflowError(BaseError): ...
114
114
  class WorkflowCancelError(WorkflowError): ...
115
115
 
116
116
 
117
- class WorkflowSkipError(WorkflowError): ...
118
-
119
-
120
117
  class WorkflowTimeoutError(WorkflowError): ...
121
118
 
122
119
 
@@ -39,7 +39,6 @@ from pydantic import BaseModel, Discriminator, Field, SecretStr, Tag
39
39
  from pydantic.functional_validators import field_validator, model_validator
40
40
  from typing_extensions import Self
41
41
 
42
- from . import JobSkipError
43
42
  from .__types import DictData, DictStr, Matrix, StrOrNone
44
43
  from .errors import JobCancelError, JobError, to_dict
45
44
  from .result import (
@@ -774,7 +773,7 @@ def local_execute_strategy(
774
773
  *,
775
774
  result: Optional[Result] = None,
776
775
  event: Optional[Event] = None,
777
- ) -> Result:
776
+ ) -> tuple[Status, Result]:
778
777
  """Local strategy execution with passing dynamic parameters from the
779
778
  job execution and strategy matrix.
780
779
 
@@ -799,7 +798,7 @@ def local_execute_strategy(
799
798
  :raise JobError: If stage execution raise any error as `StageError`.
800
799
  :raise JobError: If the result from execution has `FAILED` status.
801
800
 
802
- :rtype: Result
801
+ :rtype: tuple[Status, Result]
803
802
  """
804
803
  result: Result = result or Result(
805
804
  run_id=gen_id(job.id or "EMPTY", unique=True),
@@ -899,9 +898,7 @@ def local_execute_strategy(
899
898
  },
900
899
  },
901
900
  )
902
- if status == SKIP:
903
- raise JobSkipError("All stage was skipped.")
904
- return result
901
+ return status, result
905
902
 
906
903
 
907
904
  def local_execute(
@@ -1017,14 +1014,13 @@ def local_execute(
1017
1014
 
1018
1015
  for i, future in enumerate(done, start=0):
1019
1016
  try:
1020
- statuses[i] = future.result().status
1017
+ statuses[i], _ = future.result()
1021
1018
  except JobError as e:
1022
1019
  statuses[i] = get_status_from_error(e)
1023
1020
  result.trace.error(
1024
- f"[JOB]: {ls} Error Handler:||{e.__class__.__name__}: {e}"
1021
+ f"[JOB]: {ls} Handler:||{e.__class__.__name__}: {e}"
1025
1022
  )
1026
- if not isinstance(e, JobSkipError):
1027
- mark_errors(context, e)
1023
+ mark_errors(context, e)
1028
1024
  except CancelledError:
1029
1025
  pass
1030
1026
 
@@ -848,7 +848,7 @@ class FileAudit(BaseAudit):
848
848
  "audit_path", extras=self.extras
849
849
  ) / self.filename_fmt.format(name=self.name, release=self.release)
850
850
 
851
- def save(self, excluded: Optional[list[str]]) -> Self:
851
+ def save(self, excluded: Optional[list[str]] = None) -> Self:
852
852
  """Save logging data that receive a context data from a workflow
853
853
  execution result.
854
854
 
@@ -28,7 +28,6 @@ from . import (
28
28
  StageSkipError,
29
29
  WorkflowCancelError,
30
30
  WorkflowError,
31
- WorkflowSkipError,
32
31
  )
33
32
  from .__types import DictData
34
33
  from .conf import dynamic
@@ -106,13 +105,12 @@ def get_status_from_error(
106
105
  JobSkipError,
107
106
  WorkflowError,
108
107
  WorkflowCancelError,
109
- WorkflowSkipError,
110
108
  Exception,
111
109
  BaseException,
112
110
  ]
113
111
  ) -> Status:
114
112
  """Get the Status from the error object."""
115
- if isinstance(error, (StageSkipError, JobSkipError, WorkflowSkipError)):
113
+ if isinstance(error, (StageSkipError, JobSkipError)):
116
114
  return SKIP
117
115
  elif isinstance(
118
116
  error, (StageCancelError, JobCancelError, WorkflowCancelError)
@@ -1610,6 +1610,9 @@ class ParallelStage(BaseNestedStage):
1610
1610
  (Default is None)
1611
1611
 
1612
1612
  :raise StageCancelError: If event was set.
1613
+ :raise StageCancelError: If result from a nested-stage return canceled
1614
+ status.
1615
+ :raise StageError: If result from a nested-stage return failed status.
1613
1616
 
1614
1617
  :rtype: tuple[Status, Result]
1615
1618
  """
@@ -1854,9 +1857,11 @@ class ForEachStage(BaseNestedStage):
1854
1857
  result.trace.debug(f"[STAGE]: Execute Item: {item!r}")
1855
1858
  key: StrOrInt = index if self.use_index_as_key else item
1856
1859
 
1860
+ # NOTE: Create nested-context data from the passing context.
1857
1861
  context: DictData = copy.deepcopy(params)
1858
1862
  context.update({"item": item, "loop": index})
1859
1863
  nestet_context: DictData = {"item": item, "stages": {}}
1864
+
1860
1865
  total_stage: int = len(self.stages)
1861
1866
  skips: list[bool] = [False] * total_stage
1862
1867
  for i, stage in enumerate(self.stages, start=0):
@@ -1959,6 +1964,10 @@ class ForEachStage(BaseNestedStage):
1959
1964
  ) -> Result:
1960
1965
  """Execute the stages that pass each item form the foreach field.
1961
1966
 
1967
+ This stage will use fail-fast strategy if it was set concurrency
1968
+ value more than 1. It will cancel all nested-stage execution when it has
1969
+ any item loop raise failed or canceled error.
1970
+
1962
1971
  :param params: (DictData) A parameter data.
1963
1972
  :param result: (Result) A Result instance for return context and status.
1964
1973
  :param event: (Event) An Event manager instance that use to cancel this
@@ -379,6 +379,7 @@ class Workflow(BaseModel):
379
379
  override_log_name: Optional[str] = None,
380
380
  result: Optional[Result] = None,
381
381
  timeout: int = 600,
382
+ excluded: Optional[list[str]] = None,
382
383
  ) -> Result:
383
384
  """Release the workflow which is executes workflow with writing audit
384
385
  log tracking. The method is overriding parameter with the release
@@ -405,6 +406,8 @@ class Workflow(BaseModel):
405
406
  :param result: (Result) A result object for keeping context and status
406
407
  data.
407
408
  :param timeout: (int) A workflow execution time out in second unit.
409
+ :param excluded: (list[str]) A list of key that want to exclude from
410
+ audit data.
408
411
 
409
412
  :rtype: Result
410
413
  """
@@ -453,7 +456,7 @@ class Workflow(BaseModel):
453
456
  run_id=result.run_id,
454
457
  execution_time=result.alive_time(),
455
458
  extras=self.extras,
456
- ).save(excluded=None)
459
+ ).save(excluded=excluded)
457
460
  )
458
461
  return result.catch(
459
462
  status=rs.status,
@@ -586,6 +589,19 @@ class Workflow(BaseModel):
586
589
  |-name: ...
587
590
  ╰-message: ...
588
591
 
592
+ --> Ok --> Result
593
+ |-status: FAILED
594
+ ╰-context:
595
+ ╰-errors:
596
+ |-name: ...
597
+ ╰-message: ...
598
+
599
+ --> Ok --> Result
600
+ ╰-status: SKIP
601
+
602
+ --> Ok --> Result
603
+ ╰-status: SUCCESS
604
+
589
605
  :param params: A parameter data that will parameterize before execution.
590
606
  :param run_id: (Optional[str]) A workflow running ID.
591
607
  :param parent_run_id: (Optional[str]) A parent workflow running ID.
@@ -725,25 +741,23 @@ class Workflow(BaseModel):
725
741
 
726
742
  if not_timeout_flag:
727
743
  job_queue.join()
728
- total_future: int = 0
729
- for i, future in enumerate(as_completed(futures), start=0):
744
+ for total, future in enumerate(as_completed(futures), start=0):
730
745
  try:
731
- statuses[i], _ = future.result()
746
+ statuses[total], _ = future.result()
732
747
  except WorkflowError as e:
733
- statuses[i] = get_status_from_error(e)
734
- total_future += 1
748
+ statuses[total] = get_status_from_error(e)
735
749
 
736
750
  # NOTE: Update skipped status from the job trigger.
737
751
  for i in range(skip_count):
738
- statuses[total_future + i] = SKIP
752
+ statuses[total + 1 + i] = SKIP
739
753
 
740
754
  # NOTE: Update status from none-parallel job execution.
741
755
  for i, s in enumerate(sequence_statuses, start=0):
742
- statuses[total_future + skip_count + i] = s
756
+ statuses[total + 1 + skip_count + i] = s
743
757
 
744
- status: Status = validate_statuses(statuses)
745
-
746
- return result.catch(status=status, context=context)
758
+ return result.catch(
759
+ status=validate_statuses(statuses), context=context
760
+ )
747
761
 
748
762
  event.set()
749
763
  for future in futures:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.65
3
+ Version: 0.0.66
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -27,7 +27,7 @@ Requires-Dist: ddeutil-io[toml,yaml]>=0.2.14
27
27
  Requires-Dist: pydantic==2.11.4
28
28
  Requires-Dist: pydantic-extra-types==2.10.4
29
29
  Requires-Dist: python-dotenv==1.1.0
30
- Requires-Dist: schedule<2.0.0,==1.2.2
30
+ Requires-Dist: typer==0.15.4
31
31
  Provides-Extra: all
32
32
  Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "all"
33
33
  Requires-Dist: uvicorn; extra == "all"
@@ -6,6 +6,7 @@ src/ddeutil/workflow/__cron.py
6
6
  src/ddeutil/workflow/__init__.py
7
7
  src/ddeutil/workflow/__main__.py
8
8
  src/ddeutil/workflow/__types.py
9
+ src/ddeutil/workflow/cli.py
9
10
  src/ddeutil/workflow/conf.py
10
11
  src/ddeutil/workflow/errors.py
11
12
  src/ddeutil/workflow/event.py
@@ -3,7 +3,7 @@ ddeutil-io[toml,yaml]>=0.2.14
3
3
  pydantic==2.11.4
4
4
  pydantic-extra-types==2.10.4
5
5
  python-dotenv==1.1.0
6
- schedule<2.0.0,==1.2.2
6
+ typer==0.15.4
7
7
 
8
8
  [all]
9
9
  fastapi<1.0.0,>=0.115.0
@@ -9,7 +9,6 @@ import pytest
9
9
  import rtoml
10
10
  import yaml
11
11
  from ddeutil.workflow.conf import (
12
- CallerSecretType,
13
12
  Config,
14
13
  FileLoad,
15
14
  config,
@@ -171,8 +170,3 @@ def test_dynamic():
171
170
 
172
171
  conf = dynamic("max_job_exec_timeout", f=0, extras={})
173
172
  assert conf == 0
174
-
175
-
176
- def test_workflow_secret_model():
177
- data = CallerSecretType.validate_python("${WORKFLOW_CORE_TIMEZONE}")
178
- assert data.get_secret_value() == "Asia/Bangkok"
@@ -11,7 +11,8 @@ def test_job_exec_strategy():
11
11
  job: Job = Workflow.from_conf(name="wf-run-python-raise-for-job").job(
12
12
  "job-complete"
13
13
  )
14
- rs = local_execute_strategy(job, {"sleep": "0.1"}, {})
14
+ st, rs = local_execute_strategy(job, {"sleep": "0.1"}, {})
15
+ assert st == SUCCESS
15
16
  assert rs.status == SUCCESS
16
17
  assert rs.context == {
17
18
  "status": SUCCESS,
@@ -29,7 +30,8 @@ def test_job_exec_strategy_skipped_stage():
29
30
  job: Job = Workflow.from_conf(name="wf-run-python-raise-for-job").job(
30
31
  "job-stage-condition"
31
32
  )
32
- rs = local_execute_strategy(job, {"sleep": "1"}, {})
33
+ st, rs = local_execute_strategy(job, {"sleep": "1"}, {})
34
+ assert st == SUCCESS
33
35
  assert rs.status == SUCCESS
34
36
  assert rs.context == {
35
37
  "status": SUCCESS,
@@ -1,5 +1,7 @@
1
1
  from datetime import datetime
2
2
 
3
+ import pytest
4
+ from ddeutil.workflow import WorkflowError
3
5
  from ddeutil.workflow.conf import config
4
6
  from ddeutil.workflow.result import SUCCESS, Result
5
7
  from ddeutil.workflow.workflow import (
@@ -8,6 +10,56 @@ from ddeutil.workflow.workflow import (
8
10
  )
9
11
 
10
12
 
13
+ def test_workflow_validate_release():
14
+ workflow: Workflow = Workflow.model_validate(
15
+ {"name": "wf-common-not-set-event"}
16
+ )
17
+ assert workflow.validate_release(datetime.now())
18
+ assert workflow.validate_release(datetime(2025, 5, 1, 12, 1))
19
+ assert workflow.validate_release(datetime(2025, 5, 1, 11, 12))
20
+ assert workflow.validate_release(datetime(2025, 5, 1, 10, 25, 59, 150))
21
+
22
+ workflow: Workflow = Workflow.model_validate(
23
+ {
24
+ "name": "wf-common-validate",
25
+ "on": [
26
+ {
27
+ "cronjob": "*/3 * * * *",
28
+ "timezone": "Asia/Bangkok",
29
+ }
30
+ ],
31
+ }
32
+ )
33
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 9))
34
+
35
+ with pytest.raises(WorkflowError):
36
+ workflow.validate_release(datetime(2025, 5, 1, 1, 10))
37
+
38
+ with pytest.raises(WorkflowError):
39
+ workflow.validate_release(datetime(2025, 5, 1, 1, 1))
40
+
41
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 3))
42
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 3, 10, 100))
43
+
44
+ workflow: Workflow = Workflow.model_validate(
45
+ {
46
+ "name": "wf-common-validate",
47
+ "on": [
48
+ {
49
+ "cronjob": "* * * * *",
50
+ "timezone": "Asia/Bangkok",
51
+ }
52
+ ],
53
+ }
54
+ )
55
+
56
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 9))
57
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 10))
58
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 1))
59
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 3))
60
+ assert workflow.validate_release(datetime(2025, 5, 1, 1, 3, 10, 100))
61
+
62
+
11
63
  def test_workflow_release():
12
64
  workflow: Workflow = Workflow.model_validate(
13
65
  obj={
@@ -1 +0,0 @@
1
- __version__: str = "0.0.65"
@@ -1,30 +0,0 @@
1
- import typer
2
-
3
- app = typer.Typer()
4
-
5
-
6
- @app.callback()
7
- def callback():
8
- """
9
- Awesome Portal Gun
10
- """
11
-
12
-
13
- @app.command()
14
- def provision():
15
- """
16
- Shoot the portal gun
17
- """
18
- typer.echo("Shooting portal gun")
19
-
20
-
21
- @app.command()
22
- def job():
23
- """
24
- Load the portal gun
25
- """
26
- typer.echo("Loading portal gun")
27
-
28
-
29
- if __name__ == "__main__":
30
- app()