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.
@@ -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
- # NOTE: Prepare `on` data
304
- cls.__bypass_on__(loader_data, externals=externals)
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 externals:
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 Loader and keep on model object to on field
363
+ # NOTE: Pass on value to SimLoad and keep on model object to the on
364
+ # field.
326
365
  data["on"] = [
327
366
  (
328
- Loader(n, externals=(externals or {})).data
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.set_outputs(
902
- job.execute(
903
- params=params,
904
- run_id=result.run_id,
905
- parent_run_id=result.parent_run_id,
906
- event=event,
907
- ).context,
908
- to=params,
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 not job.check_needs(context["jobs"]):
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 not job.check_needs(context["jobs"]):
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.38
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.3
27
- Requires-Dist: pydantic==2.10.6
28
- Requires-Dist: python-dotenv==1.0.1
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
- ## :round_pushpin: Installation
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=nUvuf0abbrBoZoHVyqcyvfYEilSx2oTLDwA7Az8m5UU,28
2
- ddeutil/workflow/__cron.py,sha256=3i-wmjTlh0ADCzN9pLKaWHzJkXzC72aIBmVEQSbyCCE,26895
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=CK1jfzyHP9P-MB0ElhpJZ59ZFGJC9MkQuAop5739_9k,4304
5
- ddeutil/workflow/audit.py,sha256=wx70RKRdHj1d2431ilpt9OPTInMByjqXkYff7l5pvF4,8230
6
- ddeutil/workflow/caller.py,sha256=oFjB9zpG93aeVd-T_4QMz5kk64lo-iyxaPmCo_1_AzU,5693
7
- ddeutil/workflow/conf.py,sha256=MHzBeLZukFeIQ-YhxOz5uKCnGYqbhYdpwAEh9A9h_OM,12216
8
- ddeutil/workflow/context.py,sha256=fdGUuQnsjDCDGibPHKMWc5PA3pArHpfv6Aff2nVvZ0U,1618
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=3oDY-THXYMvmW0kKkHN2VMJyq0u0_4s9H0eX40Iiw6E,24267
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=YMebYpNjqg6RWaE17sicwM3uthupeBGSGCnDGy4aKd8,26286
16
- ddeutil/workflow/stages.py,sha256=aHMPOPxRiSo2jBFqQOlua01sQ18RMvjaKk2Dotnu7v4,36480
17
- ddeutil/workflow/templates.py,sha256=A0JgZFGkBv-AX-EskZj656nG5zFd3j1PpLpyXihf6Xg,10967
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=v6LTbXZ34yWmddZquBxpahxCW1PFb3177q1T3Ssf5C0,46636
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.38.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
30
- ddeutil_workflow-0.0.38.dist-info/METADATA,sha256=haHYX6Ui8EXTi2qJA5x_SOWSmVqmXRKsHLdgyMp7Two,19511
31
- ddeutil_workflow-0.0.38.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
32
- ddeutil_workflow-0.0.38.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
33
- ddeutil_workflow-0.0.38.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5