ddeutil-workflow 0.0.29__py3-none-any.whl → 0.0.31__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.29"
1
+ __version__: str = "0.0.31"
@@ -845,5 +845,6 @@ __all__ = (
845
845
  "CronJob",
846
846
  "CronJobYear",
847
847
  "CronRunner",
848
+ "Options",
848
849
  "WEEKDAYS",
849
850
  )
@@ -47,8 +47,10 @@ from .params import (
47
47
  from .result import Result
48
48
  from .scheduler import (
49
49
  Schedule,
50
- WorkflowSchedule,
50
+ ScheduleWorkflow,
51
+ schedule_control,
51
52
  schedule_runner,
53
+ schedule_task,
52
54
  )
53
55
  from .stage import (
54
56
  BashStage,
@@ -83,6 +85,8 @@ from .utils import (
83
85
  make_exec,
84
86
  )
85
87
  from .workflow import (
88
+ Release,
89
+ ReleaseQueue,
86
90
  Workflow,
87
91
  WorkflowTask,
88
92
  )
@@ -18,7 +18,7 @@ from fastapi.responses import UJSONResponse
18
18
  from ..__about__ import __version__
19
19
  from ..conf import config, get_logger
20
20
  from ..scheduler import ReleaseThread, ReleaseThreads
21
- from ..workflow import WorkflowQueue, WorkflowTask
21
+ from ..workflow import ReleaseQueue, WorkflowTask
22
22
  from .repeat import repeat_at
23
23
 
24
24
  load_dotenv()
@@ -31,7 +31,7 @@ class State(TypedDict):
31
31
  scheduler: list[str]
32
32
  workflow_threads: ReleaseThreads
33
33
  workflow_tasks: list[WorkflowTask]
34
- workflow_queue: dict[str, WorkflowQueue]
34
+ workflow_queue: dict[str, ReleaseQueue]
35
35
 
36
36
 
37
37
  @contextlib.asynccontextmanager
ddeutil/workflow/conf.py CHANGED
@@ -582,6 +582,10 @@ Log = Union[
582
582
 
583
583
 
584
584
  def get_log() -> type[Log]: # pragma: no cov
585
+ """Get logging class that dynamic base on the config log path value.
586
+
587
+ :rtype: type[Log]
588
+ """
585
589
  if config.log_path.is_file():
586
590
  return SQLiteLog
587
591
  return FileLog
ddeutil/workflow/cron.py CHANGED
@@ -5,16 +5,17 @@
5
5
  # ------------------------------------------------------------------------------
6
6
  from __future__ import annotations
7
7
 
8
+ from dataclasses import fields
8
9
  from datetime import datetime
9
- from typing import Annotated, Literal
10
+ from typing import Annotated, Literal, Union
10
11
  from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
11
12
 
12
- from pydantic import BaseModel, ConfigDict, Field
13
+ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
13
14
  from pydantic.functional_serializers import field_serializer
14
15
  from pydantic.functional_validators import field_validator, model_validator
15
16
  from typing_extensions import Self
16
17
 
17
- from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner
18
+ from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
18
19
  from .__types import DictData, DictStr, TupleStr
19
20
  from .conf import Loader
20
21
 
@@ -47,6 +48,8 @@ def interval2crontab(
47
48
  '0 0 1 * *'
48
49
  >>> interval2crontab(interval='monthly', day='tuesday', time='12:00')
49
50
  '12 0 1 * 2'
51
+
52
+ :rtype: str
50
53
  """
51
54
  d: str = "*"
52
55
  if interval == "weekly":
@@ -64,12 +67,19 @@ class On(BaseModel):
64
67
  """On Pydantic model (Warped crontab object by model).
65
68
 
66
69
  See Also:
67
- * ``generate()`` is the main usecase of this schedule object.
70
+ * ``generate()`` is the main use-case of this schedule object.
68
71
  """
69
72
 
70
73
  model_config = ConfigDict(arbitrary_types_allowed=True)
71
74
 
72
75
  # NOTE: This is fields of the base schedule.
76
+ extras: Annotated[
77
+ DictData,
78
+ Field(
79
+ default_factory=dict,
80
+ description="An extras mapping parameters",
81
+ ),
82
+ ]
73
83
  cronjob: Annotated[
74
84
  CronJob,
75
85
  Field(description="Cron job of this schedule"),
@@ -81,13 +91,6 @@ class On(BaseModel):
81
91
  alias="timezone",
82
92
  ),
83
93
  ] = "Etc/UTC"
84
- extras: Annotated[
85
- DictData,
86
- Field(
87
- default_factory=dict,
88
- description="An extras mapping parameters",
89
- ),
90
- ]
91
94
 
92
95
  @classmethod
93
96
  def from_value(cls, value: DictStr, externals: DictData) -> Self:
@@ -153,6 +156,7 @@ class On(BaseModel):
153
156
 
154
157
  @model_validator(mode="before")
155
158
  def __prepare_values(cls, values: DictData) -> DictData:
159
+ """Extract tz key from value and change name to timezone key."""
156
160
  if tz := values.pop("tz", None):
157
161
  values["timezone"] = tz
158
162
  return values
@@ -160,24 +164,55 @@ class On(BaseModel):
160
164
  @field_validator("tz")
161
165
  def __validate_tz(cls, value: str) -> str:
162
166
  """Validate timezone value that able to initialize with ZoneInfo after
163
- it passing to this model in before mode."""
167
+ it passing to this model in before mode.
168
+
169
+ :rtype: str
170
+ """
164
171
  try:
165
172
  _ = ZoneInfo(value)
166
173
  return value
167
174
  except ZoneInfoNotFoundError as err:
168
175
  raise ValueError(f"Invalid timezone: {value}") from err
169
176
 
170
- @field_validator("cronjob", mode="before")
171
- def __prepare_cronjob(cls, value: str | CronJob) -> CronJob:
172
- """Prepare crontab value that able to receive with string type."""
173
- return CronJob(value) if isinstance(value, str) else value
177
+ @field_validator(
178
+ "cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
179
+ )
180
+ def __prepare_cronjob(
181
+ cls, value: str | CronJob, info: ValidationInfo
182
+ ) -> CronJob:
183
+ """Prepare crontab value that able to receive with string type.
184
+ This step will get options kwargs from extras and pass to the
185
+ CronJob object.
186
+
187
+ :rtype: CronJob
188
+ """
189
+ extras: DictData = info.data.get("extras", {})
190
+ return (
191
+ CronJob(
192
+ value,
193
+ option={
194
+ name: extras[name]
195
+ for name in (f.name for f in fields(Options))
196
+ if name in extras
197
+ },
198
+ )
199
+ if isinstance(value, str)
200
+ else value
201
+ )
174
202
 
175
203
  @field_serializer("cronjob")
176
204
  def __serialize_cronjob(self, value: CronJob) -> str:
205
+ """Serialize the cronjob field that store with CronJob object.
206
+
207
+ :rtype: str
208
+ """
177
209
  return str(value)
178
210
 
179
211
  def generate(self, start: str | datetime) -> CronRunner:
180
- """Return Cron runner object."""
212
+ """Return Cron runner object.
213
+
214
+ :rtype: CronRunner
215
+ """
181
216
  if isinstance(start, str):
182
217
  start: datetime = datetime.fromisoformat(start)
183
218
  elif not isinstance(start, datetime):
@@ -187,6 +222,8 @@ class On(BaseModel):
187
222
  def next(self, start: str | datetime) -> CronRunner:
188
223
  """Return a next datetime from Cron runner object that start with any
189
224
  date that given from input.
225
+
226
+ :rtype: CronRunner
190
227
  """
191
228
  runner: CronRunner = self.generate(start=start)
192
229
 
@@ -209,7 +246,26 @@ class YearOn(On):
209
246
  Field(description="Cron job of this schedule"),
210
247
  ]
211
248
 
212
- @field_validator("cronjob", mode="before")
213
- def __prepare_cronjob(cls, value: str | CronJobYear) -> CronJobYear:
214
- """Prepare crontab value that able to receive with string type."""
215
- return CronJobYear(value) if isinstance(value, str) else value
249
+ @field_validator(
250
+ "cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
251
+ )
252
+ def __prepare_cronjob(
253
+ cls, value: str | CronJobYear, info: ValidationInfo
254
+ ) -> CronJobYear:
255
+ """Prepare crontab value that able to receive with string type.
256
+
257
+ :rtype: CronJobYear
258
+ """
259
+ extras: DictData = info.data.get("extras", {})
260
+ return (
261
+ CronJobYear(
262
+ value,
263
+ option={
264
+ name: extras[name]
265
+ for name in (f.name for f in fields(Options))
266
+ if name in extras
267
+ },
268
+ )
269
+ if isinstance(value, str)
270
+ else value
271
+ )
@@ -29,3 +29,6 @@ class WorkflowFailException(WorkflowException): ...
29
29
 
30
30
 
31
31
  class ParamValueException(WorkflowException): ...
32
+
33
+
34
+ class ScheduleException(BaseWorkflowException): ...
ddeutil/workflow/hook.py CHANGED
@@ -79,7 +79,9 @@ def make_registry(submodule: str) -> dict[str, Registry]:
79
79
  :rtype: dict[str, Registry]
80
80
  """
81
81
  rs: dict[str, Registry] = {}
82
- for module in config.regis_hook | ["ddeutil.vendors"]:
82
+ regis_hooks: list[str] = config.regis_hook
83
+ regis_hooks.extend(["ddeutil.vendors"])
84
+ for module in regis_hooks:
83
85
  # NOTE: try to sequential import task functions
84
86
  try:
85
87
  importer = import_module(f"{module}.{submodule}")
@@ -5,6 +5,7 @@
5
5
  # ------------------------------------------------------------------------------
6
6
  from __future__ import annotations
7
7
 
8
+ import decimal
8
9
  import logging
9
10
  from abc import ABC, abstractmethod
10
11
  from datetime import date, datetime
@@ -49,7 +50,7 @@ class BaseParam(BaseModel, ABC):
49
50
 
50
51
  class DefaultParam(BaseParam):
51
52
  """Default Parameter that will check default if it required. This model do
52
- not implement the receive method.
53
+ not implement the `receive` method.
53
54
  """
54
55
 
55
56
  required: bool = Field(
@@ -68,6 +69,15 @@ class DefaultParam(BaseParam):
68
69
  )
69
70
 
70
71
 
72
+ # TODO: Not implement this parameter yet
73
+ class DateParam(DefaultParam):
74
+ """Date parameter."""
75
+
76
+ type: Literal["date"] = "date"
77
+
78
+ def receive(self, value: Optional[str | date] = None) -> date: ...
79
+
80
+
71
81
  class DatetimeParam(DefaultParam):
72
82
  """Datetime parameter."""
73
83
 
@@ -145,6 +155,13 @@ class IntParam(DefaultParam):
145
155
  return value
146
156
 
147
157
 
158
+ # TODO: Not implement this parameter yet
159
+ class DecimalParam(DefaultParam):
160
+ type: Literal["decimal"] = "decimal"
161
+
162
+ def receive(self, value: float | None = None) -> decimal.Decimal: ...
163
+
164
+
148
165
  class ChoiceParam(BaseParam):
149
166
  """Choice parameter."""
150
167
 
@@ -94,6 +94,7 @@ class Result:
94
94
  # NOTE: Check the context has jobs key.
95
95
  if "jobs" not in self.__dict__["context"]:
96
96
  self.__dict__["context"]["jobs"] = {}
97
+
97
98
  self.__dict__["context"]["jobs"].update(result.context)
98
99
 
99
100
  # NOTE: Update running ID from an incoming result.