ddeutil-workflow 0.0.56__py3-none-any.whl → 0.0.58__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/logs.py CHANGED
@@ -29,7 +29,7 @@ from typing_extensions import Self
29
29
 
30
30
  from .__types import DictData, DictStr
31
31
  from .conf import config, dynamic
32
- from .utils import cut_id, get_dt_now
32
+ from .utils import cut_id, get_dt_now, prepare_newline
33
33
 
34
34
 
35
35
  @lru_cache
@@ -70,16 +70,28 @@ def get_dt_tznow() -> datetime: # pragma: no cov
70
70
  return get_dt_now(tz=config.tz)
71
71
 
72
72
 
73
+ PREFIX_LOGS: dict[str, dict] = {
74
+ "CALLER": {"emoji": ""},
75
+ "STAGE": {"emoji": ""},
76
+ "JOB": {"emoji": ""},
77
+ "WORKFLOW": {"emoji": "🏃"},
78
+ "RELEASE": {"emoji": ""},
79
+ "POKE": {"emoji": ""},
80
+ } # pragma: no cov
81
+
82
+
73
83
  class TraceMeta(BaseModel): # pragma: no cov
74
- """Trace Meta model."""
84
+ """Trace Metadata model for making the current metadata of this CPU, Memory
85
+ process, and thread data.
86
+ """
75
87
 
76
- mode: Literal["stdout", "stderr"]
77
- datetime: str
78
- process: int
79
- thread: int
80
- message: str
81
- filename: str
82
- lineno: int
88
+ mode: Literal["stdout", "stderr"] = Field(description="A meta mode.")
89
+ datetime: str = Field(description="A datetime in string format.")
90
+ process: int = Field(description="A process ID.")
91
+ thread: int = Field(description="A thread ID.")
92
+ message: str = Field(description="A message log.")
93
+ filename: str = Field(description="A filename of this log.")
94
+ lineno: int = Field(description="A line number of this log.")
83
95
 
84
96
  @classmethod
85
97
  def make(
@@ -91,6 +103,11 @@ class TraceMeta(BaseModel): # pragma: no cov
91
103
  ) -> Self:
92
104
  """Make the current TraceMeta instance that catching local state.
93
105
 
106
+ :param mode: A metadata mode.
107
+ :param message: A message.
108
+ :param extras: (DictData) An extra parameter that want to override core
109
+ config values.
110
+
94
111
  :rtype: Self
95
112
  """
96
113
  frame_info: Traceback = getframeinfo(
@@ -135,11 +152,9 @@ class TraceData(BaseModel): # pragma: no cov
135
152
  """
136
153
  data: DictStr = {"stdout": "", "stderr": "", "meta": []}
137
154
 
138
- if (file / "stdout.txt").exists():
139
- data["stdout"] = (file / "stdout.txt").read_text(encoding="utf-8")
140
-
141
- if (file / "stderr.txt").exists():
142
- data["stderr"] = (file / "stderr.txt").read_text(encoding="utf-8")
155
+ for mode in ("stdout", "stderr"):
156
+ if (file / f"{mode}.txt").exists():
157
+ data[mode] = (file / f"{mode}.txt").read_text(encoding="utf-8")
143
158
 
144
159
  if (file / "metadata.json").exists():
145
160
  data["meta"] = [
@@ -232,7 +247,7 @@ class BaseTrace(ABC): # pragma: no cov
232
247
 
233
248
  :param message: (str) A message that want to log.
234
249
  """
235
- msg: str = self.make_message(message)
250
+ msg: str = prepare_newline(self.make_message(message))
236
251
 
237
252
  if mode != "debug" or (
238
253
  mode == "debug" and dynamic("debug", extras=self.extras)
@@ -281,16 +296,30 @@ class BaseTrace(ABC): # pragma: no cov
281
296
  """
282
297
  self.__logging(message, mode="exception", is_err=True)
283
298
 
299
+ async def __alogging(
300
+ self, message: str, mode: str, *, is_err: bool = False
301
+ ) -> None:
302
+ """Write trace log with append mode and logging this message with any
303
+ logging level.
304
+
305
+ :param message: (str) A message that want to log.
306
+ """
307
+ msg: str = prepare_newline(self.make_message(message))
308
+
309
+ if mode != "debug" or (
310
+ mode == "debug" and dynamic("debug", extras=self.extras)
311
+ ):
312
+ await self.awriter(msg, is_err=is_err)
313
+
314
+ getattr(logger, mode)(msg, stacklevel=3)
315
+
284
316
  async def adebug(self, message: str) -> None: # pragma: no cov
285
317
  """Async write trace log with append mode and logging this message with
286
318
  the DEBUG level.
287
319
 
288
320
  :param message: (str) A message that want to log.
289
321
  """
290
- msg: str = self.make_message(message)
291
- if dynamic("debug", extras=self.extras):
292
- await self.awriter(msg)
293
- logger.info(msg, stacklevel=2)
322
+ await self.__alogging(message, mode="debug")
294
323
 
295
324
  async def ainfo(self, message: str) -> None: # pragma: no cov
296
325
  """Async write trace log with append mode and logging this message with
@@ -298,9 +327,7 @@ class BaseTrace(ABC): # pragma: no cov
298
327
 
299
328
  :param message: (str) A message that want to log.
300
329
  """
301
- msg: str = self.make_message(message)
302
- await self.awriter(msg)
303
- logger.info(msg, stacklevel=2)
330
+ await self.__alogging(message, mode="info")
304
331
 
305
332
  async def awarning(self, message: str) -> None: # pragma: no cov
306
333
  """Async write trace log with append mode and logging this message with
@@ -308,9 +335,7 @@ class BaseTrace(ABC): # pragma: no cov
308
335
 
309
336
  :param message: (str) A message that want to log.
310
337
  """
311
- msg: str = self.make_message(message)
312
- await self.awriter(msg)
313
- logger.warning(msg, stacklevel=2)
338
+ await self.__alogging(message, mode="warning")
314
339
 
315
340
  async def aerror(self, message: str) -> None: # pragma: no cov
316
341
  """Async write trace log with append mode and logging this message with
@@ -318,9 +343,7 @@ class BaseTrace(ABC): # pragma: no cov
318
343
 
319
344
  :param message: (str) A message that want to log.
320
345
  """
321
- msg: str = self.make_message(message)
322
- await self.awriter(msg, is_err=True)
323
- logger.error(msg, stacklevel=2)
346
+ await self.__alogging(message, mode="error", is_err=True)
324
347
 
325
348
  async def aexception(self, message: str) -> None: # pragma: no cov
326
349
  """Async write trace log with append mode and logging this message with
@@ -328,9 +351,7 @@ class BaseTrace(ABC): # pragma: no cov
328
351
 
329
352
  :param message: (str) A message that want to log.
330
353
  """
331
- msg: str = self.make_message(message)
332
- await self.awriter(msg, is_err=True)
333
- logger.exception(msg, stacklevel=2)
354
+ await self.__alogging(message, mode="exception", is_err=True)
334
355
 
335
356
 
336
357
  class FileTrace(BaseTrace): # pragma: no cov
@@ -344,7 +365,7 @@ class FileTrace(BaseTrace): # pragma: no cov
344
365
  ) -> Iterator[TraceData]: # pragma: no cov
345
366
  """Find trace logs.
346
367
 
347
- :param path: (Path)
368
+ :param path: (Path) A trace path that want to find.
348
369
  :param extras: An extra parameter that want to override core config.
349
370
  """
350
371
  for file in sorted(
@@ -357,16 +378,16 @@ class FileTrace(BaseTrace): # pragma: no cov
357
378
  def find_trace_with_id(
358
379
  cls,
359
380
  run_id: str,
360
- force_raise: bool = True,
361
381
  *,
382
+ force_raise: bool = True,
362
383
  path: Path | None = None,
363
384
  extras: Optional[DictData] = None,
364
385
  ) -> TraceData:
365
386
  """Find trace log with an input specific run ID.
366
387
 
367
388
  :param run_id: A running ID of trace log.
368
- :param force_raise:
369
- :param path:
389
+ :param force_raise: (bool)
390
+ :param path: (Path)
370
391
  :param extras: An extra parameter that want to override core config.
371
392
  """
372
393
  base_path: Path = path or dynamic("trace_path", extras=extras)
@@ -445,6 +466,7 @@ class FileTrace(BaseTrace): # pragma: no cov
445
466
  async def awriter(
446
467
  self, message: str, is_err: bool = False
447
468
  ) -> None: # pragma: no cov
469
+ """Write with async mode."""
448
470
  if not dynamic("enable_write_log", extras=self.extras):
449
471
  return
450
472
 
@@ -744,7 +766,7 @@ class FileAudit(BaseAudit):
744
766
 
745
767
  # NOTE: Check environ variable was set for real writing.
746
768
  if not dynamic("enable_write_audit", extras=self.extras):
747
- trace.debug("[LOG]: Skip writing log cause config was set")
769
+ trace.debug("[AUDIT]: Skip writing log cause config was set")
748
770
  return self
749
771
 
750
772
  log_file: Path = (
@@ -813,7 +835,7 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
813
835
 
814
836
  # NOTE: Check environ variable was set for real writing.
815
837
  if not dynamic("enable_write_audit", extras=self.extras):
816
- trace.debug("[LOG]: Skip writing log cause config was set")
838
+ trace.debug("[AUDIT]: Skip writing log cause config was set")
817
839
  return self
818
840
 
819
841
  raise NotImplementedError("SQLiteAudit does not implement yet.")
@@ -190,10 +190,13 @@ class IntParam(DefaultParam):
190
190
 
191
191
 
192
192
  class FloatParam(DefaultParam): # pragma: no cov
193
+ """Float parameter."""
194
+
193
195
  type: Literal["float"] = "float"
194
196
  precision: int = 6
195
197
 
196
198
  def rounding(self, value: float) -> float:
199
+ """Rounding float value with the specific precision field."""
197
200
  round_str: str = f"{{0:.{self.precision}f}}"
198
201
  return float(round_str.format(round(value, self.precision)))
199
202
 
@@ -224,6 +227,7 @@ class DecimalParam(DefaultParam): # pragma: no cov
224
227
  precision: int = 6
225
228
 
226
229
  def rounding(self, value: Decimal) -> Decimal:
230
+ """Rounding float value with the specific precision field."""
227
231
  return value.quantize(Decimal(10) ** -self.precision)
228
232
 
229
233
  def receive(self, value: float | Decimal | None = None) -> Decimal:
@@ -37,6 +37,14 @@ class Status(IntEnum):
37
37
  SKIP: int = 3
38
38
  CANCEL: int = 4
39
39
 
40
+ @property
41
+ def emoji(self) -> str:
42
+ """Return the emoji value of this status.
43
+
44
+ :rtype: str
45
+ """
46
+ return {0: "✅", 1: "❌", 2: "🟡", 3: "⏩", 4: "🚫"}[self.value]
47
+
40
48
 
41
49
  SUCCESS = Status.SUCCESS
42
50
  FAILED = Status.FAILED
@@ -46,10 +54,7 @@ CANCEL = Status.CANCEL
46
54
 
47
55
 
48
56
  @dataclass(
49
- config=ConfigDict(
50
- arbitrary_types_allowed=True,
51
- use_enum_values=True,
52
- ),
57
+ config=ConfigDict(arbitrary_types_allowed=True, use_enum_values=True),
53
58
  )
54
59
  class Result:
55
60
  """Result Pydantic Model for passing and receiving data context from any
@@ -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
- # [x] Use dynamic config
7
6
  """The main schedule running is `schedule_runner` function that trigger the
8
7
  multiprocess of `schedule_control` function for listing schedules on the
9
8
  config by `Loader.finds(Schedule)`.
@@ -17,6 +16,11 @@ functions; `workflow_task`, and `workflow_monitor`.
17
16
  The `schedule_task` will run `task.release` method in threading object
18
17
  for multithreading strategy. This `release` method will run only one crontab
19
18
  value with the on field.
19
+
20
+ Steps:
21
+ - Extract all schedule config on the conf path.
22
+ - Slice schedules to multiprocess
23
+ - Start running task.
20
24
  """
21
25
  from __future__ import annotations
22
26
 
@@ -53,7 +57,7 @@ except ImportError: # pragma: no cov
53
57
  from .__cron import CronRunner
54
58
  from .__types import DictData, TupleStr
55
59
  from .conf import FileLoad, Loader, dynamic
56
- from .event import On
60
+ from .event import Crontab
57
61
  from .exceptions import ScheduleException, WorkflowException
58
62
  from .logs import Audit, get_audit
59
63
  from .result import SUCCESS, Result
@@ -99,9 +103,9 @@ class ScheduleWorkflow(BaseModel):
99
103
  description="An alias name of workflow that use for schedule model.",
100
104
  )
101
105
  name: str = Field(description="A workflow name.")
102
- on: list[On] = Field(
106
+ on: list[Crontab] = Field(
103
107
  default_factory=list,
104
- description="An override the list of On object values.",
108
+ description="An override the list of Crontab object values.",
105
109
  )
106
110
  values: DictData = Field(
107
111
  default_factory=dict,
@@ -154,15 +158,17 @@ class ScheduleWorkflow(BaseModel):
154
158
  return data
155
159
 
156
160
  @field_validator("on", mode="after")
157
- def __on_no_dup__(cls, value: list[On], info: ValidationInfo) -> list[On]:
161
+ def __on_no_dup__(
162
+ cls, value: list[Crontab], info: ValidationInfo
163
+ ) -> list[Crontab]:
158
164
  """Validate the on fields should not contain duplicate values and if it
159
165
  contains every minute value, it should have only one on value.
160
166
 
161
- :param value: (list[On]) A list of `On` object.
167
+ :param value: (list[Crontab]) A list of `Crontab` object.
162
168
  :param info: (ValidationInfo) An validation info object for getting an
163
169
  extra parameter.
164
170
 
165
- :rtype: list[On]
171
+ :rtype: list[Crontab]
166
172
  """
167
173
  set_ons: set[str] = {str(on.cronjob) for on in value}
168
174
  if len(set_ons) != len(value):
@@ -205,7 +211,7 @@ class ScheduleWorkflow(BaseModel):
205
211
 
206
212
  # IMPORTANT: Create the default 'on' value if it does not pass the `on`
207
213
  # field to the Schedule object.
208
- ons: list[On] = self.on or wf.on.copy()
214
+ ons: list[Crontab] = self.on or wf.on.copy()
209
215
  workflow_tasks: list[WorkflowTask] = []
210
216
  for on in ons:
211
217
 
@@ -699,7 +705,7 @@ def schedule_control(
699
705
  :rtype: Result
700
706
  """
701
707
  audit: type[Audit] = audit or get_audit(extras=extras)
702
- result: Result = Result().set_parent_run_id(parent_run_id)
708
+ result: Result = Result.construct_with_rs_or_id(parent_run_id=parent_run_id)
703
709
 
704
710
  # NOTE: Create the start and stop datetime.
705
711
  start_date: datetime = datetime.now(tz=dynamic("tz", extras=extras))