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/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.61"
|
ddeutil/workflow/__types.py
CHANGED
@@ -20,6 +20,7 @@ from typing import Any, Optional, TypedDict, Union
|
|
20
20
|
|
21
21
|
from typing_extensions import Self
|
22
22
|
|
23
|
+
StrOrNone = Optional[str]
|
23
24
|
StrOrInt = Union[str, int]
|
24
25
|
TupleStr = tuple[str, ...]
|
25
26
|
DictData = dict[str, Any]
|
@@ -42,7 +43,7 @@ class CallerRe:
|
|
42
43
|
|
43
44
|
full: str
|
44
45
|
caller: str
|
45
|
-
caller_prefix:
|
46
|
+
caller_prefix: StrOrNone
|
46
47
|
caller_last: str
|
47
48
|
post_filters: str
|
48
49
|
|
@@ -50,6 +51,9 @@ class CallerRe:
|
|
50
51
|
def from_regex(cls, match: Match[str]) -> Self:
|
51
52
|
"""Class construct from matching result.
|
52
53
|
|
54
|
+
:param match: A match string object for contract this Caller regex data
|
55
|
+
class.
|
56
|
+
|
53
57
|
:rtype: Self
|
54
58
|
"""
|
55
59
|
return cls(full=match.group(0), **match.groupdict())
|
@@ -121,10 +125,13 @@ class Re:
|
|
121
125
|
)
|
122
126
|
|
123
127
|
@classmethod
|
124
|
-
def finditer_caller(cls, value) -> Iterator[CallerRe]:
|
128
|
+
def finditer_caller(cls, value: str) -> Iterator[CallerRe]:
|
125
129
|
"""Generate CallerRe object that create from matching object that
|
126
130
|
extract with re.finditer function.
|
127
131
|
|
132
|
+
:param value: (str) A string value that want to finditer with the caller
|
133
|
+
regular expression.
|
134
|
+
|
128
135
|
:rtype: Iterator[CallerRe]
|
129
136
|
"""
|
130
137
|
for found in cls.RE_CALLER.finditer(value):
|
ddeutil/workflow/event.py
CHANGED
@@ -3,8 +3,9 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""Event module
|
7
|
-
`CrontabYear`
|
6
|
+
"""Event module include all event object for trigger the Workflow to release.
|
7
|
+
Now, it has only `Crontab` and `CrontabYear` event models on this module because
|
8
|
+
I think it is the core event for workflow orchestration.
|
8
9
|
"""
|
9
10
|
from __future__ import annotations
|
10
11
|
|
@@ -86,7 +87,7 @@ class Crontab(BaseModel):
|
|
86
87
|
CronJob,
|
87
88
|
Field(
|
88
89
|
description=(
|
89
|
-
"A Cronjob object that use for validate and generate datetime."
|
90
|
+
"A Cronjob object that use for validate and generate datetime."
|
90
91
|
),
|
91
92
|
),
|
92
93
|
]
|
@@ -117,7 +118,6 @@ class Crontab(BaseModel):
|
|
117
118
|
passing["cronjob"] = interval2crontab(
|
118
119
|
**{v: value[v] for v in value if v in ("interval", "day", "time")}
|
119
120
|
)
|
120
|
-
print(passing)
|
121
121
|
return cls(extras=extras | passing.pop("extras", {}), **passing)
|
122
122
|
|
123
123
|
@classmethod
|
@@ -170,9 +170,10 @@ class Crontab(BaseModel):
|
|
170
170
|
|
171
171
|
@model_validator(mode="before")
|
172
172
|
def __prepare_values(cls, data: Any) -> Any:
|
173
|
-
"""Extract tz key from
|
173
|
+
"""Extract a `tz` key from data and change the key name from `tz` to
|
174
|
+
`timezone`.
|
174
175
|
|
175
|
-
:param data: (DictData) A data that want to pass for create
|
176
|
+
:param data: (DictData) A data that want to pass for create a Crontab
|
176
177
|
model.
|
177
178
|
|
178
179
|
:rtype: DictData
|
@@ -198,7 +199,7 @@ class Crontab(BaseModel):
|
|
198
199
|
"cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
|
199
200
|
)
|
200
201
|
def __prepare_cronjob(
|
201
|
-
cls, value: str
|
202
|
+
cls, value: Union[str, CronJob], info: ValidationInfo
|
202
203
|
) -> CronJob:
|
203
204
|
"""Prepare crontab value that able to receive with string type.
|
204
205
|
This step will get options kwargs from extras field and pass to the
|
@@ -234,7 +235,7 @@ class Crontab(BaseModel):
|
|
234
235
|
"""
|
235
236
|
return str(value)
|
236
237
|
|
237
|
-
def generate(self, start: str
|
238
|
+
def generate(self, start: Union[str, datetime]) -> CronRunner:
|
238
239
|
"""Return CronRunner object from an initial datetime.
|
239
240
|
|
240
241
|
:param start: (str | datetime) A string or datetime for generate the
|
@@ -248,7 +249,7 @@ class Crontab(BaseModel):
|
|
248
249
|
raise TypeError("start value should be str or datetime type.")
|
249
250
|
return self.cronjob.schedule(date=start, tz=self.tz)
|
250
251
|
|
251
|
-
def next(self, start: str
|
252
|
+
def next(self, start: Union[str, datetime]) -> CronRunner:
|
252
253
|
"""Return a next datetime from Cron runner object that start with any
|
253
254
|
date that given from input.
|
254
255
|
|
@@ -277,16 +278,18 @@ class CrontabYear(Crontab):
|
|
277
278
|
CronJobYear,
|
278
279
|
Field(
|
279
280
|
description=(
|
280
|
-
"A Cronjob object that use for validate and generate datetime."
|
281
|
+
"A Cronjob object that use for validate and generate datetime."
|
281
282
|
),
|
282
283
|
),
|
283
284
|
]
|
284
285
|
|
285
286
|
@field_validator(
|
286
|
-
"cronjob",
|
287
|
+
"cronjob",
|
288
|
+
mode="before",
|
289
|
+
json_schema_input_type=Union[CronJobYear, str],
|
287
290
|
)
|
288
291
|
def __prepare_cronjob(
|
289
|
-
cls, value:
|
292
|
+
cls, value: Union[CronJobYear, str], info: ValidationInfo
|
290
293
|
) -> CronJobYear:
|
291
294
|
"""Prepare crontab value that able to receive with string type.
|
292
295
|
This step will get options kwargs from extras field and pass to the
|
@@ -311,3 +314,15 @@ class CrontabYear(Crontab):
|
|
311
314
|
if isinstance(value, str)
|
312
315
|
else value
|
313
316
|
)
|
317
|
+
|
318
|
+
|
319
|
+
Event = Annotated[
|
320
|
+
Union[
|
321
|
+
CronJobYear,
|
322
|
+
CronJob,
|
323
|
+
],
|
324
|
+
Field(
|
325
|
+
union_mode="smart",
|
326
|
+
description="An event models.",
|
327
|
+
),
|
328
|
+
] # pragma: no cov
|
ddeutil/workflow/exceptions.py
CHANGED
@@ -9,7 +9,7 @@ annotate for handle error only.
|
|
9
9
|
"""
|
10
10
|
from __future__ import annotations
|
11
11
|
|
12
|
-
from typing import Literal, Optional, TypedDict, overload
|
12
|
+
from typing import Literal, Optional, TypedDict, Union, overload
|
13
13
|
|
14
14
|
|
15
15
|
class ErrorData(TypedDict):
|
@@ -55,8 +55,9 @@ class BaseWorkflowException(Exception):
|
|
55
55
|
|
56
56
|
def to_dict(
|
57
57
|
self, with_refs: bool = False
|
58
|
-
) -> ErrorData
|
59
|
-
"""Return ErrorData data from the current exception object.
|
58
|
+
) -> Union[ErrorData, dict[str, ErrorData]]:
|
59
|
+
"""Return ErrorData data from the current exception object. If with_refs
|
60
|
+
flag was set, it will return mapping of refs and itself data.
|
60
61
|
|
61
62
|
:rtype: ErrorData
|
62
63
|
"""
|
ddeutil/workflow/job.py
CHANGED
@@ -39,7 +39,7 @@ from pydantic import BaseModel, Discriminator, Field, SecretStr, Tag
|
|
39
39
|
from pydantic.functional_validators import field_validator, model_validator
|
40
40
|
from typing_extensions import Self
|
41
41
|
|
42
|
-
from .__types import DictData, DictStr, Matrix
|
42
|
+
from .__types import DictData, DictStr, Matrix, StrOrNone
|
43
43
|
from .exceptions import (
|
44
44
|
JobException,
|
45
45
|
StageException,
|
@@ -329,14 +329,14 @@ class Job(BaseModel):
|
|
329
329
|
... }
|
330
330
|
"""
|
331
331
|
|
332
|
-
id:
|
332
|
+
id: StrOrNone = Field(
|
333
333
|
default=None,
|
334
334
|
description=(
|
335
335
|
"A job ID that was set from Workflow model after initialize step. "
|
336
336
|
"If this model create standalone, it will be None."
|
337
337
|
),
|
338
338
|
)
|
339
|
-
desc:
|
339
|
+
desc: StrOrNone = Field(
|
340
340
|
default=None,
|
341
341
|
description="A job description that can be markdown syntax.",
|
342
342
|
)
|
@@ -345,7 +345,7 @@ class Job(BaseModel):
|
|
345
345
|
description="A target node for this job to use for execution.",
|
346
346
|
alias="runs-on",
|
347
347
|
)
|
348
|
-
condition:
|
348
|
+
condition: StrOrNone = Field(
|
349
349
|
default=None,
|
350
350
|
description="A job condition statement to allow job executable.",
|
351
351
|
alias="if",
|
@@ -526,7 +526,7 @@ class Job(BaseModel):
|
|
526
526
|
output: DictData,
|
527
527
|
to: DictData,
|
528
528
|
*,
|
529
|
-
job_id:
|
529
|
+
job_id: StrOrNone = None,
|
530
530
|
) -> DictData:
|
531
531
|
"""Set an outputs from execution result context to the received context
|
532
532
|
with a `to` input parameter. The result context from job strategy
|
@@ -567,7 +567,7 @@ class Job(BaseModel):
|
|
567
567
|
:param output: (DictData) A result data context that want to extract
|
568
568
|
and transfer to the `strategies` key in receive context.
|
569
569
|
:param to: (DictData) A received context data.
|
570
|
-
:param job_id: (
|
570
|
+
:param job_id: (StrOrNone) A job ID if the `id` field does not set.
|
571
571
|
|
572
572
|
:rtype: DictData
|
573
573
|
"""
|
@@ -607,8 +607,8 @@ class Job(BaseModel):
|
|
607
607
|
self,
|
608
608
|
params: DictData,
|
609
609
|
*,
|
610
|
-
run_id:
|
611
|
-
parent_run_id:
|
610
|
+
run_id: StrOrNone = None,
|
611
|
+
parent_run_id: StrOrNone = None,
|
612
612
|
event: Optional[Event] = None,
|
613
613
|
) -> Result:
|
614
614
|
"""Job execution with passing dynamic parameters from the workflow
|
@@ -800,8 +800,8 @@ def local_execute(
|
|
800
800
|
job: Job,
|
801
801
|
params: DictData,
|
802
802
|
*,
|
803
|
-
run_id:
|
804
|
-
parent_run_id:
|
803
|
+
run_id: StrOrNone = None,
|
804
|
+
parent_run_id: StrOrNone = None,
|
805
805
|
event: Optional[Event] = None,
|
806
806
|
) -> Result:
|
807
807
|
"""Local job execution with passing dynamic parameters from the workflow
|
@@ -919,8 +919,8 @@ def self_hosted_execute(
|
|
919
919
|
job: Job,
|
920
920
|
params: DictData,
|
921
921
|
*,
|
922
|
-
run_id:
|
923
|
-
parent_run_id:
|
922
|
+
run_id: StrOrNone = None,
|
923
|
+
parent_run_id: StrOrNone = None,
|
924
924
|
event: Optional[Event] = None,
|
925
925
|
) -> Result: # pragma: no cov
|
926
926
|
"""Self-Hosted job execution with passing dynamic parameters from the
|
@@ -982,8 +982,8 @@ def azure_batch_execute(
|
|
982
982
|
job: Job,
|
983
983
|
params: DictData,
|
984
984
|
*,
|
985
|
-
run_id:
|
986
|
-
parent_run_id:
|
985
|
+
run_id: StrOrNone = None,
|
986
|
+
parent_run_id: StrOrNone = None,
|
987
987
|
event: Optional[Event] = None,
|
988
988
|
) -> Result: # pragma: no cov
|
989
989
|
"""Azure Batch job execution that will run all job's stages on the Azure
|
@@ -1036,8 +1036,8 @@ def docker_execution(
|
|
1036
1036
|
job: Job,
|
1037
1037
|
params: DictData,
|
1038
1038
|
*,
|
1039
|
-
run_id:
|
1040
|
-
parent_run_id:
|
1039
|
+
run_id: StrOrNone = None,
|
1040
|
+
parent_run_id: StrOrNone = None,
|
1041
1041
|
event: Optional[Event] = None,
|
1042
1042
|
): # pragma: no cov
|
1043
1043
|
"""Docker job execution.
|
ddeutil/workflow/logs.py
CHANGED
@@ -4,13 +4,17 @@
|
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
# [x] Use fix config for `get_logger`, and Model initialize step.
|
7
|
-
"""A Logs module contain Trace
|
7
|
+
"""A Logs module contain Trace and Audit Pydantic models for process log from
|
8
|
+
the core workflow engine. I separate part of log to 2 types:
|
9
|
+
- Trace: A stdout and stderr log
|
10
|
+
- Audit: An audit release log for tracking incremental running workflow.
|
8
11
|
"""
|
9
12
|
from __future__ import annotations
|
10
13
|
|
11
14
|
import json
|
12
15
|
import logging
|
13
16
|
import os
|
17
|
+
import re
|
14
18
|
from abc import ABC, abstractmethod
|
15
19
|
from collections.abc import Iterator
|
16
20
|
from datetime import datetime
|
@@ -18,7 +22,8 @@ from functools import lru_cache
|
|
18
22
|
from inspect import Traceback, currentframe, getframeinfo
|
19
23
|
from pathlib import Path
|
20
24
|
from threading import get_ident
|
21
|
-
from
|
25
|
+
from types import FrameType
|
26
|
+
from typing import ClassVar, Final, Literal, Optional, TypeVar, Union
|
22
27
|
|
23
28
|
from pydantic import BaseModel, ConfigDict, Field
|
24
29
|
from pydantic.functional_validators import model_validator
|
@@ -28,12 +33,14 @@ from .__types import DictData
|
|
28
33
|
from .conf import config, dynamic
|
29
34
|
from .utils import cut_id, get_dt_now, prepare_newline
|
30
35
|
|
36
|
+
METADATA: str = "metadata.json"
|
37
|
+
|
31
38
|
|
32
39
|
@lru_cache
|
33
40
|
def get_logger(name: str):
|
34
41
|
"""Return logger object with an input module name.
|
35
42
|
|
36
|
-
:param name: A module name that want to log.
|
43
|
+
:param name: (str) A module name that want to log.
|
37
44
|
"""
|
38
45
|
lg = logging.getLogger(name)
|
39
46
|
|
@@ -67,14 +74,59 @@ def get_dt_tznow() -> datetime: # pragma: no cov
|
|
67
74
|
return get_dt_now(tz=config.tz)
|
68
75
|
|
69
76
|
|
70
|
-
PREFIX_LOGS: dict[str, dict] = {
|
71
|
-
"CALLER": {
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
"
|
76
|
-
"
|
77
|
+
PREFIX_LOGS: Final[dict[str, dict]] = {
|
78
|
+
"CALLER": {
|
79
|
+
"emoji": "📍",
|
80
|
+
"desc": "logs from any usage from custom caller function.",
|
81
|
+
},
|
82
|
+
"STAGE": {"emoji": "⚙️", "desc": "logs from stages module."},
|
83
|
+
"JOB": {"emoji": "⛓️", "desc": "logs from job module."},
|
84
|
+
"WORKFLOW": {"emoji": "🏃", "desc": "logs from workflow module."},
|
85
|
+
"RELEASE": {"emoji": "📅", "desc": "logs from release workflow method."},
|
86
|
+
"POKING": {"emoji": "⏰", "desc": "logs from poke workflow method."},
|
77
87
|
} # pragma: no cov
|
88
|
+
PREFIX_DEFAULT: Final[str] = "CALLER"
|
89
|
+
PREFIX_LOGS_REGEX: re.Pattern[str] = re.compile(
|
90
|
+
rf"(^\[(?P<name>{'|'.join(PREFIX_LOGS)})]:\s?)?(?P<message>.*)",
|
91
|
+
re.MULTILINE | re.DOTALL | re.ASCII | re.VERBOSE,
|
92
|
+
) # pragma: no cov
|
93
|
+
|
94
|
+
|
95
|
+
class PrefixMsg(BaseModel):
|
96
|
+
"""Prefix Message model for receive grouping dict from searching prefix data
|
97
|
+
from logging message.
|
98
|
+
"""
|
99
|
+
|
100
|
+
name: Optional[str] = Field(default=None)
|
101
|
+
message: Optional[str] = Field(default=None)
|
102
|
+
|
103
|
+
def prepare(self, extras: Optional[DictData] = None) -> str:
|
104
|
+
"""Prepare message with force add prefix before writing trace log.
|
105
|
+
|
106
|
+
:param extras: (DictData) An extra parameter that want to get the
|
107
|
+
`log_add_emoji` flag.
|
108
|
+
|
109
|
+
:rtype: str
|
110
|
+
"""
|
111
|
+
name: str = self.name or PREFIX_DEFAULT
|
112
|
+
emoji: str = (
|
113
|
+
f"{PREFIX_LOGS[name]['emoji']} "
|
114
|
+
if (extras or {}).get("log_add_emoji", True)
|
115
|
+
else ""
|
116
|
+
)
|
117
|
+
return f"{emoji}[{name}]: {self.message}"
|
118
|
+
|
119
|
+
|
120
|
+
def extract_msg_prefix(msg: str) -> PrefixMsg:
|
121
|
+
"""Extract message prefix from an input message.
|
122
|
+
|
123
|
+
:param msg: A message that want to extract.
|
124
|
+
|
125
|
+
:rtype: PrefixMsg
|
126
|
+
"""
|
127
|
+
return PrefixMsg.model_validate(
|
128
|
+
obj=PREFIX_LOGS_REGEX.search(msg).groupdict()
|
129
|
+
)
|
78
130
|
|
79
131
|
|
80
132
|
class TraceMeta(BaseModel): # pragma: no cov
|
@@ -91,6 +143,28 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
91
143
|
filename: str = Field(description="A filename of this log.")
|
92
144
|
lineno: int = Field(description="A line number of this log.")
|
93
145
|
|
146
|
+
@classmethod
|
147
|
+
def dynamic_frame(
|
148
|
+
cls, frame: FrameType, *, extras: Optional[DictData] = None
|
149
|
+
) -> Traceback:
|
150
|
+
"""Dynamic Frame information base on the `logs_trace_frame_layer` config
|
151
|
+
value that was set from the extra parameter.
|
152
|
+
|
153
|
+
:param frame: (FrameType) The current frame that want to dynamic.
|
154
|
+
:param extras: (DictData) An extra parameter that want to get the
|
155
|
+
`logs_trace_frame_layer` config value.
|
156
|
+
"""
|
157
|
+
extras: DictData = extras or {}
|
158
|
+
layer: int = extras.get("logs_trace_frame_layer", 4)
|
159
|
+
for _ in range(layer):
|
160
|
+
_frame: Optional[FrameType] = frame.f_back
|
161
|
+
if _frame is None:
|
162
|
+
raise ValueError(
|
163
|
+
f"Layer value does not valid, the maximum frame is: {_ + 1}"
|
164
|
+
)
|
165
|
+
frame: FrameType = _frame
|
166
|
+
return getframeinfo(frame)
|
167
|
+
|
94
168
|
@classmethod
|
95
169
|
def make(
|
96
170
|
cls,
|
@@ -100,7 +174,8 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
100
174
|
*,
|
101
175
|
extras: Optional[DictData] = None,
|
102
176
|
) -> Self:
|
103
|
-
"""Make the current
|
177
|
+
"""Make the current metric for contract this TraceMeta model instance
|
178
|
+
that will catch local states like PID, thread identity.
|
104
179
|
|
105
180
|
:param mode: (Literal["stdout", "stderr"]) A metadata mode.
|
106
181
|
:param message: (str) A message.
|
@@ -110,9 +185,8 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
110
185
|
|
111
186
|
:rtype: Self
|
112
187
|
"""
|
113
|
-
|
114
|
-
|
115
|
-
)
|
188
|
+
frame: FrameType = currentframe()
|
189
|
+
frame_info: Traceback = cls.dynamic_frame(frame, extras=extras)
|
116
190
|
extras: DictData = extras or {}
|
117
191
|
return cls(
|
118
192
|
mode=mode,
|
@@ -157,13 +231,11 @@ class TraceData(BaseModel): # pragma: no cov
|
|
157
231
|
if (file / f"{mode}.txt").exists():
|
158
232
|
data[mode] = (file / f"{mode}.txt").read_text(encoding="utf-8")
|
159
233
|
|
160
|
-
if (file /
|
234
|
+
if (file / METADATA).exists():
|
161
235
|
data["meta"] = [
|
162
236
|
json.loads(line)
|
163
237
|
for line in (
|
164
|
-
(file /
|
165
|
-
.read_text(encoding="utf-8")
|
166
|
-
.splitlines()
|
238
|
+
(file / METADATA).read_text(encoding="utf-8").splitlines()
|
167
239
|
)
|
168
240
|
]
|
169
241
|
|
@@ -263,7 +335,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
263
335
|
|
264
336
|
:param message: (str) A message that want to log.
|
265
337
|
"""
|
266
|
-
msg: str =
|
338
|
+
msg: str = self.make_message(message)
|
267
339
|
|
268
340
|
if mode != "debug" or (
|
269
341
|
mode == "debug" and dynamic("debug", extras=self.extras)
|
@@ -320,7 +392,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
320
392
|
|
321
393
|
:param message: (str) A message that want to log.
|
322
394
|
"""
|
323
|
-
msg: str =
|
395
|
+
msg: str = self.make_message(message)
|
324
396
|
|
325
397
|
if mode != "debug" or (
|
326
398
|
mode == "debug" and dynamic("debug", extras=self.extras)
|
@@ -441,13 +513,15 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
441
513
|
return f"{cut_parent_run_id} -> {cut_run_id}"
|
442
514
|
|
443
515
|
def make_message(self, message: str) -> str:
|
444
|
-
"""Prepare and Make a message before write and log
|
516
|
+
"""Prepare and Make a message before write and log steps.
|
445
517
|
|
446
518
|
:param message: (str) A message that want to prepare and make before.
|
447
519
|
|
448
520
|
:rtype: str
|
449
521
|
"""
|
450
|
-
return
|
522
|
+
return prepare_newline(
|
523
|
+
f"({self.cut_id}) {extract_msg_prefix(message).prepare(self.extras)}"
|
524
|
+
)
|
451
525
|
|
452
526
|
def writer(self, message: str, level: str, is_err: bool = False) -> None:
|
453
527
|
"""Write a trace message after making to target file and write metadata
|
@@ -468,7 +542,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
468
542
|
|
469
543
|
mode: Literal["stdout", "stderr"] = "stderr" if is_err else "stdout"
|
470
544
|
trace_meta: TraceMeta = TraceMeta.make(
|
471
|
-
mode=mode, level=level, message=message
|
545
|
+
mode=mode, level=level, message=message, extras=self.extras
|
472
546
|
)
|
473
547
|
|
474
548
|
with (self.pointer / f"{mode}.txt").open(
|
@@ -477,9 +551,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
477
551
|
fmt: str = dynamic("log_format_file", extras=self.extras)
|
478
552
|
f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
479
553
|
|
480
|
-
with (self.pointer /
|
481
|
-
mode="at", encoding="utf-8"
|
482
|
-
) as f:
|
554
|
+
with (self.pointer / METADATA).open(mode="at", encoding="utf-8") as f:
|
483
555
|
f.write(trace_meta.model_dump_json() + "\n")
|
484
556
|
|
485
557
|
async def awriter(
|
@@ -496,7 +568,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
496
568
|
|
497
569
|
mode: Literal["stdout", "stderr"] = "stderr" if is_err else "stdout"
|
498
570
|
trace_meta: TraceMeta = TraceMeta.make(
|
499
|
-
mode=mode, level=level, message=message
|
571
|
+
mode=mode, level=level, message=message, extras=self.extras
|
500
572
|
)
|
501
573
|
|
502
574
|
async with aiofiles.open(
|
@@ -506,7 +578,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
506
578
|
await f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
507
579
|
|
508
580
|
async with aiofiles.open(
|
509
|
-
self.pointer /
|
581
|
+
self.pointer / METADATA, mode="at", encoding="utf-8"
|
510
582
|
) as f:
|
511
583
|
await f.write(trace_meta.model_dump_json() + "\n")
|
512
584
|
|