ddeutil-workflow 0.0.22__py3-none-any.whl → 0.0.24__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 +6 -0
- ddeutil/workflow/__init__.py +16 -14
- ddeutil/workflow/api.py +2 -2
- ddeutil/workflow/cli.py +2 -2
- ddeutil/workflow/conf.py +18 -2
- ddeutil/workflow/{on.py → cron.py} +3 -3
- ddeutil/workflow/job.py +2 -2
- ddeutil/workflow/params.py +176 -0
- ddeutil/workflow/result.py +102 -0
- ddeutil/workflow/route.py +1 -1
- ddeutil/workflow/scheduler.py +220 -173
- ddeutil/workflow/stage.py +6 -1
- ddeutil/workflow/utils.py +4 -245
- ddeutil/workflow/workflow.py +145 -133
- {ddeutil_workflow-0.0.22.dist-info → ddeutil_workflow-0.0.24.dist-info}/METADATA +4 -3
- ddeutil_workflow-0.0.24.dist-info/RECORD +24 -0
- ddeutil_workflow-0.0.22.dist-info/RECORD +0 -22
- {ddeutil_workflow-0.0.22.dist-info → ddeutil_workflow-0.0.24.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.22.dist-info → ddeutil_workflow-0.0.24.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.22.dist-info → ddeutil_workflow-0.0.24.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.22.dist-info → ddeutil_workflow-0.0.24.dist-info}/top_level.txt +0 -0
ddeutil/workflow/stage.py
CHANGED
@@ -51,9 +51,9 @@ from typing_extensions import Self
|
|
51
51
|
from .__types import DictData, DictStr, Re, TupleStr
|
52
52
|
from .conf import config, get_logger
|
53
53
|
from .exceptions import StageException
|
54
|
+
from .result import Result
|
54
55
|
from .utils import (
|
55
56
|
Registry,
|
56
|
-
Result,
|
57
57
|
TagFunc,
|
58
58
|
cut_id,
|
59
59
|
gen_id,
|
@@ -346,6 +346,11 @@ class EmptyStage(BaseStage):
|
|
346
346
|
f"( {param2template(self.echo, params=params) or '...'} )"
|
347
347
|
)
|
348
348
|
if self.sleep > 0:
|
349
|
+
if self.sleep > 30:
|
350
|
+
logger.info(
|
351
|
+
f"({cut_id(run_id)}) [STAGE]: ... sleep "
|
352
|
+
f"({self.sleep} seconds)"
|
353
|
+
)
|
349
354
|
time.sleep(self.sleep)
|
350
355
|
return Result(status=0, context={}, run_id=run_id)
|
351
356
|
|
ddeutil/workflow/utils.py
CHANGED
@@ -9,11 +9,9 @@ import inspect
|
|
9
9
|
import logging
|
10
10
|
import stat
|
11
11
|
import time
|
12
|
-
from abc import ABC, abstractmethod
|
13
12
|
from ast import Call, Constant, Expr, Module, Name, parse
|
14
13
|
from collections.abc import Iterator
|
15
|
-
from
|
16
|
-
from datetime import date, datetime, timedelta
|
14
|
+
from datetime import datetime, timedelta
|
17
15
|
from functools import wraps
|
18
16
|
from hashlib import md5
|
19
17
|
from importlib import import_module
|
@@ -21,7 +19,7 @@ from inspect import isfunction
|
|
21
19
|
from itertools import chain, islice, product
|
22
20
|
from pathlib import Path
|
23
21
|
from random import randrange
|
24
|
-
from typing import Any, Callable,
|
22
|
+
from typing import Any, Callable, Protocol, TypeVar, Union
|
25
23
|
from zoneinfo import ZoneInfo
|
26
24
|
|
27
25
|
try:
|
@@ -31,14 +29,11 @@ except ImportError:
|
|
31
29
|
|
32
30
|
from ddeutil.core import getdot, hasdot, hash_str, import_string, lazy
|
33
31
|
from ddeutil.io import search_env_replace
|
34
|
-
from pydantic import BaseModel
|
35
|
-
from pydantic.dataclasses import dataclass
|
36
|
-
from pydantic.functional_validators import model_validator
|
37
|
-
from typing_extensions import Self
|
32
|
+
from pydantic import BaseModel
|
38
33
|
|
39
34
|
from .__types import DictData, Matrix, Re
|
40
35
|
from .conf import config
|
41
|
-
from .exceptions import
|
36
|
+
from .exceptions import UtilException
|
42
37
|
|
43
38
|
T = TypeVar("T")
|
44
39
|
P = ParamSpec("P")
|
@@ -198,238 +193,6 @@ def make_registry(submodule: str) -> dict[str, Registry]:
|
|
198
193
|
return rs
|
199
194
|
|
200
195
|
|
201
|
-
class BaseParam(BaseModel, ABC):
|
202
|
-
"""Base Parameter that use to make Params Model."""
|
203
|
-
|
204
|
-
desc: Optional[str] = Field(
|
205
|
-
default=None, description="A description of parameter providing."
|
206
|
-
)
|
207
|
-
required: bool = Field(
|
208
|
-
default=True,
|
209
|
-
description="A require flag that force to pass this parameter value.",
|
210
|
-
)
|
211
|
-
type: str = Field(description="A type of parameter.")
|
212
|
-
|
213
|
-
@abstractmethod
|
214
|
-
def receive(self, value: Optional[Any] = None) -> Any:
|
215
|
-
raise NotImplementedError(
|
216
|
-
"Receive value and validate typing before return valid value."
|
217
|
-
)
|
218
|
-
|
219
|
-
|
220
|
-
class DefaultParam(BaseParam):
|
221
|
-
"""Default Parameter that will check default if it required. This model do
|
222
|
-
not implement the receive method.
|
223
|
-
"""
|
224
|
-
|
225
|
-
required: bool = Field(
|
226
|
-
default=False,
|
227
|
-
description="A require flag for the default-able parameter value.",
|
228
|
-
)
|
229
|
-
default: Optional[str] = Field(
|
230
|
-
default=None,
|
231
|
-
description="A default value if parameter does not pass.",
|
232
|
-
)
|
233
|
-
|
234
|
-
@abstractmethod
|
235
|
-
def receive(self, value: Optional[Any] = None) -> Any:
|
236
|
-
raise NotImplementedError(
|
237
|
-
"Receive value and validate typing before return valid value."
|
238
|
-
)
|
239
|
-
|
240
|
-
|
241
|
-
class DatetimeParam(DefaultParam):
|
242
|
-
"""Datetime parameter."""
|
243
|
-
|
244
|
-
type: Literal["datetime"] = "datetime"
|
245
|
-
default: datetime = Field(default_factory=get_dt_now)
|
246
|
-
|
247
|
-
def receive(self, value: str | datetime | date | None = None) -> datetime:
|
248
|
-
"""Receive value that match with datetime. If a input value pass with
|
249
|
-
None, it will use default value instead.
|
250
|
-
|
251
|
-
:param value: A value that want to validate with datetime parameter
|
252
|
-
type.
|
253
|
-
:rtype: datetime
|
254
|
-
"""
|
255
|
-
if value is None:
|
256
|
-
return self.default
|
257
|
-
|
258
|
-
if isinstance(value, datetime):
|
259
|
-
return value
|
260
|
-
elif isinstance(value, date):
|
261
|
-
return datetime(value.year, value.month, value.day)
|
262
|
-
elif not isinstance(value, str):
|
263
|
-
raise ParamValueException(
|
264
|
-
f"Value that want to convert to datetime does not support for "
|
265
|
-
f"type: {type(value)}"
|
266
|
-
)
|
267
|
-
try:
|
268
|
-
return datetime.fromisoformat(value)
|
269
|
-
except ValueError:
|
270
|
-
raise ParamValueException(
|
271
|
-
f"Invalid isoformat string: {value!r}"
|
272
|
-
) from None
|
273
|
-
|
274
|
-
|
275
|
-
class StrParam(DefaultParam):
|
276
|
-
"""String parameter."""
|
277
|
-
|
278
|
-
type: Literal["str"] = "str"
|
279
|
-
|
280
|
-
def receive(self, value: str | None = None) -> str | None:
|
281
|
-
"""Receive value that match with str.
|
282
|
-
|
283
|
-
:param value: A value that want to validate with string parameter type.
|
284
|
-
:rtype: str | None
|
285
|
-
"""
|
286
|
-
if value is None:
|
287
|
-
return self.default
|
288
|
-
return str(value)
|
289
|
-
|
290
|
-
|
291
|
-
class IntParam(DefaultParam):
|
292
|
-
"""Integer parameter."""
|
293
|
-
|
294
|
-
type: Literal["int"] = "int"
|
295
|
-
default: Optional[int] = Field(
|
296
|
-
default=None,
|
297
|
-
description="A default value if parameter does not pass.",
|
298
|
-
)
|
299
|
-
|
300
|
-
def receive(self, value: int | None = None) -> int | None:
|
301
|
-
"""Receive value that match with int.
|
302
|
-
|
303
|
-
:param value: A value that want to validate with integer parameter type.
|
304
|
-
:rtype: int | None
|
305
|
-
"""
|
306
|
-
if value is None:
|
307
|
-
return self.default
|
308
|
-
if not isinstance(value, int):
|
309
|
-
try:
|
310
|
-
return int(str(value))
|
311
|
-
except ValueError as err:
|
312
|
-
raise ParamValueException(
|
313
|
-
f"Value can not convert to int, {value}, with base 10"
|
314
|
-
) from err
|
315
|
-
return value
|
316
|
-
|
317
|
-
|
318
|
-
class ChoiceParam(BaseParam):
|
319
|
-
"""Choice parameter."""
|
320
|
-
|
321
|
-
type: Literal["choice"] = "choice"
|
322
|
-
options: list[str] = Field(description="A list of choice parameters.")
|
323
|
-
|
324
|
-
def receive(self, value: str | None = None) -> str:
|
325
|
-
"""Receive value that match with options.
|
326
|
-
|
327
|
-
:param value: A value that want to select from the options field.
|
328
|
-
:rtype: str
|
329
|
-
"""
|
330
|
-
# NOTE:
|
331
|
-
# Return the first value in options if does not pass any input value
|
332
|
-
if value is None:
|
333
|
-
return self.options[0]
|
334
|
-
if value not in self.options:
|
335
|
-
raise ParamValueException(
|
336
|
-
f"{value!r} does not match any value in choice options."
|
337
|
-
)
|
338
|
-
return value
|
339
|
-
|
340
|
-
|
341
|
-
Param = Union[
|
342
|
-
ChoiceParam,
|
343
|
-
DatetimeParam,
|
344
|
-
IntParam,
|
345
|
-
StrParam,
|
346
|
-
]
|
347
|
-
|
348
|
-
|
349
|
-
@dataclass
|
350
|
-
class Result:
|
351
|
-
"""Result Pydantic Model for passing and receiving data context from any
|
352
|
-
module execution process like stage execution, job execution, or workflow
|
353
|
-
execution.
|
354
|
-
|
355
|
-
For comparison property, this result will use ``status``, ``context``,
|
356
|
-
and ``_run_id`` fields to comparing with other result instance.
|
357
|
-
"""
|
358
|
-
|
359
|
-
status: int = field(default=2)
|
360
|
-
context: DictData = field(default_factory=dict)
|
361
|
-
|
362
|
-
# NOTE: Ignore this field to compare another result model with __eq__.
|
363
|
-
run_id: Optional[str] = field(default=None)
|
364
|
-
parent_run_id: Optional[str] = field(default=None, compare=False)
|
365
|
-
|
366
|
-
@model_validator(mode="after")
|
367
|
-
def __prepare_run_id(self) -> Self:
|
368
|
-
"""Prepare running ID which use default ID if it initialize at the first
|
369
|
-
time
|
370
|
-
|
371
|
-
:rtype: Self
|
372
|
-
"""
|
373
|
-
self._run_id = gen_id("manual", unique=True)
|
374
|
-
return self
|
375
|
-
|
376
|
-
def set_run_id(self, running_id: str) -> Self:
|
377
|
-
"""Set a running ID.
|
378
|
-
|
379
|
-
:param running_id: A running ID that want to update on this model.
|
380
|
-
:rtype: Self
|
381
|
-
"""
|
382
|
-
self.run_id = running_id
|
383
|
-
return self
|
384
|
-
|
385
|
-
def set_parent_run_id(self, running_id: str) -> Self:
|
386
|
-
"""Set a parent running ID.
|
387
|
-
|
388
|
-
:param running_id: A running ID that want to update on this model.
|
389
|
-
:rtype: Self
|
390
|
-
"""
|
391
|
-
self.parent_run_id: str = running_id
|
392
|
-
return self
|
393
|
-
|
394
|
-
def catch(self, status: int, context: DictData) -> Self:
|
395
|
-
"""Catch the status and context to current data."""
|
396
|
-
self.__dict__["status"] = status
|
397
|
-
self.__dict__["context"].update(context)
|
398
|
-
return self
|
399
|
-
|
400
|
-
def receive(self, result: Result) -> Self:
|
401
|
-
"""Receive context from another result object.
|
402
|
-
|
403
|
-
:rtype: Self
|
404
|
-
"""
|
405
|
-
self.__dict__["status"] = result.status
|
406
|
-
self.__dict__["context"].update(result.context)
|
407
|
-
|
408
|
-
# NOTE: Update running ID from an incoming result.
|
409
|
-
self.parent_run_id = result.parent_run_id
|
410
|
-
self.run_id = result.run_id
|
411
|
-
return self
|
412
|
-
|
413
|
-
def receive_jobs(self, result: Result) -> Self:
|
414
|
-
"""Receive context from another result object that use on the workflow
|
415
|
-
execution which create a ``jobs`` keys on the context if it do not
|
416
|
-
exist.
|
417
|
-
|
418
|
-
:rtype: Self
|
419
|
-
"""
|
420
|
-
self.__dict__["status"] = result.status
|
421
|
-
|
422
|
-
# NOTE: Check the context has jobs key.
|
423
|
-
if "jobs" not in self.__dict__["context"]:
|
424
|
-
self.__dict__["context"]["jobs"] = {}
|
425
|
-
self.__dict__["context"]["jobs"].update(result.context)
|
426
|
-
|
427
|
-
# NOTE: Update running ID from an incoming result.
|
428
|
-
self.parent_run_id: str = result.parent_run_id
|
429
|
-
self.run_id: str = result.run_id
|
430
|
-
return self
|
431
|
-
|
432
|
-
|
433
196
|
def make_exec(path: str | Path) -> None:
|
434
197
|
"""Change mode of file to be executable file.
|
435
198
|
|
@@ -814,10 +577,6 @@ def batch(iterable: Iterator[Any], n: int) -> Iterator[Any]:
|
|
814
577
|
yield chain((first_el,), chunk_it)
|
815
578
|
|
816
579
|
|
817
|
-
def queue2str(queue: list[datetime]) -> Iterator[str]: # pragma: no cov
|
818
|
-
return (f"{q:%Y-%m-%d %H:%M:%S}" for q in queue)
|
819
|
-
|
820
|
-
|
821
580
|
def cut_id(run_id: str, *, num: int = 6):
|
822
581
|
"""Cutting running ID with length.
|
823
582
|
|