ddeutil-workflow 0.0.16__py3-none-any.whl → 0.0.18__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/stage.py CHANGED
@@ -13,8 +13,8 @@ handle stage error on this stage model. I think stage model should have a lot of
13
13
  usecase and it does not worry when I want to create a new one.
14
14
 
15
15
  Execution --> Ok --> Result with 0
16
- --> Error --> Raise StageException
17
- --> Result with 1 (if env var was set)
16
+ --> Error --> Result with 1 (if env var was set)
17
+ --> Raise StageException
18
18
 
19
19
  On the context I/O that pass to a stage object at execute process. The
20
20
  execute method receives a `params={"params": {...}}` value for mapping to
@@ -48,9 +48,8 @@ from pydantic.functional_validators import model_validator
48
48
  from typing_extensions import Self
49
49
 
50
50
  from .__types import DictData, DictStr, Re, TupleStr
51
- from .conf import config
51
+ from .conf import config, get_logger
52
52
  from .exceptions import StageException
53
- from .log import get_logger
54
53
  from .utils import (
55
54
  Registry,
56
55
  Result,
@@ -94,12 +93,12 @@ def handler_result(message: str | None = None) -> DecoratorResult:
94
93
  status: 0
95
94
  context:
96
95
  outputs: ...
97
- --> Error --> Raise StageException
98
- --> Result (if env var was set)
96
+ --> Error --> Result (if env var was set)
99
97
  status: 1
100
98
  context:
101
99
  error: ...
102
100
  error_message: ...
101
+ --> Error --> Raise StageException
103
102
 
104
103
  On the last step, it will set the running ID on a return result object
105
104
  from current stage ID before release the final result.
@@ -190,6 +189,9 @@ class BaseStage(BaseModel, ABC):
190
189
  method will validate name and id fields should not contain any template
191
190
  parameter (exclude matrix template).
192
191
 
192
+ :raise ValueError: When the ID and name fields include matrix parameter
193
+ template with the 'matrix.' string value.
194
+
193
195
  :rtype: Self
194
196
  """
195
197
  if self.run_id is None:
@@ -199,7 +201,7 @@ class BaseStage(BaseModel, ABC):
199
201
  # template. (allow only matrix)
200
202
  if not_in_template(self.id) or not_in_template(self.name):
201
203
  raise ValueError(
202
- "Stage name and ID should only template with matrix."
204
+ "Stage name and ID should only template with 'matrix.'"
203
205
  )
204
206
 
205
207
  return self
@@ -236,10 +238,10 @@ class BaseStage(BaseModel, ABC):
236
238
  The result of the `to` variable will be;
237
239
 
238
240
  ... (iii) to: {
239
- 'stages': {
240
- '<stage-id>': {'outputs': {'foo': 'bar'}}
241
- }
241
+ 'stages': {
242
+ '<stage-id>': {'outputs': {'foo': 'bar'}}
242
243
  }
244
+ }
243
245
 
244
246
  :param output: A output data that want to extract to an output key.
245
247
  :param to: A context data that want to add output result.
@@ -273,6 +275,11 @@ class BaseStage(BaseModel, ABC):
273
275
  """Return true if condition of this stage do not correct. This process
274
276
  use build-in eval function to execute the if-condition.
275
277
 
278
+ :raise StageException: When it has any error raise from the eval
279
+ condition statement.
280
+ :raise StageException: When return type of the eval condition statement
281
+ does not return with boolean type.
282
+
276
283
  :param params: A parameters that want to pass to condition template.
277
284
  :rtype: bool
278
285
  """
@@ -460,6 +467,15 @@ class PyStage(BaseStage):
460
467
  ),
461
468
  )
462
469
 
470
+ @staticmethod
471
+ def pick_keys_from_locals(values: DictData) -> Iterator[str]:
472
+ from inspect import ismodule
473
+
474
+ for value in values:
475
+ if value == "__annotations__" or ismodule(values[value]):
476
+ continue
477
+ yield value
478
+
463
479
  def set_outputs(self, output: DictData, to: DictData) -> DictData:
464
480
  """Override set an outputs method for the Python execution process that
465
481
  extract output from all the locals values.
@@ -469,15 +485,19 @@ class PyStage(BaseStage):
469
485
  :rtype: DictData
470
486
  """
471
487
  # NOTE: The output will fileter unnecessary keys from locals.
472
- _locals: DictData = output["locals"]
488
+ lc: DictData = output.get("locals", {})
473
489
  super().set_outputs(
474
- {k: _locals[k] for k in _locals if k != "__annotations__"}, to=to
490
+ (
491
+ {k: lc[k] for k in self.pick_keys_from_locals(lc)}
492
+ | {k: output[k] for k in output if k.startswith("error")}
493
+ ),
494
+ to=to,
475
495
  )
476
496
 
477
- # NOTE:
478
- # Override value that changing from the globals that pass via exec.
479
- _globals: DictData = output["globals"]
480
- to.update({k: _globals[k] for k in to if k in _globals})
497
+ # NOTE: Override value that changing from the globals that pass via the
498
+ # exec function.
499
+ gb: DictData = output.get("globals", {})
500
+ to.update({k: gb[k] for k in to if k in gb})
481
501
  return to
482
502
 
483
503
  @handler_result()
@@ -495,15 +515,15 @@ class PyStage(BaseStage):
495
515
  _globals: DictData = (
496
516
  globals() | params | param2template(self.vars, params)
497
517
  )
498
- _locals: DictData = {}
518
+ lc: DictData = {}
499
519
 
500
520
  # NOTE: Start exec the run statement.
501
521
  logger.info(f"({self.run_id}) [STAGE]: Py-Execute: {self.name}")
502
- exec(run, _globals, _locals)
522
+ exec(run, _globals, lc)
503
523
 
504
524
  return Result(
505
525
  status=0,
506
- context={"locals": _locals, "globals": _globals},
526
+ context={"locals": lc, "globals": _globals},
507
527
  )
508
528
 
509
529
 
@@ -522,6 +542,11 @@ def extract_hook(hook: str) -> Callable[[], TagFunc]:
522
542
  """Extract Hook function from string value to hook partial function that
523
543
  does run it at runtime.
524
544
 
545
+ :raise NotImplementedError: When the searching hook's function result does
546
+ not exist in the registry.
547
+ :raise NotImplementedError: When the searching hook's tag result does not
548
+ exists in the registry with its function key.
549
+
525
550
  :param hook: A hook value that able to match with Task regex.
526
551
  :rtype: Callable[[], TagFunc]
527
552
  """
@@ -581,6 +606,11 @@ class HookStage(BaseStage):
581
606
  def execute(self, params: DictData) -> Result:
582
607
  """Execute the Hook function that already in the hook registry.
583
608
 
609
+ :raise ValueError: When the necessary arguments of hook function do not
610
+ set from the input params argument.
611
+ :raise TypeError: When the return type of hook function does not be
612
+ dict type.
613
+
584
614
  :param params: A parameter that want to pass before run any statement.
585
615
  :type params: DictData
586
616
  :rtype: Result
ddeutil/workflow/utils.py CHANGED
@@ -37,9 +37,10 @@ from pydantic.functional_validators import model_validator
37
37
  from typing_extensions import Self
38
38
 
39
39
  from .__types import DictData, Matrix, Re
40
- from .conf import config, load_config
40
+ from .conf import config
41
41
  from .exceptions import ParamValueException, UtilException
42
42
 
43
+ T = TypeVar("T")
43
44
  P = ParamSpec("P")
44
45
  AnyModel = TypeVar("AnyModel", bound=BaseModel)
45
46
  AnyModelType = type[AnyModel]
@@ -99,7 +100,7 @@ def gen_id(
99
100
  if not isinstance(value, str):
100
101
  value: str = str(value)
101
102
 
102
- if config.workflow_id_simple_mode:
103
+ if config.gen_id_simple_mode:
103
104
  return hash_str(f"{(value if sensitive else value.lower())}", n=10) + (
104
105
  f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}" if unique else ""
105
106
  )
@@ -160,7 +161,7 @@ def make_registry(submodule: str) -> dict[str, Registry]:
160
161
  :rtype: dict[str, Registry]
161
162
  """
162
163
  rs: dict[str, Registry] = {}
163
- for module in load_config().engine.registry:
164
+ for module in config.regis_hook:
164
165
  # NOTE: try to sequential import task functions
165
166
  try:
166
167
  importer = import_module(f"{module}.{submodule}")
@@ -228,15 +229,6 @@ class DefaultParam(BaseParam):
228
229
  "Receive value and validate typing before return valid value."
229
230
  )
230
231
 
231
- @model_validator(mode="after")
232
- def __check_default(self) -> Self:
233
- """Check default value should pass when it set required."""
234
- if self.required and self.default is None:
235
- raise ParamValueException(
236
- "Default should be set when this parameter was required."
237
- )
238
- return self
239
-
240
232
 
241
233
  class DatetimeParam(DefaultParam):
242
234
  """Datetime parameter."""
@@ -497,7 +489,7 @@ def make_filter_registry() -> dict[str, FilterRegistry]:
497
489
  :rtype: dict[str, Registry]
498
490
  """
499
491
  rs: dict[str, Registry] = {}
500
- for module in load_config().engine.registry_filter:
492
+ for module in config.regis_filter:
501
493
  # NOTE: try to sequential import task functions
502
494
  try:
503
495
  importer = import_module(module)
@@ -529,11 +521,11 @@ def get_args_const(
529
521
  raise UtilException(
530
522
  f"Post-filter: {expr} does not valid because it raise syntax error."
531
523
  ) from None
532
- body: list[Expr] = mod.body
533
524
 
525
+ body: list[Expr] = mod.body
534
526
  if len(body) > 1:
535
527
  raise UtilException(
536
- "Post-filter function should be only one calling per wf"
528
+ "Post-filter function should be only one calling per workflow."
537
529
  )
538
530
 
539
531
  caller: Union[Name, Call]
@@ -549,12 +541,15 @@ def get_args_const(
549
541
  keywords: dict[str, Constant] = {k.arg: k.value for k in caller.keywords}
550
542
 
551
543
  if any(not isinstance(i, Constant) for i in args):
552
- raise UtilException("Argument should be constant.")
544
+ raise UtilException(f"Argument of {expr} should be constant.")
545
+
546
+ if any(not isinstance(i, Constant) for i in keywords.values()):
547
+ raise UtilException(f"Keyword argument of {expr} should be constant.")
553
548
 
554
549
  return name.id, args, keywords
555
550
 
556
551
 
557
- @custom_filter("fmt")
552
+ @custom_filter("fmt") # pragma: no cov
558
553
  def datetime_format(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
559
554
  """Format datetime object to string with the format."""
560
555
  if isinstance(value, datetime):
@@ -565,16 +560,18 @@ def datetime_format(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
565
560
 
566
561
 
567
562
  def map_post_filter(
568
- value: Any,
563
+ value: T,
569
564
  post_filter: list[str],
570
565
  filters: dict[str, FilterRegistry],
571
- ) -> Any:
566
+ ) -> T:
572
567
  """Mapping post-filter to value with sequence list of filter function name
573
568
  that will get from the filter registry.
574
569
 
575
570
  :param value: A string value that want to mapped with filter function.
576
571
  :param post_filter: A list of post-filter function name.
577
572
  :param filters: A filter registry.
573
+
574
+ :rtype: T
578
575
  """
579
576
  for _filter in post_filter:
580
577
  func_name, _args, _kwargs = get_args_const(_filter)
@@ -597,6 +594,8 @@ def map_post_filter(
597
594
  value: Any = func(value)
598
595
  else:
599
596
  value: Any = f_func(value, *args, **kwargs)
597
+ except UtilException:
598
+ raise
600
599
  except Exception as err:
601
600
  logger.warning(str(err))
602
601
  raise UtilException(
@@ -609,8 +608,8 @@ def map_post_filter(
609
608
  def not_in_template(value: Any, *, not_in: str = "matrix.") -> bool:
610
609
  """Check value should not pass template with not_in value prefix.
611
610
 
612
- :param value:
613
- :param not_in:
611
+ :param value: A value that want to find parameter template prefix.
612
+ :param not_in: The not in string that use in the `.startswith` function.
614
613
  :rtype: bool
615
614
  """
616
615
  if isinstance(value, dict):
@@ -628,7 +627,7 @@ def not_in_template(value: Any, *, not_in: str = "matrix.") -> bool:
628
627
  def has_template(value: Any) -> bool:
629
628
  """Check value include templating string.
630
629
 
631
- :param value:
630
+ :param value: A value that want to find parameter template.
632
631
  :rtype: bool
633
632
  """
634
633
  if isinstance(value, dict):
@@ -784,6 +783,7 @@ def batch(iterable: Iterator[Any], n: int) -> Iterator[Any]:
784
783
  """
785
784
  if n < 1:
786
785
  raise ValueError("n must be at least one")
786
+
787
787
  it: Iterator[Any] = iter(iterable)
788
788
  while True:
789
789
  chunk_it = islice(it, n)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Lightweight workflow orchestration with less dependencies
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -23,7 +23,8 @@ Requires-Python: >=3.9.13
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: ddeutil >=0.4.3
26
- Requires-Dist: ddeutil-io[yaml] >=0.2.3
26
+ Requires-Dist: ddeutil-io[toml,yaml] >=0.2.3
27
+ Requires-Dist: pydantic ==2.9.2
27
28
  Requires-Dist: python-dotenv ==1.0.1
28
29
  Requires-Dist: typer <1.0.0,==0.12.5
29
30
  Requires-Dist: schedule <2.0.0,==1.2.2
@@ -33,6 +34,7 @@ Requires-Dist: fastapi <1.0.0,>=0.115.0 ; extra == 'api'
33
34
  # Workflow
34
35
 
35
36
  [![test](https://github.com/ddeutils/ddeutil-workflow/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/ddeutils/ddeutil-workflow/actions/workflows/tests.yml)
37
+ [![codecov](https://codecov.io/gh/ddeutils/ddeutil-workflow/graph/badge.svg?token=3NDPN2I0H9)](https://codecov.io/gh/ddeutils/ddeutil-workflow)
36
38
  [![pypi version](https://img.shields.io/pypi/v/ddeutil-workflow)](https://pypi.org/project/ddeutil-workflow/)
37
39
  [![python support version](https://img.shields.io/pypi/pyversions/ddeutil-workflow)](https://pypi.org/project/ddeutil-workflow/)
38
40
  [![size](https://img.shields.io/github/languages/code-size/ddeutils/ddeutil-workflow)](https://github.com/ddeutils/ddeutil-workflow)
@@ -74,8 +76,9 @@ configuration. It called **Metadata Driven Data Workflow**.
74
76
 
75
77
  ## :round_pushpin: Installation
76
78
 
77
- This project need `ddeutil-io` extension namespace packages. If you want to install
78
- this package with application add-ons, you should add `app` in installation;
79
+ This project need `ddeutil` and `ddeutil-io` extension namespace packages.
80
+ If you want to install this package with application add-ons, you should add
81
+ `app` in installation;
79
82
 
80
83
  | Usecase | Install Optional | Support |
81
84
  |-------------------|------------------------------------------|--------------------|
@@ -181,29 +184,30 @@ and do not raise any error to you.
181
184
 
182
185
  | Environment | Component | Default | Description | Remark |
183
186
  |:----------------------------------------|-----------|----------------------------------|--------------------------------------------------------------------------------------------------------------------|--------|
184
- | `WORKFLOW_ROOT_PATH` | Core | . | The root path of the workflow application | |
185
- | `WORKFLOW_CORE_REGISTRY` | Core | src.ddeutil.workflow,tests.utils | List of importable string for the hook stage | |
186
- | `WORKFLOW_CORE_REGISTRY_FILTER` | Core | ddeutil.workflow.utils | List of importable string for the filter template | |
187
- | `WORKFLOW_CORE_PATH_CONF` | Core | conf | The config path that keep all template `.yaml` files | |
188
- | `WORKFLOW_CORE_TIMEZONE` | Core | Asia/Bangkok | A Timezone string value that will pass to `ZoneInfo` object | |
189
- | `WORKFLOW_CORE_STAGE_DEFAULT_ID` | Core | true | A flag that enable default stage ID that use for catch an execution output | |
190
- | `WORKFLOW_CORE_STAGE_RAISE_ERROR` | Core | false | A flag that all stage raise StageException from stage execution | |
187
+ | `WORKFLOW_ROOT_PATH` | Core | . | The root path of the workflow application. | |
188
+ | `WORKFLOW_CORE_REGISTRY` | Core | src.ddeutil.workflow,tests.utils | List of importable string for the hook stage. | |
189
+ | `WORKFLOW_CORE_REGISTRY_FILTER` | Core | ddeutil.workflow.utils | List of importable string for the filter template. | |
190
+ | `WORKFLOW_CORE_PATH_CONF` | Core | conf | The config path that keep all template `.yaml` files. | |
191
+ | `WORKFLOW_CORE_TIMEZONE` | Core | Asia/Bangkok | A Timezone string value that will pass to `ZoneInfo` object. | |
192
+ | `WORKFLOW_CORE_STAGE_DEFAULT_ID` | Core | true | A flag that enable default stage ID that use for catch an execution output. | |
193
+ | `WORKFLOW_CORE_STAGE_RAISE_ERROR` | Core | false | A flag that all stage raise StageException from stage execution. | |
191
194
  | `WORKFLOW_CORE_JOB_DEFAULT_ID` | Core | false | A flag that enable default job ID that use for catch an execution output. The ID that use will be sequence number. | |
192
- | `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | | |
193
- | `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor | |
194
- | `WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE` | Core | true | | |
195
- | `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode | |
196
- | `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination | |
197
- | `WORKFLOW_APP_MAX_PROCESS` | Schedule | 2 | The maximum process worker number that run in scheduler app module | |
198
- | `WORKFLOW_APP_MAX_SCHEDULE_PER_PROCESS` | Schedule | 100 | A schedule per process that run parallel | |
199
- | `WORKFLOW_APP_STOP_BOUNDARY_DELTA` | Schedule | '{"minutes": 5, "seconds": 20}' | A time delta value that use to stop scheduler app in json string format | |
195
+ | `WORKFLOW_CORE_JOB_RAISE_ERROR` | Core | true | A flag that all job raise JobException from job strategy execution. | |
196
+ | `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | . | |
197
+ | `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor. | |
198
+ | `WORKFLOW_CORE_GENERATE_ID_SIMPLE_MODE` | Core | true | A flog that enable generating ID with `md5` algorithm. | |
199
+ | `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode. | |
200
+ | `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination. | |
201
+ | `WORKFLOW_APP_MAX_PROCESS` | Schedule | 2 | The maximum process worker number that run in scheduler app module. | |
202
+ | `WORKFLOW_APP_MAX_SCHEDULE_PER_PROCESS` | Schedule | 100 | A schedule per process that run parallel. | |
203
+ | `WORKFLOW_APP_STOP_BOUNDARY_DELTA` | Schedule | '{"minutes": 5, "seconds": 20}' | A time delta value that use to stop scheduler app in json string format. | |
200
204
 
201
205
  **API Application**:
202
206
 
203
- | Environment | Component | Default | Description | Remark |
204
- |:--------------------------------------|-----------|---------|-----------------------------------------------------------------------------------|--------|
205
- | `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` | API | true | A flag that enable workflow route to manage execute manually and workflow logging | |
206
- | `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` | API | true | A flag that enable run scheduler | |
207
+ | Environment | Component | Default | Description | Remark |
208
+ |:--------------------------------------|-----------|---------|------------------------------------------------------------------------------------|--------|
209
+ | `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` | API | true | A flag that enable workflow route to manage execute manually and workflow logging. | |
210
+ | `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` | API | true | A flag that enable run scheduler. | |
207
211
 
208
212
  ## :rocket: Deployment
209
213
 
@@ -0,0 +1,21 @@
1
+ ddeutil/workflow/__about__.py,sha256=b5h9QJ6GhQ-EDPZTcMYoeJZy8blWgeG9xjpFBHrVLPg,28
2
+ ddeutil/workflow/__cron.py,sha256=ZiuV4ASkXvAyFJYxEb9PKiAFNYnUt4AJozu_kH3pI4U,25777
3
+ ddeutil/workflow/__init__.py,sha256=HA0tjGBXJItNPsAqvhnFUXU0fP0K6iMMfMtJ37tRwcw,1385
4
+ ddeutil/workflow/__types.py,sha256=yizLXzjQpBt_WPaof2pIyncitJvYeksw4Q1zYJeuCLA,3707
5
+ ddeutil/workflow/api.py,sha256=vUT2RVS9sF3hvY-IrzAEnahxwq4ZFYP0G3xfctHbNsw,4701
6
+ ddeutil/workflow/cli.py,sha256=baHhvtI8snbHYHeThoX401Cd6SMB2boyyCbCtTrIl3E,3278
7
+ ddeutil/workflow/conf.py,sha256=4j7m2blvCPlz_me4SBHf_exViUK3ZLLBCwldPztHJKo,15390
8
+ ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
9
+ ddeutil/workflow/job.py,sha256=kSllDDiSnDpyFnIT9-Sum6OHQ16Pn5h2t5_-XljHbgk,23979
10
+ ddeutil/workflow/on.py,sha256=rneZB5HyFWTBWriGef999bovA3glQIK6LTgC996q9Gc,7334
11
+ ddeutil/workflow/repeat.py,sha256=s0azh-f5JQeow7kpxM8GKlqgAmKL7oU6St3L4Ggx4cY,4925
12
+ ddeutil/workflow/route.py,sha256=JALwOH6xKu5rnII7DgA1Lbp_E5ehCoBbOW_eKqB_Olk,6753
13
+ ddeutil/workflow/scheduler.py,sha256=baCYbv5f8HiQgV36fUvkkUpSiIRhrznuwKefsgKjHv4,47546
14
+ ddeutil/workflow/stage.py,sha256=6Ng3RiCSrnQ-FUsRRcuG2ClMD6ifiQlgyBFi6tohfxI,25455
15
+ ddeutil/workflow/utils.py,sha256=ouuQ3mqjKVzuchCcvVelo8Hh8c6UJ4_lHPqejcxNDRA,25147
16
+ ddeutil_workflow-0.0.18.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
17
+ ddeutil_workflow-0.0.18.dist-info/METADATA,sha256=VLZchB_AWG5kMf7RYFTyKI3zkxWdj7k942f_XZpBxyQ,13606
18
+ ddeutil_workflow-0.0.18.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
19
+ ddeutil_workflow-0.0.18.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
20
+ ddeutil_workflow-0.0.18.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
21
+ ddeutil_workflow-0.0.18.dist-info/RECORD,,
ddeutil/workflow/log.py DELETED
@@ -1,195 +0,0 @@
1
- # ------------------------------------------------------------------------------
2
- # Copyright (c) 2022 Korawich Anuttra. All rights reserved.
3
- # Licensed under the MIT License. See LICENSE in the project root for
4
- # license information.
5
- # ------------------------------------------------------------------------------
6
- from __future__ import annotations
7
-
8
- import json
9
- import logging
10
- from abc import ABC, abstractmethod
11
- from datetime import datetime
12
- from functools import lru_cache
13
- from pathlib import Path
14
- from typing import ClassVar, Optional, Union
15
-
16
- from pydantic import BaseModel, Field
17
- from pydantic.functional_validators import model_validator
18
- from typing_extensions import Self
19
-
20
- from .__types import DictData
21
- from .conf import config, load_config
22
-
23
-
24
- @lru_cache
25
- def get_logger(name: str):
26
- """Return logger object with an input module name.
27
-
28
- :param name: A module name that want to log.
29
- """
30
- logger = logging.getLogger(name)
31
- formatter = logging.Formatter(
32
- fmt=(
33
- "%(asctime)s.%(msecs)03d (%(name)-10s, %(process)-5d, "
34
- "%(thread)-5d) [%(levelname)-7s] %(message)-120s "
35
- "(%(filename)s:%(lineno)s)"
36
- ),
37
- datefmt="%Y-%m-%d %H:%M:%S",
38
- )
39
- stream = logging.StreamHandler()
40
- stream.setFormatter(formatter)
41
- logger.addHandler(stream)
42
-
43
- logger.setLevel(logging.DEBUG if config.debug else logging.INFO)
44
- return logger
45
-
46
-
47
- class BaseLog(BaseModel, ABC):
48
- """Base Log Pydantic Model with abstraction class property that implement
49
- only model fields. This model should to use with inherit to logging
50
- sub-class like file, sqlite, etc.
51
- """
52
-
53
- name: str = Field(description="A workflow name.")
54
- on: str = Field(description="A cronjob string of this piepline schedule.")
55
- release: datetime = Field(description="A release datetime.")
56
- context: DictData = Field(
57
- default_factory=dict,
58
- description=(
59
- "A context data that receive from a workflow execution result.",
60
- ),
61
- )
62
- parent_run_id: Optional[str] = Field(default=None)
63
- run_id: str
64
- update: datetime = Field(default_factory=datetime.now)
65
-
66
- @model_validator(mode="after")
67
- def __model_action(self) -> Self:
68
- """Do before the Log action with WORKFLOW_LOG_ENABLE_WRITE env variable.
69
-
70
- :rtype: Self
71
- """
72
- if config.enable_write_log:
73
- self.do_before()
74
- return self
75
-
76
- def do_before(self) -> None:
77
- """To something before end up of initial log model."""
78
-
79
- @abstractmethod
80
- def save(self, excluded: list[str] | None) -> None:
81
- """Save this model logging to target logging store."""
82
- raise NotImplementedError("Log should implement ``save`` method.")
83
-
84
-
85
- class FileLog(BaseLog):
86
- """File Log Pydantic Model that use to saving log data from result of
87
- workflow execution. It inherit from BaseLog model that implement the
88
- ``self.save`` method for file.
89
- """
90
-
91
- filename: ClassVar[str] = (
92
- "./logs/workflow={name}/release={release:%Y%m%d%H%M%S}"
93
- )
94
-
95
- def do_before(self) -> None:
96
- """Create directory of release before saving log file."""
97
- self.pointer().mkdir(parents=True, exist_ok=True)
98
-
99
- @classmethod
100
- def find_logs(cls, name: str):
101
- pointer: Path = (
102
- load_config().engine.paths.root / f"./logs/workflow={name}"
103
- )
104
- for file in pointer.glob("./release=*/*.log"):
105
- with file.open(mode="r", encoding="utf-8") as f:
106
- yield json.load(f)
107
-
108
- @classmethod
109
- def find_log(cls, name: str, release: datetime | None = None):
110
- if release is not None:
111
- pointer: Path = (
112
- load_config().engine.paths.root
113
- / f"./logs/workflow={name}/release={release:%Y%m%d%H%M%S}"
114
- )
115
- if not pointer.exists():
116
- raise FileNotFoundError(
117
- f"Pointer: ./logs/workflow={name}/"
118
- f"release={release:%Y%m%d%H%M%S} does not found."
119
- )
120
- return cls.model_validate(
121
- obj=json.loads(pointer.read_text(encoding="utf-8"))
122
- )
123
- raise NotImplementedError("Find latest log does not implement yet.")
124
-
125
- @classmethod
126
- def is_pointed(
127
- cls,
128
- name: str,
129
- release: datetime,
130
- *,
131
- queue: list[datetime] | None = None,
132
- ) -> bool:
133
- """Check this log already point in the destination.
134
-
135
- :param name: A workflow name.
136
- :param release: A release datetime.
137
- :param queue: A list of queue of datetime that already run in the
138
- future.
139
- """
140
- # NOTE: Check environ variable was set for real writing.
141
- if not config.enable_write_log:
142
- return False
143
-
144
- # NOTE: create pointer path that use the same logic of pointer method.
145
- pointer: Path = load_config().engine.paths.root / cls.filename.format(
146
- name=name, release=release
147
- )
148
-
149
- if not queue:
150
- return pointer.exists()
151
- return pointer.exists() or (release in queue)
152
-
153
- def pointer(self) -> Path:
154
- """Return release directory path that was generated from model data.
155
-
156
- :rtype: Path
157
- """
158
- return load_config().engine.paths.root / self.filename.format(
159
- name=self.name, release=self.release
160
- )
161
-
162
- def save(self, excluded: list[str] | None) -> Self:
163
- """Save logging data that receive a context data from a workflow
164
- execution result.
165
-
166
- :param excluded: An excluded list of key name that want to pass in the
167
- model_dump method.
168
- :rtype: Self
169
- """
170
- # NOTE: Check environ variable was set for real writing.
171
- if not config.enable_write_log:
172
- return self
173
-
174
- log_file: Path = self.pointer() / f"{self.run_id}.log"
175
- log_file.write_text(
176
- json.dumps(
177
- self.model_dump(exclude=excluded),
178
- default=str,
179
- indent=2,
180
- ),
181
- encoding="utf-8",
182
- )
183
- return self
184
-
185
-
186
- class SQLiteLog(BaseLog):
187
-
188
- def save(self, excluded: list[str] | None) -> None:
189
- raise NotImplementedError("SQLiteLog does not implement yet.")
190
-
191
-
192
- Log = Union[
193
- FileLog,
194
- SQLiteLog,
195
- ]
@@ -1,22 +0,0 @@
1
- ddeutil/workflow/__about__.py,sha256=J3F-a05BmGpnyxPdhYxOJtDuTmXUjC0rvelcqfmfPRQ,28
2
- ddeutil/workflow/__init__.py,sha256=-DIy8SGFsD7_wqp-V-K8v8jTxacmqrcyj_SFx1WS6qg,687
3
- ddeutil/workflow/__types.py,sha256=yizLXzjQpBt_WPaof2pIyncitJvYeksw4Q1zYJeuCLA,3707
4
- ddeutil/workflow/api.py,sha256=7LQeR9w2Mq_vFTjqEK3dPiI1de-ENC_eyozr_UG8JMA,4717
5
- ddeutil/workflow/cli.py,sha256=GgVWPrSrG8ZTUGMTmHHngBi5LK0w5sAIMepciE5G_kc,3294
6
- ddeutil/workflow/conf.py,sha256=Pzw2LGaKtOUgEvczp9_PRhzO2HBigQx5XUEdrlNQFm4,10244
7
- ddeutil/workflow/cron.py,sha256=naWefHc3EnVo41Yf1zQeXOzF27YlTlnfj0XnQ6_HO-U,25514
8
- ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
9
- ddeutil/workflow/job.py,sha256=8198kktH8WqMR2H6VWx2Pq6TFc2gmBdH3qZvVERRJx0,22668
10
- ddeutil/workflow/log.py,sha256=uxnxzeYTG7bu2ShchYehRnyr9YAdQyiMsUEhm8RbDDQ,6285
11
- ddeutil/workflow/on.py,sha256=2Kt0GIgrp_na1lA-TdJz1Wwo_mdcgxhTZdQ5BU2rj0E,7236
12
- ddeutil/workflow/repeat.py,sha256=mAj2BUDdcCe828gG-1NNxaz57mHebra5aC2VbjEzVJE,4939
13
- ddeutil/workflow/route.py,sha256=HveQ2c5MDVYW5YdMsF6g30DX2liR6vNt3_20NirMGQ8,6769
14
- ddeutil/workflow/scheduler.py,sha256=u1r5JhI7HSZzJ9__yruQen3mmo6Pd3slvEMtFfRFeOc,46995
15
- ddeutil/workflow/stage.py,sha256=I5bGASI-UUCcS-figIHdrV_LycDu_2mwjt4eZBR3Z_E,24268
16
- ddeutil/workflow/utils.py,sha256=PgPvcLoRYFobUUS56rSUFuN-zTyejgJ-gl36nNqQuWM,25117
17
- ddeutil_workflow-0.0.16.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
18
- ddeutil_workflow-0.0.16.dist-info/METADATA,sha256=_1jCgcqLcSKB2EHZcotmCtZec2KPKAYCidy28ZqQTe8,13190
19
- ddeutil_workflow-0.0.16.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
20
- ddeutil_workflow-0.0.16.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
21
- ddeutil_workflow-0.0.16.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
22
- ddeutil_workflow-0.0.16.dist-info/RECORD,,