ddeutil-workflow 0.0.40__py3-none-any.whl → 0.0.41__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.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__init__.py +21 -25
- ddeutil/workflow/api/api.py +1 -1
- ddeutil/workflow/api/routes/logs.py +1 -2
- ddeutil/workflow/api/routes/workflows.py +1 -1
- ddeutil/workflow/conf.py +11 -1
- ddeutil/workflow/job.py +55 -4
- ddeutil/workflow/logs.py +334 -72
- ddeutil/workflow/params.py +1 -0
- ddeutil/workflow/result.py +6 -15
- ddeutil/workflow/{templates.py → reusables.py} +195 -9
- ddeutil/workflow/scheduler.py +1 -1
- ddeutil/workflow/stages.py +188 -24
- ddeutil/workflow/utils.py +11 -0
- ddeutil/workflow/workflow.py +16 -8
- {ddeutil_workflow-0.0.40.dist-info → ddeutil_workflow-0.0.41.dist-info}/METADATA +7 -1
- ddeutil_workflow-0.0.41.dist-info/RECORD +31 -0
- ddeutil/workflow/audit.py +0 -257
- ddeutil/workflow/caller.py +0 -179
- ddeutil_workflow-0.0.40.dist-info/RECORD +0 -33
- {ddeutil_workflow-0.0.40.dist-info → ddeutil_workflow-0.0.41.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.40.dist-info → ddeutil_workflow-0.0.41.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.40.dist-info → ddeutil_workflow-0.0.41.dist-info}/top_level.txt +0 -0
    
        ddeutil/workflow/stages.py
    CHANGED
    
    | @@ -3,6 +3,7 @@ | |
| 3 3 | 
             
            # Licensed under the MIT License. See LICENSE in the project root for
         | 
| 4 4 | 
             
            # license information.
         | 
| 5 5 | 
             
            # ------------------------------------------------------------------------------
         | 
| 6 | 
            +
            # [x] Use config
         | 
| 6 7 | 
             
            """Stage Model that use for getting stage data template from the Job Model.
         | 
| 7 8 | 
             
            The stage handle the minimize task that run in some thread (same thread at
         | 
| 8 9 | 
             
            its job owner) that mean it is the lowest executor of a workflow that can
         | 
| @@ -41,6 +42,7 @@ from inspect import Parameter | |
| 41 42 | 
             
            from pathlib import Path
         | 
| 42 43 | 
             
            from subprocess import CompletedProcess
         | 
| 43 44 | 
             
            from textwrap import dedent
         | 
| 45 | 
            +
            from threading import Event
         | 
| 44 46 | 
             
            from typing import Annotated, Optional, Union
         | 
| 45 47 |  | 
| 46 48 | 
             
            from pydantic import BaseModel, Field
         | 
| @@ -48,11 +50,10 @@ from pydantic.functional_validators import model_validator | |
| 48 50 | 
             
            from typing_extensions import Self
         | 
| 49 51 |  | 
| 50 52 | 
             
            from .__types import DictData, DictStr, TupleStr
         | 
| 51 | 
            -
            from .caller import TagFunc, extract_call
         | 
| 52 53 | 
             
            from .conf import config
         | 
| 53 54 | 
             
            from .exceptions import StageException, to_dict
         | 
| 54 55 | 
             
            from .result import Result, Status
         | 
| 55 | 
            -
            from . | 
| 56 | 
            +
            from .reusables import TagFunc, extract_call, not_in_template, param2template
         | 
| 56 57 | 
             
            from .utils import (
         | 
| 57 58 | 
             
                gen_id,
         | 
| 58 59 | 
             
                make_exec,
         | 
| @@ -93,6 +94,10 @@ class BaseStage(BaseModel, ABC): | |
| 93 94 | 
             
                    description="A stage condition statement to allow stage executable.",
         | 
| 94 95 | 
             
                    alias="if",
         | 
| 95 96 | 
             
                )
         | 
| 97 | 
            +
                extras: DictData = Field(
         | 
| 98 | 
            +
                    default_factory=dict,
         | 
| 99 | 
            +
                    description="An extra override values.",
         | 
| 100 | 
            +
                )
         | 
| 96 101 |  | 
| 97 102 | 
             
                @property
         | 
| 98 103 | 
             
                def iden(self) -> str:
         | 
| @@ -126,7 +131,11 @@ class BaseStage(BaseModel, ABC): | |
| 126 131 |  | 
| 127 132 | 
             
                @abstractmethod
         | 
| 128 133 | 
             
                def execute(
         | 
| 129 | 
            -
                    self, | 
| 134 | 
            +
                    self,
         | 
| 135 | 
            +
                    params: DictData,
         | 
| 136 | 
            +
                    *,
         | 
| 137 | 
            +
                    result: Result | None = None,
         | 
| 138 | 
            +
                    event: Event | None = None,
         | 
| 130 139 | 
             
                ) -> Result:
         | 
| 131 140 | 
             
                    """Execute abstraction method that action something by sub-model class.
         | 
| 132 141 | 
             
                    This is important method that make this class is able to be the stage.
         | 
| @@ -135,11 +144,22 @@ class BaseStage(BaseModel, ABC): | |
| 135 144 | 
             
                        execution.
         | 
| 136 145 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 137 146 | 
             
                        data.
         | 
| 147 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 148 | 
            +
                        was not force stopped.
         | 
| 138 149 |  | 
| 139 150 | 
             
                    :rtype: Result
         | 
| 140 151 | 
             
                    """
         | 
| 141 152 | 
             
                    raise NotImplementedError("Stage should implement `execute` method.")
         | 
| 142 153 |  | 
| 154 | 
            +
                async def axecute(
         | 
| 155 | 
            +
                    self,
         | 
| 156 | 
            +
                    params: DictData,
         | 
| 157 | 
            +
                    *,
         | 
| 158 | 
            +
                    result: Result | None = None,
         | 
| 159 | 
            +
                    event: Event | None,
         | 
| 160 | 
            +
                ) -> Result:  # pragma: no cov
         | 
| 161 | 
            +
                    ...
         | 
| 162 | 
            +
             | 
| 143 163 | 
             
                def handler_execute(
         | 
| 144 164 | 
             
                    self,
         | 
| 145 165 | 
             
                    params: DictData,
         | 
| @@ -149,6 +169,7 @@ class BaseStage(BaseModel, ABC): | |
| 149 169 | 
             
                    result: Result | None = None,
         | 
| 150 170 | 
             
                    raise_error: bool = False,
         | 
| 151 171 | 
             
                    to: DictData | None = None,
         | 
| 172 | 
            +
                    event: Event | None = None,
         | 
| 152 173 | 
             
                ) -> Result:
         | 
| 153 174 | 
             
                    """Handler stage execution result from the stage `execute` method.
         | 
| 154 175 |  | 
| @@ -183,6 +204,7 @@ class BaseStage(BaseModel, ABC): | |
| 183 204 | 
             
                    :param raise_error: (bool) A flag that all this method raise error
         | 
| 184 205 | 
             
                    :param to: (DictData) A target object for auto set the return output
         | 
| 185 206 | 
             
                        after execution.
         | 
| 207 | 
            +
                    :param event: (Event) An event manager that pass to the stage execution.
         | 
| 186 208 |  | 
| 187 209 | 
             
                    :rtype: Result
         | 
| 188 210 | 
             
                    """
         | 
| @@ -194,7 +216,7 @@ class BaseStage(BaseModel, ABC): | |
| 194 216 | 
             
                    )
         | 
| 195 217 |  | 
| 196 218 | 
             
                    try:
         | 
| 197 | 
            -
                        rs: Result = self.execute(params, result=result)
         | 
| 219 | 
            +
                        rs: Result = self.execute(params, result=result, event=event)
         | 
| 198 220 | 
             
                        if to is not None:
         | 
| 199 221 | 
             
                            return self.set_outputs(rs.context, to=to)
         | 
| 200 222 | 
             
                        return rs
         | 
| @@ -322,7 +344,11 @@ class EmptyStage(BaseStage): | |
| 322 344 | 
             
                )
         | 
| 323 345 |  | 
| 324 346 | 
             
                def execute(
         | 
| 325 | 
            -
                    self, | 
| 347 | 
            +
                    self,
         | 
| 348 | 
            +
                    params: DictData,
         | 
| 349 | 
            +
                    *,
         | 
| 350 | 
            +
                    result: Result | None = None,
         | 
| 351 | 
            +
                    event: Event | None = None,
         | 
| 326 352 | 
             
                ) -> Result:
         | 
| 327 353 | 
             
                    """Execution method for the Empty stage that do only logging out to
         | 
| 328 354 | 
             
                    stdout. This method does not use the `handler_result` decorator because
         | 
| @@ -335,6 +361,8 @@ class EmptyStage(BaseStage): | |
| 335 361 | 
             
                        But this stage does not pass any output.
         | 
| 336 362 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 337 363 | 
             
                        data.
         | 
| 364 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 365 | 
            +
                        was not force stopped.
         | 
| 338 366 |  | 
| 339 367 | 
             
                    :rtype: Result
         | 
| 340 368 | 
             
                    """
         | 
| @@ -354,9 +382,25 @@ class EmptyStage(BaseStage): | |
| 354 382 | 
             
                    return result.catch(status=Status.SUCCESS)
         | 
| 355 383 |  | 
| 356 384 | 
             
                # TODO: Draft async execute method for the perf improvement.
         | 
| 357 | 
            -
                async def  | 
| 358 | 
            -
                    self, | 
| 385 | 
            +
                async def axecute(
         | 
| 386 | 
            +
                    self,
         | 
| 387 | 
            +
                    params: DictData,
         | 
| 388 | 
            +
                    *,
         | 
| 389 | 
            +
                    result: Result | None = None,
         | 
| 390 | 
            +
                    event: Event | None,
         | 
| 359 391 | 
             
                ) -> Result:  # pragma: no cov
         | 
| 392 | 
            +
                    """Async execution method for this Empty stage that only logging out to
         | 
| 393 | 
            +
                    stdout.
         | 
| 394 | 
            +
             | 
| 395 | 
            +
                    :param params: (DictData) A context data that want to add output result.
         | 
| 396 | 
            +
                        But this stage does not pass any output.
         | 
| 397 | 
            +
                    :param result: (Result) A result object for keeping context and status
         | 
| 398 | 
            +
                        data.
         | 
| 399 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 400 | 
            +
                        was not force stopped.
         | 
| 401 | 
            +
             | 
| 402 | 
            +
                    :rtype: Result
         | 
| 403 | 
            +
                    """
         | 
| 360 404 | 
             
                    if result is None:  # pragma: no cov
         | 
| 361 405 | 
             
                        result: Result = Result(
         | 
| 362 406 | 
             
                            run_id=gen_id(self.name + (self.id or ""), unique=True)
         | 
| @@ -367,7 +411,11 @@ class EmptyStage(BaseStage): | |
| 367 411 | 
             
                        f"( {param2template(self.echo, params=params) or '...'} )"
         | 
| 368 412 | 
             
                    )
         | 
| 369 413 |  | 
| 370 | 
            -
                     | 
| 414 | 
            +
                    if self.sleep > 0:
         | 
| 415 | 
            +
                        if self.sleep > 5:
         | 
| 416 | 
            +
                            result.trace.info(f"[STAGE]: ... sleep ({self.sleep} seconds)")
         | 
| 417 | 
            +
                        await asyncio.sleep(self.sleep)
         | 
| 418 | 
            +
             | 
| 371 419 | 
             
                    return result.catch(status=Status.SUCCESS)
         | 
| 372 420 |  | 
| 373 421 |  | 
| @@ -438,7 +486,11 @@ class BashStage(BaseStage): | |
| 438 486 | 
             
                    Path(f"./{f_name}").unlink()
         | 
| 439 487 |  | 
| 440 488 | 
             
                def execute(
         | 
| 441 | 
            -
                    self, | 
| 489 | 
            +
                    self,
         | 
| 490 | 
            +
                    params: DictData,
         | 
| 491 | 
            +
                    *,
         | 
| 492 | 
            +
                    result: Result | None = None,
         | 
| 493 | 
            +
                    event: Event | None = None,
         | 
| 442 494 | 
             
                ) -> Result:
         | 
| 443 495 | 
             
                    """Execute the Bash statement with the Python build-in ``subprocess``
         | 
| 444 496 | 
             
                    package.
         | 
| @@ -446,6 +498,8 @@ class BashStage(BaseStage): | |
| 446 498 | 
             
                    :param params: A parameter data that want to use in this execution.
         | 
| 447 499 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 448 500 | 
             
                        data.
         | 
| 501 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 502 | 
            +
                        was not force stopped.
         | 
| 449 503 |  | 
| 450 504 | 
             
                    :rtype: Result
         | 
| 451 505 | 
             
                    """
         | 
| @@ -555,7 +609,11 @@ class PyStage(BaseStage): | |
| 555 609 | 
             
                    return to
         | 
| 556 610 |  | 
| 557 611 | 
             
                def execute(
         | 
| 558 | 
            -
                    self, | 
| 612 | 
            +
                    self,
         | 
| 613 | 
            +
                    params: DictData,
         | 
| 614 | 
            +
                    *,
         | 
| 615 | 
            +
                    result: Result | None = None,
         | 
| 616 | 
            +
                    event: Event | None = None,
         | 
| 559 617 | 
             
                ) -> Result:
         | 
| 560 618 | 
             
                    """Execute the Python statement that pass all globals and input params
         | 
| 561 619 | 
             
                    to globals argument on ``exec`` build-in function.
         | 
| @@ -563,6 +621,8 @@ class PyStage(BaseStage): | |
| 563 621 | 
             
                    :param params: A parameter that want to pass before run any statement.
         | 
| 564 622 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 565 623 | 
             
                        data.
         | 
| 624 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 625 | 
            +
                        was not force stopped.
         | 
| 566 626 |  | 
| 567 627 | 
             
                    :rtype: Result
         | 
| 568 628 | 
             
                    """
         | 
| @@ -629,7 +689,11 @@ class CallStage(BaseStage): | |
| 629 689 | 
             
                )
         | 
| 630 690 |  | 
| 631 691 | 
             
                def execute(
         | 
| 632 | 
            -
                    self, | 
| 692 | 
            +
                    self,
         | 
| 693 | 
            +
                    params: DictData,
         | 
| 694 | 
            +
                    *,
         | 
| 695 | 
            +
                    result: Result | None = None,
         | 
| 696 | 
            +
                    event: Event | None = None,
         | 
| 633 697 | 
             
                ) -> Result:
         | 
| 634 698 | 
             
                    """Execute the Call function that already in the call registry.
         | 
| 635 699 |  | 
| @@ -638,11 +702,12 @@ class CallStage(BaseStage): | |
| 638 702 | 
             
                    :raise TypeError: When the return type of call function does not be
         | 
| 639 703 | 
             
                        dict type.
         | 
| 640 704 |  | 
| 641 | 
            -
                    :param params: A parameter that want to pass before run any | 
| 642 | 
            -
             | 
| 705 | 
            +
                    :param params: (DictData) A parameter that want to pass before run any
         | 
| 706 | 
            +
                        statement.
         | 
| 643 707 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 644 708 | 
             
                        data.
         | 
| 645 | 
            -
                    : | 
| 709 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 710 | 
            +
                        was not force stopped.
         | 
| 646 711 |  | 
| 647 712 | 
             
                    :rtype: Result
         | 
| 648 713 | 
             
                    """
         | 
| @@ -728,7 +793,11 @@ class TriggerStage(BaseStage): | |
| 728 793 | 
             
                )
         | 
| 729 794 |  | 
| 730 795 | 
             
                def execute(
         | 
| 731 | 
            -
                    self, | 
| 796 | 
            +
                    self,
         | 
| 797 | 
            +
                    params: DictData,
         | 
| 798 | 
            +
                    *,
         | 
| 799 | 
            +
                    result: Result | None = None,
         | 
| 800 | 
            +
                    event: Event | None = None,
         | 
| 732 801 | 
             
                ) -> Result:
         | 
| 733 802 | 
             
                    """Trigger another workflow execution. It will wait the trigger
         | 
| 734 803 | 
             
                    workflow running complete before catching its result.
         | 
| @@ -736,6 +805,8 @@ class TriggerStage(BaseStage): | |
| 736 805 | 
             
                    :param params: A parameter data that want to use in this execution.
         | 
| 737 806 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 738 807 | 
             
                        data.
         | 
| 808 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 809 | 
            +
                        was not force stopped.
         | 
| 739 810 |  | 
| 740 811 | 
             
                    :rtype: Result
         | 
| 741 812 | 
             
                    """
         | 
| @@ -838,7 +909,11 @@ class ParallelStage(BaseStage):  # pragma: no cov | |
| 838 909 | 
             
                    return context
         | 
| 839 910 |  | 
| 840 911 | 
             
                def execute(
         | 
| 841 | 
            -
                    self, | 
| 912 | 
            +
                    self,
         | 
| 913 | 
            +
                    params: DictData,
         | 
| 914 | 
            +
                    *,
         | 
| 915 | 
            +
                    result: Result | None = None,
         | 
| 916 | 
            +
                    event: Event | None = None,
         | 
| 842 917 | 
             
                ) -> Result:
         | 
| 843 918 | 
             
                    """Execute the stages that parallel each branch via multi-threading mode
         | 
| 844 919 | 
             
                    or async mode by changing `async_mode` flag.
         | 
| @@ -846,6 +921,8 @@ class ParallelStage(BaseStage):  # pragma: no cov | |
| 846 921 | 
             
                    :param params: A parameter that want to pass before run any statement.
         | 
| 847 922 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 848 923 | 
             
                        data.
         | 
| 924 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 925 | 
            +
                        was not force stopped.
         | 
| 849 926 |  | 
| 850 927 | 
             
                    :rtype: Result
         | 
| 851 928 | 
             
                    """
         | 
| @@ -910,19 +987,34 @@ class ForEachStage(BaseStage): | |
| 910 987 | 
             
                    ),
         | 
| 911 988 | 
             
                )
         | 
| 912 989 | 
             
                stages: list[Stage] = Field(
         | 
| 990 | 
            +
                    default_factory=list,
         | 
| 913 991 | 
             
                    description=(
         | 
| 914 992 | 
             
                        "A list of stage that will run with each item in the foreach field."
         | 
| 915 993 | 
             
                    ),
         | 
| 916 994 | 
             
                )
         | 
| 995 | 
            +
                concurrent: int = Field(
         | 
| 996 | 
            +
                    default=1,
         | 
| 997 | 
            +
                    gt=0,
         | 
| 998 | 
            +
                    description=(
         | 
| 999 | 
            +
                        "A concurrent value allow to run each item at the same time. It "
         | 
| 1000 | 
            +
                        "will be sequential mode if this value equal 1."
         | 
| 1001 | 
            +
                    ),
         | 
| 1002 | 
            +
                )
         | 
| 917 1003 |  | 
| 918 1004 | 
             
                def execute(
         | 
| 919 | 
            -
                    self, | 
| 1005 | 
            +
                    self,
         | 
| 1006 | 
            +
                    params: DictData,
         | 
| 1007 | 
            +
                    *,
         | 
| 1008 | 
            +
                    result: Result | None = None,
         | 
| 1009 | 
            +
                    event: Event | None = None,
         | 
| 920 1010 | 
             
                ) -> Result:
         | 
| 921 1011 | 
             
                    """Execute the stages that pass each item form the foreach field.
         | 
| 922 1012 |  | 
| 923 1013 | 
             
                    :param params: A parameter that want to pass before run any statement.
         | 
| 924 1014 | 
             
                    :param result: (Result) A result object for keeping context and status
         | 
| 925 1015 | 
             
                        data.
         | 
| 1016 | 
            +
                    :param event: (Event) An event manager that use to track parent execute
         | 
| 1017 | 
            +
                        was not force stopped.
         | 
| 926 1018 |  | 
| 927 1019 | 
             
                    :rtype: Result
         | 
| 928 1020 | 
             
                    """
         | 
| @@ -933,6 +1025,7 @@ class ForEachStage(BaseStage): | |
| 933 1025 |  | 
| 934 1026 | 
             
                    rs: DictData = {"items": self.foreach, "foreach": {}}
         | 
| 935 1027 | 
             
                    status = Status.SUCCESS
         | 
| 1028 | 
            +
                    # TODO: Implement concurrent more than 1.
         | 
| 936 1029 | 
             
                    for item in self.foreach:
         | 
| 937 1030 | 
             
                        result.trace.debug(f"[STAGE]: Execute foreach item: {item!r}")
         | 
| 938 1031 | 
             
                        params["item"] = item
         | 
| @@ -969,6 +1062,33 @@ class ForEachStage(BaseStage): | |
| 969 1062 | 
             
                    return result.catch(status=status, context=rs)
         | 
| 970 1063 |  | 
| 971 1064 |  | 
| 1065 | 
            +
            # TODO: Not implement this stages yet
         | 
| 1066 | 
            +
            class UntilStage(BaseStage):  # pragma: no cov
         | 
| 1067 | 
            +
                """Until execution stage."""
         | 
| 1068 | 
            +
             | 
| 1069 | 
            +
                until: str = Field(description="A until condition.")
         | 
| 1070 | 
            +
                item: Union[str, int, bool] = Field(description="An initial value.")
         | 
| 1071 | 
            +
                stages: list[Stage] = Field(
         | 
| 1072 | 
            +
                    default_factory=list,
         | 
| 1073 | 
            +
                    description=(
         | 
| 1074 | 
            +
                        "A list of stage that will run with each item until condition "
         | 
| 1075 | 
            +
                        "correct."
         | 
| 1076 | 
            +
                    ),
         | 
| 1077 | 
            +
                )
         | 
| 1078 | 
            +
                max_until_not_change: int = Field(
         | 
| 1079 | 
            +
                    default=3,
         | 
| 1080 | 
            +
                    description="The maximum value of loop if condition not change.",
         | 
| 1081 | 
            +
                )
         | 
| 1082 | 
            +
             | 
| 1083 | 
            +
                def execute(
         | 
| 1084 | 
            +
                    self,
         | 
| 1085 | 
            +
                    params: DictData,
         | 
| 1086 | 
            +
                    *,
         | 
| 1087 | 
            +
                    result: Result | None = None,
         | 
| 1088 | 
            +
                    event: Event | None = None,
         | 
| 1089 | 
            +
                ) -> Result: ...
         | 
| 1090 | 
            +
             | 
| 1091 | 
            +
             | 
| 972 1092 | 
             
            # TODO: Not implement this stages yet
         | 
| 973 1093 | 
             
            class IfStage(BaseStage):  # pragma: no cov
         | 
| 974 1094 | 
             
                """If execution stage.
         | 
| @@ -1008,19 +1128,45 @@ class IfStage(BaseStage):  # pragma: no cov | |
| 1008 1128 | 
             
                match: list[dict[str, Union[str, Stage]]]
         | 
| 1009 1129 |  | 
| 1010 1130 | 
             
                def execute(
         | 
| 1011 | 
            -
                    self, | 
| 1131 | 
            +
                    self,
         | 
| 1132 | 
            +
                    params: DictData,
         | 
| 1133 | 
            +
                    *,
         | 
| 1134 | 
            +
                    result: Result | None = None,
         | 
| 1135 | 
            +
                    event: Event | None = None,
         | 
| 1012 1136 | 
             
                ) -> Result: ...
         | 
| 1013 1137 |  | 
| 1014 1138 |  | 
| 1015 1139 | 
             
            class RaiseStage(BaseStage):  # pragma: no cov
         | 
| 1140 | 
            +
                """Raise error stage that raise StageException that use a message field for
         | 
| 1141 | 
            +
                making error message before raise.
         | 
| 1142 | 
            +
             | 
| 1143 | 
            +
                Data Validate:
         | 
| 1144 | 
            +
                    >>> stage = {
         | 
| 1145 | 
            +
                    ...     "name": "Raise stage",
         | 
| 1146 | 
            +
                    ...     "raise": "raise this stage",
         | 
| 1147 | 
            +
                    ... }
         | 
| 1148 | 
            +
             | 
| 1149 | 
            +
                """
         | 
| 1150 | 
            +
             | 
| 1016 1151 | 
             
                message: str = Field(
         | 
| 1017 1152 | 
             
                    description="An error message that want to raise",
         | 
| 1018 1153 | 
             
                    alias="raise",
         | 
| 1019 1154 | 
             
                )
         | 
| 1020 1155 |  | 
| 1021 1156 | 
             
                def execute(
         | 
| 1022 | 
            -
                    self, | 
| 1157 | 
            +
                    self,
         | 
| 1158 | 
            +
                    params: DictData,
         | 
| 1159 | 
            +
                    *,
         | 
| 1160 | 
            +
                    result: Result | None = None,
         | 
| 1161 | 
            +
                    event: Event | None = None,
         | 
| 1023 1162 | 
             
                ) -> Result:
         | 
| 1163 | 
            +
                    """Raise the stage."""
         | 
| 1164 | 
            +
                    if result is None:  # pragma: no cov
         | 
| 1165 | 
            +
                        result: Result = Result(
         | 
| 1166 | 
            +
                            run_id=gen_id(self.name + (self.id or ""), unique=True)
         | 
| 1167 | 
            +
                        )
         | 
| 1168 | 
            +
             | 
| 1169 | 
            +
                    result.trace.error(f"[STAGE]: ... raise ( {self.message} )")
         | 
| 1024 1170 | 
             
                    raise StageException(self.message)
         | 
| 1025 1171 |  | 
| 1026 1172 |  | 
| @@ -1031,7 +1177,11 @@ class HookStage(BaseStage):  # pragma: no cov | |
| 1031 1177 | 
             
                callback: str
         | 
| 1032 1178 |  | 
| 1033 1179 | 
             
                def execute(
         | 
| 1034 | 
            -
                    self, | 
| 1180 | 
            +
                    self,
         | 
| 1181 | 
            +
                    params: DictData,
         | 
| 1182 | 
            +
                    *,
         | 
| 1183 | 
            +
                    result: Result | None = None,
         | 
| 1184 | 
            +
                    event: Event | None = None,
         | 
| 1035 1185 | 
             
                ) -> Result: ...
         | 
| 1036 1186 |  | 
| 1037 1187 |  | 
| @@ -1039,13 +1189,19 @@ class HookStage(BaseStage):  # pragma: no cov | |
| 1039 1189 | 
             
            class DockerStage(BaseStage):  # pragma: no cov
         | 
| 1040 1190 | 
             
                """Docker container stage execution."""
         | 
| 1041 1191 |  | 
| 1042 | 
            -
                image: str
         | 
| 1192 | 
            +
                image: str = Field(
         | 
| 1193 | 
            +
                    description="A Docker image url with tag that want to run.",
         | 
| 1194 | 
            +
                )
         | 
| 1043 1195 | 
             
                env: DictData = Field(default_factory=dict)
         | 
| 1044 1196 | 
             
                volume: DictData = Field(default_factory=dict)
         | 
| 1045 1197 | 
             
                auth: DictData = Field(default_factory=dict)
         | 
| 1046 1198 |  | 
| 1047 1199 | 
             
                def execute(
         | 
| 1048 | 
            -
                    self, | 
| 1200 | 
            +
                    self,
         | 
| 1201 | 
            +
                    params: DictData,
         | 
| 1202 | 
            +
                    *,
         | 
| 1203 | 
            +
                    result: Result | None = None,
         | 
| 1204 | 
            +
                    event: Event | None = None,
         | 
| 1049 1205 | 
             
                ) -> Result: ...
         | 
| 1050 1206 |  | 
| 1051 1207 |  | 
| @@ -1059,7 +1215,11 @@ class VirtualPyStage(PyStage):  # pragma: no cov | |
| 1059 1215 | 
             
                def create_py_file(self, py: str, run_id: str | None): ...
         | 
| 1060 1216 |  | 
| 1061 1217 | 
             
                def execute(
         | 
| 1062 | 
            -
                    self, | 
| 1218 | 
            +
                    self,
         | 
| 1219 | 
            +
                    params: DictData,
         | 
| 1220 | 
            +
                    *,
         | 
| 1221 | 
            +
                    result: Result | None = None,
         | 
| 1222 | 
            +
                    event: Event | None = None,
         | 
| 1063 1223 | 
             
                ) -> Result:
         | 
| 1064 1224 | 
             
                    return super().execute(params, result=result)
         | 
| 1065 1225 |  | 
| @@ -1068,7 +1228,11 @@ class VirtualPyStage(PyStage):  # pragma: no cov | |
| 1068 1228 | 
             
            class SensorStage(BaseStage):  # pragma: no cov
         | 
| 1069 1229 |  | 
| 1070 1230 | 
             
                def execute(
         | 
| 1071 | 
            -
                    self, | 
| 1231 | 
            +
                    self,
         | 
| 1232 | 
            +
                    params: DictData,
         | 
| 1233 | 
            +
                    *,
         | 
| 1234 | 
            +
                    result: Result | None = None,
         | 
| 1235 | 
            +
                    event: Event | None = None,
         | 
| 1072 1236 | 
             
                ) -> Result: ...
         | 
| 1073 1237 |  | 
| 1074 1238 |  | 
    
        ddeutil/workflow/utils.py
    CHANGED
    
    | @@ -3,6 +3,8 @@ | |
| 3 3 | 
             
            # Licensed under the MIT License. See LICENSE in the project root for
         | 
| 4 4 | 
             
            # license information.
         | 
| 5 5 | 
             
            # ------------------------------------------------------------------------------
         | 
| 6 | 
            +
            # [ ] Use config
         | 
| 7 | 
            +
            """Utility function model."""
         | 
| 6 8 | 
             
            from __future__ import annotations
         | 
| 7 9 |  | 
| 8 10 | 
             
            import stat
         | 
| @@ -154,6 +156,15 @@ def gen_id( | |
| 154 156 | 
             
                ).hexdigest()
         | 
| 155 157 |  | 
| 156 158 |  | 
| 159 | 
            +
            def default_gen_id() -> str:
         | 
| 160 | 
            +
                """Return running ID which use for making default ID for the Result model if
         | 
| 161 | 
            +
                a run_id field initializes at the first time.
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                :rtype: str
         | 
| 164 | 
            +
                """
         | 
| 165 | 
            +
                return gen_id("manual", unique=True)
         | 
| 166 | 
            +
             | 
| 167 | 
            +
             | 
| 157 168 | 
             
            def make_exec(path: str | Path) -> None:
         | 
| 158 169 | 
             
                """Change mode of file to be executable file.
         | 
| 159 170 |  | 
    
        ddeutil/workflow/workflow.py
    CHANGED
    
    | @@ -31,14 +31,14 @@ from typing_extensions import Self | |
| 31 31 |  | 
| 32 32 | 
             
            from .__cron import CronJob, CronRunner
         | 
| 33 33 | 
             
            from .__types import DictData, TupleStr
         | 
| 34 | 
            -
            from .audit import Audit, get_audit
         | 
| 35 34 | 
             
            from .conf import Loader, SimLoad, config, get_logger
         | 
| 36 35 | 
             
            from .cron import On
         | 
| 37 36 | 
             
            from .exceptions import JobException, WorkflowException
         | 
| 38 37 | 
             
            from .job import Job, TriggerState
         | 
| 38 | 
            +
            from .logs import Audit, get_audit
         | 
| 39 39 | 
             
            from .params import Param
         | 
| 40 40 | 
             
            from .result import Result, Status
         | 
| 41 | 
            -
            from . | 
| 41 | 
            +
            from .reusables import has_template, param2template
         | 
| 42 42 | 
             
            from .utils import (
         | 
| 43 43 | 
             
                gen_id,
         | 
| 44 44 | 
             
                get_dt_now,
         | 
| @@ -271,6 +271,10 @@ class Workflow(BaseModel): | |
| 271 271 | 
             
                    default_factory=dict,
         | 
| 272 272 | 
             
                    description="A mapping of job ID and job model that already loaded.",
         | 
| 273 273 | 
             
                )
         | 
| 274 | 
            +
                extras: DictData = Field(
         | 
| 275 | 
            +
                    default_factory=dict,
         | 
| 276 | 
            +
                    description="An extra override values.",
         | 
| 277 | 
            +
                )
         | 
| 274 278 |  | 
| 275 279 | 
             
                @classmethod
         | 
| 276 280 | 
             
                def from_loader(
         | 
| @@ -297,9 +301,10 @@ class Workflow(BaseModel): | |
| 297 301 | 
             
                        raise ValueError(f"Type {loader.type} does not match with {cls}")
         | 
| 298 302 |  | 
| 299 303 | 
             
                    loader_data: DictData = copy.deepcopy(loader.data)
         | 
| 300 | 
            -
             | 
| 301 | 
            -
                    # NOTE: Add name to loader data
         | 
| 302 304 | 
             
                    loader_data["name"] = name.replace(" ", "_")
         | 
| 305 | 
            +
                    if externals:  # pragma: no cov
         | 
| 306 | 
            +
                        loader_data["extras"] = externals
         | 
| 307 | 
            +
             | 
| 303 308 | 
             
                    cls.__bypass_on__(
         | 
| 304 309 | 
             
                        loader_data, path=loader.conf_path, externals=externals
         | 
| 305 310 | 
             
                    )
         | 
| @@ -333,9 +338,10 @@ class Workflow(BaseModel): | |
| 333 338 | 
             
                        raise ValueError(f"Type {loader.type} does not match with {cls}")
         | 
| 334 339 |  | 
| 335 340 | 
             
                    loader_data: DictData = copy.deepcopy(loader.data)
         | 
| 336 | 
            -
             | 
| 337 | 
            -
                    # NOTE: Add name to loader data
         | 
| 338 341 | 
             
                    loader_data["name"] = name.replace(" ", "_")
         | 
| 342 | 
            +
                    if externals:  # pragma: no cov
         | 
| 343 | 
            +
                        loader_data["extras"] = externals
         | 
| 344 | 
            +
             | 
| 339 345 | 
             
                    cls.__bypass_on__(loader_data, path=path, externals=externals)
         | 
| 340 346 | 
             
                    return cls.model_validate(obj=loader_data)
         | 
| 341 347 |  | 
| @@ -446,7 +452,6 @@ class Workflow(BaseModel): | |
| 446 452 | 
             
                                f"{self.name!r}."
         | 
| 447 453 | 
             
                            )
         | 
| 448 454 |  | 
| 449 | 
            -
                        # NOTE: update a job id with its job id from workflow template
         | 
| 450 455 | 
             
                        self.jobs[job].id = job
         | 
| 451 456 |  | 
| 452 457 | 
             
                    # VALIDATE: Validate workflow name should not dynamic with params
         | 
| @@ -476,7 +481,10 @@ class Workflow(BaseModel): | |
| 476 481 | 
             
                            f"A Job {name!r} does not exists in this workflow, "
         | 
| 477 482 | 
             
                            f"{self.name!r}"
         | 
| 478 483 | 
             
                        )
         | 
| 479 | 
            -
                     | 
| 484 | 
            +
                    job: Job = self.jobs[name]
         | 
| 485 | 
            +
                    if self.extras:
         | 
| 486 | 
            +
                        job.extras = self.extras
         | 
| 487 | 
            +
                    return job
         | 
| 480 488 |  | 
| 481 489 | 
             
                def parameterize(self, params: DictData) -> DictData:
         | 
| 482 490 | 
             
                    """Prepare a passing parameters before use it in execution process.
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.4
         | 
| 2 2 | 
             
            Name: ddeutil-workflow
         | 
| 3 | 
            -
            Version: 0.0. | 
| 3 | 
            +
            Version: 0.0.41
         | 
| 4 4 | 
             
            Summary: Lightweight workflow orchestration
         | 
| 5 5 | 
             
            Author-email: ddeutils <korawich.anu@gmail.com>
         | 
| 6 6 | 
             
            License: MIT
         | 
| @@ -27,6 +27,12 @@ Requires-Dist: ddeutil-io[toml,yaml]>=0.2.10 | |
| 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
         | 
| 30 | 
            +
            Provides-Extra: all
         | 
| 31 | 
            +
            Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "all"
         | 
| 32 | 
            +
            Requires-Dist: httpx; extra == "all"
         | 
| 33 | 
            +
            Requires-Dist: ujson; extra == "all"
         | 
| 34 | 
            +
            Requires-Dist: aiofiles; extra == "all"
         | 
| 35 | 
            +
            Requires-Dist: aiohttp; extra == "all"
         | 
| 30 36 | 
             
            Provides-Extra: api
         | 
| 31 37 | 
             
            Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
         | 
| 32 38 | 
             
            Requires-Dist: httpx; extra == "api"
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            ddeutil/workflow/__about__.py,sha256=chYiprva6VKQg6XFgrwBoMIWSlSshsb-yyHtFoiipvc,28
         | 
| 2 | 
            +
            ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
         | 
| 3 | 
            +
            ddeutil/workflow/__init__.py,sha256=cYWwG2utpsYvdwqvkFSRWi_Q6gylDgNQBcIWcF5NFs4,1861
         | 
| 4 | 
            +
            ddeutil/workflow/__types.py,sha256=8jBdbfb3aZSetjz0mvNrpGHwwxJff7mK8_4v41cLqlc,4316
         | 
| 5 | 
            +
            ddeutil/workflow/conf.py,sha256=wQXL5bfEUx8DbS6cEIRtsDjvQZzw7sGPvs-g5r2zOeM,12095
         | 
| 6 | 
            +
            ddeutil/workflow/context.py,sha256=vsk4JQL7t3KsnKPfshw3O7YrPFo2h4rnnNd3B-G9Kj4,1700
         | 
| 7 | 
            +
            ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
         | 
| 8 | 
            +
            ddeutil/workflow/exceptions.py,sha256=fO37f9p7lOjIJgVOpKE_1X44yJTwBepyukZV9a7NNm4,1241
         | 
| 9 | 
            +
            ddeutil/workflow/job.py,sha256=TAtOFOFWHGqXlc6QWvtum1KpfWFDs5AfNbYMtwvrIE8,30102
         | 
| 10 | 
            +
            ddeutil/workflow/logs.py,sha256=nbyoUONqSZ4QpowgMs962m-Qs-UDfcyMHs4bxIAT470,18782
         | 
| 11 | 
            +
            ddeutil/workflow/params.py,sha256=Mv-D2DY5inm1ug0lsgCPDkO5wT_AUhc5XEF5jxgDx6U,8036
         | 
| 12 | 
            +
            ddeutil/workflow/result.py,sha256=ynZB0g_vEEXn24034J-hatjNWDBmRAj38S8SqGRM-8I,4029
         | 
| 13 | 
            +
            ddeutil/workflow/reusables.py,sha256=Rw7qS2cQM4SBxuIyqiyJN5yZNBfObISXWRIgDUMC2fY,17449
         | 
| 14 | 
            +
            ddeutil/workflow/scheduler.py,sha256=Y4VuVDIHz64l3IN-7tKP76qrMKShpgXXjX64mKjSWLo,27651
         | 
| 15 | 
            +
            ddeutil/workflow/stages.py,sha256=Kp3VLhKMMlgQyMLEYWJfBdR8DoCD0nuzSY0-r-P8eS8,41574
         | 
| 16 | 
            +
            ddeutil/workflow/utils.py,sha256=XwkxOpPaHbvjCKCGA3kVriEYabKyZ_P6pTbkYPnK704,7380
         | 
| 17 | 
            +
            ddeutil/workflow/workflow.py,sha256=s2z_QMTmfsRjiw25CuKFQIqY9smCbKjFwbBwCZkikfc,49615
         | 
| 18 | 
            +
            ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
         | 
| 19 | 
            +
            ddeutil/workflow/api/api.py,sha256=C9f6w11zE1SHz8lwjRxVOwrO90pCMr9REj2WLQsO0lI,5190
         | 
| 20 | 
            +
            ddeutil/workflow/api/log.py,sha256=NMTnOnsBrDB5129329xF2myLdrb-z9k1MQrmrP7qXJw,1818
         | 
| 21 | 
            +
            ddeutil/workflow/api/repeat.py,sha256=cycd1-91j-4v6uY1SkrZHd9l95e-YgVC4UCSNNFuGJ8,5277
         | 
| 22 | 
            +
            ddeutil/workflow/api/routes/__init__.py,sha256=qoGtOMyVgQ5nTUc8J8wH27A8isaxl3IFCX8qoyibeCY,484
         | 
| 23 | 
            +
            ddeutil/workflow/api/routes/job.py,sha256=YVta083i8vU8-o4WdKFwDpfdC9vN1dZ6goZSmNlQXHA,1954
         | 
| 24 | 
            +
            ddeutil/workflow/api/routes/logs.py,sha256=TeRDrEelbKS2Hu_EovgLh0bOdmSv9mfnrIZsrE7uPD4,5353
         | 
| 25 | 
            +
            ddeutil/workflow/api/routes/schedules.py,sha256=uWYDOwlV8w56hKQmfkQFwdZ6t2gZSJeCdBIzMmJenAQ,4824
         | 
| 26 | 
            +
            ddeutil/workflow/api/routes/workflows.py,sha256=TiGlwosHucccVmGv_SE9mvs3_BR2c6QZWQhw--aAv5w,4537
         | 
| 27 | 
            +
            ddeutil_workflow-0.0.41.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
         | 
| 28 | 
            +
            ddeutil_workflow-0.0.41.dist-info/METADATA,sha256=ACsWRIhGZuaRo_d400dlAFsSRlNcha8Vy0PzVMkE3NU,19729
         | 
| 29 | 
            +
            ddeutil_workflow-0.0.41.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
         | 
| 30 | 
            +
            ddeutil_workflow-0.0.41.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
         | 
| 31 | 
            +
            ddeutil_workflow-0.0.41.dist-info/RECORD,,
         |