ddeutil-workflow 0.0.41__py3-none-any.whl → 0.0.43__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 +5 -1
- ddeutil/workflow/api/api.py +7 -7
- ddeutil/workflow/api/routes/schedules.py +5 -5
- ddeutil/workflow/api/routes/workflows.py +2 -2
- ddeutil/workflow/conf.py +39 -28
- ddeutil/workflow/cron.py +12 -13
- ddeutil/workflow/exceptions.py +13 -3
- ddeutil/workflow/job.py +40 -42
- ddeutil/workflow/logs.py +33 -6
- ddeutil/workflow/params.py +77 -18
- ddeutil/workflow/result.py +36 -8
- ddeutil/workflow/reusables.py +16 -13
- ddeutil/workflow/scheduler.py +32 -37
- ddeutil/workflow/stages.py +285 -120
- ddeutil/workflow/utils.py +0 -1
- ddeutil/workflow/workflow.py +127 -90
- {ddeutil_workflow-0.0.41.dist-info → ddeutil_workflow-0.0.43.dist-info}/METADATA +29 -32
- ddeutil_workflow-0.0.43.dist-info/RECORD +30 -0
- ddeutil/workflow/context.py +0 -61
- ddeutil_workflow-0.0.41.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.41.dist-info → ddeutil_workflow-0.0.43.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.41.dist-info → ddeutil_workflow-0.0.43.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.41.dist-info → ddeutil_workflow-0.0.43.dist-info}/top_level.txt +0 -0
ddeutil/workflow/logs.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 fix config
|
6
7
|
"""A Logs module contain TraceLog dataclass and AuditLog model.
|
7
8
|
"""
|
8
9
|
from __future__ import annotations
|
@@ -160,7 +161,6 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
160
161
|
"""
|
161
162
|
msg: str = self.make_message(message)
|
162
163
|
|
163
|
-
# NOTE: Write file if debug mode was enabled.
|
164
164
|
if config.debug:
|
165
165
|
self.writer(msg)
|
166
166
|
|
@@ -206,6 +206,32 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
206
206
|
self.writer(msg, is_err=True)
|
207
207
|
logger.exception(msg, stacklevel=2)
|
208
208
|
|
209
|
+
async def adebug(self, message: str) -> None: # pragma: no cov
|
210
|
+
msg: str = self.make_message(message)
|
211
|
+
if config.debug:
|
212
|
+
await self.awriter(msg)
|
213
|
+
logger.info(msg, stacklevel=2)
|
214
|
+
|
215
|
+
async def ainfo(self, message: str) -> None: # pragma: no cov
|
216
|
+
msg: str = self.make_message(message)
|
217
|
+
await self.awriter(msg)
|
218
|
+
logger.info(msg, stacklevel=2)
|
219
|
+
|
220
|
+
async def awarning(self, message: str) -> None: # pragma: no cov
|
221
|
+
msg: str = self.make_message(message)
|
222
|
+
await self.awriter(msg)
|
223
|
+
logger.warning(msg, stacklevel=2)
|
224
|
+
|
225
|
+
async def aerror(self, message: str) -> None: # pragma: no cov
|
226
|
+
msg: str = self.make_message(message)
|
227
|
+
await self.awriter(msg, is_err=True)
|
228
|
+
logger.error(msg, stacklevel=2)
|
229
|
+
|
230
|
+
async def aexception(self, message: str) -> None: # pragma: no cov
|
231
|
+
msg: str = self.make_message(message)
|
232
|
+
await self.awriter(msg, is_err=True)
|
233
|
+
logger.exception(msg, stacklevel=2)
|
234
|
+
|
209
235
|
|
210
236
|
class FileTraceLog(BaseTraceLog): # pragma: no cov
|
211
237
|
"""Trace Log object that write file to the local storage."""
|
@@ -296,11 +322,13 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
296
322
|
async def awriter(
|
297
323
|
self, message: str, is_err: bool = False
|
298
324
|
) -> None: # pragma: no cov
|
299
|
-
"""TODO: Use `aiofiles` for make writer method support async."""
|
300
325
|
if not config.enable_write_log:
|
301
326
|
return
|
302
327
|
|
303
|
-
|
328
|
+
try:
|
329
|
+
import aiofiles
|
330
|
+
except ImportError as e:
|
331
|
+
raise ImportError("Async mode need aiofiles package") from e
|
304
332
|
|
305
333
|
write_file: str = "stderr" if is_err else "stdout"
|
306
334
|
trace_meta: TraceMeda = TraceMeda.make(mode=write_file, message=message)
|
@@ -468,9 +496,8 @@ class FileAudit(BaseAudit):
|
|
468
496
|
f"release={release:%Y%m%d%H%M%S} does not found."
|
469
497
|
)
|
470
498
|
|
471
|
-
|
472
|
-
|
473
|
-
) as f:
|
499
|
+
latest_file: Path = max(pointer.glob("./*.log"), key=os.path.getctime)
|
500
|
+
with latest_file.open(mode="r", encoding="utf-8") as f:
|
474
501
|
return cls.model_validate(obj=json.load(f))
|
475
502
|
|
476
503
|
@classmethod
|
ddeutil/workflow/params.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
# [ ] Use config
|
7
6
|
"""This module include all Param Pydantic Models that use for parsing an
|
8
7
|
incoming parameters that was passed to the Workflow and Schedule objects before
|
9
8
|
execution or release methods.
|
@@ -18,6 +17,7 @@ from abc import ABC, abstractmethod
|
|
18
17
|
from datetime import date, datetime
|
19
18
|
from typing import Annotated, Any, Literal, Optional, TypeVar, Union
|
20
19
|
|
20
|
+
from ddeutil.core import str2dict, str2list
|
21
21
|
from pydantic import BaseModel, Field
|
22
22
|
|
23
23
|
from .__types import TupleStr
|
@@ -31,6 +31,8 @@ __all__: TupleStr = (
|
|
31
31
|
"IntParam",
|
32
32
|
"Param",
|
33
33
|
"StrParam",
|
34
|
+
"ArrayParam",
|
35
|
+
"MapParam",
|
34
36
|
)
|
35
37
|
|
36
38
|
T = TypeVar("T")
|
@@ -42,7 +44,10 @@ class BaseParam(BaseModel, ABC):
|
|
42
44
|
"""
|
43
45
|
|
44
46
|
desc: Optional[str] = Field(
|
45
|
-
default=None,
|
47
|
+
default=None,
|
48
|
+
description=(
|
49
|
+
"A description of this parameter provide to the workflow model."
|
50
|
+
),
|
46
51
|
)
|
47
52
|
required: bool = Field(
|
48
53
|
default=True,
|
@@ -52,6 +57,7 @@ class BaseParam(BaseModel, ABC):
|
|
52
57
|
|
53
58
|
@abstractmethod
|
54
59
|
def receive(self, value: Optional[T] = None) -> T:
|
60
|
+
"""Abstract method receive value to this parameter model."""
|
55
61
|
raise NotImplementedError(
|
56
62
|
"Receive value and validate typing before return valid value."
|
57
63
|
)
|
@@ -66,13 +72,14 @@ class DefaultParam(BaseParam):
|
|
66
72
|
default=False,
|
67
73
|
description="A require flag for the default-able parameter value.",
|
68
74
|
)
|
69
|
-
default: Optional[
|
75
|
+
default: Optional[Any] = Field(
|
70
76
|
default=None,
|
71
77
|
description="A default value if parameter does not pass.",
|
72
78
|
)
|
73
79
|
|
74
80
|
@abstractmethod
|
75
81
|
def receive(self, value: Optional[Any] = None) -> Any:
|
82
|
+
"""Abstract method receive value to this parameter model."""
|
76
83
|
raise NotImplementedError(
|
77
84
|
"Receive value and validate typing before return valid value."
|
78
85
|
)
|
@@ -82,7 +89,10 @@ class DateParam(DefaultParam): # pragma: no cov
|
|
82
89
|
"""Date parameter model."""
|
83
90
|
|
84
91
|
type: Literal["date"] = "date"
|
85
|
-
default: date = Field(
|
92
|
+
default: date = Field(
|
93
|
+
default_factory=get_d_now,
|
94
|
+
description="A default date that make from the current date func.",
|
95
|
+
)
|
86
96
|
|
87
97
|
def receive(self, value: Optional[str | datetime | date] = None) -> date:
|
88
98
|
"""Receive value that match with date. If an input value pass with
|
@@ -116,7 +126,12 @@ class DatetimeParam(DefaultParam):
|
|
116
126
|
"""Datetime parameter model."""
|
117
127
|
|
118
128
|
type: Literal["datetime"] = "datetime"
|
119
|
-
default: datetime = Field(
|
129
|
+
default: datetime = Field(
|
130
|
+
default_factory=get_dt_now,
|
131
|
+
description=(
|
132
|
+
"A default datetime that make from the current datetime func."
|
133
|
+
),
|
134
|
+
)
|
120
135
|
|
121
136
|
def receive(self, value: str | datetime | date | None = None) -> datetime:
|
122
137
|
"""Receive value that match with datetime. If an input value pass with
|
@@ -167,10 +182,6 @@ class IntParam(DefaultParam):
|
|
167
182
|
"""Integer parameter."""
|
168
183
|
|
169
184
|
type: Literal["int"] = "int"
|
170
|
-
default: Optional[int] = Field(
|
171
|
-
default=None,
|
172
|
-
description="A default value if parameter does not pass.",
|
173
|
-
)
|
174
185
|
|
175
186
|
def receive(self, value: int | None = None) -> int | None:
|
176
187
|
"""Receive value that match with int.
|
@@ -222,36 +233,84 @@ class ChoiceParam(BaseParam):
|
|
222
233
|
return value
|
223
234
|
|
224
235
|
|
225
|
-
# TODO: Not implement this parameter yet
|
226
236
|
class MapParam(DefaultParam): # pragma: no cov
|
237
|
+
"""Map parameter."""
|
227
238
|
|
228
239
|
type: Literal["map"] = "map"
|
229
|
-
default: dict[Any, Any] = Field(
|
240
|
+
default: dict[Any, Any] = Field(
|
241
|
+
default_factory=dict,
|
242
|
+
description="A default dict that make from the dict built-in func.",
|
243
|
+
)
|
244
|
+
|
245
|
+
def receive(
|
246
|
+
self,
|
247
|
+
value: Optional[Union[dict[Any, Any], str]] = None,
|
248
|
+
) -> dict[Any, Any]:
|
249
|
+
"""Receive value that match with map type.
|
230
250
|
|
231
|
-
|
251
|
+
:param value: A value that want to validate with map parameter type.
|
252
|
+
:rtype: dict[Any, Any]
|
253
|
+
"""
|
232
254
|
if value is None:
|
233
255
|
return self.default
|
234
256
|
|
257
|
+
if isinstance(value, str):
|
258
|
+
try:
|
259
|
+
value: dict[Any, Any] = str2dict(value)
|
260
|
+
except ValueError as e:
|
261
|
+
raise ParamValueException(
|
262
|
+
f"Value that want to convert to map does not support for "
|
263
|
+
f"type: {type(value)}"
|
264
|
+
) from e
|
265
|
+
elif not isinstance(value, dict):
|
266
|
+
raise ParamValueException(
|
267
|
+
f"Value of map param support only string-dict or dict type, "
|
268
|
+
f"not {type(value)}"
|
269
|
+
)
|
270
|
+
return value
|
271
|
+
|
235
272
|
|
236
|
-
# TODO: Not implement this parameter yet
|
237
273
|
class ArrayParam(DefaultParam): # pragma: no cov
|
274
|
+
"""Array parameter."""
|
238
275
|
|
239
276
|
type: Literal["array"] = "array"
|
240
|
-
default: list[Any] = Field(
|
277
|
+
default: list[Any] = Field(
|
278
|
+
default_factory=list,
|
279
|
+
description="A default list that make from the list built-in func.",
|
280
|
+
)
|
241
281
|
|
242
|
-
def receive(
|
282
|
+
def receive(
|
283
|
+
self, value: Optional[Union[list[T], tuple[T, ...], str]] = None
|
284
|
+
) -> list[T]:
|
285
|
+
"""Receive value that match with array type.
|
286
|
+
|
287
|
+
:param value: A value that want to validate with array parameter type.
|
288
|
+
:rtype: list[Any]
|
289
|
+
"""
|
243
290
|
if value is None:
|
244
291
|
return self.default
|
245
|
-
if
|
292
|
+
if isinstance(value, str):
|
293
|
+
try:
|
294
|
+
value: list[T] = str2list(value)
|
295
|
+
except ValueError as e:
|
296
|
+
raise ParamValueException(
|
297
|
+
f"Value that want to convert to array does not support for "
|
298
|
+
f"type: {type(value)}"
|
299
|
+
) from e
|
300
|
+
elif isinstance(value, (tuple, set)):
|
301
|
+
return list(value)
|
302
|
+
elif not isinstance(value, list):
|
246
303
|
raise ParamValueException(
|
247
|
-
f"Value
|
248
|
-
f"
|
304
|
+
f"Value of map param support only string-list or list type, "
|
305
|
+
f"not {type(value)}"
|
249
306
|
)
|
250
307
|
return value
|
251
308
|
|
252
309
|
|
253
310
|
Param = Annotated[
|
254
311
|
Union[
|
312
|
+
MapParam,
|
313
|
+
ArrayParam,
|
255
314
|
ChoiceParam,
|
256
315
|
DatetimeParam,
|
257
316
|
DateParam,
|
ddeutil/workflow/result.py
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""
|
7
|
-
|
6
|
+
"""Result module. It is the data context transfer objects that use by all object
|
7
|
+
in this package. This module provide Status enum object and Result dataclass.
|
8
8
|
"""
|
9
9
|
from __future__ import annotations
|
10
10
|
|
@@ -23,13 +23,19 @@ from .logs import TraceLog, get_dt_tznow, get_trace
|
|
23
23
|
from .utils import default_gen_id, gen_id
|
24
24
|
|
25
25
|
__all__: TupleStr = (
|
26
|
+
"SUCCESS",
|
27
|
+
"FAILED",
|
28
|
+
"WAIT",
|
29
|
+
"SKIP",
|
26
30
|
"Result",
|
27
31
|
"Status",
|
28
32
|
)
|
29
33
|
|
30
34
|
|
31
35
|
class Status(IntEnum):
|
32
|
-
"""Status Int Enum object
|
36
|
+
"""Status Int Enum object that use for tracking execution status to the
|
37
|
+
Result dataclass object.
|
38
|
+
"""
|
33
39
|
|
34
40
|
SUCCESS: int = 0
|
35
41
|
FAILED: int = 1
|
@@ -37,8 +43,17 @@ class Status(IntEnum):
|
|
37
43
|
SKIP: int = 3
|
38
44
|
|
39
45
|
|
46
|
+
SUCCESS = Status.SUCCESS
|
47
|
+
FAILED = Status.FAILED
|
48
|
+
WAIT = Status.WAIT
|
49
|
+
SKIP = Status.SKIP
|
50
|
+
|
51
|
+
|
40
52
|
@dataclass(
|
41
|
-
config=ConfigDict(
|
53
|
+
config=ConfigDict(
|
54
|
+
arbitrary_types_allowed=True,
|
55
|
+
use_enum_values=True,
|
56
|
+
),
|
42
57
|
)
|
43
58
|
class Result:
|
44
59
|
"""Result Pydantic Model for passing and receiving data context from any
|
@@ -49,8 +64,9 @@ class Result:
|
|
49
64
|
and ``_run_id`` fields to comparing with other result instance.
|
50
65
|
"""
|
51
66
|
|
52
|
-
status: Status = field(default=
|
67
|
+
status: Status = field(default=WAIT)
|
53
68
|
context: DictData = field(default_factory=dict)
|
69
|
+
errors: DictData = field(default_factory=dict)
|
54
70
|
run_id: Optional[str] = field(default_factory=default_gen_id)
|
55
71
|
parent_run_id: Optional[str] = field(default=None, compare=False)
|
56
72
|
ts: datetime = field(default_factory=get_dt_tznow, compare=False)
|
@@ -64,9 +80,16 @@ class Result:
|
|
64
80
|
run_id: str | None = None,
|
65
81
|
parent_run_id: str | None = None,
|
66
82
|
id_logic: str | None = None,
|
67
|
-
) -> Self:
|
83
|
+
) -> Self:
|
68
84
|
"""Create the Result object or set parent running id if passing Result
|
69
85
|
object.
|
86
|
+
|
87
|
+
:param result:
|
88
|
+
:param run_id:
|
89
|
+
:param parent_run_id:
|
90
|
+
:param id_logic:
|
91
|
+
|
92
|
+
:rtype: Self
|
70
93
|
"""
|
71
94
|
if result is None:
|
72
95
|
result: Result = cls(
|
@@ -101,18 +124,23 @@ class Result:
|
|
101
124
|
self,
|
102
125
|
status: int | Status,
|
103
126
|
context: DictData | None = None,
|
127
|
+
error: DictData | None = None,
|
104
128
|
) -> Self:
|
105
129
|
"""Catch the status and context to this Result object. This method will
|
106
130
|
use between a child execution return a result, and it wants to pass
|
107
131
|
status and context to this object.
|
108
132
|
|
109
|
-
:param status:
|
110
|
-
:param context:
|
133
|
+
:param status: A status enum object.
|
134
|
+
:param context: A context data that will update to the current context.
|
135
|
+
:param error: An error data that will update to the current errors.
|
136
|
+
|
137
|
+
:rtype: Self
|
111
138
|
"""
|
112
139
|
self.__dict__["status"] = (
|
113
140
|
Status(status) if isinstance(status, int) else status
|
114
141
|
)
|
115
142
|
self.__dict__["context"].update(context or {})
|
143
|
+
self.__dict__["errors"].update(error or {})
|
116
144
|
return self
|
117
145
|
|
118
146
|
def alive_time(self) -> float: # pragma: no cov
|
ddeutil/workflow/reusables.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
|
-
# [x] Use config
|
6
|
+
# [x] Use dynamic config
|
7
7
|
"""Reusables module that keep any templating functions."""
|
8
8
|
from __future__ import annotations
|
9
9
|
|
@@ -25,7 +25,7 @@ from ddeutil.core import getdot, import_string, lazy
|
|
25
25
|
from ddeutil.io import search_env_replace
|
26
26
|
|
27
27
|
from .__types import DictData, Re
|
28
|
-
from .conf import
|
28
|
+
from .conf import dynamic
|
29
29
|
from .exceptions import UtilException
|
30
30
|
|
31
31
|
T = TypeVar("T")
|
@@ -91,7 +91,7 @@ def make_filter_registry(
|
|
91
91
|
:rtype: dict[str, FilterRegistry]
|
92
92
|
"""
|
93
93
|
rs: dict[str, FilterRegistry] = {}
|
94
|
-
for module in registers
|
94
|
+
for module in dynamic("regis_filter", f=registers):
|
95
95
|
# NOTE: try to sequential import task functions
|
96
96
|
try:
|
97
97
|
importer = import_module(module)
|
@@ -277,7 +277,7 @@ def str2template(
|
|
277
277
|
:rtype: str
|
278
278
|
"""
|
279
279
|
filters: dict[str, FilterRegistry] = filters or make_filter_registry(
|
280
|
-
registers
|
280
|
+
registers=registers
|
281
281
|
)
|
282
282
|
|
283
283
|
# NOTE: remove space before and after this string value.
|
@@ -328,7 +328,7 @@ def param2template(
|
|
328
328
|
params: DictData,
|
329
329
|
filters: dict[str, FilterRegistry] | None = None,
|
330
330
|
*,
|
331
|
-
|
331
|
+
extras: Optional[DictData] = None,
|
332
332
|
) -> T:
|
333
333
|
"""Pass param to template string that can search by ``RE_CALLER`` regular
|
334
334
|
expression.
|
@@ -337,25 +337,25 @@ def param2template(
|
|
337
337
|
:param params: A parameter value that getting with matched regular
|
338
338
|
expression.
|
339
339
|
:param filters: A filter mapping for mapping with `map_post_filter` func.
|
340
|
-
:param
|
340
|
+
:param extras: (Optional[list[str]]) An Override extras.
|
341
341
|
|
342
342
|
:rtype: T
|
343
343
|
:returns: An any getter value from the params input.
|
344
344
|
"""
|
345
|
+
registers: Optional[list[str]] = (
|
346
|
+
extras.get("regis_filter") if extras else None
|
347
|
+
)
|
345
348
|
filters: dict[str, FilterRegistry] = filters or make_filter_registry(
|
346
|
-
registers
|
349
|
+
registers=registers
|
347
350
|
)
|
348
351
|
if isinstance(value, dict):
|
349
352
|
return {
|
350
|
-
k: param2template(value[k], params, filters,
|
353
|
+
k: param2template(value[k], params, filters, extras=extras)
|
351
354
|
for k in value
|
352
355
|
}
|
353
356
|
elif isinstance(value, (list, tuple, set)):
|
354
357
|
return type(value)(
|
355
|
-
[
|
356
|
-
param2template(i, params, filters, registers=registers)
|
357
|
-
for i in value
|
358
|
-
]
|
358
|
+
[param2template(i, params, filters, extras=extras) for i in value]
|
359
359
|
)
|
360
360
|
elif not isinstance(value, str):
|
361
361
|
return value
|
@@ -447,8 +447,11 @@ def make_registry(
|
|
447
447
|
:rtype: dict[str, Registry]
|
448
448
|
"""
|
449
449
|
rs: dict[str, Registry] = {}
|
450
|
-
regis_calls: list[str] =
|
450
|
+
regis_calls: list[str] = dynamic(
|
451
|
+
"regis_call", f=registries
|
452
|
+
) # pragma: no cov
|
451
453
|
regis_calls.extend(["ddeutil.vendors"])
|
454
|
+
|
452
455
|
for module in regis_calls:
|
453
456
|
# NOTE: try to sequential import task functions
|
454
457
|
try:
|
ddeutil/workflow/scheduler.py
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
|
7
|
-
The main schedule running is `schedule_runner` function that trigger the
|
6
|
+
# [x] Use fix config
|
7
|
+
"""The main schedule running is `schedule_runner` function that trigger the
|
8
8
|
multiprocess of `schedule_control` function for listing schedules on the
|
9
9
|
config by `Loader.finds(Schedule)`.
|
10
10
|
|
@@ -56,14 +56,13 @@ from .conf import Loader, SimLoad, config, get_logger
|
|
56
56
|
from .cron import On
|
57
57
|
from .exceptions import ScheduleException, WorkflowException
|
58
58
|
from .logs import Audit, get_audit
|
59
|
-
from .result import
|
59
|
+
from .result import SUCCESS, Result
|
60
60
|
from .utils import batch, delay
|
61
61
|
from .workflow import Release, ReleaseQueue, Workflow, WorkflowTask
|
62
62
|
|
63
63
|
P = ParamSpec("P")
|
64
|
-
logger = get_logger("ddeutil.workflow")
|
65
64
|
|
66
|
-
|
65
|
+
logger = get_logger("ddeutil.workflow")
|
67
66
|
logging.getLogger("schedule").setLevel(logging.INFO)
|
68
67
|
|
69
68
|
|
@@ -86,9 +85,9 @@ class ScheduleWorkflow(BaseModel):
|
|
86
85
|
model.
|
87
86
|
|
88
87
|
This on field does not equal to the on field of Workflow model, but it
|
89
|
-
uses same logic to generate running release date with crontab object. It
|
90
|
-
for override the on field if the schedule time was change but you do
|
91
|
-
want to change on the workflow model.
|
88
|
+
uses same logic to generate running release date with crontab object. It
|
89
|
+
uses for override the on field if the schedule time was change, but you do
|
90
|
+
not want to change on the workflow model.
|
92
91
|
"""
|
93
92
|
|
94
93
|
alias: Optional[str] = Field(
|
@@ -177,7 +176,7 @@ class ScheduleWorkflow(BaseModel):
|
|
177
176
|
start_date: datetime,
|
178
177
|
queue: dict[str, ReleaseQueue],
|
179
178
|
*,
|
180
|
-
|
179
|
+
extras: DictData | None = None,
|
181
180
|
) -> list[WorkflowTask]:
|
182
181
|
"""Return the list of WorkflowTask object from the specific input
|
183
182
|
datetime that mapping with the on field.
|
@@ -187,17 +186,17 @@ class ScheduleWorkflow(BaseModel):
|
|
187
186
|
|
188
187
|
:param start_date: A start date that get from the workflow schedule.
|
189
188
|
:param queue: A mapping of name and list of datetime for queue.
|
190
|
-
:param
|
189
|
+
:param extras: An extra parameters that pass to the Loader object.
|
191
190
|
|
192
191
|
:rtype: list[WorkflowTask]
|
193
192
|
:return: Return the list of WorkflowTask object from the specific
|
194
193
|
input datetime that mapping with the on field.
|
195
194
|
"""
|
196
195
|
workflow_tasks: list[WorkflowTask] = []
|
197
|
-
extras: DictData =
|
196
|
+
extras: DictData = extras or {}
|
198
197
|
|
199
198
|
# NOTE: Loading workflow model from the name of workflow.
|
200
|
-
wf: Workflow = Workflow.
|
199
|
+
wf: Workflow = Workflow.from_conf(self.name, extras=extras)
|
201
200
|
wf_queue: ReleaseQueue = queue[self.alias]
|
202
201
|
|
203
202
|
# IMPORTANT: Create the default 'on' value if it does not pass the `on`
|
@@ -254,24 +253,24 @@ class Schedule(BaseModel):
|
|
254
253
|
return dedent(value)
|
255
254
|
|
256
255
|
@classmethod
|
257
|
-
def
|
256
|
+
def from_conf(
|
258
257
|
cls,
|
259
258
|
name: str,
|
260
|
-
|
259
|
+
extras: DictData | None = None,
|
261
260
|
) -> Self:
|
262
261
|
"""Create Schedule instance from the Loader object that only receive
|
263
262
|
an input schedule name. The loader object will use this schedule name to
|
264
263
|
searching configuration data of this schedule model in conf path.
|
265
264
|
|
266
265
|
:param name: (str) A schedule name that want to pass to Loader object.
|
267
|
-
:param
|
266
|
+
:param extras: An extra parameters that want to pass to Loader
|
268
267
|
object.
|
269
268
|
|
270
269
|
:raise ValueError: If the type does not match with current object.
|
271
270
|
|
272
271
|
:rtype: Self
|
273
272
|
"""
|
274
|
-
loader: Loader = Loader(name, externals=(
|
273
|
+
loader: Loader = Loader(name, externals=(extras or {}))
|
275
274
|
|
276
275
|
# NOTE: Validate the config type match with current connection model
|
277
276
|
if loader.type != cls.__name__:
|
@@ -325,16 +324,16 @@ class Schedule(BaseModel):
|
|
325
324
|
start_date: datetime,
|
326
325
|
queue: dict[str, ReleaseQueue],
|
327
326
|
*,
|
328
|
-
|
327
|
+
extras: DictData | None = None,
|
329
328
|
) -> list[WorkflowTask]:
|
330
329
|
"""Return the list of WorkflowTask object from the specific input
|
331
330
|
datetime that mapping with the on field from workflow schedule model.
|
332
331
|
|
333
332
|
:param start_date: A start date that get from the workflow schedule.
|
334
|
-
:param queue: A mapping of name and list of
|
335
|
-
|
336
|
-
:param
|
337
|
-
|
333
|
+
:param queue: (dict[str, ReleaseQueue]) A mapping of name and list of
|
334
|
+
datetime for queue.
|
335
|
+
:param extras: (DictData) An extra parameters that pass to the Loader
|
336
|
+
object.
|
338
337
|
|
339
338
|
:rtype: list[WorkflowTask]
|
340
339
|
:return: Return the list of WorkflowTask object from the specific
|
@@ -348,7 +347,7 @@ class Schedule(BaseModel):
|
|
348
347
|
queue[workflow.alias] = ReleaseQueue()
|
349
348
|
|
350
349
|
workflow_tasks.extend(
|
351
|
-
workflow.tasks(start_date, queue=queue,
|
350
|
+
workflow.tasks(start_date, queue=queue, extras=extras)
|
352
351
|
)
|
353
352
|
|
354
353
|
return workflow_tasks
|
@@ -357,14 +356,14 @@ class Schedule(BaseModel):
|
|
357
356
|
self,
|
358
357
|
*,
|
359
358
|
stop: datetime | None = None,
|
360
|
-
|
359
|
+
extras: DictData | None = None,
|
361
360
|
audit: type[Audit] | None = None,
|
362
361
|
parent_run_id: str | None = None,
|
363
362
|
) -> Result: # pragma: no cov
|
364
363
|
"""Pending this schedule tasks with the schedule package.
|
365
364
|
|
366
365
|
:param stop: A datetime value that use to stop running schedule.
|
367
|
-
:param
|
366
|
+
:param extras: An extra parameters that pass to Loader.
|
368
367
|
:param audit: An audit class that use on the workflow task release for
|
369
368
|
writing its release audit context.
|
370
369
|
:param parent_run_id: A parent workflow running ID for this release.
|
@@ -385,9 +384,7 @@ class Schedule(BaseModel):
|
|
385
384
|
) + timedelta(minutes=1)
|
386
385
|
|
387
386
|
scheduler_pending(
|
388
|
-
tasks=self.tasks(
|
389
|
-
start_date_waiting, queue=queue, externals=externals
|
390
|
-
),
|
387
|
+
tasks=self.tasks(start_date_waiting, queue=queue, extras=extras),
|
391
388
|
stop=stop_date,
|
392
389
|
queue=queue,
|
393
390
|
threads=threads,
|
@@ -395,7 +392,7 @@ class Schedule(BaseModel):
|
|
395
392
|
audit=audit,
|
396
393
|
)
|
397
394
|
|
398
|
-
return result.catch(status=
|
395
|
+
return result.catch(status=SUCCESS)
|
399
396
|
|
400
397
|
|
401
398
|
ResultOrCancel = Union[type[CancelJob], Result]
|
@@ -574,9 +571,7 @@ def schedule_task(
|
|
574
571
|
f"[SCHEDULE]: End schedule task that run since "
|
575
572
|
f"{current_date:%Y-%m-%d %H:%M:%S} {'=' * 30}"
|
576
573
|
)
|
577
|
-
return result.catch(
|
578
|
-
status=Status.SUCCESS, context={"task_date": current_date}
|
579
|
-
)
|
574
|
+
return result.catch(status=SUCCESS, context={"task_date": current_date})
|
580
575
|
|
581
576
|
|
582
577
|
def monitor(
|
@@ -692,7 +687,7 @@ def scheduler_pending(
|
|
692
687
|
f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
|
693
688
|
)
|
694
689
|
return result.catch(
|
695
|
-
status=
|
690
|
+
status=SUCCESS,
|
696
691
|
context={
|
697
692
|
"threads": [
|
698
693
|
{
|
@@ -709,7 +704,7 @@ def scheduler_pending(
|
|
709
704
|
def schedule_control(
|
710
705
|
schedules: list[str],
|
711
706
|
stop: datetime | None = None,
|
712
|
-
|
707
|
+
extras: DictData | None = None,
|
713
708
|
*,
|
714
709
|
audit: type[Audit] | None = None,
|
715
710
|
parent_run_id: str | None = None,
|
@@ -720,7 +715,7 @@ def schedule_control(
|
|
720
715
|
|
721
716
|
:param schedules: A list of workflow names that want to schedule running.
|
722
717
|
:param stop: A datetime value that use to stop running schedule.
|
723
|
-
:param
|
718
|
+
:param extras: An extra parameters that pass to Loader.
|
724
719
|
:param audit: An audit class that use on the workflow task release for
|
725
720
|
writing its release audit context.
|
726
721
|
:param parent_run_id: A parent workflow running ID for this release.
|
@@ -745,10 +740,10 @@ def schedule_control(
|
|
745
740
|
tasks: list[WorkflowTask] = []
|
746
741
|
for name in schedules:
|
747
742
|
tasks.extend(
|
748
|
-
Schedule.
|
743
|
+
Schedule.from_conf(name, extras=extras).tasks(
|
749
744
|
start_date_waiting,
|
750
745
|
queue=queue,
|
751
|
-
|
746
|
+
extras=extras,
|
752
747
|
),
|
753
748
|
)
|
754
749
|
|
@@ -761,7 +756,7 @@ def schedule_control(
|
|
761
756
|
audit=audit,
|
762
757
|
)
|
763
758
|
|
764
|
-
return result.catch(status=
|
759
|
+
return result.catch(status=SUCCESS, context={"schedules": schedules})
|
765
760
|
|
766
761
|
|
767
762
|
def schedule_runner(
|