ddeutil-workflow 0.0.74__py3-none-any.whl → 0.0.75__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.
@@ -1 +1 @@
1
- __version__: str = "0.0.74"
1
+ __version__: str = "0.0.75"
@@ -793,10 +793,11 @@ class CronRunner:
793
793
  "Invalid type of `tz` parameter, it should be str or "
794
794
  "ZoneInfo instance."
795
795
  )
796
- try:
797
- self.tz = ZoneInfo(tz)
798
- except ZoneInfoNotFoundError as err:
799
- raise ValueError(f"Invalid timezone: {tz}") from err
796
+ else:
797
+ try:
798
+ self.tz = ZoneInfo(tz)
799
+ except ZoneInfoNotFoundError as err:
800
+ raise ValueError(f"Invalid timezone: {tz}") from err
800
801
 
801
802
  # NOTE: Prepare date
802
803
  if date:
@@ -807,6 +808,7 @@ class CronRunner:
807
808
  if tz is not None:
808
809
  self.date: datetime = date.astimezone(self.tz)
809
810
  else:
811
+ self.tz = date.tzinfo
810
812
  self.date: datetime = date
811
813
  else:
812
814
  self.date: datetime = datetime.now(tz=self.tz)
ddeutil/workflow/conf.py CHANGED
@@ -89,16 +89,6 @@ class Config: # pragma: no cov
89
89
  """
90
90
  return Path(env("CORE_CONF_PATH", "./conf"))
91
91
 
92
- @property
93
- def tz(self) -> ZoneInfo:
94
- """Timezone value that return with the `ZoneInfo` object and use for all
95
- datetime object in this workflow engine.
96
-
97
- Returns:
98
- ZoneInfo: The timezone configuration for the workflow engine.
99
- """
100
- return ZoneInfo(env("CORE_TIMEZONE", "UTC"))
101
-
102
92
  @property
103
93
  def generate_id_simple_mode(self) -> bool:
104
94
  """Flag for generate running ID with simple mode. That does not use
@@ -143,6 +133,16 @@ class Config: # pragma: no cov
143
133
  """
144
134
  return str2bool(env("LOG_DEBUG_MODE", "true"))
145
135
 
136
+ @property
137
+ def log_tz(self) -> ZoneInfo:
138
+ """Timezone value that return with the `ZoneInfo` object and use for all
139
+ datetime object in this workflow engine.
140
+
141
+ Returns:
142
+ ZoneInfo: The timezone configuration for the workflow engine.
143
+ """
144
+ return ZoneInfo(env("LOG_TIMEZONE", "UTC"))
145
+
146
146
  @property
147
147
  def log_format(self) -> str:
148
148
  return env(
@@ -52,7 +52,7 @@ from pydantic import BaseModel, Field
52
52
 
53
53
  from .__types import StrOrInt
54
54
  from .errors import ParamError
55
- from .utils import get_d_now, get_dt_now
55
+ from .utils import UTC, get_d_now, get_dt_now
56
56
 
57
57
  T = TypeVar("T")
58
58
 
@@ -169,16 +169,18 @@ class DatetimeParam(DefaultParam):
169
169
  return self.default
170
170
 
171
171
  if isinstance(value, datetime):
172
- return value
172
+ if value.tzinfo is None:
173
+ return value.replace(tzinfo=UTC)
174
+ return value.astimezone(UTC)
173
175
  elif isinstance(value, date):
174
- return datetime(value.year, value.month, value.day)
176
+ return datetime(value.year, value.month, value.day, tzinfo=UTC)
175
177
  elif not isinstance(value, str):
176
178
  raise ParamError(
177
179
  f"Value that want to convert to datetime does not support for "
178
180
  f"type: {type(value)}"
179
181
  )
180
182
  try:
181
- return datetime.fromisoformat(value)
183
+ return datetime.fromisoformat(value).replace(tzinfo=UTC)
182
184
  except ValueError:
183
185
  raise ParamError(
184
186
  f"Invalid the ISO format string for datetime: {value!r}"
@@ -43,7 +43,7 @@ from . import (
43
43
  from .__types import DictData
44
44
  from .audits import Trace, get_trace
45
45
  from .errors import ResultError
46
- from .utils import default_gen_id, get_dt_ntz_now
46
+ from .utils import default_gen_id, get_dt_now
47
47
 
48
48
 
49
49
  class Status(str, Enum):
@@ -190,7 +190,7 @@ class Result:
190
190
  info: DictData = field(default_factory=dict)
191
191
  run_id: Optional[str] = field(default_factory=default_gen_id)
192
192
  parent_run_id: Optional[str] = field(default=None, compare=False)
193
- ts: datetime = field(default_factory=get_dt_ntz_now, compare=False)
193
+ ts: datetime = field(default_factory=get_dt_now, compare=False)
194
194
  trace: Optional[Trace] = field(default=None, compare=False, repr=False)
195
195
  extras: DictData = field(default_factory=dict, compare=False, repr=False)
196
196
 
@@ -266,7 +266,7 @@ class Result:
266
266
 
267
267
  :rtype: float
268
268
  """
269
- return (get_dt_ntz_now() - self.ts).total_seconds()
269
+ return (get_dt_now() - self.ts).total_seconds()
270
270
 
271
271
 
272
272
  def catch(
@@ -167,7 +167,9 @@ class TraceMeta(BaseModel): # pragma: no cov
167
167
 
168
168
  mode: Literal["stdout", "stderr"] = Field(description="A meta mode.")
169
169
  level: str = Field(description="A log level.")
170
- datetime: str = Field(description="A datetime in string format.")
170
+ datetime: str = Field(
171
+ description="A datetime string with the specific config format."
172
+ )
171
173
  process: int = Field(description="A process ID.")
172
174
  thread: int = Field(description="A thread ID.")
173
175
  message: str = Field(description="A message log.")
@@ -234,9 +236,9 @@ class TraceMeta(BaseModel): # pragma: no cov
234
236
  mode=mode,
235
237
  level=level,
236
238
  datetime=(
237
- get_dt_now(tz=dynamic("tz", extras=extras)).strftime(
238
- dynamic("log_datetime_format", extras=extras)
239
- )
239
+ get_dt_now()
240
+ .astimezone(dynamic("log_tz", extras=extras))
241
+ .strftime(dynamic("log_datetime_format", extras=extras))
240
242
  ),
241
243
  process=os.getpid(),
242
244
  thread=get_ident(),
ddeutil/workflow/utils.py CHANGED
@@ -109,45 +109,32 @@ def replace_sec(dt: datetime) -> datetime:
109
109
 
110
110
 
111
111
  def clear_tz(dt: datetime) -> datetime:
112
- """Replace timezone info on an input datetime object to None."""
113
- return dt.replace(tzinfo=None)
112
+ """Replace timezone info on an input datetime object to UTC."""
113
+ return dt.replace(tzinfo=UTC)
114
114
 
115
115
 
116
- def get_dt_now(tz: Optional[ZoneInfo] = None, offset: float = 0.0) -> datetime:
116
+ def get_dt_now(offset: float = 0.0) -> datetime:
117
117
  """Return the current datetime object.
118
118
 
119
- :param tz: A ZoneInfo object for replace timezone of return datetime object.
120
119
  :param offset: An offset second value.
121
120
 
122
121
  :rtype: datetime
123
122
  :return: The current datetime object that use an input timezone or UTC.
124
123
  """
125
- return datetime.now(tz=tz) - timedelta(seconds=offset)
124
+ return datetime.now().replace(tzinfo=UTC) - timedelta(seconds=offset)
126
125
 
127
126
 
128
- def get_dt_ntz_now() -> datetime: # pragma: no cov
129
- """Get current datetime with no timezone.
130
-
131
- Returns the current datetime object using the None timezone.
132
-
133
- Returns:
134
- datetime: Current datetime with no timezone
135
- """
136
- return get_dt_now(tz=None)
137
-
138
-
139
- def get_d_now(
140
- tz: Optional[ZoneInfo] = None, offset: float = 0.0
141
- ) -> date: # pragma: no cov
127
+ def get_d_now(offset: float = 0.0) -> date: # pragma: no cov
142
128
  """Return the current date object.
143
129
 
144
- :param tz: A ZoneInfo object for replace timezone of return date object.
145
130
  :param offset: An offset second value.
146
131
 
147
132
  :rtype: date
148
133
  :return: The current date object that use an input timezone or UTC.
149
134
  """
150
- return (datetime.now(tz=tz) - timedelta(seconds=offset)).date()
135
+ return (
136
+ datetime.now().replace(tzinfo=UTC) - timedelta(seconds=offset)
137
+ ).date()
151
138
 
152
139
 
153
140
  def get_diff_sec(dt: datetime, offset: float = 0.0) -> int:
@@ -240,7 +227,7 @@ def gen_id(
240
227
  if not isinstance(value, str):
241
228
  value: str = str(value)
242
229
 
243
- dt: datetime = datetime.now(tz=dynamic("tz", extras=extras))
230
+ dt: datetime = datetime.now(tz=UTC)
244
231
  if dynamic("generate_id_simple_mode", f=simple_mode, extras=extras):
245
232
  return (f"{dt:%Y%m%d%H%M%S%f}T" if unique else "") + hash_str(
246
233
  f"{(value if sensitive else value.lower())}", n=10
@@ -36,7 +36,6 @@ from queue import Queue
36
36
  from textwrap import dedent
37
37
  from threading import Event as ThreadEvent
38
38
  from typing import Any, Optional, Union
39
- from zoneinfo import ZoneInfo
40
39
 
41
40
  from pydantic import BaseModel, Field
42
41
  from pydantic.functional_validators import field_validator, model_validator
@@ -64,8 +63,9 @@ from .result import (
64
63
  from .reusables import has_template, param2template
65
64
  from .traces import Trace, get_trace
66
65
  from .utils import (
66
+ UTC,
67
67
  gen_id,
68
- get_dt_ntz_now,
68
+ get_dt_now,
69
69
  replace_sec,
70
70
  )
71
71
 
@@ -153,14 +153,14 @@ class Workflow(BaseModel):
153
153
  description="A mapping of job ID and job model that already loaded.",
154
154
  )
155
155
  created_at: datetime = Field(
156
- default_factory=get_dt_ntz_now,
156
+ default_factory=get_dt_now,
157
157
  description=(
158
158
  "A created datetime of this workflow template when loading from "
159
159
  "file."
160
160
  ),
161
161
  )
162
162
  updated_dt: datetime = Field(
163
- default_factory=get_dt_ntz_now,
163
+ default_factory=get_dt_now,
164
164
  description=(
165
165
  "A updated datetime of this workflow template when loading from "
166
166
  "file."
@@ -369,12 +369,15 @@ class Workflow(BaseModel):
369
369
  Returns:
370
370
  datetime: The validated release datetime.
371
371
  """
372
- release: datetime = replace_sec(dt.replace(tzinfo=None))
372
+ if dt.tzinfo is None:
373
+ dt = dt.replace(tzinfo=UTC)
374
+
375
+ release: datetime = replace_sec(dt.astimezone(UTC))
373
376
  if not self.on:
374
377
  return release
375
378
 
376
379
  for on in self.on.schedule:
377
- if release == on.cronjob.schedule(release).next:
380
+ if release == on.cronjob.schedule(release, tz=UTC).next:
378
381
  return release
379
382
  raise WorkflowError(
380
383
  "Release datetime does not support for this workflow"
@@ -385,8 +388,8 @@ class Workflow(BaseModel):
385
388
  release: datetime,
386
389
  params: DictData,
387
390
  *,
388
- release_type: ReleaseType = NORMAL,
389
391
  run_id: Optional[str] = None,
392
+ release_type: ReleaseType = NORMAL,
390
393
  audit: type[Audit] = None,
391
394
  override_log_name: Optional[str] = None,
392
395
  timeout: int = 600,
@@ -420,25 +423,27 @@ class Workflow(BaseModel):
420
423
  :rtype: Result
421
424
  """
422
425
  name: str = override_log_name or self.name
426
+
427
+ # NOTE: Generate the parent running ID with not None value.
423
428
  if run_id:
424
429
  parent_run_id: str = run_id
425
430
  run_id: str = gen_id(name, unique=True)
426
431
  else:
427
432
  run_id: str = gen_id(name, unique=True)
428
433
  parent_run_id: str = run_id
434
+
429
435
  context: DictData = {}
430
436
  trace: Trace = get_trace(
431
437
  run_id, parent_run_id=parent_run_id, extras=self.extras
432
438
  )
433
439
  release: datetime = self.validate_release(dt=release)
434
440
  trace.info(f"[RELEASE]: Start {name!r} : {release:%Y-%m-%d %H:%M:%S}")
435
- tz: ZoneInfo = dynamic("tz", extras=self.extras)
436
441
  values: DictData = param2template(
437
442
  params,
438
443
  params={
439
444
  "release": {
440
445
  "logical_date": release,
441
- "execute_date": datetime.now(tz=tz),
446
+ "execute_date": get_dt_now(),
442
447
  "run_id": run_id,
443
448
  }
444
449
  },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.74
3
+ Version: 0.0.75
4
4
  Summary: Lightweight workflow orchestration with YAML template
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -68,7 +68,7 @@ by a `.yaml` template.
68
68
  3. All parallel tasks inside workflow core engine use **Multi-Threading** pool
69
69
  (Python 3.13 unlock GIL 🐍🔓)
70
70
  4. Recommend to pass a **Secret Value** with environment variable in YAML template 🔐
71
- 5. Any datatime value convert to **No Timezone**
71
+ 5. Any datatime value convert to **UTC Timezone** 🌐
72
72
 
73
73
  ---
74
74
 
@@ -288,10 +288,10 @@ it will use default value and do not raise any error to you.
288
288
  | **REGISTRY_CALLER** | CORE | `.` | List of importable string for the call stage. |
289
289
  | **REGISTRY_FILTER** | CORE | `ddeutil.workflow.templates` | List of importable string for the filter template. |
290
290
  | **CONF_PATH** | CORE | `./conf` | The config path that keep all template `.yaml` files. |
291
- | **TIMEZONE** | CORE | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
292
291
  | **STAGE_DEFAULT_ID** | CORE | `false` | A flag that enable default stage ID that use for catch an execution output. |
293
292
  | **GENERATE_ID_SIMPLE_MODE** | CORE | `true` | A flog that enable generating ID with `md5` algorithm. |
294
293
  | **DEBUG_MODE** | LOG | `true` | A flag that enable logging with debug level mode. |
294
+ | **TIMEZONE** | LOG | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
295
295
  | **FORMAT** | LOG | `%(asctime)s.%(msecs)03d (%(name)-10s, %(process)-5d,%(thread)-5d) [%(levelname)-7s] %(message)-120s (%(filename)s:%(lineno)s)` | A trace message console format. |
296
296
  | **FORMAT_FILE** | LOG | `{datetime} ({process:5d}, {thread:5d}) {message:120s} ({filename}:{lineno})` | A trace message format that use to write to target pointer. |
297
297
  | **DATETIME_FORMAT** | LOG | `%Y-%m-%d %H:%M:%S` | A datetime format of the trace log. |
@@ -1,30 +1,30 @@
1
- ddeutil/workflow/__about__.py,sha256=ovscLwcOTC_clQ-KeqVwtDeutK0Mcfq20RBXHRUIFew,28
2
- ddeutil/workflow/__cron.py,sha256=9I_gAXgZu1agInPoDi9r1lH7LUWq7gnDOmUrLxXmkWE,28840
1
+ ddeutil/workflow/__about__.py,sha256=iaM2-rdtvV7sEgUAaF8bPdKBNJ3_zhFdOi5A1NC-s3U,28
2
+ ddeutil/workflow/__cron.py,sha256=0_40rdL3QEChMcikrLQfQPb67YfHmbYr_aPCp6NM7Cw,28912
3
3
  ddeutil/workflow/__init__.py,sha256=kjKFdRNOh19IiLdmSFPKesU8BqSijEWHRZ8_fKGXFUk,3276
4
4
  ddeutil/workflow/__main__.py,sha256=Qd-f8z2Q2vpiEP2x6PBFsJrpACWDVxFKQk820MhFmHo,59
5
5
  ddeutil/workflow/__types.py,sha256=tA2vsr6mzTSzbWB1sb62c5GgxODlfVRz6FvgLNJtQao,4788
6
6
  ddeutil/workflow/audits.py,sha256=PZ0dDBBANpXoLHlDrmlEoIXr0iYK9PCw4-9tPJX9UXE,12528
7
7
  ddeutil/workflow/cli.py,sha256=V42C3lmtzRUiIjgMjLlr7v600PX0A3Aqbp8ccUSgQew,5244
8
- ddeutil/workflow/conf.py,sha256=aXclEcxQKYYDb09O5EBQxmJmjPXNT73NMxeq238xO3s,16264
8
+ ddeutil/workflow/conf.py,sha256=UCw6v2GFD3tA2LRbp7vLifXniey0P5Ef0U9eBPknrWk,16267
9
9
  ddeutil/workflow/errors.py,sha256=n10YjXptY4iiY57FFVq22aedlUojpMuB5cM7aQ0KNpo,5522
10
10
  ddeutil/workflow/event.py,sha256=siChcBhsu4ejzW1fK0tjHPXQVaSUCSxPYDgDrh6duwo,13676
11
11
  ddeutil/workflow/job.py,sha256=oCjxG1Zz2D9Yc2SuZth3k1geIQ-AJ3oLs_38g5NMXLE,43994
12
- ddeutil/workflow/params.py,sha256=5DMDp7ZJwa1pM-oATV4kEPDQsaL7Ol8NavR9FZk_uqg,13519
13
- ddeutil/workflow/result.py,sha256=XBRZtD6b_5kJCFKvyk0ECWG1IjKIHc1Ad1mFvzpoPpY,9147
12
+ ddeutil/workflow/params.py,sha256=Cyz142OcvENIZrM7Efc2xuGPmmFBhROifP5ojoaCezg,13658
13
+ ddeutil/workflow/result.py,sha256=LBzBdVlqXsG4vgHQadb_bfuxY32yQ-dlBf-HmnX-dBk,9135
14
14
  ddeutil/workflow/reusables.py,sha256=q_OA-oifCGIhW_5j6hTZXZk7FBOmDt0xVrtNnscJfNg,23294
15
15
  ddeutil/workflow/stages.py,sha256=2Rz9NGVOftIqiYaSgasHDS8MvgvLp3jtAjBHEwdtx7U,121837
16
- ddeutil/workflow/traces.py,sha256=nSHOF7DPolLjNEKCu1OwiFg1z2xLvQp_eu-mY5BZCOA,28288
17
- ddeutil/workflow/utils.py,sha256=NqekWLsU2YK2ejvYk83N-8mjSXjxmBxGN7gPXRKONko,11337
18
- ddeutil/workflow/workflow.py,sha256=lcW2sYRWaYpq2dKoeKS5sQSV5lF2sF6loeUU-AGJ5kE,41159
16
+ ddeutil/workflow/traces.py,sha256=KFGeIFk1ocDWsaNkjXJk3LQHvq8dRfIf3hZsTuCpF1Y,28334
17
+ ddeutil/workflow/utils.py,sha256=EXhIuWzOJHvlcoAdyvuDUomGtMTIB59HxOLpj2VJ1bI,10857
18
+ ddeutil/workflow/workflow.py,sha256=izGz5teb0unlKtnpklvcdWFHK1af60IJVeSbUhMdPtA,41206
19
19
  ddeutil/workflow/api/__init__.py,sha256=5DzYL3ngceoRshh5HYCSVWChqNJSiP01E1bEd8XxPi0,4799
20
20
  ddeutil/workflow/api/log_conf.py,sha256=WfS3udDLSyrP-C80lWOvxxmhd_XWKvQPkwDqKblcH3E,1834
21
21
  ddeutil/workflow/api/routes/__init__.py,sha256=JRaJZB0D6mgR17MbZo8yLtdYDtD62AA8MdKlFqhG84M,420
22
22
  ddeutil/workflow/api/routes/job.py,sha256=-lbZ_hS9pEdSy6zeke5qrXEgdNxtQ2w9in7cHuM2Jzs,2536
23
23
  ddeutil/workflow/api/routes/logs.py,sha256=RiZ62eQVMWArPHE3lpan955U4DdLLkethlvSMlwF7Mg,5312
24
24
  ddeutil/workflow/api/routes/workflows.py,sha256=1Mqx4Hft4uJglgJI-Wcw-JzkhomFYZrtP0DnQDBkAFQ,4410
25
- ddeutil_workflow-0.0.74.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
26
- ddeutil_workflow-0.0.74.dist-info/METADATA,sha256=ZLpNyhuTVBCxVMLFegOvMfCLBkQXjrULcbaCwvY6F0I,15775
27
- ddeutil_workflow-0.0.74.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- ddeutil_workflow-0.0.74.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
29
- ddeutil_workflow-0.0.74.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
30
- ddeutil_workflow-0.0.74.dist-info/RECORD,,
25
+ ddeutil_workflow-0.0.75.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
26
+ ddeutil_workflow-0.0.75.dist-info/METADATA,sha256=sJGNuv_qmZziEljC1T-9tCCOAGT3ovafMkAdRg-BvPo,15781
27
+ ddeutil_workflow-0.0.75.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ ddeutil_workflow-0.0.75.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
29
+ ddeutil_workflow-0.0.75.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
30
+ ddeutil_workflow-0.0.75.dist-info/RECORD,,