ddeutil-workflow 0.0.38__py3-none-any.whl → 0.0.40__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/__cron.py +89 -25
- ddeutil/workflow/__types.py +2 -0
- ddeutil/workflow/audit.py +7 -5
- ddeutil/workflow/caller.py +11 -10
- ddeutil/workflow/conf.py +8 -23
- ddeutil/workflow/context.py +2 -0
- ddeutil/workflow/job.py +127 -17
- ddeutil/workflow/scheduler.py +40 -1
- ddeutil/workflow/stages.py +55 -38
- ddeutil/workflow/templates.py +39 -20
- ddeutil/workflow/workflow.py +81 -23
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/METADATA +5 -5
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/RECORD +17 -17
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/WHEEL +1 -1
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/top_level.txt +0 -0
    
        ddeutil/workflow/workflow.py
    CHANGED
    
    | @@ -3,7 +3,7 @@ | |
| 3 3 | 
             
            # Licensed under the MIT License. See LICENSE in the project root for
         | 
| 4 4 | 
             
            # license information.
         | 
| 5 5 | 
             
            # ------------------------------------------------------------------------------
         | 
| 6 | 
            -
            """A Workflow module."""
         | 
| 6 | 
            +
            """A Workflow module that is the core model of this package."""
         | 
| 7 7 | 
             
            from __future__ import annotations
         | 
| 8 8 |  | 
| 9 9 | 
             
            import copy
         | 
| @@ -18,6 +18,7 @@ from datetime import datetime, timedelta | |
| 18 18 | 
             
            from enum import Enum
         | 
| 19 19 | 
             
            from functools import partial, total_ordering
         | 
| 20 20 | 
             
            from heapq import heappop, heappush
         | 
| 21 | 
            +
            from pathlib import Path
         | 
| 21 22 | 
             
            from queue import Queue
         | 
| 22 23 | 
             
            from textwrap import dedent
         | 
| 23 24 | 
             
            from threading import Event
         | 
| @@ -31,10 +32,10 @@ from typing_extensions import Self | |
| 31 32 | 
             
            from .__cron import CronJob, CronRunner
         | 
| 32 33 | 
             
            from .__types import DictData, TupleStr
         | 
| 33 34 | 
             
            from .audit import Audit, get_audit
         | 
| 34 | 
            -
            from .conf import Loader, config, get_logger
         | 
| 35 | 
            +
            from .conf import Loader, SimLoad, config, get_logger
         | 
| 35 36 | 
             
            from .cron import On
         | 
| 36 37 | 
             
            from .exceptions import JobException, WorkflowException
         | 
| 37 | 
            -
            from .job import Job
         | 
| 38 | 
            +
            from .job import Job, TriggerState
         | 
| 38 39 | 
             
            from .params import Param
         | 
| 39 40 | 
             
            from .result import Result, Status
         | 
| 40 41 | 
             
            from .templates import has_template, param2template
         | 
| @@ -299,33 +300,71 @@ class Workflow(BaseModel): | |
| 299 300 |  | 
| 300 301 | 
             
                    # NOTE: Add name to loader data
         | 
| 301 302 | 
             
                    loader_data["name"] = name.replace(" ", "_")
         | 
| 303 | 
            +
                    cls.__bypass_on__(
         | 
| 304 | 
            +
                        loader_data, path=loader.conf_path, externals=externals
         | 
| 305 | 
            +
                    )
         | 
| 306 | 
            +
                    return cls.model_validate(obj=loader_data)
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                @classmethod
         | 
| 309 | 
            +
                def from_path(
         | 
| 310 | 
            +
                    cls,
         | 
| 311 | 
            +
                    name: str,
         | 
| 312 | 
            +
                    path: Path,
         | 
| 313 | 
            +
                    externals: DictData | None = None,
         | 
| 314 | 
            +
                ) -> Self:
         | 
| 315 | 
            +
                    """Create Workflow instance from the specific path. The loader object
         | 
| 316 | 
            +
                    will use this workflow name and path to searching configuration data of
         | 
| 317 | 
            +
                    this workflow model.
         | 
| 302 318 |  | 
| 303 | 
            -
                     | 
| 304 | 
            -
                     | 
| 319 | 
            +
                    :param name: (str) A workflow name that want to pass to Loader object.
         | 
| 320 | 
            +
                    :param path: (Path) A config path that want to search.
         | 
| 321 | 
            +
                    :param externals: An external parameters that want to pass to Loader
         | 
| 322 | 
            +
                        object.
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                    :raise ValueError: If the type does not match with current object.
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                    :rtype: Self
         | 
| 327 | 
            +
                    """
         | 
| 328 | 
            +
                    loader: SimLoad = SimLoad(
         | 
| 329 | 
            +
                        name, conf_path=path, externals=(externals or {})
         | 
| 330 | 
            +
                    )
         | 
| 331 | 
            +
                    # NOTE: Validate the config type match with current connection model
         | 
| 332 | 
            +
                    if loader.type != cls.__name__:
         | 
| 333 | 
            +
                        raise ValueError(f"Type {loader.type} does not match with {cls}")
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                    loader_data: DictData = copy.deepcopy(loader.data)
         | 
| 336 | 
            +
             | 
| 337 | 
            +
                    # NOTE: Add name to loader data
         | 
| 338 | 
            +
                    loader_data["name"] = name.replace(" ", "_")
         | 
| 339 | 
            +
                    cls.__bypass_on__(loader_data, path=path, externals=externals)
         | 
| 305 340 | 
             
                    return cls.model_validate(obj=loader_data)
         | 
| 306 341 |  | 
| 307 342 | 
             
                @classmethod
         | 
| 308 343 | 
             
                def __bypass_on__(
         | 
| 309 344 | 
             
                    cls,
         | 
| 310 345 | 
             
                    data: DictData,
         | 
| 346 | 
            +
                    path: Path,
         | 
| 311 347 | 
             
                    externals: DictData | None = None,
         | 
| 312 348 | 
             
                ) -> DictData:
         | 
| 313 349 | 
             
                    """Bypass the on data to loaded config data.
         | 
| 314 350 |  | 
| 315 | 
            -
                    :param data:
         | 
| 316 | 
            -
                    :param  | 
| 351 | 
            +
                    :param data: A data to construct to this Workflow model.
         | 
| 352 | 
            +
                    :param path: A config path.
         | 
| 353 | 
            +
                    :param externals: An external parameters that want to pass to SimLoad
         | 
| 354 | 
            +
                        object.
         | 
| 317 355 | 
             
                    :rtype: DictData
         | 
| 318 356 | 
             
                    """
         | 
| 319 357 | 
             
                    if on := data.pop("on", []):
         | 
| 320 358 | 
             
                        if isinstance(on, str):
         | 
| 321 | 
            -
                            on = [on]
         | 
| 359 | 
            +
                            on: list[str] = [on]
         | 
| 322 360 | 
             
                        if any(not isinstance(i, (dict, str)) for i in on):
         | 
| 323 361 | 
             
                            raise TypeError("The ``on`` key should be list of str or dict")
         | 
| 324 362 |  | 
| 325 | 
            -
                        # NOTE: Pass on value to  | 
| 363 | 
            +
                        # NOTE: Pass on value to SimLoad and keep on model object to the on
         | 
| 364 | 
            +
                        #   field.
         | 
| 326 365 | 
             
                        data["on"] = [
         | 
| 327 366 | 
             
                            (
         | 
| 328 | 
            -
                                 | 
| 367 | 
            +
                                SimLoad(n, conf_path=path, externals=(externals or {})).data
         | 
| 329 368 | 
             
                                if isinstance(n, str)
         | 
| 330 369 | 
             
                                else n
         | 
| 331 370 | 
             
                            )
         | 
| @@ -882,8 +921,6 @@ class Workflow(BaseModel): | |
| 882 921 | 
             
                            f"workflow."
         | 
| 883 922 | 
             
                        )
         | 
| 884 923 |  | 
| 885 | 
            -
                    result.trace.info(f"[WORKFLOW]: Start execute job: {job_id!r}")
         | 
| 886 | 
            -
             | 
| 887 924 | 
             
                    if event and event.is_set():  # pragma: no cov
         | 
| 888 925 | 
             
                        raise WorkflowException(
         | 
| 889 926 | 
             
                            "Workflow job was canceled from event that had set before "
         | 
| @@ -898,15 +935,20 @@ class Workflow(BaseModel): | |
| 898 935 | 
             
                    #
         | 
| 899 936 | 
             
                    try:
         | 
| 900 937 | 
             
                        job: Job = self.jobs[job_id]
         | 
| 901 | 
            -
                        job. | 
| 902 | 
            -
                             | 
| 903 | 
            -
             | 
| 904 | 
            -
             | 
| 905 | 
            -
             | 
| 906 | 
            -
             | 
| 907 | 
            -
             | 
| 908 | 
            -
             | 
| 909 | 
            -
             | 
| 938 | 
            +
                        if job.is_skipped(params=params):
         | 
| 939 | 
            +
                            result.trace.info(f"[JOB]: Skip job: {job_id!r}")
         | 
| 940 | 
            +
                            job.set_outputs(output={"SKIP": {"skipped": True}}, to=params)
         | 
| 941 | 
            +
                        else:
         | 
| 942 | 
            +
                            result.trace.info(f"[JOB]: Start execute job: {job_id!r}")
         | 
| 943 | 
            +
                            job.set_outputs(
         | 
| 944 | 
            +
                                job.execute(
         | 
| 945 | 
            +
                                    params=params,
         | 
| 946 | 
            +
                                    run_id=result.run_id,
         | 
| 947 | 
            +
                                    parent_run_id=result.parent_run_id,
         | 
| 948 | 
            +
                                    event=event,
         | 
| 949 | 
            +
                                ).context,
         | 
| 950 | 
            +
                                to=params,
         | 
| 951 | 
            +
                            )
         | 
| 910 952 | 
             
                    except JobException as err:
         | 
| 911 953 | 
             
                        result.trace.error(f"[WORKFLOW]: {err.__class__.__name__}: {err}")
         | 
| 912 954 | 
             
                        if raise_error:
         | 
| @@ -1054,11 +1096,20 @@ class Workflow(BaseModel): | |
| 1054 1096 | 
             
                            job_id: str = job_queue.get()
         | 
| 1055 1097 | 
             
                            job: Job = self.jobs[job_id]
         | 
| 1056 1098 |  | 
| 1057 | 
            -
                            if  | 
| 1099 | 
            +
                            if (check := job.check_needs(context["jobs"])).is_waiting():
         | 
| 1058 1100 | 
             
                                job_queue.task_done()
         | 
| 1059 1101 | 
             
                                job_queue.put(job_id)
         | 
| 1060 1102 | 
             
                                time.sleep(0.15)
         | 
| 1061 1103 | 
             
                                continue
         | 
| 1104 | 
            +
                            elif check == TriggerState.failed:  # pragma: no cov
         | 
| 1105 | 
            +
                                raise WorkflowException(
         | 
| 1106 | 
            +
                                    "Check job trigger rule was failed."
         | 
| 1107 | 
            +
                                )
         | 
| 1108 | 
            +
                            elif check == TriggerState.skipped:  # pragma: no cov
         | 
| 1109 | 
            +
                                result.trace.info(f"[JOB]: Skip job: {job_id!r}")
         | 
| 1110 | 
            +
                                job.set_outputs({"SKIP": {"skipped": True}}, to=context)
         | 
| 1111 | 
            +
                                job_queue.task_done()
         | 
| 1112 | 
            +
                                continue
         | 
| 1062 1113 |  | 
| 1063 1114 | 
             
                            # NOTE: Start workflow job execution with deep copy context data
         | 
| 1064 1115 | 
             
                            #   before release.
         | 
| @@ -1149,11 +1200,18 @@ class Workflow(BaseModel): | |
| 1149 1200 | 
             
                        job: Job = self.jobs[job_id]
         | 
| 1150 1201 |  | 
| 1151 1202 | 
             
                        # NOTE: Waiting dependency job run successful before release.
         | 
| 1152 | 
            -
                        if  | 
| 1203 | 
            +
                        if (check := job.check_needs(context["jobs"])).is_waiting():
         | 
| 1153 1204 | 
             
                            job_queue.task_done()
         | 
| 1154 1205 | 
             
                            job_queue.put(job_id)
         | 
| 1155 1206 | 
             
                            time.sleep(0.075)
         | 
| 1156 1207 | 
             
                            continue
         | 
| 1208 | 
            +
                        elif check == TriggerState.failed:  # pragma: no cov
         | 
| 1209 | 
            +
                            raise WorkflowException("Check job trigger rule was failed.")
         | 
| 1210 | 
            +
                        elif check == TriggerState.skipped:  # pragma: no cov
         | 
| 1211 | 
            +
                            result.trace.info(f"[JOB]: Skip job: {job_id!r}")
         | 
| 1212 | 
            +
                            job.set_outputs({"SKIP": {"skipped": True}}, to=context)
         | 
| 1213 | 
            +
                            job_queue.task_done()
         | 
| 1214 | 
            +
                            continue
         | 
| 1157 1215 |  | 
| 1158 1216 | 
             
                        # NOTE: Start workflow job execution with deep copy context data
         | 
| 1159 1217 | 
             
                        #   before release. This job execution process will run until
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.4
         | 
| 2 2 | 
             
            Name: ddeutil-workflow
         | 
| 3 | 
            -
            Version: 0.0. | 
| 3 | 
            +
            Version: 0.0.40
         | 
| 4 4 | 
             
            Summary: Lightweight workflow orchestration
         | 
| 5 5 | 
             
            Author-email: ddeutils <korawich.anu@gmail.com>
         | 
| 6 6 | 
             
            License: MIT
         | 
| @@ -23,9 +23,9 @@ Requires-Python: >=3.9.13 | |
| 23 23 | 
             
            Description-Content-Type: text/markdown
         | 
| 24 24 | 
             
            License-File: LICENSE
         | 
| 25 25 | 
             
            Requires-Dist: ddeutil>=0.4.6
         | 
| 26 | 
            -
            Requires-Dist: ddeutil-io[toml,yaml]>=0.2. | 
| 27 | 
            -
            Requires-Dist: pydantic==2. | 
| 28 | 
            -
            Requires-Dist: python-dotenv==1.0 | 
| 26 | 
            +
            Requires-Dist: ddeutil-io[toml,yaml]>=0.2.10
         | 
| 27 | 
            +
            Requires-Dist: pydantic==2.11.1
         | 
| 28 | 
            +
            Requires-Dist: python-dotenv==1.1.0
         | 
| 29 29 | 
             
            Requires-Dist: schedule<2.0.0,==1.2.2
         | 
| 30 30 | 
             
            Provides-Extra: api
         | 
| 31 31 | 
             
            Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
         | 
| @@ -130,7 +130,7 @@ flowchart LR | |
| 130 130 | 
             
            > - [Google **Workflows**](https://cloud.google.com/workflows)
         | 
| 131 131 | 
             
            > - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
         | 
| 132 132 |  | 
| 133 | 
            -
            ##  | 
| 133 | 
            +
            ## 📦 Installation
         | 
| 134 134 |  | 
| 135 135 | 
             
            This project need `ddeutil` and `ddeutil-io` extension namespace packages.
         | 
| 136 136 | 
             
            If you want to install this package with application add-ons, you should add
         | 
| @@ -1,22 +1,22 @@ | |
| 1 | 
            -
            ddeutil/workflow/__about__.py,sha256= | 
| 2 | 
            -
            ddeutil/workflow/__cron.py,sha256= | 
| 1 | 
            +
            ddeutil/workflow/__about__.py,sha256=4_L4aAteV6ecIgxXQuBmMHF6LoFXtYgCVakxe2GlHjk,28
         | 
| 2 | 
            +
            ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
         | 
| 3 3 | 
             
            ddeutil/workflow/__init__.py,sha256=hIM2Ha-7F3YF3aLsu4IY3N-UvD_EE1ai6DO6WL2mILg,1908
         | 
| 4 | 
            -
            ddeutil/workflow/__types.py,sha256= | 
| 5 | 
            -
            ddeutil/workflow/audit.py,sha256= | 
| 6 | 
            -
            ddeutil/workflow/caller.py,sha256= | 
| 7 | 
            -
            ddeutil/workflow/conf.py,sha256= | 
| 8 | 
            -
            ddeutil/workflow/context.py,sha256= | 
| 4 | 
            +
            ddeutil/workflow/__types.py,sha256=8jBdbfb3aZSetjz0mvNrpGHwwxJff7mK8_4v41cLqlc,4316
         | 
| 5 | 
            +
            ddeutil/workflow/audit.py,sha256=BpzLI6ZKXi6xQO8C2sAHxSi8RAfF31dMPdjn3DbYlw8,8364
         | 
| 6 | 
            +
            ddeutil/workflow/caller.py,sha256=M7_nan1DbRUAHEIQoQc1qIX4AHgI5fKFNx0KDbzhDMk,5736
         | 
| 7 | 
            +
            ddeutil/workflow/conf.py,sha256=USYzITPwBS5Q_pl3DTTdFK8OxeL9GQu4GUXqi8nKpJo,11819
         | 
| 8 | 
            +
            ddeutil/workflow/context.py,sha256=vsk4JQL7t3KsnKPfshw3O7YrPFo2h4rnnNd3B-G9Kj4,1700
         | 
| 9 9 | 
             
            ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
         | 
| 10 10 | 
             
            ddeutil/workflow/exceptions.py,sha256=fO37f9p7lOjIJgVOpKE_1X44yJTwBepyukZV9a7NNm4,1241
         | 
| 11 | 
            -
            ddeutil/workflow/job.py,sha256= | 
| 11 | 
            +
            ddeutil/workflow/job.py,sha256=LCQf_3gyTmnj6aAQ0ksz4lJxU10-F5MfVFHczkWt_VE,28669
         | 
| 12 12 | 
             
            ddeutil/workflow/logs.py,sha256=mAKQjNri-oourd3dBLLG3Fqoo4QG27U9IO2h6IqCOU0,10196
         | 
| 13 13 | 
             
            ddeutil/workflow/params.py,sha256=qw9XJyjh2ocf9pf6h_XiYHLOvQN4R5TMqPElmItKnRM,8019
         | 
| 14 14 | 
             
            ddeutil/workflow/result.py,sha256=cDkItrhpzZfMS1Oj8IZX8O-KBD4KZYDi43XJZvvC3Gc,4318
         | 
| 15 | 
            -
            ddeutil/workflow/scheduler.py,sha256= | 
| 16 | 
            -
            ddeutil/workflow/stages.py,sha256= | 
| 17 | 
            -
            ddeutil/workflow/templates.py,sha256= | 
| 15 | 
            +
            ddeutil/workflow/scheduler.py,sha256=daNdcTp8dQN0gXuK3cg7K7idspOcqKNmfOLTWirNEUM,27652
         | 
| 16 | 
            +
            ddeutil/workflow/stages.py,sha256=ONBR7GjgLEbA21wNioBrhOucwqj6M1v2ht5JhipoWSg,36994
         | 
| 17 | 
            +
            ddeutil/workflow/templates.py,sha256=yA2xgrSXcxfBxNT2hc6v06HkVY_0RKsc1UwdJRip9EE,11554
         | 
| 18 18 | 
             
            ddeutil/workflow/utils.py,sha256=Fz5y-LK_JfikDfvMKcFaxad_VvCnr7UC2C9KFCbzPNA,7105
         | 
| 19 | 
            -
            ddeutil/workflow/workflow.py,sha256= | 
| 19 | 
            +
            ddeutil/workflow/workflow.py,sha256=8ForojeLboLL6dHmV5-NjtU9o4mIL7NtY2F2NFxdqZY,49400
         | 
| 20 20 | 
             
            ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
         | 
| 21 21 | 
             
            ddeutil/workflow/api/api.py,sha256=gGQtqkzyJNaJIfka_w2M1lrCS3Ep46re2Dznsk9RxYQ,5191
         | 
| 22 22 | 
             
            ddeutil/workflow/api/log.py,sha256=NMTnOnsBrDB5129329xF2myLdrb-z9k1MQrmrP7qXJw,1818
         | 
| @@ -26,8 +26,8 @@ ddeutil/workflow/api/routes/job.py,sha256=YVta083i8vU8-o4WdKFwDpfdC9vN1dZ6goZSmN | |
| 26 26 | 
             
            ddeutil/workflow/api/routes/logs.py,sha256=uEQ6k5PwRg-k0eSiSFArXfyeDq5xzepxILrRnwgAe1I,5373
         | 
| 27 27 | 
             
            ddeutil/workflow/api/routes/schedules.py,sha256=uWYDOwlV8w56hKQmfkQFwdZ6t2gZSJeCdBIzMmJenAQ,4824
         | 
| 28 28 | 
             
            ddeutil/workflow/api/routes/workflows.py,sha256=KVywA7vD9b4QrfmWBdSFF5chj34yJe1zNCzl6iBMeGI,4538
         | 
| 29 | 
            -
            ddeutil_workflow-0.0. | 
| 30 | 
            -
            ddeutil_workflow-0.0. | 
| 31 | 
            -
            ddeutil_workflow-0.0. | 
| 32 | 
            -
            ddeutil_workflow-0.0. | 
| 33 | 
            -
            ddeutil_workflow-0.0. | 
| 29 | 
            +
            ddeutil_workflow-0.0.40.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
         | 
| 30 | 
            +
            ddeutil_workflow-0.0.40.dist-info/METADATA,sha256=AdIiZTWsPhfcVba7x2OrWw5CN3y0OQB-dRdBb9RQh2o,19501
         | 
| 31 | 
            +
            ddeutil_workflow-0.0.40.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
         | 
| 32 | 
            +
            ddeutil_workflow-0.0.40.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
         | 
| 33 | 
            +
            ddeutil_workflow-0.0.40.dist-info/RECORD,,
         | 
| 
            File without changes
         | 
| 
            File without changes
         |