ddeutil-workflow 0.0.73__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.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__cron.py +20 -12
- ddeutil/workflow/__init__.py +119 -10
- ddeutil/workflow/__types.py +53 -41
- ddeutil/workflow/api/__init__.py +74 -3
- ddeutil/workflow/api/routes/job.py +15 -29
- ddeutil/workflow/api/routes/logs.py +9 -9
- ddeutil/workflow/api/routes/workflows.py +3 -3
- ddeutil/workflow/audits.py +70 -55
- ddeutil/workflow/cli.py +1 -15
- ddeutil/workflow/conf.py +71 -26
- ddeutil/workflow/errors.py +86 -19
- ddeutil/workflow/event.py +268 -169
- ddeutil/workflow/job.py +331 -192
- ddeutil/workflow/params.py +43 -11
- ddeutil/workflow/result.py +96 -70
- ddeutil/workflow/reusables.py +56 -6
- ddeutil/workflow/stages.py +1059 -572
- ddeutil/workflow/traces.py +205 -124
- ddeutil/workflow/utils.py +58 -19
- ddeutil/workflow/workflow.py +435 -296
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/METADATA +27 -17
- ddeutil_workflow-0.0.75.dist-info/RECORD +30 -0
- ddeutil_workflow-0.0.73.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/top_level.txt +0 -0
ddeutil/workflow/event.py
CHANGED
@@ -3,10 +3,36 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
"""Event Scheduling Module for Workflow Orchestration.
|
7
|
+
|
8
|
+
This module provides event-driven scheduling capabilities for workflows, with
|
9
|
+
a primary focus on cron-based scheduling. It includes models for defining
|
10
|
+
when workflows should be triggered and executed.
|
11
|
+
|
12
|
+
The core event trigger is the Crontab model, which wraps cron functionality
|
13
|
+
in a Pydantic model for validation and easy integration with the workflow system.
|
14
|
+
|
15
|
+
Attributes:
|
16
|
+
Interval: Type alias for scheduling intervals ('daily', 'weekly', 'monthly')
|
17
|
+
|
18
|
+
Classes:
|
19
|
+
Crontab: Main cron-based event scheduler.
|
20
|
+
CrontabYear: Enhanced cron scheduler with year constraints.
|
21
|
+
ReleaseEvent: Release-based event triggers.
|
22
|
+
SensorEvent: Sensor-based event monitoring.
|
23
|
+
|
24
|
+
Example:
|
25
|
+
>>> from ddeutil.workflow.event import Crontab
|
26
|
+
>>> # NOTE: Create daily schedule at 9 AM
|
27
|
+
>>> schedule = Crontab.model_validate(
|
28
|
+
... {
|
29
|
+
... "cronjob": "0 9 * * *",
|
30
|
+
... "timezone": "America/New_York",
|
31
|
+
... }
|
32
|
+
... )
|
33
|
+
>>> # NOTE: Generate next run times
|
34
|
+
>>> runner = schedule.generate(datetime.now())
|
35
|
+
>>> next_run = runner.next
|
10
36
|
"""
|
11
37
|
from __future__ import annotations
|
12
38
|
|
@@ -19,11 +45,9 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
|
|
19
45
|
from pydantic.functional_serializers import field_serializer
|
20
46
|
from pydantic.functional_validators import field_validator, model_validator
|
21
47
|
from pydantic_extra_types.timezone_name import TimeZoneName
|
22
|
-
from typing_extensions import Self
|
23
48
|
|
24
49
|
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
|
25
|
-
from .__types import DictData
|
26
|
-
from .conf import YamlParser
|
50
|
+
from .__types import DictData
|
27
51
|
|
28
52
|
Interval = Literal["daily", "weekly", "monthly"]
|
29
53
|
|
@@ -34,13 +58,16 @@ def interval2crontab(
|
|
34
58
|
day: Optional[str] = None,
|
35
59
|
time: str = "00:00",
|
36
60
|
) -> str:
|
37
|
-
"""
|
61
|
+
"""Convert interval specification to cron expression.
|
38
62
|
|
39
|
-
:
|
40
|
-
'monthly'.
|
41
|
-
|
42
|
-
|
43
|
-
|
63
|
+
Args:
|
64
|
+
interval: Scheduling interval ('daily', 'weekly', or 'monthly').
|
65
|
+
day: Day of week for weekly intervals or monthly schedules. Defaults to
|
66
|
+
Monday for weekly intervals.
|
67
|
+
time: Time of day in 'HH:MM' format. Defaults to '00:00'.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Generated crontab expression string.
|
44
71
|
|
45
72
|
Examples:
|
46
73
|
>>> interval2crontab(interval='daily', time='01:30')
|
@@ -51,8 +78,6 @@ def interval2crontab(
|
|
51
78
|
'0 0 1 * *'
|
52
79
|
>>> interval2crontab(interval='monthly', day='tuesday', time='12:00')
|
53
80
|
'12 0 1 * 2'
|
54
|
-
|
55
|
-
:rtype: str
|
56
81
|
"""
|
57
82
|
d: str = "*"
|
58
83
|
if interval == "weekly":
|
@@ -66,119 +91,35 @@ def interval2crontab(
|
|
66
91
|
return f"{h} {m} {'1' if interval == 'monthly' else '*'} * {d}"
|
67
92
|
|
68
93
|
|
69
|
-
class
|
70
|
-
"""
|
71
|
-
crontab value and generate CronRunner object from this crontab value.
|
94
|
+
class BaseCrontab(BaseModel):
|
95
|
+
"""Base class for crontab-based scheduling models.
|
72
96
|
|
73
|
-
|
74
|
-
|
97
|
+
Attributes:
|
98
|
+
extras: Additional parameters to pass to the CronJob field.
|
99
|
+
tz: Timezone string value (alias: timezone).
|
75
100
|
"""
|
76
101
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
Field(
|
82
|
-
default_factory=dict,
|
83
|
-
description=(
|
84
|
-
"An extras parameters that want to pass to the CronJob field."
|
85
|
-
),
|
86
|
-
),
|
87
|
-
]
|
88
|
-
cronjob: Annotated[
|
89
|
-
CronJob,
|
90
|
-
Field(
|
91
|
-
description=(
|
92
|
-
"A Cronjob object that use for validate and generate datetime."
|
93
|
-
),
|
94
|
-
),
|
95
|
-
]
|
96
|
-
tz: Annotated[
|
97
|
-
TimeZoneName,
|
98
|
-
Field(
|
99
|
-
description="A timezone string value.",
|
100
|
-
alias="timezone",
|
102
|
+
extras: DictData = Field(
|
103
|
+
default_factory=dict,
|
104
|
+
description=(
|
105
|
+
"An extras parameters that want to pass to the CronJob field."
|
101
106
|
),
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
""
|
107
|
-
|
108
|
-
:param value: (DictStr) A mapping value that will generate crontab
|
109
|
-
before create schedule model.
|
110
|
-
:param extras: (DictData) An extra parameter that use to override core
|
111
|
-
config value.
|
112
|
-
"""
|
113
|
-
passing: DictStr = {}
|
114
|
-
|
115
|
-
if "timezone" in value:
|
116
|
-
passing["tz"] = value.pop("timezone")
|
117
|
-
elif "tz" in value:
|
118
|
-
passing["tz"] = value.pop("tz")
|
119
|
-
|
120
|
-
passing["cronjob"] = interval2crontab(
|
121
|
-
**{v: value[v] for v in value if v in ("interval", "day", "time")}
|
122
|
-
)
|
123
|
-
return cls(extras=extras | passing.pop("extras", {}), **passing)
|
124
|
-
|
125
|
-
@classmethod
|
126
|
-
def from_conf(
|
127
|
-
cls,
|
128
|
-
name: str,
|
129
|
-
*,
|
130
|
-
extras: DictData | None = None,
|
131
|
-
) -> Self:
|
132
|
-
"""Constructor from the name of config loader that will use loader
|
133
|
-
object for getting the `Crontab` data.
|
134
|
-
|
135
|
-
:param name: (str) A name of config that will get from loader.
|
136
|
-
:param extras: (DictData) An extra parameter that use to override core
|
137
|
-
config values.
|
138
|
-
|
139
|
-
:rtype: Self
|
140
|
-
"""
|
141
|
-
extras: DictData = extras or {}
|
142
|
-
loader: YamlParser = YamlParser(name, extras=extras, obj=cls)
|
143
|
-
|
144
|
-
# NOTE: Validate the config type match with current connection model
|
145
|
-
if loader.type != cls.__name__:
|
146
|
-
raise ValueError(f"Type {loader.type} does not match with {cls}")
|
147
|
-
|
148
|
-
loader_data: DictData = loader.data
|
149
|
-
if "interval" in loader_data:
|
150
|
-
return cls.model_validate(
|
151
|
-
obj=dict(
|
152
|
-
cronjob=interval2crontab(
|
153
|
-
**{
|
154
|
-
v: loader_data[v]
|
155
|
-
for v in loader_data
|
156
|
-
if v in ("interval", "day", "time")
|
157
|
-
}
|
158
|
-
),
|
159
|
-
extras=extras | loader_data.pop("extras", {}),
|
160
|
-
**loader_data,
|
161
|
-
)
|
162
|
-
)
|
163
|
-
if "cronjob" not in loader_data:
|
164
|
-
raise ValueError("Config does not set `cronjob` or `interval` keys")
|
165
|
-
return cls.model_validate(
|
166
|
-
obj=dict(
|
167
|
-
cronjob=loader_data.pop("cronjob"),
|
168
|
-
extras=extras | loader_data.pop("extras", {}),
|
169
|
-
**loader_data,
|
170
|
-
)
|
171
|
-
)
|
107
|
+
)
|
108
|
+
tz: TimeZoneName = Field(
|
109
|
+
default="UTC",
|
110
|
+
description="A timezone string value.",
|
111
|
+
alias="timezone",
|
112
|
+
)
|
172
113
|
|
173
114
|
@model_validator(mode="before")
|
174
115
|
def __prepare_values(cls, data: Any) -> Any:
|
175
|
-
"""Extract
|
176
|
-
`timezone`.
|
116
|
+
"""Extract and rename timezone key in input data.
|
177
117
|
|
178
|
-
:
|
179
|
-
model.
|
118
|
+
Args:
|
119
|
+
data: Input data dictionary for creating Crontab model.
|
180
120
|
|
181
|
-
:
|
121
|
+
Returns:
|
122
|
+
Modified data dictionary with standardized timezone key.
|
182
123
|
"""
|
183
124
|
if isinstance(data, dict) and (tz := data.pop("tz", None)):
|
184
125
|
data["timezone"] = tz
|
@@ -186,10 +127,16 @@ class Crontab(BaseModel):
|
|
186
127
|
|
187
128
|
@field_validator("tz")
|
188
129
|
def __validate_tz(cls, value: str) -> str:
|
189
|
-
"""Validate timezone value
|
190
|
-
it passing to this model in before mode.
|
130
|
+
"""Validate timezone value.
|
191
131
|
|
192
|
-
:
|
132
|
+
Args:
|
133
|
+
value: Timezone string to validate.
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
Validated timezone string.
|
137
|
+
|
138
|
+
Raises:
|
139
|
+
ValueError: If timezone is invalid.
|
193
140
|
"""
|
194
141
|
try:
|
195
142
|
_ = ZoneInfo(value)
|
@@ -197,21 +144,118 @@ class Crontab(BaseModel):
|
|
197
144
|
except ZoneInfoNotFoundError as e:
|
198
145
|
raise ValueError(f"Invalid timezone: {value}") from e
|
199
146
|
|
147
|
+
|
148
|
+
class CrontabValue(BaseCrontab):
|
149
|
+
"""Crontab model using interval-based specification.
|
150
|
+
|
151
|
+
Attributes:
|
152
|
+
interval: Scheduling interval ('daily', 'weekly', 'monthly').
|
153
|
+
day: Day specification for weekly/monthly schedules.
|
154
|
+
time: Time of day in 'HH:MM' format.
|
155
|
+
"""
|
156
|
+
|
157
|
+
interval: Interval
|
158
|
+
day: Optional[str] = Field(default=None)
|
159
|
+
time: str = Field(default="00:00")
|
160
|
+
|
161
|
+
@property
|
162
|
+
def cronjob(self) -> CronJob:
|
163
|
+
"""Get CronJob object built from interval format.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
CronJob instance configured with interval-based schedule.
|
167
|
+
"""
|
168
|
+
return CronJob(
|
169
|
+
value=interval2crontab(self.interval, day=self.day, time=self.time)
|
170
|
+
)
|
171
|
+
|
172
|
+
def generate(self, start: Union[str, datetime]) -> CronRunner:
|
173
|
+
"""Generate CronRunner from initial datetime.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
start: Starting datetime (string or datetime object).
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
CronRunner instance for schedule generation.
|
180
|
+
|
181
|
+
Raises:
|
182
|
+
TypeError: If start parameter is neither string nor datetime.
|
183
|
+
"""
|
184
|
+
if isinstance(start, str):
|
185
|
+
start: datetime = datetime.fromisoformat(start)
|
186
|
+
elif not isinstance(start, datetime):
|
187
|
+
raise TypeError("start value should be str or datetime type.")
|
188
|
+
return self.cronjob.schedule(date=start, tz=self.tz)
|
189
|
+
|
190
|
+
def next(self, start: Union[str, datetime]) -> CronRunner:
|
191
|
+
"""Get next scheduled datetime after given start time.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
start: Starting datetime for schedule generation.
|
195
|
+
|
196
|
+
Returns:
|
197
|
+
CronRunner instance positioned at next scheduled time.
|
198
|
+
"""
|
199
|
+
runner: CronRunner = self.generate(start=start)
|
200
|
+
|
201
|
+
# NOTE: ship the next date of runner object that create from start.
|
202
|
+
_ = runner.next
|
203
|
+
|
204
|
+
return runner
|
205
|
+
|
206
|
+
|
207
|
+
class Crontab(BaseCrontab):
|
208
|
+
"""Cron event model wrapping CronJob functionality.
|
209
|
+
|
210
|
+
A Pydantic model that encapsulates crontab scheduling functionality with
|
211
|
+
validation and datetime generation capabilities.
|
212
|
+
|
213
|
+
Attributes:
|
214
|
+
cronjob: CronJob instance for schedule validation and datetime generation.
|
215
|
+
tz: Timezone string value (alias: timezone).
|
216
|
+
"""
|
217
|
+
|
218
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
219
|
+
|
220
|
+
cronjob: CronJob = Field(
|
221
|
+
description=(
|
222
|
+
"A Cronjob object that use for validate and generate datetime."
|
223
|
+
),
|
224
|
+
)
|
225
|
+
tz: TimeZoneName = Field(
|
226
|
+
default="UTC",
|
227
|
+
description="A timezone string value.",
|
228
|
+
alias="timezone",
|
229
|
+
)
|
230
|
+
|
231
|
+
@model_validator(mode="before")
|
232
|
+
def __prepare_values(cls, data: Any) -> Any:
|
233
|
+
"""Prepare input data by standardizing timezone key.
|
234
|
+
|
235
|
+
Args:
|
236
|
+
data: Input dictionary for model creation.
|
237
|
+
|
238
|
+
Returns:
|
239
|
+
Modified dictionary with standardized timezone key.
|
240
|
+
"""
|
241
|
+
if isinstance(data, dict) and (tz := data.pop("tz", None)):
|
242
|
+
data["timezone"] = tz
|
243
|
+
return data
|
244
|
+
|
200
245
|
@field_validator(
|
201
246
|
"cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
|
202
247
|
)
|
203
248
|
def __prepare_cronjob(
|
204
249
|
cls, value: Union[str, CronJob], info: ValidationInfo
|
205
250
|
) -> CronJob:
|
206
|
-
"""Prepare
|
207
|
-
This step will get options kwargs from extras field and pass to the
|
208
|
-
CronJob object.
|
251
|
+
"""Prepare and validate cronjob input.
|
209
252
|
|
210
|
-
:
|
211
|
-
|
212
|
-
|
253
|
+
Args:
|
254
|
+
value: Raw cronjob value (string or CronJob instance).
|
255
|
+
info: Validation context containing extra parameters.
|
213
256
|
|
214
|
-
:
|
257
|
+
Returns:
|
258
|
+
Configured CronJob instance.
|
215
259
|
"""
|
216
260
|
extras: DictData = info.data.get("extras", {})
|
217
261
|
return (
|
@@ -229,21 +273,27 @@ class Crontab(BaseModel):
|
|
229
273
|
|
230
274
|
@field_serializer("cronjob")
|
231
275
|
def __serialize_cronjob(self, value: CronJob) -> str:
|
232
|
-
"""Serialize
|
276
|
+
"""Serialize CronJob instance to string representation.
|
233
277
|
|
234
|
-
:
|
278
|
+
Args:
|
279
|
+
value: CronJob instance to serialize.
|
235
280
|
|
236
|
-
:
|
281
|
+
Returns:
|
282
|
+
String representation of the CronJob.
|
237
283
|
"""
|
238
284
|
return str(value)
|
239
285
|
|
240
286
|
def generate(self, start: Union[str, datetime]) -> CronRunner:
|
241
|
-
"""
|
287
|
+
"""Generate schedule runner from start time.
|
288
|
+
|
289
|
+
Args:
|
290
|
+
start: Starting datetime (string or datetime object).
|
242
291
|
|
243
|
-
:
|
244
|
-
CronRunner
|
292
|
+
Returns:
|
293
|
+
CronRunner instance for schedule generation.
|
245
294
|
|
246
|
-
:
|
295
|
+
Raises:
|
296
|
+
TypeError: If start parameter is neither string nor datetime.
|
247
297
|
"""
|
248
298
|
if isinstance(start, str):
|
249
299
|
start: datetime = datetime.fromisoformat(start)
|
@@ -252,13 +302,13 @@ class Crontab(BaseModel):
|
|
252
302
|
return self.cronjob.schedule(date=start, tz=self.tz)
|
253
303
|
|
254
304
|
def next(self, start: Union[str, datetime]) -> CronRunner:
|
255
|
-
"""
|
256
|
-
date that given from input.
|
305
|
+
"""Get runner positioned at next scheduled time.
|
257
306
|
|
258
|
-
:
|
259
|
-
|
307
|
+
Args:
|
308
|
+
start: Starting datetime for schedule generation.
|
260
309
|
|
261
|
-
:
|
310
|
+
Returns:
|
311
|
+
CronRunner instance positioned at next scheduled time.
|
262
312
|
"""
|
263
313
|
runner: CronRunner = self.generate(start=start)
|
264
314
|
|
@@ -269,21 +319,22 @@ class Crontab(BaseModel):
|
|
269
319
|
|
270
320
|
|
271
321
|
class CrontabYear(Crontab):
|
272
|
-
"""Cron event
|
273
|
-
use by some data schedule tools like AWS Glue.
|
274
|
-
"""
|
322
|
+
"""Cron event model with enhanced year-based scheduling.
|
275
323
|
|
276
|
-
|
324
|
+
Extends the base Crontab model to support year-specific scheduling,
|
325
|
+
particularly useful for tools like AWS Glue.
|
277
326
|
|
278
|
-
|
279
|
-
|
280
|
-
|
327
|
+
Attributes:
|
328
|
+
cronjob: CronJobYear instance for year-aware schedule validation and generation.
|
329
|
+
"""
|
330
|
+
|
331
|
+
cronjob: CronJobYear = (
|
281
332
|
Field(
|
282
333
|
description=(
|
283
334
|
"A Cronjob object that use for validate and generate datetime."
|
284
335
|
),
|
285
336
|
),
|
286
|
-
|
337
|
+
)
|
287
338
|
|
288
339
|
@field_validator(
|
289
340
|
"cronjob",
|
@@ -293,15 +344,14 @@ class CrontabYear(Crontab):
|
|
293
344
|
def __prepare_cronjob(
|
294
345
|
cls, value: Union[CronJobYear, str], info: ValidationInfo
|
295
346
|
) -> CronJobYear:
|
296
|
-
"""Prepare
|
297
|
-
This step will get options kwargs from extras field and pass to the
|
298
|
-
CronJobYear object.
|
347
|
+
"""Prepare and validate year-aware cronjob input.
|
299
348
|
|
300
|
-
:
|
301
|
-
|
302
|
-
|
349
|
+
Args:
|
350
|
+
value: Raw cronjob value (string or CronJobYear instance).
|
351
|
+
info: Validation context containing extra parameters.
|
303
352
|
|
304
|
-
:
|
353
|
+
Returns:
|
354
|
+
Configured CronJobYear instance with applied options.
|
305
355
|
"""
|
306
356
|
extras: DictData = info.data.get("extras", {})
|
307
357
|
return (
|
@@ -318,24 +368,73 @@ class CrontabYear(Crontab):
|
|
318
368
|
)
|
319
369
|
|
320
370
|
|
321
|
-
|
322
|
-
|
371
|
+
Cron = Annotated[
|
372
|
+
Union[
|
373
|
+
CrontabYear,
|
374
|
+
Crontab,
|
375
|
+
CrontabValue,
|
376
|
+
],
|
377
|
+
Field(
|
378
|
+
union_mode="smart",
|
379
|
+
description="Event model type supporting year-based, standard, and interval-based cron scheduling.",
|
380
|
+
),
|
381
|
+
] # pragma: no cov
|
382
|
+
|
383
|
+
|
384
|
+
class Event(BaseModel):
|
385
|
+
"""Event model."""
|
323
386
|
|
387
|
+
schedule: list[Cron] = Field(
|
388
|
+
default_factory=list,
|
389
|
+
description="A list of Cron schedule.",
|
390
|
+
)
|
324
391
|
release: list[str] = Field(
|
392
|
+
default_factory=list,
|
325
393
|
description=(
|
326
394
|
"A list of workflow name that want to receive event from release"
|
327
395
|
"trigger."
|
328
|
-
)
|
396
|
+
),
|
329
397
|
)
|
330
398
|
|
399
|
+
@field_validator("schedule", mode="after")
|
400
|
+
def __on_no_dup_and_reach_limit__(
|
401
|
+
cls,
|
402
|
+
value: list[Crontab],
|
403
|
+
) -> list[Crontab]:
|
404
|
+
"""Validate the on fields should not contain duplicate values and if it
|
405
|
+
contains the every minute value more than one value, it will remove to
|
406
|
+
only one value.
|
407
|
+
|
408
|
+
Args:
|
409
|
+
value: A list of on object.
|
331
410
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
411
|
+
Returns:
|
412
|
+
list[CronJobYear | Crontab]: The validated list of Crontab objects.
|
413
|
+
|
414
|
+
Raises:
|
415
|
+
ValueError: If it has some duplicate value.
|
416
|
+
"""
|
417
|
+
set_ons: set[str] = {str(on.cronjob) for on in value}
|
418
|
+
if len(set_ons) != len(value):
|
419
|
+
raise ValueError(
|
420
|
+
"The on fields should not contain duplicate on value."
|
421
|
+
)
|
422
|
+
|
423
|
+
# WARNING:
|
424
|
+
# if '* * * * *' in set_ons and len(set_ons) > 1:
|
425
|
+
# raise ValueError(
|
426
|
+
# "If it has every minute cronjob on value, it should have "
|
427
|
+
# "only one value in the on field."
|
428
|
+
# )
|
429
|
+
set_tz: set[str] = {on.tz for on in value}
|
430
|
+
if len(set_tz) > 1:
|
431
|
+
raise ValueError(
|
432
|
+
f"The on fields should not contain multiple timezone, "
|
433
|
+
f"{list(set_tz)}."
|
434
|
+
)
|
435
|
+
|
436
|
+
if len(set_ons) > 10:
|
437
|
+
raise ValueError(
|
438
|
+
"The number of the on should not more than 10 crontabs."
|
439
|
+
)
|
440
|
+
return value
|