ddeutil-workflow 0.0.59__py3-none-any.whl → 0.0.61__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/__types.py +9 -2
- ddeutil/workflow/event.py +27 -12
- ddeutil/workflow/exceptions.py +4 -3
- ddeutil/workflow/job.py +16 -16
- ddeutil/workflow/logs.py +100 -28
- ddeutil/workflow/params.py +54 -21
- ddeutil/workflow/result.py +1 -1
- ddeutil/workflow/reusables.py +2 -0
- ddeutil/workflow/stages.py +63 -33
- ddeutil/workflow/utils.py +28 -5
- ddeutil/workflow/workflow.py +201 -204
- {ddeutil_workflow-0.0.59.dist-info → ddeutil_workflow-0.0.61.dist-info}/METADATA +3 -1
- ddeutil_workflow-0.0.61.dist-info/RECORD +31 -0
- {ddeutil_workflow-0.0.59.dist-info → ddeutil_workflow-0.0.61.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.59.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.59.dist-info → ddeutil_workflow-0.0.61.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.59.dist-info → ddeutil_workflow-0.0.61.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.59.dist-info → ddeutil_workflow-0.0.61.dist-info}/top_level.txt +0 -0
ddeutil/workflow/params.py
CHANGED
@@ -20,6 +20,7 @@ from typing import Annotated, Any, Literal, Optional, TypeVar, Union
|
|
20
20
|
from ddeutil.core import str2dict, str2list
|
21
21
|
from pydantic import BaseModel, Field
|
22
22
|
|
23
|
+
from .__types import StrOrInt
|
23
24
|
from .exceptions import ParamValueException
|
24
25
|
from .utils import get_d_now, get_dt_now
|
25
26
|
|
@@ -159,10 +160,11 @@ class StrParam(DefaultParam):
|
|
159
160
|
|
160
161
|
type: Literal["str"] = "str"
|
161
162
|
|
162
|
-
def receive(self, value: Optional[
|
163
|
+
def receive(self, value: Optional[Any] = None) -> Optional[str]:
|
163
164
|
"""Receive value that match with str.
|
164
165
|
|
165
|
-
:param value: A value that want to validate with string parameter
|
166
|
+
:param value: (Any) A value that want to validate with string parameter
|
167
|
+
type.
|
166
168
|
:rtype: Optional[str]
|
167
169
|
"""
|
168
170
|
if value is None:
|
@@ -175,7 +177,7 @@ class IntParam(DefaultParam):
|
|
175
177
|
|
176
178
|
type: Literal["int"] = "int"
|
177
179
|
|
178
|
-
def receive(self, value: Optional[
|
180
|
+
def receive(self, value: Optional[StrOrInt] = None) -> Optional[int]:
|
179
181
|
"""Receive value that match with int.
|
180
182
|
|
181
183
|
:param value: A value that want to validate with integer parameter type.
|
@@ -200,13 +202,24 @@ class FloatParam(DefaultParam): # pragma: no cov
|
|
200
202
|
precision: int = 6
|
201
203
|
|
202
204
|
def rounding(self, value: float) -> float:
|
203
|
-
"""Rounding float value with the specific precision field.
|
205
|
+
"""Rounding float value with the specific precision field.
|
206
|
+
|
207
|
+
:param value: A float value that want to round with the precision value.
|
208
|
+
|
209
|
+
:rtype: float
|
210
|
+
"""
|
204
211
|
round_str: str = f"{{0:.{self.precision}f}}"
|
205
212
|
return float(round_str.format(round(value, self.precision)))
|
206
213
|
|
207
|
-
def receive(
|
214
|
+
def receive(
|
215
|
+
self, value: Optional[Union[float, int, str]] = None
|
216
|
+
) -> Optional[float]:
|
217
|
+
"""Receive value that match with float.
|
208
218
|
|
209
|
-
|
219
|
+
:param value: A value that want to validate with float parameter type.
|
220
|
+
:rtype: float | None
|
221
|
+
"""
|
222
|
+
if value is None:
|
210
223
|
return self.default
|
211
224
|
|
212
225
|
if isinstance(value, float):
|
@@ -217,11 +230,7 @@ class FloatParam(DefaultParam): # pragma: no cov
|
|
217
230
|
raise TypeError(
|
218
231
|
"Received value type does not math with str, float, or int."
|
219
232
|
)
|
220
|
-
|
221
|
-
try:
|
222
|
-
return self.rounding(float(value))
|
223
|
-
except Exception:
|
224
|
-
raise
|
233
|
+
return self.rounding(float(value))
|
225
234
|
|
226
235
|
|
227
236
|
class DecimalParam(DefaultParam): # pragma: no cov
|
@@ -231,12 +240,28 @@ class DecimalParam(DefaultParam): # pragma: no cov
|
|
231
240
|
precision: int = 6
|
232
241
|
|
233
242
|
def rounding(self, value: Decimal) -> Decimal:
|
234
|
-
"""Rounding float value with the specific precision field.
|
243
|
+
"""Rounding float value with the specific precision field.
|
244
|
+
|
245
|
+
:param value: (Decimal) A Decimal value that want to round with the
|
246
|
+
precision value.
|
247
|
+
|
248
|
+
:rtype: Decimal
|
249
|
+
"""
|
235
250
|
return value.quantize(Decimal(10) ** -self.precision)
|
236
251
|
|
237
|
-
def receive(
|
252
|
+
def receive(
|
253
|
+
self, value: Optional[Union[float, int, str, Decimal]] = None
|
254
|
+
) -> Decimal:
|
255
|
+
"""Receive value that match with decimal.
|
238
256
|
|
239
|
-
|
257
|
+
:param value: (float | Decimal) A value that want to validate with
|
258
|
+
decimal parameter type.
|
259
|
+
:rtype: Decimal | None
|
260
|
+
"""
|
261
|
+
if value is None:
|
262
|
+
return self.default
|
263
|
+
|
264
|
+
if isinstance(value, (float, int)):
|
240
265
|
return self.rounding(Decimal(value))
|
241
266
|
elif isinstance(value, Decimal):
|
242
267
|
return self.rounding(value)
|
@@ -261,11 +286,12 @@ class ChoiceParam(BaseParam):
|
|
261
286
|
description="A list of choice parameters that able be str or int.",
|
262
287
|
)
|
263
288
|
|
264
|
-
def receive(self, value:
|
289
|
+
def receive(self, value: Optional[StrOrInt] = None) -> StrOrInt:
|
265
290
|
"""Receive value that match with options.
|
266
291
|
|
267
|
-
:param value: A value that want to select from the options
|
268
|
-
|
292
|
+
:param value: (str | int) A value that want to select from the options
|
293
|
+
field.
|
294
|
+
:rtype: str | int
|
269
295
|
"""
|
270
296
|
# NOTE:
|
271
297
|
# Return the first value in options if it does not pass any input
|
@@ -279,7 +305,7 @@ class ChoiceParam(BaseParam):
|
|
279
305
|
return value
|
280
306
|
|
281
307
|
|
282
|
-
class MapParam(DefaultParam):
|
308
|
+
class MapParam(DefaultParam):
|
283
309
|
"""Map parameter."""
|
284
310
|
|
285
311
|
type: Literal["map"] = "map"
|
@@ -295,6 +321,7 @@ class MapParam(DefaultParam): # pragma: no cov
|
|
295
321
|
"""Receive value that match with map type.
|
296
322
|
|
297
323
|
:param value: A value that want to validate with map parameter type.
|
324
|
+
|
298
325
|
:rtype: dict[Any, Any]
|
299
326
|
"""
|
300
327
|
if value is None:
|
@@ -316,7 +343,7 @@ class MapParam(DefaultParam): # pragma: no cov
|
|
316
343
|
return value
|
317
344
|
|
318
345
|
|
319
|
-
class ArrayParam(DefaultParam):
|
346
|
+
class ArrayParam(DefaultParam):
|
320
347
|
"""Array parameter."""
|
321
348
|
|
322
349
|
type: Literal["array"] = "array"
|
@@ -326,7 +353,7 @@ class ArrayParam(DefaultParam): # pragma: no cov
|
|
326
353
|
)
|
327
354
|
|
328
355
|
def receive(
|
329
|
-
self, value: Optional[Union[list[T], tuple[T, ...], str]] = None
|
356
|
+
self, value: Optional[Union[list[T], tuple[T, ...], set[T], str]] = None
|
330
357
|
) -> list[T]:
|
331
358
|
"""Receive value that match with array type.
|
332
359
|
|
@@ -365,5 +392,11 @@ Param = Annotated[
|
|
365
392
|
IntParam,
|
366
393
|
StrParam,
|
367
394
|
],
|
368
|
-
Field(
|
395
|
+
Field(
|
396
|
+
discriminator="type",
|
397
|
+
description=(
|
398
|
+
"A parameter models that use for validate and receive on the "
|
399
|
+
"workflow execution."
|
400
|
+
),
|
401
|
+
),
|
369
402
|
]
|
ddeutil/workflow/result.py
CHANGED
ddeutil/workflow/reusables.py
CHANGED
ddeutil/workflow/stages.py
CHANGED
@@ -35,6 +35,7 @@ import json
|
|
35
35
|
import subprocess
|
36
36
|
import sys
|
37
37
|
import time
|
38
|
+
import traceback
|
38
39
|
import uuid
|
39
40
|
from abc import ABC, abstractmethod
|
40
41
|
from collections.abc import AsyncIterator, Iterator
|
@@ -58,19 +59,21 @@ from pydantic import BaseModel, Field
|
|
58
59
|
from pydantic.functional_validators import model_validator
|
59
60
|
from typing_extensions import Self
|
60
61
|
|
61
|
-
from .__types import DictData, DictStr, StrOrInt, TupleStr
|
62
|
+
from .__types import DictData, DictStr, StrOrInt, StrOrNone, TupleStr
|
62
63
|
from .conf import dynamic
|
63
64
|
from .exceptions import StageException, to_dict
|
64
65
|
from .result import CANCEL, FAILED, SUCCESS, WAIT, Result, Status
|
65
66
|
from .reusables import TagFunc, extract_call, not_in_template, param2template
|
66
67
|
from .utils import (
|
67
68
|
delay,
|
69
|
+
dump_all,
|
68
70
|
filter_func,
|
69
71
|
gen_id,
|
70
72
|
make_exec,
|
71
73
|
)
|
72
74
|
|
73
75
|
T = TypeVar("T")
|
76
|
+
DictOrModel = Union[DictData, BaseModel]
|
74
77
|
|
75
78
|
|
76
79
|
class BaseStage(BaseModel, ABC):
|
@@ -87,7 +90,7 @@ class BaseStage(BaseModel, ABC):
|
|
87
90
|
default_factory=dict,
|
88
91
|
description="An extra parameter that override core config values.",
|
89
92
|
)
|
90
|
-
id:
|
93
|
+
id: StrOrNone = Field(
|
91
94
|
default=None,
|
92
95
|
description=(
|
93
96
|
"A stage ID that use to keep execution output or getting by job "
|
@@ -97,7 +100,7 @@ class BaseStage(BaseModel, ABC):
|
|
97
100
|
name: str = Field(
|
98
101
|
description="A stage name that want to logging when start execution.",
|
99
102
|
)
|
100
|
-
condition:
|
103
|
+
condition: StrOrNone = Field(
|
101
104
|
default=None,
|
102
105
|
description=(
|
103
106
|
"A stage condition statement to allow stage executable. This field "
|
@@ -162,8 +165,8 @@ class BaseStage(BaseModel, ABC):
|
|
162
165
|
self,
|
163
166
|
params: DictData,
|
164
167
|
*,
|
165
|
-
run_id:
|
166
|
-
parent_run_id:
|
168
|
+
run_id: StrOrNone = None,
|
169
|
+
parent_run_id: StrOrNone = None,
|
167
170
|
result: Optional[Result] = None,
|
168
171
|
event: Optional[Event] = None,
|
169
172
|
raise_error: Optional[bool] = None,
|
@@ -221,7 +224,10 @@ class BaseStage(BaseModel, ABC):
|
|
221
224
|
return self.execute(params, result=result, event=event)
|
222
225
|
except Exception as e:
|
223
226
|
e_name: str = e.__class__.__name__
|
224
|
-
result.trace.error(
|
227
|
+
result.trace.error(
|
228
|
+
f"[STAGE]: Error Handler:||{e_name}:||{e}||"
|
229
|
+
f"{traceback.format_exc()}"
|
230
|
+
)
|
225
231
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
226
232
|
if isinstance(e, StageException):
|
227
233
|
raise
|
@@ -411,8 +417,8 @@ class BaseAsyncStage(BaseStage):
|
|
411
417
|
self,
|
412
418
|
params: DictData,
|
413
419
|
*,
|
414
|
-
run_id:
|
415
|
-
parent_run_id:
|
420
|
+
run_id: StrOrNone = None,
|
421
|
+
parent_run_id: StrOrNone = None,
|
416
422
|
result: Optional[Result] = None,
|
417
423
|
event: Optional[Event] = None,
|
418
424
|
raise_error: Optional[bool] = None,
|
@@ -469,7 +475,7 @@ class EmptyStage(BaseAsyncStage):
|
|
469
475
|
... }
|
470
476
|
"""
|
471
477
|
|
472
|
-
echo:
|
478
|
+
echo: StrOrNone = Field(
|
473
479
|
default=None,
|
474
480
|
description="A message that want to show on the stdout.",
|
475
481
|
)
|
@@ -598,14 +604,14 @@ class BashStage(BaseAsyncStage):
|
|
598
604
|
)
|
599
605
|
|
600
606
|
@contextlib.asynccontextmanager
|
601
|
-
async def
|
602
|
-
self, bash: str, env: DictStr, run_id:
|
607
|
+
async def async_create_sh_file(
|
608
|
+
self, bash: str, env: DictStr, run_id: StrOrNone = None
|
603
609
|
) -> AsyncIterator[TupleStr]:
|
604
610
|
"""Async create and write `.sh` file with the `aiofiles` package.
|
605
611
|
|
606
612
|
:param bash: (str) A bash statement.
|
607
613
|
:param env: (DictStr) An environment variable that set before run bash.
|
608
|
-
:param run_id: (
|
614
|
+
:param run_id: (StrOrNone) A running stage ID that use for writing sh
|
609
615
|
file instead generate by UUID4.
|
610
616
|
|
611
617
|
:rtype: AsyncIterator[TupleStr]
|
@@ -635,14 +641,14 @@ class BashStage(BaseAsyncStage):
|
|
635
641
|
|
636
642
|
@contextlib.contextmanager
|
637
643
|
def create_sh_file(
|
638
|
-
self, bash: str, env: DictStr, run_id:
|
644
|
+
self, bash: str, env: DictStr, run_id: StrOrNone = None
|
639
645
|
) -> Iterator[TupleStr]:
|
640
646
|
"""Create and write the `.sh` file before giving this file name to
|
641
647
|
context. After that, it will auto delete this file automatic.
|
642
648
|
|
643
649
|
:param bash: (str) A bash statement.
|
644
650
|
:param env: (DictStr) An environment variable that set before run bash.
|
645
|
-
:param run_id: (
|
651
|
+
:param run_id: (StrOrNone) A running stage ID that use for writing sh
|
646
652
|
file instead generate by UUID4.
|
647
653
|
|
648
654
|
:rtype: Iterator[TupleStr]
|
@@ -752,7 +758,7 @@ class BashStage(BaseAsyncStage):
|
|
752
758
|
dedent(self.bash.strip("\n")), params, extras=self.extras
|
753
759
|
)
|
754
760
|
|
755
|
-
async with self.
|
761
|
+
async with self.async_create_sh_file(
|
756
762
|
bash=bash,
|
757
763
|
env=param2template(self.env, params, extras=self.extras),
|
758
764
|
run_id=result.run_id,
|
@@ -1170,13 +1176,12 @@ class CallStage(BaseAsyncStage):
|
|
1170
1176
|
args.pop("result")
|
1171
1177
|
|
1172
1178
|
args = self.parse_model_args(call_func, args, result)
|
1173
|
-
|
1174
1179
|
if inspect.iscoroutinefunction(call_func):
|
1175
|
-
rs:
|
1180
|
+
rs: DictOrModel = await call_func(
|
1176
1181
|
**param2template(args, params, extras=self.extras)
|
1177
1182
|
)
|
1178
1183
|
else:
|
1179
|
-
rs:
|
1184
|
+
rs: DictOrModel = call_func(
|
1180
1185
|
**param2template(args, params, extras=self.extras)
|
1181
1186
|
)
|
1182
1187
|
|
@@ -1190,7 +1195,7 @@ class CallStage(BaseAsyncStage):
|
|
1190
1195
|
f"serialize, you must set return be `dict` or Pydantic "
|
1191
1196
|
f"model."
|
1192
1197
|
)
|
1193
|
-
return result.catch(status=SUCCESS, context=rs)
|
1198
|
+
return result.catch(status=SUCCESS, context=dump_all(rs, by_alias=True))
|
1194
1199
|
|
1195
1200
|
@staticmethod
|
1196
1201
|
def parse_model_args(
|
@@ -1294,11 +1299,12 @@ class TriggerStage(BaseStage):
|
|
1294
1299
|
extras=self.extras | {"stage_raise_error": True},
|
1295
1300
|
).execute(
|
1296
1301
|
params=param2template(self.params, params, extras=self.extras),
|
1297
|
-
|
1302
|
+
run_id=None,
|
1303
|
+
parent_run_id=result.parent_run_id,
|
1298
1304
|
event=event,
|
1299
1305
|
)
|
1300
1306
|
if rs.status == FAILED:
|
1301
|
-
err_msg:
|
1307
|
+
err_msg: StrOrNone = (
|
1302
1308
|
f" with:\n{msg}"
|
1303
1309
|
if (msg := rs.context.get("errors", {}).get("message"))
|
1304
1310
|
else "."
|
@@ -1826,7 +1832,10 @@ class UntilStage(BaseNestedStage):
|
|
1826
1832
|
... "stages": [
|
1827
1833
|
... {
|
1828
1834
|
... "name": "Start increase item value.",
|
1829
|
-
... "run":
|
1835
|
+
... "run": (
|
1836
|
+
... "item = ${{ item }}\\n"
|
1837
|
+
... "item += 1\\n"
|
1838
|
+
... )
|
1830
1839
|
... },
|
1831
1840
|
... ],
|
1832
1841
|
... }
|
@@ -2215,9 +2224,7 @@ class CaseStage(BaseNestedStage):
|
|
2215
2224
|
extras=self.extras,
|
2216
2225
|
)
|
2217
2226
|
|
2218
|
-
_case:
|
2219
|
-
self.case, params, extras=self.extras
|
2220
|
-
)
|
2227
|
+
_case: StrOrNone = param2template(self.case, params, extras=self.extras)
|
2221
2228
|
|
2222
2229
|
result.trace.info(f"[STAGE]: Execute Case-Stage: {_case!r}.")
|
2223
2230
|
_else: Optional[Match] = None
|
@@ -2396,8 +2403,14 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
2396
2403
|
|
2397
2404
|
:rtype: Result
|
2398
2405
|
"""
|
2399
|
-
|
2400
|
-
|
2406
|
+
try:
|
2407
|
+
from docker import DockerClient
|
2408
|
+
from docker.errors import ContainerError
|
2409
|
+
except ImportError:
|
2410
|
+
raise ImportError(
|
2411
|
+
"Docker stage need the docker package, you should install it "
|
2412
|
+
"by `pip install docker` first."
|
2413
|
+
) from None
|
2401
2414
|
|
2402
2415
|
client = DockerClient(
|
2403
2416
|
base_url="unix://var/run/docker.sock", version="auto"
|
@@ -2459,7 +2472,7 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
2459
2472
|
exit_status,
|
2460
2473
|
None,
|
2461
2474
|
f"{self.image}:{self.tag}",
|
2462
|
-
out,
|
2475
|
+
out.decode("utf-8"),
|
2463
2476
|
)
|
2464
2477
|
output_file: Path = Path(f".docker.{result.run_id}.logs/outputs.json")
|
2465
2478
|
if not output_file.exists():
|
@@ -2518,15 +2531,19 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
2518
2531
|
py: str,
|
2519
2532
|
values: DictData,
|
2520
2533
|
deps: list[str],
|
2521
|
-
run_id:
|
2534
|
+
run_id: StrOrNone = None,
|
2522
2535
|
) -> Iterator[str]:
|
2523
|
-
"""Create the
|
2536
|
+
"""Create the `.py` file and write an input Python statement and its
|
2537
|
+
Python dependency on the header of this file.
|
2538
|
+
|
2539
|
+
The format of Python dependency was followed by the `uv`
|
2540
|
+
recommended.
|
2524
2541
|
|
2525
2542
|
:param py: A Python string statement.
|
2526
2543
|
:param values: A variable that want to set before running this
|
2527
2544
|
:param deps: An additional Python dependencies that want install before
|
2528
2545
|
run this python stage.
|
2529
|
-
:param run_id: (
|
2546
|
+
:param run_id: (StrOrNone) A running ID of this stage execution.
|
2530
2547
|
"""
|
2531
2548
|
run_id: str = run_id or uuid.uuid4()
|
2532
2549
|
f_name: str = f"{run_id}.py"
|
@@ -2536,7 +2553,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
2536
2553
|
f"{var} = {value!r}" for var, value in values.items()
|
2537
2554
|
)
|
2538
2555
|
|
2539
|
-
# NOTE: uv supports PEP 723 — inline TOML metadata.
|
2556
|
+
# NOTE: `uv` supports PEP 723 — inline TOML metadata.
|
2540
2557
|
f.write(
|
2541
2558
|
dedent(
|
2542
2559
|
f"""
|
@@ -2595,6 +2612,16 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
2595
2612
|
run_id=result.run_id,
|
2596
2613
|
) as py:
|
2597
2614
|
result.trace.debug(f"[STAGE]: ... Create `{py}` file.")
|
2615
|
+
try:
|
2616
|
+
import uv
|
2617
|
+
|
2618
|
+
_ = uv
|
2619
|
+
except ImportError:
|
2620
|
+
raise ImportError(
|
2621
|
+
"The VirtualPyStage need you to install `uv` before"
|
2622
|
+
"execution."
|
2623
|
+
) from None
|
2624
|
+
|
2598
2625
|
rs: CompletedProcess = subprocess.run(
|
2599
2626
|
["uv", "run", py, "--no-cache"],
|
2600
2627
|
# ["uv", "run", "--python", "3.9", py],
|
@@ -2644,5 +2671,8 @@ Stage = Annotated[
|
|
2644
2671
|
RaiseStage,
|
2645
2672
|
EmptyStage,
|
2646
2673
|
],
|
2647
|
-
Field(
|
2674
|
+
Field(
|
2675
|
+
union_mode="smart",
|
2676
|
+
description="A stage models that already implemented on this package.",
|
2677
|
+
),
|
2648
2678
|
] # pragma: no cov
|
ddeutil/workflow/utils.py
CHANGED
@@ -15,16 +15,17 @@ from inspect import isfunction
|
|
15
15
|
from itertools import chain, islice, product
|
16
16
|
from pathlib import Path
|
17
17
|
from random import randrange
|
18
|
-
from typing import Any, Final, Optional, TypeVar, Union
|
18
|
+
from typing import Any, Final, Optional, TypeVar, Union, overload
|
19
19
|
from zoneinfo import ZoneInfo
|
20
20
|
|
21
21
|
from ddeutil.core import hash_str
|
22
|
+
from pydantic import BaseModel
|
22
23
|
|
23
24
|
from .__types import DictData, Matrix
|
24
25
|
|
25
26
|
T = TypeVar("T")
|
26
27
|
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
27
|
-
|
28
|
+
MARK_NEWLINE: Final[str] = "||"
|
28
29
|
|
29
30
|
|
30
31
|
def prepare_newline(msg: str) -> str:
|
@@ -34,11 +35,12 @@ def prepare_newline(msg: str) -> str:
|
|
34
35
|
|
35
36
|
:rtype: str
|
36
37
|
"""
|
37
|
-
|
38
|
-
|
38
|
+
# NOTE: Remove ending with "\n" and replace "\n" with the "||" value.
|
39
|
+
msg: str = msg.strip("\n").replace("\n", MARK_NEWLINE)
|
40
|
+
if MARK_NEWLINE not in msg:
|
39
41
|
return msg
|
40
42
|
|
41
|
-
msg_lines: list[str] = msg.split(
|
43
|
+
msg_lines: list[str] = msg.split(MARK_NEWLINE)
|
42
44
|
msg_last: str = msg_lines[-1]
|
43
45
|
msg_body: str = (
|
44
46
|
"\n" + "\n".join(f" ... | \t{s}" for s in msg_lines[1:-1])
|
@@ -288,3 +290,24 @@ def cut_id(run_id: str, *, num: int = 6) -> str:
|
|
288
290
|
dt, simple = run_id.split("T", maxsplit=1)
|
289
291
|
return dt[:12] + simple[-num:]
|
290
292
|
return run_id[:12] + run_id[-num:]
|
293
|
+
|
294
|
+
|
295
|
+
@overload
|
296
|
+
def dump_all(value: BaseModel, by_alias: bool = False) -> DictData: ...
|
297
|
+
|
298
|
+
|
299
|
+
@overload
|
300
|
+
def dump_all(value: T, by_alias: bool = False) -> T: ...
|
301
|
+
|
302
|
+
|
303
|
+
def dump_all(
|
304
|
+
value: Union[T, BaseModel], by_alias: bool = False
|
305
|
+
) -> Union[T, DictData]:
|
306
|
+
"""Dump all BaseModel object to dict."""
|
307
|
+
if isinstance(value, dict):
|
308
|
+
return {k: dump_all(value[k], by_alias=by_alias) for k in value}
|
309
|
+
elif isinstance(value, (list, tuple, set)):
|
310
|
+
return type(value)([dump_all(i, by_alias=by_alias) for i in value])
|
311
|
+
elif isinstance(value, BaseModel):
|
312
|
+
return value.model_dump(by_alias=by_alias)
|
313
|
+
return value
|