@@ -29,44 +29,19 @@ class ManytaskUiConfig(CustomBaseModel):
29
29
task_url_template : str # $GROUP_NAME $TASK_NAME vars are available
30
30
links : Optional [dict [str , str ]] = None # pedantic 3.9 require Optional, not | None
31
31
32
-
33
- class ManytaskDeadlinesType (Enum ):
34
- HARD = "hard"
35
- INTERPOLATE = "interpolate"
36
-
37
-
38
- class ManytaskDeadlinesConfig (CustomBaseModel ):
39
- timezone : str
40
-
41
- # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions
42
- deadlines : ManytaskDeadlinesType = ManytaskDeadlinesType .HARD
43
- max_submissions : Optional [int ] = None
44
- submission_penalty : float = 0
45
-
46
- task_url : Optional [AnyUrl ] = None # $GROUP_NAME $TASK_NAME vars are available
47
-
48
- @field_validator ("task_url" )
32
+ @field_validator ("task_url_template" )
49
33
@classmethod
50
- def check_task_url (cls , data : AnyUrl | None ) -> AnyUrl | None :
51
- if data is not None and data .scheme not in ("http" , "https" ):
52
- raise ValueError ("task_url should be http or https" )
34
+ def check_task_url_template (cls , data : str | None ) -> str | None :
35
+ if data is not None and (not data .startswith ("http://" ) and not data .startswith ("https://" )):
36
+ raise ValueError ("task_url_template should be http or https" )
37
+ # if data is not None and "$GROUP_NAME" not in data and "$TASK_NAME" not in data:
38
+ # raise ValueError("task_url should contain at least one of $GROUP_NAME and $TASK_NAME vars")
53
39
return data
54
40
55
- @field_validator ("max_submissions" )
56
- @classmethod
57
- def check_max_submissions (cls , data : int | None ) -> int | None :
58
- if data is not None and data <= 0 :
59
- raise ValueError ("max_submissions should be positive" )
60
- return data
61
41
62
- @field_validator ("timezone" )
63
- @classmethod
64
- def check_valid_timezone (cls , timezone : str ) -> str :
65
- try :
66
- ZoneInfo (timezone )
67
- except ZoneInfoNotFoundError as e :
68
- raise ValueError (str (e ))
69
- return timezone
42
+ class ManytaskDeadlinesType (Enum ):
43
+ HARD = "hard"
44
+ INTERPOLATE = "interpolate"
70
45
71
46
72
47
class ManytaskTaskConfig (CustomBaseModel ):
@@ -102,6 +77,11 @@ class ManytaskGroupConfig(CustomBaseModel):
102
77
def name (self ) -> str :
103
78
return self .group
104
79
80
+ def replace_timezone (self , timezone : ZoneInfo ) -> None :
81
+ self .start = self .start .replace (tzinfo = timezone )
82
+ self .end = self .end .replace (tzinfo = timezone ) if isinstance (self .end , datetime ) else self .end
83
+ self .steps = {k : v .replace (tzinfo = timezone ) for k , v in self .steps .items () if isinstance (v , datetime )}
84
+
105
85
@model_validator (mode = "after" )
106
86
def check_dates (self ) -> "ManytaskGroupConfig" :
107
87
# check end
@@ -135,36 +115,106 @@ def check_dates(self) -> "ManytaskGroupConfig":
135
115
return self
136
116
137
117
138
- class ManytaskConfig (CustomBaseModel , YamlLoaderMixin [ "ManytaskConfig" ] ):
139
- """Manytask configuration."""
118
+ class ManytaskDeadlinesConfig (CustomBaseModel ):
119
+ timezone : str
140
120
141
- version : int # if config exists, version is always present
121
+ # Note: use Optional/Union[...] instead of ... | None as pydantic does not support | in older python versions
122
+ deadlines : ManytaskDeadlinesType = ManytaskDeadlinesType .HARD
123
+ max_submissions : Optional [int ] = None
124
+ submission_penalty : float = 0
142
125
143
- settings : ManytaskSettingsConfig
144
- ui : ManytaskUiConfig
145
- deadlines : ManytaskDeadlinesConfig
146
- schedule : list [ManytaskGroupConfig ]
126
+ schedule : list [ManytaskGroupConfig ] # list of groups with tasks
127
+
128
+ def get_now_with_timezone (self ) -> datetime :
129
+ return datetime .now (tz = ZoneInfo (self .timezone ))
130
+
131
+ @field_validator ("max_submissions" )
132
+ @classmethod
133
+ def check_max_submissions (cls , data : int | None ) -> int | None :
134
+ if data is not None and data <= 0 :
135
+ raise ValueError ("max_submissions should be positive" )
136
+ return data
137
+
138
+ @field_validator ("submission_penalty" )
139
+ @classmethod
140
+ def check_submission_penalty (cls , data : float ) -> float :
141
+ if data < 0 :
142
+ raise ValueError ("submission_penalty should be non-negative" )
143
+ return data
144
+
145
+ @field_validator ("timezone" )
146
+ @classmethod
147
+ def check_valid_timezone (cls , timezone : str ) -> str :
148
+ try :
149
+ ZoneInfo (timezone )
150
+ except ZoneInfoNotFoundError as e :
151
+ raise ValueError (str (e ))
152
+ return timezone
153
+
154
+ @field_validator ("schedule" )
155
+ @classmethod
156
+ def check_group_task_names_unique (cls , data : list [ManytaskGroupConfig ]) -> list [ManytaskGroupConfig ]:
157
+ group_names = [group .name for group in data ]
158
+ tasks_names = [task .name for group in data for task in group .tasks ]
159
+
160
+ # group names unique
161
+ group_names_duplicates = [name for name in group_names if group_names .count (name ) > 1 ]
162
+ if group_names_duplicates :
163
+ raise ValueError (f"Group names should be unique, duplicates: { group_names_duplicates } " )
164
+
165
+ # task names unique
166
+ tasks_names_duplicates = [name for name in tasks_names if tasks_names .count (name ) > 1 ]
167
+ if tasks_names_duplicates :
168
+ raise ValueError (f"Task names should be unique, duplicates: { tasks_names_duplicates } " )
169
+
170
+ # # group names and task names not intersect (except single task in a group with the same name)
171
+ # no_single_task_groups = [group for group in data if not (len(group.tasks) == 1
172
+ # and group.name == group.tasks[0].name)]
173
+
174
+ return data
175
+
176
+ @model_validator (mode = "after" )
177
+ def set_timezone (self ) -> "ManytaskDeadlinesConfig" :
178
+ timezone = ZoneInfo (self .timezone )
179
+ for group in self .schedule :
180
+ group .replace_timezone (timezone )
181
+ return self
147
182
148
183
def get_groups (
149
184
self ,
150
185
enabled : bool | None = None ,
186
+ started : bool | None = None ,
187
+ * ,
188
+ now : datetime | None = None ,
151
189
) -> list [ManytaskGroupConfig ]:
190
+ if now is None :
191
+ now = self .get_now_with_timezone ()
192
+
152
193
groups = [group for group in self .schedule ]
153
194
154
195
if enabled is not None :
155
196
groups = [group for group in groups if group .enabled == enabled ]
156
197
157
- # TODO: check time
198
+ if started is not None :
199
+ if started :
200
+ groups = [group for group in groups if group .start <= now ]
201
+ else :
202
+ groups = [group for group in groups if group .start > now ]
158
203
159
204
return groups
160
205
161
206
def get_tasks (
162
207
self ,
163
208
enabled : bool | None = None ,
209
+ started : bool | None = None ,
210
+ * ,
211
+ now : datetime | None = None ,
164
212
) -> list [ManytaskTaskConfig ]:
165
213
# TODO: refactor
214
+ if now is None :
215
+ now = self .get_now_with_timezone ()
166
216
167
- groups = self .get_groups ()
217
+ groups = self .get_groups (started = started , now = now )
168
218
169
219
if enabled is True :
170
220
groups = [group for group in groups if group .enabled ]
@@ -185,31 +235,39 @@ def get_tasks(
185
235
if extra_task not in tasks :
186
236
tasks .append (extra_task )
187
237
188
- # TODO: check time
189
-
190
238
return tasks
191
239
240
+
241
+ class ManytaskConfig (CustomBaseModel , YamlLoaderMixin ["ManytaskConfig" ]):
242
+ """Manytask configuration."""
243
+
244
+ version : int # if config exists, version is always present
245
+
246
+ settings : ManytaskSettingsConfig
247
+ ui : ManytaskUiConfig
248
+ deadlines : ManytaskDeadlinesConfig
249
+
250
+ def get_groups (
251
+ self ,
252
+ enabled : bool | None = None ,
253
+ started : bool | None = None ,
254
+ * ,
255
+ now : datetime | None = None ,
256
+ ) -> list [ManytaskGroupConfig ]:
257
+ return self .deadlines .get_groups (enabled = enabled , started = started , now = now )
258
+
259
+ def get_tasks (
260
+ self ,
261
+ enabled : bool | None = None ,
262
+ started : bool | None = None ,
263
+ * ,
264
+ now : datetime | None = None ,
265
+ ) -> list [ManytaskTaskConfig ]:
266
+ return self .deadlines .get_tasks (enabled = enabled , started = started , now = now )
267
+
192
268
@field_validator ("version" )
193
269
@classmethod
194
270
def check_version (cls , data : int ) -> int :
195
271
if data != 1 :
196
272
raise ValueError (f"Only version 1 is supported for { cls .__name__ } " )
197
273
return data
198
-
199
- @field_validator ("schedule" )
200
- @classmethod
201
- def check_group_names_unique (cls , data : list [ManytaskGroupConfig ]) -> list [ManytaskGroupConfig ]:
202
- groups = [group .name for group in data ]
203
- duplicates = [name for name in groups if groups .count (name ) > 1 ]
204
- if duplicates :
205
- raise ValueError (f"Group names should be unique, duplicates: { duplicates } " )
206
- return data
207
-
208
- @field_validator ("schedule" )
209
- @classmethod
210
- def check_task_names_unique (cls , data : list [ManytaskGroupConfig ]) -> list [ManytaskGroupConfig ]:
211
- tasks_names = [task .name for group in data for task in group .tasks ]
212
- duplicates = [name for name in tasks_names if tasks_names .count (name ) > 1 ]
213
- if duplicates :
214
- raise ValueError (f"Task names should be unique, duplicates: { duplicates } " )
215
- return data
0 commit comments